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

com.wavemaker.commons.util.XMLWriter Maven / Gradle / Ivy

There is a newer version: 11.9.4
Show newest version
/**
 * Copyright (C) 2020 WaveMaker, Inc.
 * 

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* http://www.apache.org/licenses/LICENSE-2.0 *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wavemaker.commons.util; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import org.apache.commons.lang3.StringEscapeUtils; /** * API for writing XML. * * @author Simon Toens */ public class XMLWriter { private static final String SEPARATOR = "\n"; private static final int DEFAULT_INDENT = 2; private static final int DEFAULT_MAX_ATTRS_ON_SAME_LINE = 2; private int startIndent; private static final String ENCODING = "utf-8"; private String currentShortNS; private boolean wroteFirstElement; private boolean hasElements; private boolean incompleteOpenTag; private boolean hasAttributes; private final SortedMap namespaces = new TreeMap<>(); // decides if we write closing element on same line as opening element // or on new line. this applies for both and .../> private boolean closeOnNewLine; private final Stack elementStack = new Stack(); private final PrintWriter pw; // configurable at construction time private final int indent; private final int maxAttrsOnSameLine; // configurable with setter method // a // vs. // // a // private boolean textOnSameLineAsParentElement = true; public XMLWriter(PrintWriter pw) { this(pw, DEFAULT_INDENT); } public XMLWriter(PrintWriter pw, int indent) { this(pw, indent, DEFAULT_MAX_ATTRS_ON_SAME_LINE); } public XMLWriter(PrintWriter pw, int indent, int maxAttrsOnSameLine) { this.indent = indent; this.maxAttrsOnSameLine = maxAttrsOnSameLine; this.pw = pw; } /** * Calls flush on underlying PrintWriter. */ public void flush() { this.pw.flush(); } /** * Writes DOCTYPE, publicID, privateID. Can only be called before adding any elements. * * @param publicID * @param privateID */ public void addDoctype(String doctypeName, String publicID, String systemID) { if (this.hasElements) { throw new MalformedXMLRuntimeException("Cannot init document after elements have been added"); } StringBuilder dtdString = new StringBuilder(); dtdString.append("").append(SEPARATOR); writeGeneric(dtdString.toString()); } /** * Add version. Can only be called before adding any elements. */ public void addVersion() { addVersion(false); } public void addVersion(boolean standalone) { if (this.hasElements) { throw new MalformedXMLRuntimeException("Cannot write version after elements have been added"); } this.pw.print(""); this.pw.print(SEPARATOR); } public void setCurrentShortNS(String s) { if (!this.namespaces.containsKey(s)) { throw new MalformedXMLRuntimeException("Short NS \"" + s + "\" has not been declared. Known short NS: " + this.namespaces.keySet()); } this.currentShortNS = s; } public void unsetCurrentShortNS() { this.currentShortNS = null; } /** * The current element will be closed on a new line, and attributes added will each be on a new line. */ public void forceCloseOnNewLine() { this.closeOnNewLine = true; } public boolean willCloseOnNewLine() { return this.closeOnNewLine; } public void setStartIndent(int startIndent) { this.startIndent = startIndent; } /** * Switches the behavior for addElementWithTextChild. a vs.
* a
*
*/ public void setTextOnSameLineAsParentElement(boolean b) { this.textOnSameLineAsParentElement = b; } public void addNamespace(String shortNS, String longNS) { this.namespaces.put(shortNS, longNS); } public void addComment(String comment) { StringBuilder sb = new StringBuilder(); if (this.hasElements) { finishIncompleteTag(); sb.append(SEPARATOR); } sb.append(getIndent()).append(""); if (!this.hasElements) { sb.append(SEPARATOR); } writeGeneric(sb.toString()); } /** * Adds attribute (name and value) to current XML element. */ public void addAttribute(String name, String value) { if (!this.incompleteOpenTag) { throw new MalformedXMLRuntimeException("Illegal call to addAttribute"); } this.hasAttributes = true; if (this.closeOnNewLine) { this.pw.print(SEPARATOR); this.pw.print(getIndent()); } else { this.pw.print(" "); } this.pw.print(name); this.pw.print("=\""); this.pw.print(value); this.pw.print("\""); } /** * Adds attributes to current XML element, represented as Map. Uses the keys as attribute names and corresponding * elements as attribute values. Calls String.valueOf(...) on keys and values. */ public void addAttribute(Map attributes) { String[] attributesArray = new String[attributes.size() * 2]; int index = 0; for (Map.Entry entry : attributes.entrySet()) { attributesArray[index] = entry.getKey(); attributesArray[index + 1] = entry.getValue(); index += 2; } addAttribute(attributesArray); } /** * Adds attributes to current XML element. Attribute names and values are passed in as String Array, using the * following format: {n1, v1, n2, v2, n3, v3, ...} */ public void addAttribute(String... attributes) { // if no attributes yet for this element, we can decided // on the formatting (all attributes on same line or not) if (!this.hasAttributes && attributes.length / 2 > this.maxAttrsOnSameLine) { this.closeOnNewLine = true; } for (int i = 0; i < attributes.length; i += 2) { String key = attributes[i]; String value = attributes[i + 1]; addAttribute(key, value); } } /** * Adds nested closed elements. */ public void addNestedElements(String... elementNames) { for (String elementName : elementNames) { addElement(elementName); } } /** * Adds many closed elements. */ public void addClosedElements(String... elementNames) { for (String elementName : elementNames) { addClosedElement(elementName); } } /** * Adds a single closed element. */ public void addClosedElement(String elementName) { addElement(elementName); closeElement(); } /** * Adds a closed element with attributes */ public void addClosedElement(String elementName, String... attributes) { addElement(elementName, attributes); closeElement(); } /** * Writes a new XML element to PrintWriter. If another XML element has been written and not closed, writes this * element as a child. */ public void addElement(String elementName) { this.hasElements = true; finishIncompleteTag(); boolean addNamespaces = !this.wroteFirstElement; if (this.wroteFirstElement) { this.pw.print(SEPARATOR); } else { this.wroteFirstElement = true; } this.pw.print(getIndent()); this.pw.print("<"); if (this.currentShortNS != null) { elementName = qualify(elementName, this.currentShortNS); } this.pw.print(elementName); this.elementStack.push(elementName); this.incompleteOpenTag = true; this.hasAttributes = false; this.closeOnNewLine = false; if (addNamespaces && !this.namespaces.isEmpty()) { // prepend short NS with "xmlns:", then add as attributes String[] ns = new String[this.namespaces.size() * 2]; int i = 0; for (Map.Entry e : this.namespaces.entrySet()) { ns[i] = qualify(e.getKey(), "xmlns"); ns[i + 1] = e.getValue(); i += 2; } // for short->long ns mappings, put each attr on a new line this.closeOnNewLine = true; addAttribute(ns); } } /** * Convenience method for passing parent and child text element. The result is
* <elementName>textChild</elementName>
* if setTextOnSameLineAsParentElement is set to true on this instance of XMLWriter.
* * Otherwise the result is:
* <elementName>
* textChild
* </elementName> */ public void addElement(String elementName, String textChild) { addElement(elementName); addText(textChild, !this.textOnSameLineAsParentElement); closeElement(!this.textOnSameLineAsParentElement); } /** * Writes a new XML element to PrintWriter. If another XML element has been written and not closed, writes this * element as a child. Also adds passed attributes. */ public void addElement(String elementName, String... attributes) { addElement(elementName); addAttribute(attributes); } /** * Writes a new, closed, XML element to PrintWriter. If another XML element has been written and not closed, writes * this element as a child. Adds passed attributes and character data. */ public void addClosedTextElement(String elementName, String text, String... attributes) { addElement(elementName); addAttribute(attributes); addText(text, false); closeElement(false); } /** * Writes a new XML element to PrintWriter. If another XML element has been written and not closed, writes this * element as a child. Also adds passed attributes. */ public void addElement(String elementName, Map attributes) { addElement(elementName); addAttribute(attributes); } /** * Closes the last XML element that has been written. */ public void closeElement() { closeElement(true); } public void closeElement(boolean elementOnNewLine) { if (this.elementStack.isEmpty()) { throw new MalformedXMLRuntimeException("Illegal call to closeElement"); } String element = this.elementStack.pop(); if (this.incompleteOpenTag) { if (this.closeOnNewLine) { this.pw.print(SEPARATOR); this.pw.print(getIndent()); } this.pw.print("/>"); this.incompleteOpenTag = false; } else { if (elementOnNewLine) { this.pw.print(SEPARATOR); this.pw.print(getIndent()); } this.pw.print(""); } this.hasAttributes = false; } /** * Writes Text as child element to the current element. */ public void addText(String in) { addText(in, true); } public void addText(String in, boolean onNewLine) { if (in.trim().length() == 0) { return; } finishIncompleteTag(); if (onNewLine) { this.pw.print(SEPARATOR); this.pw.print(getIndent()); } this.pw.print(StringEscapeUtils.escapeXml(in.trim())); } public void addCDATA(String in) { if (in.trim().length() == 0) { return; } finishIncompleteTag(); StringBuilder sb = new StringBuilder(); sb.append(SEPARATOR).append(getIndent()).append(""); writeGeneric(sb.toString()); } /** * Closes all XML elements that have been added, and not yet closed, and flushes the underlying PrintWriter. */ public void finish() { closeAll(); this.pw.flush(); } /** * @return The current indentation */ public String getIndent() { return getIndent(getStackSize()); } public String getLineSep() { return SEPARATOR; } private void closeAll() { while (!this.elementStack.isEmpty()) { closeElement(); } } private void finishIncompleteTag() { if (this.incompleteOpenTag) { this.pw.print(">"); this.incompleteOpenTag = false; } } private void writeGeneric(String in) { this.pw.print(in); } private String getIndent(int numUnits) { StringBuilder indentString = new StringBuilder(); int max = this.startIndent + numUnits * this.indent; for (int i = 0; i < max; i++) { indentString.append(" "); } return indentString.toString(); } private String getDefaultIndent() { return getIndent(1); } private int getStackSize() { return this.elementStack.size(); } private String qualify(String name, String ns) { return ns + ":" + name; } private static class MalformedXMLRuntimeException extends RuntimeException { private static final long serialVersionUID = 1L; private MalformedXMLRuntimeException(String message) { super(message); } } private static class Stack extends ArrayList { private static final long serialVersionUID = 1L; public void push(String s) { super.add(0, s); } public String pop() { return super.remove(0); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy