001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.io;
003
004import java.io.Closeable;
005import java.io.IOException;
006import java.io.PrintWriter;
007import java.util.HashMap;
008import java.util.Map;
009
010/**
011 * Helper class to use for xml outputting classes.
012 *
013 * @author imi
014 */
015public class XmlWriter implements Closeable {
016
017    protected final PrintWriter out;
018
019    /**
020     * Constructs a new {@code XmlWriter}.
021     * @param out print writer
022     */
023    public XmlWriter(PrintWriter out) {
024        this.out = out;
025    }
026
027    /**
028     * Flushes the stream.
029     */
030    public void flush() {
031        if (out != null) {
032            out.flush();
033        }
034    }
035
036    /**
037     * Encode the given string in XML1.0 format.
038     * Optimized to fast pass strings that don't need encoding (normal case).
039     *
040     * @param unencoded the unencoded input string
041     * @return XML1.0 string
042     */
043    public static String encode(String unencoded) {
044        return encode(unencoded, false);
045    }
046
047    /**
048     * Encode the given string in XML1.0 format.
049     * Optimized to fast pass strings that don't need encoding (normal case).
050     *
051     * @param unencoded the unencoded input string
052     * @param keepApos true if apostrophe sign should stay as it is (in order to work around
053     * a Java bug that renders
054     *     new JLabel("<html>'</html>")
055     * literally as 6 character string, see #7558)
056     * @return XML1.0 string
057     */
058    public static String encode(String unencoded, boolean keepApos) {
059        StringBuilder buffer = null;
060        if (unencoded != null) {
061            for (int i = 0; i < unencoded.length(); ++i) {
062                String encS = null;
063                if (!keepApos || unencoded.charAt(i) != '\'') {
064                    encS = XmlWriter.encoding.get(unencoded.charAt(i));
065                }
066                if (encS != null) {
067                    if (buffer == null) {
068                        buffer = new StringBuilder(unencoded.substring(0, i));
069                    }
070                    buffer.append(encS);
071                } else if (buffer != null) {
072                    buffer.append(unencoded.charAt(i));
073                }
074            }
075        }
076        return (buffer == null) ? unencoded : buffer.toString();
077    }
078
079    /**
080     * The output writer to save the values to.
081     */
082    private static final Map<Character, String> encoding = new HashMap<>();
083    static {
084        encoding.put('<', "&lt;");
085        encoding.put('>', "&gt;");
086        encoding.put('"', "&quot;");
087        encoding.put('\'', "&apos;");
088        encoding.put('&', "&amp;");
089        encoding.put('\n', "&#xA;");
090        encoding.put('\r', "&#xD;");
091        encoding.put('\t', "&#x9;");
092    }
093
094    @Override
095    public void close() throws IOException {
096        if (out != null) {
097            out.close();
098        }
099    }
100}