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.5.ee
Show newest version
/*******************************************************************************
 * Copyright (C) 2022-2023 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.
     */
    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