1 /*
   2  * Copyright (c) 2014, 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     public void setCanonical(boolean canonical) {
  62         fCanonical = canonical;
  63     }
  64 
  65     /**
  66      * Sets the output stream for printing.
  67      * @param stream OutputStream for message output.
  68      * @param encoding File encoding for message output.
  69      */
  70     public XInclHandler(OutputStream stream, String encoding)
  71             throws UnsupportedEncodingException {
  72         // At least set one encoding.
  73         if (encoding == null) {
  74             encoding = "UTF8";
  75         }
  76 
  77         fOut = new PrintWriter(new OutputStreamWriter(stream, encoding), false);
  78     }
  79 
  80     /**
  81      * Receive notification of the beginning of the document. Write the start
  82      * document tag if it's not canonical mode.
  83      * @exception org.xml.sax.SAXException Any SAX exception, possibly
  84      *            wrapping another exception.
  85      */
  86     @Override
  87     public void startDocument() throws SAXException {
  88         fElementDepth = 0;
  89 
  90         if (!fCanonical) {
  91             writeFlush("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
  92         }
  93     }
  94 
  95     /**
  96      * Receive notification of a processing instruction.
  97      * @param target The processing instruction target.
  98      * @param data The processing instruction data, or null if
  99      *             none is supplied.
 100      * @exception org.xml.sax.SAXException Any SAX exception, possibly
 101      *            wrapping another exception.
 102      */
 103     @Override
 104     public void processingInstruction (String target, String data)
 105         throws SAXException {
 106         if (fElementDepth > 0) {
 107             StringBuilder instruction = new StringBuilder("<?").append(target);
 108             if (data != null && data.length() > 0) {
 109                 instruction.append(' ').append(data);
 110             }
 111             instruction.append("?>");
 112             writeFlush(instruction.toString());
 113         }
 114     }
 115 
 116     /**
 117      * Receive notification of the start of an element then write the normalized
 118      * output to the file.
 119      * @param uri The Namespace URI, or the empty string if the
 120      *        element has no Namespace URI or if Namespace
 121      *        processing is not being performed.
 122      * @param localName The local name (without prefix), or the
 123      *        empty string if Namespace processing is not being
 124      *        performed.
 125      * @param qName The qualified name (with prefix), or the
 126      *        empty string if qualified names are not available.
 127      * @param attributes The attributes attached to the element.  If
 128      *        there are no attributes, it shall be an empty
 129      *        Attributes object.
 130      */
 131     @Override
 132     public void startElement(String uri, String local, String raw,
 133             Attributes attrs) throws SAXException {
 134         fElementDepth++;
 135         StringBuilder start = new StringBuilder().append('<').append(raw);
 136         if (attrs != null) {
 137             for (int i = 0; i < attrs.getLength(); i++) {
 138                 start.append(' ').append(attrs.getQName(i)).append("=\"").
 139                     append(normalizeAndPrint(attrs.getValue(i))).append('"');
 140             }
 141         }
 142         start.append('>');
 143         writeFlush(start.toString());
 144     }
 145 
 146     /**
 147      * Receive notification of character data inside an element and write
 148      * normalized characters to file.
 149      * @param ch The characters.
 150      * @param start The start position in the character array.
 151      * @param length The number of characters to use from the
 152      *               character array.
 153      * @exception org.xml.sax.SAXException Any SAX exception, possibly
 154      *            wrapping another exception.
 155      */
 156     @Override
 157     public void characters(char ch[], int start, int length)
 158             throws SAXException {
 159         writeFlush(normalizeAndPrint(ch, start, length));
 160     }
 161 
 162     /**
 163      * Receiving notification of ignorable whitespace in element content and
 164      * writing normalized ignorable characters to file.
 165      * @param ch The characters.
 166      * @param start The start position in the character array.
 167      * @param length The number of characters to use from the
 168      *               character array.
 169      * @exception org.xml.sax.SAXException Any SAX exception, possibly
 170      *            wrapping another exception.
 171      */
 172     @Override
 173     public void ignorableWhitespace(char ch[], int start, int length)
 174             throws SAXException {
 175         characters(ch, start, length);
 176     }
 177 
 178     /**
 179      * Receive notification of the end of an element and print end element.
 180      *
 181      * @param uri The Namespace URI, or the empty string if the
 182      *        element has no Namespace URI or if Namespace
 183      *        processing is not being performed.
 184      * @param localName The local name (without prefix), or the
 185      *        empty string if Namespace processing is not being
 186      *        performed.
 187      * @param qName The qualified name (with prefix), or the
 188      *        empty string if qualified names are not available.
 189      */
 190     @Override
 191     public void endElement(String uri, String local, String raw)
 192             throws SAXException {
 193         fElementDepth--;
 194         writeFlush("</" + raw + ">");
 195     }
 196 
 197     /**
 198      * Receive notification of a parser warning and print it out.
 199      * @param e The warning information encoded as an exception.
 200      * @exception org.xml.sax.SAXException Any SAX exception, possibly
 201      *            wrapping another exception.
 202      */
 203     @Override
 204     public void warning(SAXParseException ex) throws SAXException {
 205         printError("Warning", ex);
 206     }
 207 
 208     /**
 209      * Receive notification of a parser error and print it out.
 210      * @param e The error information encoded as an exception.
 211      * @exception org.xml.sax.SAXException Any SAX exception, possibly
 212      *            wrapping another exception.
 213 
 214      */
 215     @Override
 216     public void error(SAXParseException ex) throws SAXException {
 217         printError("Error", ex);
 218     }
 219 
 220     /**
 221      * Receive notification of a parser fatal error. Throw out fatal error
 222      * following print fatal error message.
 223      * @param e The fatal error information encoded as an exception.
 224      * @exception org.xml.sax.SAXException Any SAX exception, possibly
 225      *            wrapping another exception.
 226 
 227      */
 228     @Override
 229     public void fatalError(SAXParseException ex) throws SAXException {
 230         printError("Fatal Error", ex);
 231         throw ex;
 232     }
 233 
 234     /**
 235      * Do nothing on start DTD.
 236      * @param name The document type name.
 237      * @param publicId The declared public identifier for the
 238      *        external DTD subset, or null if none was declared.
 239      * @param systemId The declared system identifier for the
 240      *        external DTD subset, or null if none was declared.
 241      *        (Note that this is not resolved against the document
 242      *        base URI.)
 243      * @exception SAXException The application may raise an
 244      *            exception.
 245      */
 246     @Override
 247     public void startDTD(String name, String publicId, String systemId)
 248         throws SAXException {
 249     }
 250 
 251     /**
 252      * Do nothing on end DTD.
 253      * @exception SAXException The application may raise an exception.
 254      */
 255     @Override
 256     public void endDTD() throws SAXException {
 257     }
 258 
 259     /**
 260      * Do nothing on start entity.
 261      * @param name The name of the entity.  If it is a parameter
 262      *        entity, the name will begin with '%', and if it is the
 263      *        external DTD subset, it will be "[dtd]".
 264      * @exception SAXException The application may raise an exception.
 265      */
 266     @Override
 267     public void startEntity(String name) throws SAXException {
 268     }
 269 
 270     /**
 271      * Do nothing on end entity.
 272      * @param name The name of the entity.  If it is a parameter
 273      *        entity, the name will begin with '%', and if it is the
 274      *        external DTD subset, it will be "[dtd]".
 275      * @exception SAXException The application may raise an exception.
 276      */
 277     @Override
 278     public void endEntity(String name) throws SAXException {
 279     }
 280 
 281     /**
 282      * Do nothing on start CDATA section.
 283      * @exception SAXException The application may raise an exception.
 284      */
 285     @Override
 286     public void startCDATA() throws SAXException {
 287     }
 288 
 289     /**
 290      * Do nothing on end CDATA section.
 291      * @exception SAXException The application may raise an exception.
 292      */
 293     @Override
 294     public void endCDATA() throws SAXException {
 295     }
 296 
 297     /**
 298      * Report an normalized XML comment when receive a comment in the document.
 299      *
 300      * @param ch An array holding the characters in the comment.
 301      * @param start The starting position in the array.
 302      * @param length The number of characters to use from the array.
 303      * @exception SAXException The application may raise an exception.
 304      */
 305     @Override
 306     public void comment(char ch[], int start, int length) throws SAXException {
 307         if (!fCanonical && fElementDepth > 0) {
 308             writeFlush("<!--" + normalizeAndPrint(ch, start, length) + "-->");
 309         }
 310     }
 311 
 312     /**
 313      * Normalizes and prints the given string.
 314      * @param s String to be normalized
 315      */
 316     private String normalizeAndPrint(String s) {
 317         return s.chars().mapToObj(c -> normalizeAndPrint((char)c)).
 318                 collect(Collectors.joining());
 319     }
 320 
 321     /**
 322      * Normalizes and prints the given array of characters.
 323      * @param ch The characters to be normalized.
 324      * @param start The start position in the character array.
 325      * @param length The number of characters to use from the
 326      *               character array.
 327      */
 328     private String normalizeAndPrint(char[] ch, int offset, int length) {
 329         return normalizeAndPrint(new String(ch, offset, length));
 330     }
 331 
 332     /**
 333      * Normalizes given character.
 334      * @param c char to be normalized.
 335      */
 336     private String normalizeAndPrint(char c) {
 337         switch (c) {
 338             case '<':
 339                 return "&lt;";
 340             case '>':
 341                 return "&gt;";
 342             case '&':
 343                 return "&amp;";
 344             case '"':
 345                 return "&quot;";
 346             case '\r':
 347             case '\n':
 348                 return fCanonical ? "&#" + Integer.toString(c) + ";" : String.valueOf(c);
 349             default:
 350                 return String.valueOf(c);
 351         }
 352     }
 353 
 354     /**
 355      * Prints the error message.
 356      * @param type error type
 357      * @param ex exception that need to be printed
 358      */
 359     private void printError(String type, SAXParseException ex) {
 360         System.err.print("[" + type + "] ");
 361         String systemId = ex.getSystemId();
 362         if (systemId != null) {
 363             int index = systemId.lastIndexOf('/');
 364             if (index != -1)
 365                 systemId = systemId.substring(index + 1);
 366             System.err.print(systemId);
 367         }
 368         System.err.print(':' + ex.getLineNumber());
 369         System.err.print(':' + ex.getColumnNumber());
 370         System.err.println(": " + ex.getMessage());
 371         System.err.flush();
 372     }
 373 
 374     /**
 375      * Write out and flush.
 376      * @param out string to be written.
 377      */
 378     private void writeFlush(String out) {
 379         fOut.print(out);
 380         fOut.flush();
 381     }
 382 }