    001    /* ========================================================================
    002     * JCommon : a free general purpose class library for the Java(tm) platform
    003     * ========================================================================
    004     *
    005     * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
    006     *
    007     * Project Info:  http://www.jfree.org/jcommon/index.html
    008     *
    009     * This library is free software; you can redistribute it and/or modify it
    010     * under the terms of the GNU Lesser General Public License as published by
    011     * the Free Software Foundation; either version 2.1 of the License, or
    012     * (at your option) any later version.
    013     *
    014     * This library is distributed in the hope that it will be useful, but
    015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017     * License for more details.
    018     *
    019     * You should have received a copy of the GNU Lesser General Public
    020     * License along with this library; if not, write to the Free Software
    021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022     * USA.
    023     *
    024     * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
    025     * in the United States and other countries.]
    026     *
    027     * ---------------------
    028     * XMLWriterSupport.java
    029     * ---------------------
    030     * (C)opyright 2003-2005, by Thomas Morgner and Contributors.
    031     *
    032     * Original Author:  Thomas Morgner;
    033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034     *
    035     * $Id: XMLWriterSupport.java,v 1.6 2005/11/08 14:35:52 mungady Exp $
    036     *
    037     * Changes
    038     * -------
    039     * 21-Jun-2003 : Initial version (TM);
    040     * 26-Nov-2003 : Updated Javadocs (DG);
    041     *
    042     */
    043
    044    package org.jfree.xml.writer;
    045
    046    import java.io.IOException;
    047    import java.io.Writer;
    048    import java.util.Enumeration;
    049    import java.util.Iterator;
    050    import java.util.Properties;
    051
    052    /**
    053     * A support class for writing XML files.
    054     *
    055     * @author Thomas Morgner
    056     */
    057    public class XMLWriterSupport {
    058
    059        /** A constant for controlling the indent function. */
    060        public static final int OPEN_TAG_INCREASE = 1;
    061
    062        /** A constant for controlling the indent function. */
    063        public static final int CLOSE_TAG_DECREASE = 2;
    064
    065        /** A constant for controlling the indent function. */
    066        public static final int INDENT_ONLY = 3;
    067
    068        /** A constant for close. */
    069        public static final boolean CLOSE = true;
    070
    071        /** A constant for open. */
    072        public static final boolean OPEN = false;
    073
    074        /** The line separator. */
    075        private static String lineSeparator;
    076
    077        /** A list of safe tags. */
    078        private SafeTagList safeTags;
    079
    080        /** The indent level for that writer. */
    081        private int indentLevel;
    082
    083        /** The indent string. */
    084        private String indentString;
    085
    086        /**
    087         * A flag indicating whether to force a linebreak before printing the next
    088         * tag.
    089         */
    090        private boolean newLineOk;
    091
    092        /**
    093         * Default Constructor. The created XMLWriterSupport will not have no safe
    094         * tags and starts with an indention level of 0.
    095         */
    096        public XMLWriterSupport() {
    097            this(new SafeTagList(), 0);
    098        }
    099
    100        /**
    101         * Creates a new support instance.
    102         *
    103         * @param safeTags  tags that are safe for line breaks.
    104         * @param indentLevel  the index level.
    105         */
    106        public XMLWriterSupport(final SafeTagList safeTags, final int indentLevel) {
    107            this(safeTags, indentLevel, "    ");
    108        }
    109
    110        /**
    111         * Creates a new support instance.
    112         *
    113         * @param safeTags  the tags that are safe for line breaks.
    114         * @param indentLevel  the indent level.
    115         * @param indentString  the indent string.
    116         */
    117        public XMLWriterSupport(final SafeTagList safeTags, final int indentLevel,
    118                final String indentString) {
    119            if (indentString == null) {
    120                throw new NullPointerException("IndentString must not be null");
    121            }
    122
    123            this.safeTags = safeTags;
    124            this.indentLevel = indentLevel;
    125            this.indentString = indentString;
    126        }
    127
    128        /**
    129         * Starts a new block by increasing the indent level.
    130         *
    131         * @throws IOException if an IO error occurs.
    132         */
    133        public void startBlock() throws IOException {
    134            this.indentLevel++;
    135            allowLineBreak();
    136        }
    137
    138        /**
    139         * Ends the current block by decreasing the indent level.
    140         *
    141         * @throws IOException if an IO error occurs.
    142         */
    143        public void endBlock() throws IOException {
    144            this.indentLevel--;
    145            allowLineBreak();
    146        }
    147
    148        /**
    149         * Forces a linebreak on the next call to writeTag or writeCloseTag.
    150         *
    151         * @throws IOException if an IO error occurs.
    152         */
    153        public void allowLineBreak() throws IOException {
    154            this.newLineOk = true;
    155        }
    156
    157        /**
    158         * Returns the line separator.
    159         *
    160         * @return the line separator.
    161         */
    162        public static String getLineSeparator() {
    163            if (lineSeparator == null) {
    164                try {
    165                    lineSeparator = System.getProperty("line.separator", "\n");
    166                }
    167                catch (SecurityException se) {
    168                    lineSeparator = "\n";
    169                }
    170            }
    171            return lineSeparator;
    172        }
    173
    174        /**
    175         * Writes an opening XML tag that has no attributes.
    176         *
    177         * @param w  the writer.
    178         * @param name  the tag name.
    179         *
    180         * @throws java.io.IOException if there is an I/O problem.
    181         */
    182        public void writeTag(final Writer w, final String name) throws IOException {
    183            if (this.newLineOk) {
    184                w.write(getLineSeparator());
    185            }
    186            indent(w, OPEN_TAG_INCREASE);
    187
    188            w.write("<");
    189            w.write(name);
    190            w.write(">");
    191            if (getSafeTags().isSafeForOpen(name)) {
    192                w.write(getLineSeparator());
    193            }
    194        }
    195
    196        /**
    197         * Writes a closing XML tag.
    198         *
    199         * @param w  the writer.
    200         * @param tag  the tag name.
    201         *
    202         * @throws java.io.IOException if there is an I/O problem.
    203         */
    204        public void writeCloseTag(final Writer w, final String tag)
    205                throws IOException {
    206            // check whether the tag contains CData - we ma not indent such tags
    207            if (this.newLineOk || getSafeTags().isSafeForOpen(tag)) {
    208                if (this.newLineOk) {
    209                    w.write(getLineSeparator());
    210                }
    211                indent(w, CLOSE_TAG_DECREASE);
    212            }
    213            else {
    214                decreaseIndent();
    215            }
    216            w.write("</");
    217            w.write(tag);
    218            w.write(">");
    219            if (getSafeTags().isSafeForClose(tag)) {
    220                w.write(getLineSeparator());
    221            }
    222            this.newLineOk = false;
    223        }
    224
    225        /**
    226         * Writes an opening XML tag with an attribute/value pair.
    227         *
    228         * @param w  the writer.
    229         * @param name  the tag name.
    230         * @param attributeName  the attribute name.
    231         * @param attributeValue  the attribute value.
    232         * @param close  controls whether the tag is closed.
    233         *
    234         * @throws java.io.IOException if there is an I/O problem.
    235         */
    236        public void writeTag(final Writer w, final String name,
    237                final String attributeName, final String attributeValue,
    238                final boolean close) throws IOException {
    239            final AttributeList attr = new AttributeList();
    240            if (attributeName != null) {
    241                attr.setAttribute(attributeName, attributeValue);
    242            }
    243            writeTag(w, name, attr, close);
    244        }
    245
    246        /**
    247         * Writes an opening XML tag along with a list of attribute/value pairs.
    248         *
    249         * @param w  the writer.
    250         * @param name  the tag name.
    251         * @param attributes  the attributes.
    252         * @param close  controls whether the tag is closed.
    253         *
    254         * @throws java.io.IOException if there is an I/O problem.
    255         * @deprecated use the attribute list instead of the properties.
    256         */
    257        public void writeTag(final Writer w, final String name,
    258                final Properties attributes, final boolean close)
    259                throws IOException {
    260            final AttributeList attList = new AttributeList();
    261            final Enumeration keys = attributes.keys();
    262            while (keys.hasMoreElements()) {
    263                final String key = (String) keys.nextElement();
    264                attList.setAttribute(key, attributes.getProperty(key));
    265            }
    266            writeTag(w, name, attList, close);
    267        }
    268
    269        /**
    270         * Writes an opening XML tag along with a list of attribute/value pairs.
    271         *
    272         * @param w  the writer.
    273         * @param name  the tag name.
    274         * @param attributes  the attributes.
    275         * @param close  controls whether the tag is closed.
    276         *
    277         * @throws java.io.IOException if there is an I/O problem.
    278         */
    279        public void writeTag(final Writer w, final String name,
    280                final AttributeList attributes, final boolean close)
    281                throws IOException {
    282
    283            if (this.newLineOk) {
    284                w.write(getLineSeparator());
    285                this.newLineOk = false;
    286            }
    287            indent(w, OPEN_TAG_INCREASE);
    288
    289            w.write("<");
    290            w.write(name);
    291            final Iterator keys = attributes.keys();
    292            while (keys.hasNext()) {
    293                final String key = (String) keys.next();
    294                final String value = attributes.getAttribute(key);
    295                w.write(" ");
    296                w.write(key);
    297                w.write("=\"");
    298                w.write(normalize(value));
    299                w.write("\"");
    300            }
    301            if (close) {
    302                w.write("/>");
    303                if (getSafeTags().isSafeForClose(name)) {
    304                    w.write(getLineSeparator());
    305                }
    306                decreaseIndent();
    307            }
    308            else {
    309                w.write(">");
    310                if (getSafeTags().isSafeForOpen(name)) {
    311                    w.write(getLineSeparator());
    312                }
    313            }
    314        }
    315
    316        /**
    317         * Normalises a string, replacing certain characters with their escape
    318         * sequences so that the XML text is not corrupted.
    319         *
    320         * @param s  the string.
    321         *
    322         * @return the normalised string.
    323         */
    324        public static String normalize(final String s) {
    325            if (s == null) {
    326                return "";
    327            }
    328            final StringBuffer str = new StringBuffer();
    329            final int len = s.length();
    330
    331            for (int i = 0; i < len; i++) {
    332                final char ch = s.charAt(i);
    333
    334                switch (ch) {
    335                    case '<':
    336                        {
    337                            str.append("&lt;");
    338                            break;
    339                        }
    340                    case '>':
    341                        {
    342                            str.append("&gt;");
    343                            break;
    344                        }
    345                    case '&':
    346                        {
    347                            str.append("&amp;");
    348                            break;
    349                        }
    350                    case '"':
    351                        {
    352                            str.append("&quot;");
    353                            break;
    354                        }
    355                    case '\n':
    356                        {
    357                            if (i > 0) {
    358                                final char lastChar = str.charAt(str.length() - 1);
    359
    360                                if (lastChar != '\r') {
    361                                    str.append(getLineSeparator());
    362                                }
    363                                else {
    364                                    str.append('\n');
    365                                }
    366                            }
    367                            else {
    368                                str.append(getLineSeparator());
    369                            }
    370                            break;
    371                        }
    372                    default :
    373                        {
    374                            str.append(ch);
    375                        }
    376                }
    377            }
    378
    379            return (str.toString());
    380        }
    381
    382        /**
    383         * Indent the line. Called for proper indenting in various places.
    384         *
    385         * @param writer the writer which should receive the indentention.
    386         * @param increase the current indent level.
    387         * @throws java.io.IOException if writing the stream failed.
    388         */
    389        public void indent(final Writer writer, final int increase)
    390                throws IOException {
    391            if (increase == CLOSE_TAG_DECREASE) {
    392                decreaseIndent();
    393            }
    394            for (int i = 0; i < this.indentLevel; i++) {
    395                writer.write(this.indentString); // 4 spaces, we could also try tab,
    396                // but I do not know whether this works
    397                // with our XML edit pane
    398            }
    399            if (increase == OPEN_TAG_INCREASE) {
    400                increaseIndent();
    401            }
    402        }
    403
    404        /**
    405         * Returns the current indent level.
    406         *
    407         * @return the current indent level.
    408         */
    409        public int getIndentLevel() {
    410            return this.indentLevel;
    411        }
    412
    413        /**
    414         * Increases the indention by one level.
    415         */
    416        protected void increaseIndent() {
    417            this.indentLevel++;
    418        }
    419
    420        /**
    421         * Decreates the indention by one level.
    422         */
    423        protected void decreaseIndent() {
    424            this.indentLevel--;
    425        }
    426
    427        /**
    428         * Returns the list of safe tags.
    429         *
    430         * @return The list.
    431         */
    432        public SafeTagList getSafeTags() {
    433            return this.safeTags;
    434        }
    435    }