1.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/AbstractXmlFormatter.java Mon Mar 04 17:06:42 2019 +0100
1.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
1.3 @@ -1,241 +0,0 @@
1.4 -/**
1.5 - * SQL-DK
1.6 - * Copyright © 2014 František Kučera (frantovo.cz)
1.7 - *
1.8 - * This program is free software: you can redistribute it and/or modify
1.9 - * it under the terms of the GNU General Public License as published by
1.10 - * the Free Software Foundation, either version 3 of the License, or
1.11 - * (at your option) any later version.
1.12 - *
1.13 - * This program is distributed in the hope that it will be useful,
1.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
1.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1.16 - * GNU General Public License for more details.
1.17 - *
1.18 - * You should have received a copy of the GNU General Public License
1.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
1.20 - */
1.21 -package info.globalcode.sql.dk.formatting;
1.22 -
1.23 -import info.globalcode.sql.dk.ColorfulPrintWriter;
1.24 -import info.globalcode.sql.dk.ColorfulPrintWriter.TerminalColor;
1.25 -import java.util.Stack;
1.26 -import javax.xml.namespace.QName;
1.27 -import static info.globalcode.sql.dk.Functions.isEmpty;
1.28 -import static info.globalcode.sql.dk.Functions.toHex;
1.29 -import info.globalcode.sql.dk.configuration.PropertyDeclaration;
1.30 -import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL;
1.31 -import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL_DESCRIPTION;
1.32 -import java.nio.charset.Charset;
1.33 -import java.util.EmptyStackException;
1.34 -import java.util.HashMap;
1.35 -import java.util.LinkedHashMap;
1.36 -import java.util.Map;
1.37 -import java.util.Map.Entry;
1.38 -import java.util.logging.Level;
1.39 -import java.util.logging.Logger;
1.40 -
1.41 -/**
1.42 - * <p>
1.43 - * Provides helper methods for printing pretty intended and optionally colorful (syntax highlighted)
1.44 - * XML output.
1.45 - * </p>
1.46 - *
1.47 - * <p>
1.48 - * Must be used with care – bad usage can lead to invalid XML (e.g. using undeclared namespaces).
1.49 - * </p>
1.50 - *
1.51 - * @author Ing. František Kučera (frantovo.cz)
1.52 - */
1.53 -@PropertyDeclaration(name = COLORFUL, defaultValue = "false", type = Boolean.class, description = COLORFUL_DESCRIPTION)
1.54 -@PropertyDeclaration(name = AbstractXmlFormatter.PROPERTY_INDENT, defaultValue = AbstractXmlFormatter.PROPERTY_INDENT_DEFAULT, type = String.class, description = "tab or sequence of spaces used for indentation of nested elements")
1.55 -@PropertyDeclaration(name = AbstractXmlFormatter.PROPERTY_INDENT_TEXT, defaultValue = "true", type = Boolean.class, description = "whether text with line breaks should be indented; if not original whitespace will be preserved.")
1.56 -public abstract class AbstractXmlFormatter extends AbstractFormatter {
1.57 -
1.58 - private static final Logger log = Logger.getLogger(AbstractXmlFormatter.class.getName());
1.59 - public static final String PROPERTY_INDENT = "indent";
1.60 - protected static final String PROPERTY_INDENT_DEFAULT = "\t";
1.61 - public static final String PROPERTY_INDENT_TEXT = "indentText";
1.62 - private static final TerminalColor ELEMENT_COLOR = TerminalColor.Magenta;
1.63 - private static final TerminalColor ATTRIBUTE_NAME_COLOR = TerminalColor.Green;
1.64 - private static final TerminalColor ATTRIBUTE_VALUE_COLOR = TerminalColor.Yellow;
1.65 - private static final TerminalColor XML_DECLARATION_COLOR = TerminalColor.Red;
1.66 - private static final TerminalColor XML_DOCTYPE_COLOR = TerminalColor.Cyan;
1.67 - private Stack<QName> treePosition = new Stack<>();
1.68 - private final ColorfulPrintWriter out;
1.69 - private final String indent;
1.70 - private final boolean indentText;
1.71 -
1.72 - public AbstractXmlFormatter(FormatterContext formatterContext) {
1.73 - super(formatterContext);
1.74 - boolean colorful = formatterContext.getProperties().getBoolean(COLORFUL, false);
1.75 - out = new ColorfulPrintWriter(formatterContext.getOutputStream(), false, colorful);
1.76 - indent = formatterContext.getProperties().getString(PROPERTY_INDENT, PROPERTY_INDENT_DEFAULT);
1.77 - indentText = formatterContext.getProperties().getBoolean(PROPERTY_INDENT_TEXT, true);
1.78 -
1.79 - if (!indent.matches("\\s*")) {
1.80 - log.log(Level.WARNING, "Setting indent to „{0}“ is weird & freaky; in hex: {1}", new Object[]{indent, toHex(indent.getBytes())});
1.81 - }
1.82 -
1.83 - }
1.84 -
1.85 - protected void printStartDocument() {
1.86 - out.print(XML_DECLARATION_COLOR, "<?xml version=\"1.0\" encoding=\"" + Charset.defaultCharset().name() + "\"?>");
1.87 - }
1.88 -
1.89 - protected void printDoctype(String doctype) {
1.90 - out.print(XML_DOCTYPE_COLOR, "\n<!DOCTYPE " + doctype + ">");
1.91 - }
1.92 -
1.93 - protected void printEndDocument() {
1.94 - out.println();
1.95 - out.flush();
1.96 - if (!treePosition.empty()) {
1.97 - throw new IllegalStateException("Some elements are not closed: " + treePosition);
1.98 - }
1.99 - }
1.100 -
1.101 - protected void printStartElement(QName element) {
1.102 - printStartElement(element, null);
1.103 - }
1.104 -
1.105 - protected Map<QName, String> singleAttribute(QName name, String value) {
1.106 - Map<QName, String> attributes = new HashMap<>(2);
1.107 - attributes.put(name, value);
1.108 - return attributes;
1.109 - }
1.110 -
1.111 - protected void printStartElement(QName element, Map<QName, String> attributes) {
1.112 - printStartElement(element, attributes, false);
1.113 - }
1.114 -
1.115 - /**
1.116 - * @param empty whether element should be closed <codfe>… /></code> (has no content, do not
1.117 - * call {@linkplain #printEndElement()})
1.118 - */
1.119 - private void printStartElement(QName element, Map<QName, String> attributes, boolean empty) {
1.120 - printIndent();
1.121 -
1.122 - out.print(ELEMENT_COLOR, "<" + toString(element));
1.123 -
1.124 - if (attributes != null) {
1.125 - for (Entry<QName, String> attribute : attributes.entrySet()) {
1.126 - out.print(" ");
1.127 - out.print(ATTRIBUTE_NAME_COLOR, toString(attribute.getKey()));
1.128 - out.print("=");
1.129 - out.print(ATTRIBUTE_VALUE_COLOR, '"' + escapeXmlAttribute(attribute.getValue()) + '"');
1.130 - }
1.131 - }
1.132 -
1.133 - if (empty) {
1.134 - out.print(ELEMENT_COLOR, "/>");
1.135 - } else {
1.136 - out.print(ELEMENT_COLOR, ">");
1.137 - treePosition.add(element);
1.138 - }
1.139 -
1.140 - out.flush();
1.141 - }
1.142 -
1.143 - /**
1.144 - * Prints text node wrapped in given element without indenting the text and adding line breaks
1.145 - * (useful for short texts).
1.146 - *
1.147 - * @param attributes use {@linkplain LinkedHashMap} to preserve attributes order
1.148 - */
1.149 - protected void printTextElement(QName element, Map<QName, String> attributes, String text) {
1.150 - printStartElement(element, attributes);
1.151 -
1.152 - String[] lines = text.split("\\n");
1.153 -
1.154 - if (indentText && lines.length > 1) {
1.155 - for (String line : lines) {
1.156 - printText(line, true);
1.157 - }
1.158 - printEndElement(true);
1.159 - } else {
1.160 - /*
1.161 - * line breaks at the end of the text will be eaten – if you need them, use indentText = false
1.162 - */
1.163 - if (lines.length == 1 && text.endsWith("\n")) {
1.164 - text = text.substring(0, text.length() - 1);
1.165 - }
1.166 -
1.167 - printText(text, false);
1.168 - printEndElement(false);
1.169 - }
1.170 - }
1.171 -
1.172 - protected void printEmptyElement(QName element, Map<QName, String> attributes) {
1.173 - printStartElement(element, attributes, true);
1.174 - }
1.175 -
1.176 - protected void printEndElement() {
1.177 - printEndElement(true);
1.178 - }
1.179 -
1.180 - private void printEndElement(boolean indent) {
1.181 - try {
1.182 - QName name = treePosition.pop();
1.183 -
1.184 - if (indent) {
1.185 - printIndent();
1.186 - }
1.187 -
1.188 - out.print(ELEMENT_COLOR, "</" + toString(name) + ">");
1.189 - out.flush();
1.190 -
1.191 - } catch (EmptyStackException e) {
1.192 - throw new IllegalStateException("No more elements to end.", e);
1.193 - }
1.194 - }
1.195 -
1.196 - protected void printText(String s, boolean indent) {
1.197 - if (indent) {
1.198 - printIndent();
1.199 - }
1.200 - out.print(escapeXmlText(s));
1.201 - out.flush();
1.202 - }
1.203 -
1.204 - protected void printIndent() {
1.205 - out.println();
1.206 - for (int i = 0; i < treePosition.size(); i++) {
1.207 - out.print(indent);
1.208 - }
1.209 - }
1.210 -
1.211 - protected static QName qname(String name) {
1.212 - return new QName(name);
1.213 - }
1.214 -
1.215 - protected static QName qname(String prefix, String name) {
1.216 - return new QName(null, name, prefix);
1.217 - }
1.218 -
1.219 - private String toString(QName name) {
1.220 - if (isEmpty(name.getPrefix(), true)) {
1.221 - return escapeName(name.getLocalPart());
1.222 - } else {
1.223 - return escapeName(name.getPrefix()) + ":" + escapeName(name.getLocalPart());
1.224 - }
1.225 - }
1.226 -
1.227 - private String escapeName(String s) {
1.228 - // TODO: avoid ugly values in <name name="…"/>
1.229 - return s;
1.230 - }
1.231 -
1.232 - private static String escapeXmlText(String s) {
1.233 - return s.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
1.234 - // Not needed:
1.235 - // return s.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
1.236 - }
1.237 -
1.238 - /**
1.239 - * Expects attribute values enclosed in "quotes" not 'apostrophes'.
1.240 - */
1.241 - private static String escapeXmlAttribute(String s) {
1.242 - return s.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """);
1.243 - }
1.244 -}