001package io.jstach.jstachio; 002 003import java.io.IOException; 004import java.io.UncheckedIOException; 005import java.util.function.Function; 006 007import org.eclipse.jdt.annotation.Nullable; 008 009import io.jstach.jstache.JStacheContentType; 010 011/** 012 * An Escaper is an {@link Appender} used to escape content such as HTML. A 013 * {@link Formatter} is usually what will call the Escaper and like a formatter should be 014 * singleton like and expect reuse. 015 * 016 * @see JStacheContentType 017 * @author agentgt 018 */ 019public interface Escaper extends Appender<Appendable>, Function<String, String> { 020 021 /** 022 * Escapes a String by using StringBuilder and calling 023 * {@link #append(Appendable, CharSequence)}. 024 * @param t String to ge escaped. 025 * @return escaped content 026 * @throws UncheckedIOException if the appender or appendable throw an 027 * {@link IOException} 028 */ 029 @Override 030 default String apply(String t) throws UncheckedIOException { 031 StringBuilder sb = new StringBuilder(); 032 try { 033 append(sb, t); 034 } 035 catch (IOException e) { 036 throw new UncheckedIOException(e); 037 } 038 return sb.toString(); 039 } 040 041 /** 042 * Adapts a function to an Escaper. 043 * 044 * If the function is already an Escaper then it is simply returned (noop). Thus it is 045 * safe to repeatedly call this on Escaper. If the function is adapted the returned 046 * adapted Escaper does not pass native types to the inputted function. 047 * @param escapeFunction returned if it is already an escaper 048 * @return adapted Escaper 049 */ 050 public static Escaper of(Function<String, String> escapeFunction) { 051 if (escapeFunction instanceof Escaper e) { 052 return e; 053 } 054 return new FunctionEscaper(escapeFunction); 055 056 } 057 058} 059 060class FunctionEscaper implements Escaper { 061 062 private final Function<String, String> function; 063 064 public FunctionEscaper(Function<String, String> function) { 065 super(); 066 this.function = function; 067 } 068 069 @Override 070 public void append(Appendable a, CharSequence s) throws IOException { 071 a.append(function.apply(String.valueOf(s))); 072 } 073 074 @Override 075 public void append(Appendable a, @Nullable CharSequence csq, int start, int end) throws IOException { 076 if (csq == null) { 077 a.append(function.apply("null")); 078 return; 079 } 080 a.append(function.apply(String.valueOf(csq.subSequence(start, end)))); 081 } 082 083 @Override 084 public void append(Appendable a, char c) throws IOException { 085 a.append(function.apply(String.valueOf(c))); 086 } 087 088}