1 /*
   2  * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package test.auctionportal;
  24 
  25 import java.io.OutputStream;
  26 import java.io.OutputStreamWriter;
  27 import java.io.PrintWriter;
  28 import java.io.UnsupportedEncodingException;
  29 import java.util.stream.Collectors;
  30 
  31 import org.xml.sax.Attributes;
  32 import org.xml.sax.SAXException;
  33 import org.xml.sax.SAXParseException;
  34 import org.xml.sax.ext.LexicalHandler;
  35 import org.xml.sax.helpers.DefaultHandler;
  36 
  37 /**
  38  * A SAX2 event handlers.
  39  * This SAX2 ContentHandler receives callback event then print whole document
  40  * that is parsed.
  41  */
  42 public class XInclHandler extends DefaultHandler implements LexicalHandler {
  43     /**
  44      * Print writer.
  45      */
  46     private final PrintWriter fOut;
  47 
  48     /**
  49      * Canonical output.
  50      */
  51     private volatile boolean fCanonical;
  52 
  53     /**
  54      * Element depth.
  55      */
  56     private volatile int fElementDepth;
  57 
  58     /**
  59      * Sets whether output is canonical.
  60      * 
  61      * @param canonical if the output is canonical format.
  62      */
  63     public void setCanonical(boolean canonical) {
  64         fCanonical = canonical;
  65     }
  66 
  67     /**
  68      * Sets the output stream for printing.
  69      * @param stream OutputStream for message output.
  70      * @param encoding File encoding for message output.
  71      * @throws UnsupportedEncodingException if given encoding is an unsupported
  72      *         encoding name or invalid encoding name.
  73      */
  74     public XInclHandler(OutputStream stream, String encoding)
  75             throws UnsupportedEncodingException {
  76         // At least set one encoding.
  77         if (encoding == null) {
  78             encoding = "UTF8";
  79         }
  80 
  81         fOut = new PrintWriter(new OutputStreamWriter(stream, encoding), false);
  82     }
  83 
  84     /**
  85      * Receive notification of the beginning of the document. Write the start
  86      * document tag if it's not canonical mode.
  87      * @exception org.xml.sax.SAXException Any SAX exception, possibly
  88      *            wrapping another exception.
  89      */
  90     @Override
  91     public void startDocument() throws SAXException {
  92         fElementDepth = 0;
  93 
  94         if (!fCanonical) {
  95             writeFlush("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
  96         }
  97     }
  98 
  99     /**
 100      * Receive notification of a processing instruction.
 101      * @param target The processing instruction target.
 102      * @param data The processing instruction data, or null if
 103      *             none is supplied.
 104      * @exception SAXException Any SAX exception, possibly wrapping another 
 105      *            exception.
 106      */
 107     @Override
 108     public void processingInstruction (String target, String data)
 109         throws SAXException {
 110         if (fElementDepth > 0) {
 111             StringBuilder instruction = new StringBuilder("<?").append(target);
 112             if (data != null && data.length() > 0) {
 113                 instruction.append(' ').append(data);
 114             }
 115             instruction.append("?>");
 116             writeFlush(instruction.toString());
 117         }
 118     }
 119 
 120     /**
 121      * Receive notification of the start of an element then write the normalized
 122      * output to the file.
 123      * @param uri The Namespace URI, or the empty string if the
 124      *        element has no Namespace URI or if Namespace
 125      *        processing is not being performed.
 126      * @param local The local name (without prefix), or the
 127      *        empty string if Namespace processing is not being
 128      *        performed.
 129      * @param raw The qualified name (with prefix), or the
 130      *        empty string if qualified names are not available.
 131      * @param attrs The attributes attached to the element.  If
 132      *        there are no attributes, it shall be an empty
 133      *        Attributes object.
 134      * @throws SAXException Any SAX exception, possibly wrapping another 
 135      *         exception.
 136      */
 137     @Override
 138     public void startElement(String uri, String local, String raw,
 139             Attributes attrs) throws SAXException {
 140         fElementDepth++;
 141         StringBuilder start = new StringBuilder().append('<').append(raw);
 142         if (attrs != null) {
 143             for (int i = 0; i < attrs.getLength(); i++) {
 144                 start.append(' ').append(attrs.getQName(i)).append("=\"").
 145                     append(normalizeAndPrint(attrs.getValue(i))).append('"');
 146             }
 147         }
 148         start.append('>');
 149         writeFlush(start.toString());
 150     }
 151 
 152     /**
 153      * Receive notification of character data inside an element and write
 154      * normalized characters to file.
 155      * @param ch The characters.
 156      * @param start The start position in the character array.
 157      * @param length The number of characters to use from the
 158      *               character array.
 159      * @exception org.xml.sax.SAXException Any SAX exception, possibly
 160      *            wrapping another exception.
 161      */
 162     @Override
 163     public void characters(char ch[], int start, int length)
 164             throws SAXException {
 165         writeFlush(normalizeAndPrint(ch, start, length));
 166     }
 167 
 168     /**
 169      * Receiving notification of ignorable whitespace in element content and
 170      * writing normalized ignorable characters to file.
 171      * @param ch The characters.
 172      * @param start The start position in the character array.
 173      * @param length The number of characters to use from the
 174      *               character array.
 175      * @exception org.xml.sax.SAXException Any SAX exception, possibly
 176      *            wrapping another exception.
 177      */
 178     @Override
 179     public void ignorableWhitespace(char ch[], int start, int length)
 180             throws SAXException {
 181         characters(ch, start, length);
 182     }
 183 
 184     /**
 185      * Receive notification of the end of an element and print end element.
 186      *
 187      * @param uri The Namespace URI, or the empty string if the
 188      *        element has no Namespace URI or if Namespace
 189      *        processing is not being performed.
 190      * @param local The local name (without prefix), or the
 191      *        empty string if Namespace processing is not being
 192      *        performed.
 193      * @param raw The qualified name (with prefix), or the
 194      *        empty string if qualified names are not available.
 195      * @throws org.xml.sax.SAXException Any SAX exception, possibly
 196      *            wrapping another exception.
 197      */
 198     @Override
 199     public void endElement(String uri, String local, String raw)
 200             throws SAXException {
 201         fElementDepth--;
 202         writeFlush("</" + raw + ">");
 203     }
 204 
 205     /**
 206      * Receive notification of a parser warning and print it out.
 207      * @param ex The warning information encoded as an exception.
 208      * @exception org.xml.sax.SAXException Any SAX exception, possibly
 209      *            wrapping another exception.
 210      */
 211     @Override
 212     public void warning(SAXParseException ex) throws SAXException {
 213         printError("Warning", ex);
 214     }
 215 
 216     /**
 217      * Receive notification of a parser error and print it out.
 218      * @param ex The error information encoded as an exception.
 219      * @exception org.xml.sax.SAXException Any SAX exception, possibly
 220      *            wrapping another exception.
 221      */
 222     @Override
 223     public void error(SAXParseException ex) throws SAXException {
 224         printError("Error", ex);
 225     }
 226 
 227     /**
 228      * Receive notification of a parser fatal error. Throw out fatal error
 229      * following print fatal error message.
 230      * @param ex The fatal error information encoded as an exception.
 231      * @exception org.xml.sax.SAXException Any SAX exception, possibly
 232      *            wrapping another exception.
 233 
 234      */
 235     @Override
 236     public void fatalError(SAXParseException ex) throws SAXException {
 237         printError("Fatal Error", ex);
 238         throw ex;
 239     }
 240 
 241     /**
 242      * Do nothing on start DTD.
 243      * @param name The document type name.
 244      * @param publicId The declared public identifier for the
 245      *        external DTD subset, or null if none was declared.
 246      * @param systemId The declared system identifier for the
 247      *        external DTD subset, or null if none was declared.
 248      *        (Note that this is not resolved against the document
 249      *        base URI.)
 250      * @exception SAXException The application may raise an
 251      *            exception.
 252      */
 253     @Override
 254     public void startDTD(String name, String publicId, String systemId)
 255         throws SAXException {
 256     }
 257 
 258     /**
 259      * Do nothing on end DTD.
 260      * @exception SAXException The application may raise an exception.
 261      */
 262     @Override
 263     public void endDTD() throws SAXException {
 264     }
 265 
 266     /**
 267      * Do nothing on start entity.
 268      * @param name The name of the entity.  If it is a parameter
 269      *        entity, the name will begin with '%', and if it is the
 270      *        external DTD subset, it will be "[dtd]".
 271      * @exception SAXException The application may raise an exception.
 272      */
 273     @Override
 274     public void startEntity(String name) throws SAXException {
 275     }
 276 
 277     /**
 278      * Do nothing on end entity.
 279      * @param name The name of the entity.  If it is a parameter
 280      *        entity, the name will begin with '%', and if it is the
 281      *        external DTD subset, it will be "[dtd]".
 282      * @exception SAXException The application may raise an exception.
 283      */
 284     @Override
 285     public void endEntity(String name) throws SAXException {
 286     }
 287 
 288     /**
 289      * Do nothing on start CDATA section.
 290      * @exception SAXException The application may raise an exception.
 291      */
 292     @Override
 293     public void startCDATA() throws SAXException {
 294     }
 295 
 296     /**
 297      * Do nothing on end CDATA section.
 298      * @exception SAXException The application may raise an exception.
 299      */
 300     @Override
 301     public void endCDATA() throws SAXException {
 302     }
 303 
 304     /**
 305      * Report an normalized XML comment when receive a comment in the document.
 306      *
 307      * @param ch An array holding the characters in the comment.
 308      * @param start The starting position in the array.
 309      * @param length The number of characters to use from the array.
 310      * @exception SAXException The application may raise an exception.
 311      */
 312     @Override
 313     public void comment(char ch[], int start, int length) throws SAXException {
 314         if (!fCanonical && fElementDepth > 0) {
 315             writeFlush("<!--" + normalizeAndPrint(ch, start, length) + "-->");
 316         }
 317     }
 318 
 319     /**
 320      * Normalizes and prints the given string.
 321      * @param s String to be normalized
 322      */
 323     private String normalizeAndPrint(String s) {
 324         return s.chars().mapToObj(c -> normalizeAndPrint((char)c)).
 325                 collect(Collectors.joining());
 326     }
 327 
 328     /**
 329      * Normalizes and prints the given array of characters.
 330      * @param ch The characters to be normalized.
 331      * @param start The start position in the character array.
 332      * @param length The number of characters to use from the
 333      *               character array.
 334      */
 335     private String normalizeAndPrint(char[] ch, int offset, int length) {
 336         return normalizeAndPrint(new String(ch, offset, length));
 337     }
 338 
 339     /**
 340      * Normalizes given character.
 341      * @param c char to be normalized.
 342      */
 343     private String normalizeAndPrint(char c) {
 344         switch (c) {
 345             case '<':
 346                 return "&lt;";
 347             case '>':
 348                 return "&gt;";
 349             case '&':
 350                 return "&amp;";
 351             case '"':
 352                 return "&quot;";
 353             case '\r':
 354             case '\n':
 355                 return fCanonical ? "&#" + Integer.toString(c) + ";" : String.valueOf(c);
 356             default:
 357                 return String.valueOf(c);
 358         }
 359     }
 360 
 361     /**
 362      * Prints the error message.
 363      * @param type error type
 364      * @param ex exception that need to be printed
 365      */
 366     private void printError(String type, SAXParseException ex) {
 367         System.err.print("[" + type + "] ");
 368         String systemId = ex.getSystemId();
 369         if (systemId != null) {
 370             int index = systemId.lastIndexOf('/');
 371             if (index != -1)
 372                 systemId = systemId.substring(index + 1);
 373             System.err.print(systemId);
 374         }
 375         System.err.print(':' + ex.getLineNumber());
 376         System.err.print(':' + ex.getColumnNumber());
 377         System.err.println(": " + ex.getMessage());
 378         System.err.flush();
 379     }
 380 
 381     /**
 382      * Write out and flush.
 383      * @param out string to be written.
 384      */
 385     private void writeFlush(String out) {
 386         fOut.print(out);
 387         fOut.flush();
 388     }
 389 }