All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.h2.server.web.PageParser Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2004-2023 H2 Group. Multiple-Licensed under the MPL 2.0,
 * and the EPL 1.0 (https://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 */
package org.h2.server.web;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.h2.util.StringUtils;

/**
 * A page parser can parse an HTML page and replace the tags there.
 * This class is used by the H2 Console.
 */
public class PageParser {
    private static final int TAB_WIDTH = 4;

    private final String page;
    private int pos;
    private final Map settings;
    private final int len;
    private StringBuilder result;

    private PageParser(String page, Map settings, int pos) {
        this.page = page;
        this.pos = pos;
        this.len = page.length();
        this.settings = settings;
        result = new StringBuilder(len);
    }

    /**
     * Replace the tags in the HTML page with the given settings.
     *
     * @param page the HTML page
     * @param settings the settings
     * @return the converted page
     */
    public static String parse(String page, Map settings) {
        PageParser block = new PageParser(page, settings, 0);
        return block.replaceTags();
    }

    private void setError(int i) {
        String s = page.substring(0, i) + "####BUG####" + page.substring(i);
        s = PageParser.escapeHtml(s);
        result = new StringBuilder();
        result.append(s);
    }

    private String parseBlockUntil(String end) throws ParseException {
        PageParser block = new PageParser(page, settings, pos);
        block.parseAll();
        if (!block.readIf(end)) {
            throw new ParseException(page, block.pos);
        }
        pos = block.pos;
        return block.result.toString();
    }

    private String replaceTags() {
        try {
            parseAll();
            if (pos != len) {
                setError(pos);
            }
        } catch (ParseException e) {
            setError(pos);
        }
        return result.toString();
    }

    @SuppressWarnings("unchecked")
    private void parseAll() throws ParseException {
        StringBuilder buff = result;
        String p = page;
        int i = pos;
        for (; i < len; i++) {
            char c = p.charAt(i);
            switch (c) {
            case '<': {
                if (p.charAt(i + 3) == ':' && p.charAt(i + 1) == '/') {
                    // end tag
                    pos = i;
                    return;
                } else if (p.charAt(i + 2) == ':') {
                    pos = i;
                    if (readIf("");
                        int start = pos;
                        List list = (List) get(items);
                        if (list == null) {
                            result.append("?items?");
                            list = new ArrayList<>();
                        }
                        if (list.isEmpty()) {
                            parseBlockUntil("");
                        }
                        for (Object o : list) {
                            settings.put(var, o);
                            pos = start;
                            String block = parseBlockUntil("");
                            result.append(block);
                        }
                    } else if (readIf("");
                        String block = parseBlockUntil("");
                        pos--;
                        if (value.equals(val)) {
                            result.append(block);
                        }
                    } else {
                        setError(i);
                        return;
                    }
                    i = pos;
                } else {
                    buff.append(c);
                }
                break;
            }
            case '$':
                if (p.length() > i + 1 && p.charAt(i + 1) == '{') {
                    i += 2;
                    int j = p.indexOf('}', i);
                    if (j < 0) {
                        setError(i);
                        return;
                    }
                    String item = StringUtils.trimSubstring(p, i, j);
                    i = j;
                    String s = (String) get(item);
                    replaceTags(s);
                } else {
                    buff.append(c);
                }
                break;
            default:
                buff.append(c);
                break;
            }
        }
        pos = i;
    }

    @SuppressWarnings("unchecked")
    private Object get(String item) {
        int dot = item.indexOf('.');
        if (dot >= 0) {
            String sub = item.substring(dot + 1);
            item = item.substring(0, dot);
            HashMap map = (HashMap) settings.get(item);
            if (map == null) {
                return "?" + item + "?";
            }
            return map.get(sub);
        }
        return settings.get(item);
    }

    private void replaceTags(String s) {
        if (s != null) {
            result.append(PageParser.parse(s, settings));
        }
    }

    private String readParam(String name) throws ParseException {
        read(name);
        read("=");
        read("\"");
        int start = pos;
        while (page.charAt(pos) != '"') {
            pos++;
        }
        int end = pos;
        read("\"");
        String s = page.substring(start, end);
        return PageParser.parse(s, settings);
    }

    private void skipSpaces() {
        while (page.charAt(pos) == ' ') {
            pos++;
        }
    }

    private void read(String s) throws ParseException {
        if (!readIf(s)) {
            throw new ParseException(s, pos);
        }
    }

    private boolean readIf(String s) {
        skipSpaces();
        if (page.regionMatches(pos, s, 0, s.length())) {
            pos += s.length();
            skipSpaces();
            return true;
        }
        return false;
    }

    /**
     * Convert data to HTML, but don't convert newlines and multiple spaces.
     *
     * @param s the data
     * @return the escaped html text
     */
    static String escapeHtmlData(String s) {
        return escapeHtml(s, false);
    }

    /**
     * Convert data to HTML, including newlines and multiple spaces.
     *
     * @param s the data
     * @return the escaped html text
     */
    public static String escapeHtml(String s) {
        return escapeHtml(s, true);
    }

    private static String escapeHtml(String s, boolean convertBreakAndSpace) {
        if (s == null) {
            return null;
        }
        int length = s.length();
        if (convertBreakAndSpace) {
            if (length == 0) {
                return " ";
            }
        }
        StringBuilder builder = new StringBuilder(length);
        boolean convertSpace = true;
        for (int i = 0; i < length;) {
            int cp = s.codePointAt(i);
            if (cp == ' ' || cp == '\t') {
                // convert tabs into spaces
                for (int j = 0; j < (cp == ' ' ? 1 : TAB_WIDTH); j++) {
                    if (convertSpace && convertBreakAndSpace) {
                        builder.append(" ");
                    } else {
                        builder.append(' ');
                        convertSpace = true;
                    }
                }
            } else {
                convertSpace = false;
                switch (cp) {
                case '$':
                    // so that ${ } in the text is interpreted correctly
                    builder.append("$");
                    break;
                case '<':
                    builder.append("<");
                    break;
                case '>':
                    builder.append(">");
                    break;
                case '&':
                    builder.append("&");
                    break;
                case '"':
                    builder.append(""");
                    break;
                case '\'':
                    builder.append("'");
                    break;
                case '\n':
                    if (convertBreakAndSpace) {
                        builder.append("
"); convertSpace = true; } else { builder.append(cp); } break; default: if (cp >= 128) { builder.append("&#").append(cp).append(';'); } else { builder.append((char) cp); } } } i += Character.charCount(cp); } return builder.toString(); } /** * Escape text as a the javascript string. * * @param s the text * @return the javascript string */ static String escapeJavaScript(String s) { if (s == null) { return null; } int length = s.length(); if (length == 0) { return ""; } StringBuilder buff = new StringBuilder(length); for (int i = 0; i < length; i++) { char c = s.charAt(i); switch (c) { case '"': buff.append("\\\""); break; case '\'': buff.append("\\'"); break; case '\\': buff.append("\\\\"); break; case '\n': buff.append("\\n"); break; case '\r': buff.append("\\r"); break; case '\t': buff.append("\\t"); break; default: buff.append(c); break; } } return buff.toString(); } }