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

software.amazon.awssdk.protocols.xml.internal.marshall.XmlWriter Maven / Gradle / Ivy

There is a newer version: 2.29.15
Show newest version
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.protocols.xml.internal.marshall;

import java.io.IOException;
import java.io.Writer;
import java.nio.ByteBuffer;
import java.util.Date;
import java.util.Map;
import java.util.Stack;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.core.exception.SdkClientException;
import software.amazon.awssdk.utils.BinaryUtils;
import software.amazon.awssdk.utils.DateUtils;
import software.amazon.awssdk.utils.StringUtils;

/**
 * Utility for creating easily creating XML documents, one element at a time.
 */
@SdkInternalApi
class XmlWriter {

    static final String[] ESCAPE_SEARCHES = {
        // Ampersands should always be the first to escape
        "&", "\"", "'", "<", ">", "\r", "\n"
    };

    static final String[] ESCAPE_REPLACEMENTS = {
        "&", """, "'", "<", ">", "
", "
"
    };

    /** Standard XML prolog to add to the beginning of each XML document. */
    private static final String PROLOG = "";

    private static final String[] UNESCAPE_SEARCHES = {
        // Ampersands should always be the last to unescape
        """, "'", "<", ">", "
", "
", "&"
    };

    private static final String[] UNESCAPE_REPLACEMENTS = {
        "\"", "'", "<", ">", "\r", "\n", "&"
    };

    /** The writer to which the XML document created by this writer will be written. */
    private final Writer writer;

    /** Optional XML namespace attribute value to include in the root element. */
    private final String xmlns;

    private Stack elementStack = new Stack<>();
    private boolean rootElement = true;
    private boolean writtenProlog = false;

    /**
     * Creates a new XMLWriter, ready to write an XML document to the specified
     * writer. The root element in the XML document will specify an xmlns
     * attribute with the specified namespace parameter.
     *
     * @param w
     *            The writer this XMLWriter will write to.
     * @param xmlns
     *            The XML namespace to include in the xmlns attribute of the
     *            root element.
     */
    XmlWriter(Writer w, String xmlns) {
        this.writer = w;
        this.xmlns = xmlns;
    }

    /**
     * Starts a new element with the specified name at the current position in
     * the in-progress XML document.
     *
     * @param element
     *            The name of the new element.
     *
     * @return This XMLWriter so that additional method calls can be chained
     *         together.
     */
    XmlWriter startElement(String element) {
        // Only append the PROLOG if there is XML written.
        if (!writtenProlog) {
            writtenProlog = true;
            append(PROLOG);
        }
        append("<" + element);
        if (rootElement && xmlns != null) {
            append(" xmlns=\"" + xmlns + "\"");
            rootElement = false;
        }
        append(">");
        elementStack.push(element);
        return this;
    }

    /**
     * Start to write an element with xml attributes.
     *
     * @param element the elment to write
     * @param attributes the xml attribtues
     * @return the XmlWriter
     */
    XmlWriter startElement(String element, Map attributes) {
        append("<" + element);
        for (Map.Entry attribute: attributes.entrySet()) {
            append(" " + attribute.getKey() + "=\"" + attribute.getValue() + "\"");
        }
        append(">");
        elementStack.push(element);
        return this;
    }

    /**
     * Closes the last opened element at the current position in the in-progress
     * XML document.
     *
     * @return This XMLWriter so that additional method calls can be chained
     *         together.
     */
    XmlWriter endElement() {
        String lastElement = elementStack.pop();
        append("");
        return this;
    }

    /**
     * Adds the specified value as text to the current position of the in
     * progress XML document.
     *
     * @param s
     *            The text to add to the XML document.
     *
     * @return This XMLWriter so that additional method calls can be chained
     *         together.
     */
    public XmlWriter value(String s) {
        append(escapeXmlEntities(s));
        return this;
    }

    /**
     * Adds the specified value as Base64 encoded text to the current position of the in
     * progress XML document.
     *
     * @param b
     *            The binary data to add to the XML document.
     *
     * @return This XMLWriter so that additional method calls can be chained
     *         together.
     */
    public XmlWriter value(ByteBuffer b) {
        append(escapeXmlEntities(BinaryUtils.toBase64(BinaryUtils.copyBytesFrom(b))));
        return this;
    }

    /**
     * Adds the specified date as text to the current position of the
     * in-progress XML document.
     *
     * @param date
     *            The date to add to the XML document.
     *
     * @return This XMLWriter so that additional method calls can be chained
     *         together.
     */
    public XmlWriter value(Date date) {
        append(escapeXmlEntities(DateUtils.formatIso8601Date(date.toInstant())));
        return this;
    }

    /**
     * Adds the string representation of the specified object to the current
     * position of the in progress XML document.
     *
     * @param obj
     *            The object to translate to a string and add to the XML
     *            document.
     *
     * @return This XMLWriter so that additional method calls can be chained
     *         together.
     */
    public XmlWriter value(Object obj) {
        append(escapeXmlEntities(obj.toString()));
        return this;
    }

    private void append(String s) {
        try {
            writer.append(s);
        } catch (IOException e) {
            throw SdkClientException.builder().message("Unable to write XML document").cause(e).build();
        }
    }

    protected String escapeXmlEntities(String s) {
        // Unescape any escaped characters.
        if (s.contains("&")) {
            s = StringUtils.replaceEach(s, UNESCAPE_SEARCHES, UNESCAPE_REPLACEMENTS);
        }
        return StringUtils.replaceEach(s, ESCAPE_SEARCHES, ESCAPE_REPLACEMENTS);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy