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 --&gt; escaper --&gt; 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}