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 */
23package test.auctionportal;
24
25import java.io.OutputStream;
26import java.io.OutputStreamWriter;
27import java.io.PrintWriter;
28import java.io.UnsupportedEncodingException;
29import java.util.stream.Collectors;
30
31import org.xml.sax.Attributes;
32import org.xml.sax.SAXException;
33import org.xml.sax.SAXParseException;
34import org.xml.sax.ext.LexicalHandler;
35import 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 */
42public 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}
390