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}