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

org.apache.camel.xml.io.XMLWriter Maven / Gradle / Ivy

There is a newer version: 4.9.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.camel.xml.io;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.regex.Pattern;

/**
 * XML writer which emits nicely formatted documents.
 */
public class XMLWriter {

    private static final Pattern LOWERS = Pattern.compile("([\000-\037])");

    private final Writer writer;
    private final String lineIndenter;
    private final String lineSeparator;
    private final String encoding;
    private final String docType;
    private final Deque elements = new ArrayDeque<>();
    private boolean tagInProgress;
    private int depth;
    private boolean readyForNewLine;
    private boolean tagIsEmpty;

    /**
     * @param writer not null
     */
    public XMLWriter(Writer writer) throws IOException {
        this(writer, null, null);
    }

    /**
     * @param writer       not null
     * @param lineIndenter could be null, but the normal way is some spaces.
     */
    public XMLWriter(Writer writer, String lineIndenter) throws IOException {
        this(writer, lineIndenter, null, null);
    }

    /**
     * @param writer   not null
     * @param encoding could be null or invalid.
     * @param doctype  could be null.
     */
    public XMLWriter(Writer writer, String encoding, String doctype) throws IOException {
        this(writer, null, encoding, doctype);
    }

    /**
     * @param writer       not null
     * @param lineIndenter could be null, but the normal way is some spaces.
     * @param encoding     could be null or invalid.
     * @param doctype      could be null.
     */
    public XMLWriter(Writer writer, String lineIndenter, String encoding, String doctype) throws IOException {
        this(writer, lineIndenter, null, encoding, doctype);
    }

    /**
     * @param writer        not null
     * @param lineIndenter  could be null, but the normal way is some spaces.
     * @param lineSeparator could be null, but the normal way is valid line separator ("\n" on UNIX).
     * @param encoding      could be null or invalid.
     * @param doctype       could be null.
     */
    public XMLWriter(Writer writer, String lineIndenter, String lineSeparator,
                     String encoding, String doctype) throws IOException {
        this.writer = writer;
        this.lineIndenter = lineIndenter != null ? lineIndenter : "    ";
        this.lineSeparator = validateLineSeparator(lineSeparator);
        this.encoding = encoding;
        this.docType = doctype;
        if (doctype != null || encoding != null) {
            writeDocumentHeaders();
        }
    }

    private static String validateLineSeparator(String lineSeparator) {
        String ls = lineSeparator != null ? lineSeparator : System.lineSeparator();
        if (!(ls.equals("\n") || ls.equals("\r") || ls.equals("\r\n"))) {
            throw new IllegalArgumentException("Requested line separator is invalid.");
        }
        return ls;
    }

    /**
     * {@inheritDoc}
     */
    public void startElement(String name) throws IOException {
        tagIsEmpty = false;
        finishTag();
        write("<");
        write(name);
        elements.addLast(name);
        tagInProgress = true;
        depth++;
        readyForNewLine = true;
        tagIsEmpty = true;
    }

    /**
     * {@inheritDoc}
     */
    public void writeText(String text) throws IOException {
        writeText(text, true);
    }

    /**
     * {@inheritDoc}
     */
    public void writeMarkup(String text) throws IOException {
        writeText(text, false);
    }

    private void writeText(String text, boolean escapeXml) throws IOException {
        readyForNewLine = false;
        tagIsEmpty = false;
        finishTag();
        if (escapeXml) {
            text = escapeXml(text);
        }
        write(unifyLineSeparators(text));
    }

    /**
     * Parses the given String and replaces all occurrences of '\n', '\r' and '\r\n' with the system line separator.
     *
     * @param  s                        a not null String
     * @return                          a String that contains only System line separators.
     * @throws IllegalArgumentException if ls is not '\n', '\r' and '\r\n' characters.
     * @since                           1.5.7
     */
    private String unifyLineSeparators(String s) {
        if (s == null) {
            return null;
        }
        int length = s.length();
        StringBuilder buffer = new StringBuilder(length);
        for (int i = 0; i < length; i++) {
            if (s.charAt(i) == '\r') {
                if ((i + 1) < length && s.charAt(i + 1) == '\n') {
                    i++;
                }
                buffer.append(lineSeparator);
            } else if (s.charAt(i) == '\n') {
                buffer.append(lineSeparator);
            } else {
                buffer.append(s.charAt(i));
            }
        }
        return buffer.toString();
    }

    private static String escapeXml(String text) {
        return text
                .replace("&", "&")
                .replace("<", "<")
                .replace(">", ">");
    }

    private static String escapeXmlAttribute(String text) {
        text = escapeXml(text)
                .replace("\"", """)
                .replace("'", "'");
        // Windows
        text = text.replace("\r\n", "
");
        // Non printable characters
        text = LOWERS.matcher(text).replaceAll(r -> "&#" + Integer.toString(r.group(1).charAt(0)) + ";");
        return text;
    }

    /**
     * {@inheritDoc}
     */
    public void addAttribute(String key, String value) throws IOException {
        write(" ");
        write(key);
        write("=\"");
        write(escapeXmlAttribute(value));
        write("\"");
    }

    /**
     * {@inheritDoc}
     */
    public void endElement(String name) throws IOException {
        depth--;

        if (tagIsEmpty) {
            write("/");
            readyForNewLine = false;
            finishTag();
            elements.removeLast();
        } else {
            finishTag();
            write("");
        }

        readyForNewLine = true;
    }

    /**
     * Write a string to the underlying writer
     */
    private void write(String str) throws IOException {
        getWriter().write(str);
    }

    private void finishTag() throws IOException {
        if (tagInProgress) {
            write(">");
        }
        tagInProgress = false;
        if (readyForNewLine) {
            endOfLine();
        }
        readyForNewLine = false;
        tagIsEmpty = false;
    }

    /**
     * Get the string used as line indenter
     *
     * @return the line indenter
     */
    protected String getLineIndenter() {
        return lineIndenter;
    }

    /**
     * Get the string used as line separator or LS if not set.
     *
     * @return the line separator
     * @see    System#lineSeparator()
     */
    protected String getLineSeparator() {
        return lineSeparator;
    }

    /**
     * Write the end of line character (using specified line separator) and start new line with indentation
     *
     * @see #getLineIndenter()
     * @see #getLineSeparator()
     */
    protected void endOfLine() throws IOException {
        write(getLineSeparator());
        for (int i = 0; i < getDepth(); i++) {
            write(getLineIndenter());
        }
    }

    private void writeDocumentHeaders() throws IOException {
        write("");
        endOfLine();
        if (getDocType() != null) {
            write("");
            endOfLine();
        }
    }

    /**
     * Get the underlying writer
     *
     * @return the underlying writer
     */
    protected Writer getWriter() {
        return writer;
    }

    /**
     * Get the current depth in the xml indentation
     *
     * @return the current depth
     */
    protected int getDepth() {
        return depth;
    }

    /**
     * Get the current encoding in the xml
     *
     * @return the current encoding
     */
    protected String getEncoding() {
        return encoding;
    }

    /**
     * Get the docType in the xml
     *
     * @return the current docType
     */
    public String getDocType() {
        return docType;
    }

    /**
     * @return the current elementStack;
     */
    public Deque getElements() {
        return elements;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy