XML formatter: abstract + part of basic XML formatter v_0
authorFrantišek Kučera <franta-hg@frantovo.cz>
Sat, 04 Jan 2014 19:39:35 +0100
branchv_0
changeset 12867f5ff139da0
parent 127 d63de8a0a61f
child 129 331634456bf8
XML formatter: abstract + part of basic XML formatter
java/sql-dk/src/info/globalcode/sql/dk/Xmlns.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/Configuration.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/AbstractXmlFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/XhtmlFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/XmlFormatter.java
     1.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/Xmlns.java	Sat Jan 04 19:38:51 2014 +0100
     1.2 +++ b/java/sql-dk/src/info/globalcode/sql/dk/Xmlns.java	Sat Jan 04 19:39:35 2014 +0100
     1.3 @@ -25,4 +25,5 @@
     1.4  public class Xmlns {
     1.5  
     1.6  	public static final String CONFIGURATION = "https://sql-dk.globalcode.info/xmlns/configuration";
     1.7 +	public static final String BATCH_RESULT = "https://sql-dk.globalcode.info/xmlns/batchResult";
     1.8  }
     2.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/Configuration.java	Sat Jan 04 19:38:51 2014 +0100
     2.2 +++ b/java/sql-dk/src/info/globalcode/sql/dk/configuration/Configuration.java	Sat Jan 04 19:39:35 2014 +0100
     2.3 @@ -24,6 +24,7 @@
     2.4  import info.globalcode.sql.dk.formatting.TabularFormatter;
     2.5  import info.globalcode.sql.dk.formatting.TabularPrefetchingFormatter;
     2.6  import info.globalcode.sql.dk.formatting.TabularWrappingFormatter;
     2.7 +import info.globalcode.sql.dk.formatting.XhtmlFormatter;
     2.8  import info.globalcode.sql.dk.formatting.XmlFormatter;
     2.9  import java.util.ArrayList;
    2.10  import java.util.Collection;
    2.11 @@ -62,6 +63,7 @@
    2.12  		l.add(new FormatterDefinition(SilentFormatter.NAME, SilentFormatter.class.getName()));
    2.13  		l.add(new FormatterDefinition(SingleValueFormatter.NAME, SingleValueFormatter.class.getName()));
    2.14  		l.add(new FormatterDefinition(XmlFormatter.NAME, XmlFormatter.class.getName()));
    2.15 +		l.add(new FormatterDefinition(XhtmlFormatter.NAME, XhtmlFormatter.class.getName()));
    2.16  		l.add(new FormatterDefinition(TabularFormatter.NAME, TabularFormatter.class.getName()));
    2.17  		l.add(new FormatterDefinition(TabularPrefetchingFormatter.NAME, TabularPrefetchingFormatter.class.getName()));
    2.18  		l.add(new FormatterDefinition(TabularWrappingFormatter.NAME, TabularWrappingFormatter.class.getName()));
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/java/sql-dk/src/info/globalcode/sql/dk/formatting/AbstractXmlFormatter.java	Sat Jan 04 19:39:35 2014 +0100
     3.3 @@ -0,0 +1,206 @@
     3.4 +/**
     3.5 + * SQL-DK
     3.6 + * Copyright © 2014 František Kučera (frantovo.cz)
     3.7 + *
     3.8 + * This program is free software: you can redistribute it and/or modify
     3.9 + * it under the terms of the GNU General Public License as published by
    3.10 + * the Free Software Foundation, either version 3 of the License, or
    3.11 + * (at your option) any later version.
    3.12 + *
    3.13 + * This program is distributed in the hope that it will be useful,
    3.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    3.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    3.16 + * GNU General Public License for more details.
    3.17 + *
    3.18 + * You should have received a copy of the GNU General Public License
    3.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
    3.20 + */
    3.21 +package info.globalcode.sql.dk.formatting;
    3.22 +
    3.23 +import info.globalcode.sql.dk.ColorfulPrintWriter;
    3.24 +import info.globalcode.sql.dk.ColorfulPrintWriter.TerminalColor;
    3.25 +import java.util.Stack;
    3.26 +import javax.xml.namespace.QName;
    3.27 +import static info.globalcode.sql.dk.Functions.isEmpty;
    3.28 +import static info.globalcode.sql.dk.Functions.toHex;
    3.29 +import java.nio.charset.Charset;
    3.30 +import java.util.EmptyStackException;
    3.31 +import java.util.HashMap;
    3.32 +import java.util.LinkedHashMap;
    3.33 +import java.util.Map;
    3.34 +import java.util.Map.Entry;
    3.35 +import java.util.logging.Level;
    3.36 +import java.util.logging.Logger;
    3.37 +
    3.38 +/**
    3.39 + *
    3.40 + * @author Ing. František Kučera (frantovo.cz)
    3.41 + */
    3.42 +public abstract class AbstractXmlFormatter extends AbstractFormatter {
    3.43 +
    3.44 +	private static final Logger log = Logger.getLogger(AbstractXmlFormatter.class.getName());
    3.45 +	public static final String PROPERTY_COLORFUL = "color";
    3.46 +	public static final String PROPERTY_INDENT = "indent";
    3.47 +	private static final TerminalColor ELEMENT_COLOR = TerminalColor.Magenta;
    3.48 +	private static final TerminalColor ATTRIBUTE_NAME_COLOR = TerminalColor.Green;
    3.49 +	private static final TerminalColor ATTRIBUTE_VALUE_COLOR = TerminalColor.Yellow;
    3.50 +	private static final TerminalColor XML_DECLARATION_COLOR = TerminalColor.Red;
    3.51 +	private Stack<QName> treePosition = new Stack<>();
    3.52 +	private final ColorfulPrintWriter out;
    3.53 +	private final String indent;
    3.54 +
    3.55 +	public AbstractXmlFormatter(FormatterContext formatterContext) {
    3.56 +		super(formatterContext);
    3.57 +		boolean colorful = formatterContext.getProperties().getBoolean(PROPERTY_COLORFUL, false);
    3.58 +		out = new ColorfulPrintWriter(formatterContext.getOutputStream(), false, colorful);
    3.59 +		indent = formatterContext.getProperties().getString(PROPERTY_INDENT, "\t");
    3.60 +
    3.61 +		if (!indent.matches("\\s*")) {
    3.62 +			log.log(Level.WARNING, "Setting indent to „{0}“ is weird & freaky; in hex: {1}", new Object[]{indent, toHex(indent.getBytes())});
    3.63 +		}
    3.64 +
    3.65 +	}
    3.66 +
    3.67 +	protected void printStartDocument() {
    3.68 +		out.print(XML_DECLARATION_COLOR, "<?xml version=\"1.0\" encoding=\"" + Charset.defaultCharset().name() + "\"?>");
    3.69 +	}
    3.70 +
    3.71 +	protected void printEndDocument() {
    3.72 +		out.println();
    3.73 +		out.flush();
    3.74 +		if (!treePosition.empty()) {
    3.75 +			throw new IllegalStateException("Some elements are not closed: " + treePosition);
    3.76 +		}
    3.77 +	}
    3.78 +
    3.79 +	protected void printStartElement(QName element) {
    3.80 +		printStartElement(element, null);
    3.81 +	}
    3.82 +
    3.83 +	protected Map<QName, String> singleAttribute(QName name, String value) {
    3.84 +		Map<QName, String> attributes = new HashMap<>(1);
    3.85 +		attributes.put(name, value);
    3.86 +		return attributes;
    3.87 +	}
    3.88 +
    3.89 +	protected void printStartElement(QName element, Map<QName, String> attributes) {
    3.90 +		printStartElement(element, attributes, false);
    3.91 +	}
    3.92 +
    3.93 +	/**
    3.94 +	 * @param empty whether element should be closed <codfe>… /&gt;</code> (has no content, do not
    3.95 +	 * call {@linkplain #printEndElement()})
    3.96 +	 */
    3.97 +	private void printStartElement(QName element, Map<QName, String> attributes, boolean empty) {
    3.98 +		printIndent();
    3.99 +
   3.100 +		out.print(ELEMENT_COLOR, "<" + toString(element));
   3.101 +
   3.102 +		if (attributes != null) {
   3.103 +			for (Entry<QName, String> attribute : attributes.entrySet()) {
   3.104 +				out.print(" ");
   3.105 +				out.print(ATTRIBUTE_NAME_COLOR, toString(attribute.getKey()));
   3.106 +				out.print("=");
   3.107 +				out.print(ATTRIBUTE_VALUE_COLOR, '"' + escapeXmlAttribute(attribute.getValue()) + '"');
   3.108 +			}
   3.109 +		}
   3.110 +
   3.111 +		if (empty) {
   3.112 +			out.print(ELEMENT_COLOR, "/>");
   3.113 +		} else {
   3.114 +			out.print(ELEMENT_COLOR, ">");
   3.115 +			treePosition.add(element);
   3.116 +		}
   3.117 +
   3.118 +		out.flush();
   3.119 +	}
   3.120 +
   3.121 +	/**
   3.122 +	 * Prints text node wrapped in given element without indenting the text and adding line breaks
   3.123 +	 * (useful for short texts).
   3.124 +	 *
   3.125 +	 * @param attributes use {@linkplain  LinkedHashMap} to preserve attributes order
   3.126 +	 */
   3.127 +	protected void printTextElement(QName element, Map<QName, String> attributes, String text) {
   3.128 +		printStartElement(element, attributes);
   3.129 +		printText(text, false);
   3.130 +		printEndElement(false);
   3.131 +	}
   3.132 +
   3.133 +	protected void printEmptyElement(QName element, Map<QName, String> attributes) {
   3.134 +		printStartElement(element, attributes, true);
   3.135 +	}
   3.136 +
   3.137 +	protected void printEndElement() {
   3.138 +		printEndElement(true);
   3.139 +	}
   3.140 +
   3.141 +	private void printEndElement(boolean indent) {
   3.142 +		try {
   3.143 +			QName name = treePosition.pop();
   3.144 +
   3.145 +			if (indent) {
   3.146 +				printIndent();
   3.147 +			}
   3.148 +
   3.149 +			out.print(ELEMENT_COLOR, "</" + toString(name) + ">");
   3.150 +			out.flush();
   3.151 +
   3.152 +		} catch (EmptyStackException e) {
   3.153 +			throw new IllegalStateException("No more elements to end.", e);
   3.154 +		}
   3.155 +	}
   3.156 +
   3.157 +	protected void printText(String s) {
   3.158 +		printText(s, true);
   3.159 +	}
   3.160 +
   3.161 +	private void printText(String s, boolean indent) {
   3.162 +		if (indent) {
   3.163 +			printIndent();
   3.164 +		}
   3.165 +		out.print(escapeXmlText(s));
   3.166 +		out.flush();
   3.167 +	}
   3.168 +
   3.169 +	protected void printIndent() {
   3.170 +		out.println();
   3.171 +		for (int i = 0; i < treePosition.size(); i++) {
   3.172 +			out.print(indent);
   3.173 +		}
   3.174 +	}
   3.175 +
   3.176 +	protected static QName qname(String name) {
   3.177 +		return new QName(name);
   3.178 +	}
   3.179 +
   3.180 +	protected static QName qname(String prefix, String name) {
   3.181 +		return new QName(null, name, prefix);
   3.182 +	}
   3.183 +
   3.184 +	private String toString(QName name) {
   3.185 +		if (isEmpty(name.getPrefix(), true)) {
   3.186 +			return escapeName(name.getLocalPart());
   3.187 +		} else {
   3.188 +			return escapeName(name.getPrefix()) + ":" + escapeName(name.getLocalPart());
   3.189 +		}
   3.190 +	}
   3.191 +
   3.192 +	private String escapeName(String s) {
   3.193 +		// TODO: avoid ugly values in <name name="…"/>		
   3.194 +		return s;
   3.195 +	}
   3.196 +
   3.197 +	private static String escapeXmlText(String s) {
   3.198 +		return s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
   3.199 +		// Not needed:
   3.200 +		// return s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&apos;");
   3.201 +	}
   3.202 +
   3.203 +	/**
   3.204 +	 * Expects attribute values enclosed in "quotes" not 'apostrophes'.
   3.205 +	 */
   3.206 +	private static String escapeXmlAttribute(String s) {
   3.207 +		return s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
   3.208 +	}
   3.209 +}
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/java/sql-dk/src/info/globalcode/sql/dk/formatting/XhtmlFormatter.java	Sat Jan 04 19:39:35 2014 +0100
     4.3 @@ -0,0 +1,31 @@
     4.4 +/**
     4.5 + * SQL-DK
     4.6 + * Copyright © 2014 František Kučera (frantovo.cz)
     4.7 + *
     4.8 + * This program is free software: you can redistribute it and/or modify
     4.9 + * it under the terms of the GNU General Public License as published by
    4.10 + * the Free Software Foundation, either version 3 of the License, or
    4.11 + * (at your option) any later version.
    4.12 + *
    4.13 + * This program is distributed in the hope that it will be useful,
    4.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    4.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    4.16 + * GNU General Public License for more details.
    4.17 + *
    4.18 + * You should have received a copy of the GNU General Public License
    4.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
    4.20 + */
    4.21 +package info.globalcode.sql.dk.formatting;
    4.22 +
    4.23 +/**
    4.24 + *
    4.25 + * @author Ing. František Kučera (frantovo.cz)
    4.26 + */
    4.27 +public class XhtmlFormatter extends AbstractXmlFormatter {
    4.28 +
    4.29 +	public static final String NAME = "xhtml"; // bash-completion:formatter
    4.30 +
    4.31 +	public XhtmlFormatter(FormatterContext formatterContext) {
    4.32 +		super(formatterContext);
    4.33 +	}
    4.34 +}
     5.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/XmlFormatter.java	Sat Jan 04 19:38:51 2014 +0100
     5.2 +++ b/java/sql-dk/src/info/globalcode/sql/dk/formatting/XmlFormatter.java	Sat Jan 04 19:39:35 2014 +0100
     5.3 @@ -17,15 +17,103 @@
     5.4   */
     5.5  package info.globalcode.sql.dk.formatting;
     5.6  
     5.7 +import info.globalcode.sql.dk.Parameter;
     5.8 +import info.globalcode.sql.dk.Xmlns;
     5.9 +import info.globalcode.sql.dk.configuration.DatabaseDefinition;
    5.10 +import static info.globalcode.sql.dk.formatting.AbstractXmlFormatter.qname;
    5.11 +import static info.globalcode.sql.dk.Functions.notNull;
    5.12 +import info.globalcode.sql.dk.NamedParameter;
    5.13 +import java.util.LinkedHashMap;
    5.14 +import java.util.List;
    5.15 +import java.util.Map;
    5.16 +import javax.xml.namespace.QName;
    5.17 +
    5.18  /**
    5.19   *
    5.20   * @author Ing. František Kučera (frantovo.cz)
    5.21   */
    5.22 -public class XmlFormatter extends AbstractFormatter {
    5.23 +public class XmlFormatter extends AbstractXmlFormatter {
    5.24  
    5.25  	public static final String NAME = "xml"; // bash-completion:formatter
    5.26  
    5.27  	public XmlFormatter(FormatterContext formatterContext) {
    5.28  		super(formatterContext);
    5.29  	}
    5.30 +
    5.31 +	@Override
    5.32 +	public void writeStartBatch() {
    5.33 +		super.writeStartBatch();
    5.34 +		printStartDocument();
    5.35 +		printStartElement(qname("batchResults"), singleAttribute(qname("xmlns"), Xmlns.BATCH_RESULT));
    5.36 +	}
    5.37 +
    5.38 +	@Override
    5.39 +	public void writeEndBatch() {
    5.40 +		super.writeEndBatch();
    5.41 +
    5.42 +		printEndElement();
    5.43 +		printEndDocument();
    5.44 +	}
    5.45 +
    5.46 +	@Override
    5.47 +	public void writeStartDatabase(DatabaseDefinition databaseDefinition) {
    5.48 +		super.writeStartDatabase(databaseDefinition);
    5.49 +		Map<QName, String> attributes = databaseDefinition.getName() == null ? null : singleAttribute(qname("name"), databaseDefinition.getName());
    5.50 +		printStartElement(qname("database"), attributes);
    5.51 +	}
    5.52 +
    5.53 +	@Override
    5.54 +	public void writeEndDatabase() {
    5.55 +		super.writeEndDatabase();
    5.56 +		printEndElement();
    5.57 +	}
    5.58 +
    5.59 +	@Override
    5.60 +	public void writeStartResultSet() {
    5.61 +		super.writeStartResultSet();
    5.62 +		printStartElement(qname("resultSet"));
    5.63 +	}
    5.64 +
    5.65 +	@Override
    5.66 +	public void writeEndResultSet() {
    5.67 +		super.writeEndResultSet();
    5.68 +		printEndElement();
    5.69 +	}
    5.70 +
    5.71 +	@Override
    5.72 +	public void writeQuery(String sql) {
    5.73 +		super.writeQuery(sql);
    5.74 +		printTextElement(qname("sql"), null, sql);
    5.75 +	}
    5.76 +
    5.77 +	@Override
    5.78 +	public void writeParameters(List<? extends Parameter> parameters) {
    5.79 +		super.writeParameters(parameters);
    5.80 +
    5.81 +		for (Parameter p : notNull(parameters)) {
    5.82 +
    5.83 +			Map<QName, String> attributes = new LinkedHashMap<>(2);
    5.84 +			if (p instanceof NamedParameter) {
    5.85 +				attributes.put(qname("name"), ((NamedParameter) p).getName());
    5.86 +			}
    5.87 +			attributes.put(qname("type"), p.getType().name());
    5.88 +
    5.89 +			printTextElement(qname("parameter"), attributes, String.valueOf(p.getValue()));
    5.90 +		}
    5.91 +
    5.92 +	}
    5.93 +
    5.94 +	@Override
    5.95 +	public void writeColumnsHeader(ColumnsHeader header) {
    5.96 +		super.writeColumnsHeader(header);
    5.97 +
    5.98 +		for (ColumnDescriptor cd : header.getColumnDescriptors()) {
    5.99 +			Map<QName, String> attributes = new LinkedHashMap<>(4);
   5.100 +			attributes.put(qname("label"), cd.getLabel());
   5.101 +			attributes.put(qname("name"), cd.getName());
   5.102 +			attributes.put(qname("typeName"), cd.getTypeName());
   5.103 +			attributes.put(qname("type"), String.valueOf(cd.getType()));
   5.104 +			printEmptyElement(qname("columnHeader"), attributes);
   5.105 +		}
   5.106 +	}
   5.107  }