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.JStacheConfig; 010import io.jstach.jstache.JStacheContentType; 011import io.jstach.jstache.JStacheLambda; 012import io.jstach.jstache.JStacheType; 013import io.jstach.jstachio.escapers.Html; 014import io.jstach.jstachio.escapers.PlainText; 015 016/** 017 * An Escaper is an {@link Appender} used to escape content such as HTML. A 018 * {@link Formatter} is usually what will call the Escaper and like a formatter it should 019 * be singleton like and expect reuse. 020 * <p> 021 * When a template outputs an <strong>escaped</strong> variable the callstack is as 022 * follows: 023 * 024 * <pre> 025 * formatter --> escaper --> appendable 026 * </pre> 027 * 028 * Escapers are also a {@code Function<String,String>} to allow compatibility with 029 * {@linkplain JStacheType#STACHE zero dependency generated code} that expects Escapers to 030 * be of type {@code Function<String,String>}. 031 * <p> 032 * If escaping is not needed one can use {@link PlainText#of()} which will just pass the 033 * strings and primitives downstream without altering them. The default escaper unless in 034 * zero dependency mode is provided by {@link Html#of()}. 035 * <p> 036 * For context specific escaping like for example XML attributes consider using a 037 * {@link JStacheLambda} as the escaper is not passed information where in the template 038 * escaping is requested. 039 * 040 * <h2>Implementing</h2> 041 * 042 * If performance is not a concern an easier way to create an implementation is to adapt a 043 * function by using {@link #of(Function)}. 044 * <p> 045 * To implement a custom escaper: 046 * 047 * <ol> 048 * <li>Implement this interface or use {@link #of(Function)}.</li> 049 * <li>Register the custom escaper. See {@link JStacheContentType}.</li> 050 * <li>Set {@link JStacheConfig#contentType()} to the class that has the 051 * {@link JStacheContentType}.</li> 052 * </ol> 053 * 054 * @apiNote Implementations should be threadsafe and expect reuse! 055 * @see JStacheContentType 056 * @see PlainText#of() 057 * @see Html#of() 058 * @author agentgt 059 */ 060public non-sealed interface Escaper extends Appender, Function<String, String> { 061 062 /** 063 * Escapes a String by using StringBuilder and calling 064 * {@link #append(Output, CharSequence)}. 065 * <p> 066 * This method is to make Escaper implementations compatible with 067 * {@link JStacheType#STACHE zero dependency generated code} that expects Escapers to 068 * be {@code Function<String,String>}. 069 * @param t String to ge escaped. 070 * @return escaped content 071 * @throws UncheckedIOException if the appender or appendable throw an 072 * {@link IOException} 073 */ 074 @Override 075 default String apply(String t) throws UncheckedIOException { 076 var out = new Output.StringOutput(new StringBuilder()); 077 append(out, t); 078 return out.toString(); 079 } 080 081 /** 082 * Escapes the characters if it needs it. {@inheritDoc} 083 */ 084 @Override 085 public <A extends Output<E>, E extends Exception> void append(A a, CharSequence s) throws E; 086 087 /** 088 * Escapes the characters if it needs it. {@inheritDoc} 089 */ 090 @Override 091 public <A extends Output<E>, E extends Exception> void append(A a, CharSequence csq, int start, int end) throws E; 092 093 /** 094 * Escapes the character if it needs escaping. {@inheritDoc} 095 */ 096 @Override 097 public <A extends Output<E>, E extends Exception> void append(A a, char c) throws E; 098 099 /** 100 * Escapes the character if it needs escaping. The default implementation will 101 * {@link String#valueOf(short)} and call {@link #append(Output, CharSequence)}. 102 * {@inheritDoc} 103 */ 104 @Override 105 default <A extends Output<E>, E extends Exception> void append(A a, short s) throws E { 106 append(a, String.valueOf(s)); 107 } 108 109 /** 110 * Escapes the character if it needs escaping. The default implementation will 111 * {@link String#valueOf(int)} and call {@link #append(Output, CharSequence)}. 112 * {@inheritDoc} 113 */ 114 @Override 115 default <A extends Output<E>, E extends Exception> void append(A a, int i) throws E { 116 append(a, String.valueOf(i)); 117 } 118 119 /** 120 * Escapes the character if it needs escaping. The default implementation will 121 * {@link String#valueOf(long)} and call {@link #append(Output, CharSequence)}. 122 * {@inheritDoc} 123 */ 124 @Override 125 default <A extends Output<E>, E extends Exception> void append(A a, long l) throws E { 126 append(a, String.valueOf(l)); 127 } 128 129 /** 130 * Escapes the character if it needs escaping. The default implementation will 131 * {@link String#valueOf(double)} and call {@link #append(Output, CharSequence)}. 132 * {@inheritDoc} 133 */ 134 @Override 135 default <A extends Output<E>, E extends Exception> void append(A a, double d) throws E { 136 append(a, String.valueOf(d)); 137 } 138 139 /** 140 * Escapes the character if it needs escaping. The default implementation will 141 * {@link String#valueOf(boolean)} and call {@link #append(Output, CharSequence)}. 142 * {@inheritDoc} 143 */ 144 @Override 145 default <A extends Output<E>, E extends Exception> void append(A a, boolean b) throws E { 146 append(a, String.valueOf(b)); 147 } 148 149 /** 150 * Adapts a function to an Escaper. 151 * 152 * If the function is already an Escaper then it is simply returned (noop). Thus it is 153 * safe to repeatedly call this on an Escaper. If the function is adapted the returned 154 * adapted Escaper will convert native types with {@code String.valueOf} first and 155 * then apply the escape function. 156 * @param escapeFunction returned if it is already an escaper 157 * @return adapted Escaper 158 */ 159 public static Escaper of(Function<String, String> escapeFunction) { 160 if (escapeFunction instanceof Escaper e) { 161 return e; 162 } 163 return new FunctionEscaper(escapeFunction); 164 165 } 166 167} 168 169class FunctionEscaper implements Escaper { 170 171 private final Function<String, String> function; 172 173 public FunctionEscaper(Function<String, String> function) { 174 super(); 175 this.function = function; 176 } 177 178 @Override 179 public <A extends Output<E>, E extends Exception> void append(A a, CharSequence s) throws E { 180 a.append(function.apply(s.toString())); 181 } 182 183 @Override 184 public <A extends Output<E>, E extends Exception> void append(A a, @Nullable CharSequence csq, int start, int end) 185 throws E { 186 if (csq == null) { 187 a.append(function.apply("null")); 188 return; 189 } 190 a.append(function.apply(String.valueOf(csq.subSequence(start, end)))); 191 } 192 193 @Override 194 public <A extends Output<E>, E extends Exception> void append(A a, char c) throws E { 195 append(a, String.valueOf(c)); 196 } 197 198}