12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20
21 package com.sun.org.apache.xml.internal.serializer;
22
23 import com.sun.org.apache.xalan.internal.utils.SecuritySupport;
24 import com.sun.org.apache.xml.internal.serializer.utils.MsgKey;
25 import com.sun.org.apache.xml.internal.serializer.utils.Utils;
26 import com.sun.org.apache.xml.internal.serializer.utils.WrappedRuntimeException;
27 import java.io.IOException;
28 import java.io.OutputStream;
29 import java.io.OutputStreamWriter;
30 import java.io.UnsupportedEncodingException;
31 import java.io.Writer;
32 import java.util.Enumeration;
33 import java.util.Iterator;
34 import java.util.Properties;
35 import java.util.Set;
36 import java.util.StringTokenizer;
37 import java.util.ArrayList;
38 import javax.xml.transform.ErrorListener;
39 import javax.xml.transform.OutputKeys;
40 import javax.xml.transform.Transformer;
41 import javax.xml.transform.TransformerException;
42 import org.w3c.dom.Node;
43 import org.xml.sax.Attributes;
44 import org.xml.sax.ContentHandler;
45 import org.xml.sax.SAXException;
46
47 /**
48 * This abstract class is a base class for other stream
49 * serializers (xml, html, text ...) that write output to a stream.
50 *
51 * @xsl.usage internal
52 */
53 abstract public class ToStream extends SerializerBase {
54
55 private static final String COMMENT_BEGIN = "<!--";
56 private static final String COMMENT_END = "-->";
57
72 EncodingInfo m_encodingInfo = new EncodingInfo(null,null);
73
74 /**
75 * Method reference to the sun.io.CharToByteConverter#canConvert method
76 * for this encoding. Invalid if m_charToByteConverter is null.
77 */
78 java.lang.reflect.Method m_canConvertMeth;
79
80 /**
81 * Boolean that tells if we already tried to get the converter.
82 */
83 boolean m_triedToGetConverter = false;
84
85 /**
86 * Opaque reference to the sun.io.CharToByteConverter for this
87 * encoding.
88 */
89 Object m_charToByteConverter = null;
90
91 /**
92 * Stack to keep track of whether or not we need to
93 * preserve whitespace.
94 *
95 * Used to push/pop values used for the field m_ispreserve, but
96 * m_ispreserve is only relevant if m_doIndent is true.
97 * If m_doIndent is false this field has no impact.
98 *
99 */
100 protected BoolStack m_preserves = new BoolStack();
101
102 /**
103 * State flag to tell if preservation of whitespace
104 * is important.
105 *
106 * Used only in shouldIndent() but only if m_doIndent is true.
107 * If m_doIndent is false this flag has no impact.
108 *
109 */
110 protected boolean m_ispreserve = false;
111
750 final boolean temp = m_escaping;
751 m_escaping = escape;
752 return temp;
753
754 }
755
756
757 /**
758 * Might print a newline character and the indentation amount
759 * of the given depth.
760 *
761 * @param depth the indentation depth (element nesting depth)
762 *
763 * @throws org.xml.sax.SAXException if an error occurs during writing.
764 */
765 protected void indent(int depth) throws IOException
766 {
767
768 if (m_startNewLine)
769 outputLineSep();
770 /* For m_indentAmount > 0 this extra test might be slower
771 * but Xalan's default value is 0, so this extra test
772 * will run faster in that situation.
773 */
774 if (m_indentAmount > 0)
775 printSpace(depth * m_indentAmount);
776
777 }
778
779 /**
780 * Indent at the current element nesting depth.
781 * @throws IOException
782 */
783 protected void indent() throws IOException
784 {
785 indent(m_elemContext.m_currentElemDepth);
786 }
787 /**
788 * Prints <var>n</var> spaces.
789 * @param n Number of spaces to print.
790 *
791 * @throws org.xml.sax.SAXException if an error occurs when writing.
792 */
793 private void printSpace(int n) throws IOException
794 {
1217 * <p>The application must not attempt to read from the array
1218 * outside of the specified range.</p>
1219 *
1220 * <p>Note that some parsers will report whitespace using the
1221 * ignorableWhitespace() method rather than this one (validating
1222 * parsers must do so).</p>
1223 *
1224 * @param ch The characters from the XML document.
1225 * @param start The start position in the array.
1226 * @param length The number of characters to read from the array.
1227 * @throws org.xml.sax.SAXException Any SAX exception, possibly
1228 * wrapping another exception.
1229 * @see #ignorableWhitespace
1230 * @see org.xml.sax.Locator
1231 *
1232 * @throws org.xml.sax.SAXException
1233 */
1234 protected void cdata(char ch[], int start, final int length)
1235 throws org.xml.sax.SAXException
1236 {
1237
1238 try
1239 {
1240 final int old_start = start;
1241 if (m_elemContext.m_startTagOpen)
1242 {
1243 closeStartTag();
1244 m_elemContext.m_startTagOpen = false;
1245 }
1246 m_ispreserve = true;
1247
1248 if (shouldIndent())
1249 indent();
1250
1251 boolean writeCDataBrackets =
1252 (((length >= 1) && escapingNotNeeded(ch[start])));
1253
1254 /* Write out the CDATA opening delimiter only if
1255 * we are supposed to, and if we are not already in
1256 * the middle of a CDATA section
1257 */
1306 */
1307 private boolean isEscapingDisabled()
1308 {
1309 return m_disableOutputEscapingStates.peekOrFalse();
1310 }
1311
1312 /**
1313 * If available, when the disable-output-escaping attribute is used,
1314 * output raw text without escaping.
1315 *
1316 * @param ch The characters from the XML document.
1317 * @param start The start position in the array.
1318 * @param length The number of characters to read from the array.
1319 *
1320 * @throws org.xml.sax.SAXException
1321 */
1322 protected void charactersRaw(char ch[], int start, int length)
1323 throws org.xml.sax.SAXException
1324 {
1325
1326 if (m_inEntityRef)
1327 return;
1328 try
1329 {
1330 if (m_elemContext.m_startTagOpen)
1331 {
1332 closeStartTag();
1333 m_elemContext.m_startTagOpen = false;
1334 }
1335
1336 m_ispreserve = true;
1337
1338 m_writer.write(ch, start, length);
1339 }
1340 catch (IOException e)
1341 {
1342 throw new SAXException(e);
1343 }
1344
1345 }
1346
1361 * ignorableWhitespace() method rather than this one (validating
1362 * parsers must do so).</p>
1363 *
1364 * @param chars The characters from the XML document.
1365 * @param start The start position in the array.
1366 * @param length The number of characters to read from the array.
1367 * @throws org.xml.sax.SAXException Any SAX exception, possibly
1368 * wrapping another exception.
1369 * @see #ignorableWhitespace
1370 * @see org.xml.sax.Locator
1371 *
1372 * @throws org.xml.sax.SAXException
1373 */
1374 public void characters(final char chars[], final int start, final int length)
1375 throws org.xml.sax.SAXException
1376 {
1377 // It does not make sense to continue with rest of the method if the number of
1378 // characters to read from array is 0.
1379 // Section 7.6.1 of XSLT 1.0 (http://www.w3.org/TR/xslt#value-of) suggest no text node
1380 // is created if string is empty.
1381 if (length == 0 || (m_inEntityRef && !m_expandDTDEntities))
1382 return;
1383 if (m_elemContext.m_startTagOpen)
1384 {
1385 closeStartTag();
1386 m_elemContext.m_startTagOpen = false;
1387 }
1388 else if (m_needToCallStartDocument)
1389 {
1390 startDocumentInternal();
1391 }
1392
1393 if (m_cdataStartCalled || m_elemContext.m_isCdataSection)
1394 {
1395 /* either due to startCDATA() being called or due to
1396 * cdata-section-elements atribute, we need this as cdata
1397 */
1398 cdata(chars, start, length);
1399
1400 return;
1401 }
1402
1403 if (m_cdataTagOpen)
1404 closeCDATA();
1405 // the check with _escaping is a bit of a hack for XLSTC
1406
1407 if (m_disableOutputEscapingStates.peekOrFalse() || (!m_escaping))
1408 {
1409 charactersRaw(chars, start, length);
1410
1411 // time to fire off characters generation event
1412 if (m_tracer != null)
1413 super.fireCharEvent(chars, start, length);
1414
1415 return;
1416 }
1417
1418 if (m_elemContext.m_startTagOpen)
1419 {
1420 closeStartTag();
1421 m_elemContext.m_startTagOpen = false;
1422 }
1423
1424
1425 try
1426 {
1427 int i;
1428 char ch1;
1429 int startClean;
1430
1431 // skip any leading whitspace
1432 // don't go off the end and use a hand inlined version
1433 // of isWhitespace(ch)
1434 final int end = start + length;
1435 int lastDirty = start - 1; // last character that needed processing
1436 for (i = start;
1437 ((i < end)
1438 && ((ch1 = chars[i]) == 0x20
1439 || (ch1 == 0xA && m_lineSepUse)
1440 || ch1 == 0xD
1441 || ch1 == 0x09));
1442 i++)
1443 {
1444 /*
1497 i = lastDirty;
1498 }
1499 }
1500
1501 // we've reached the end. Any clean characters at the
1502 // end of the array than need to be written out?
1503 startClean = lastDirty + 1;
1504 if (i > startClean)
1505 {
1506 int lengthClean = i - startClean;
1507 m_writer.write(chars, startClean, lengthClean);
1508 }
1509
1510 // For indentation purposes, mark that we've just writen text out
1511 m_isprevtext = true;
1512 }
1513 catch (IOException e)
1514 {
1515 throw new SAXException(e);
1516 }
1517
1518 // time to fire off characters generation event
1519 if (m_tracer != null)
1520 super.fireCharEvent(chars, start, length);
1521 }
1522 /**
1523 * This method checks if a given character is between C0 or C1 range
1524 * of Control characters.
1525 * This method is added to support Control Characters for XML 1.1
1526 * If a given character is TAB (0x09), LF (0x0A) or CR (0x0D), this method
1527 * return false. Since they are whitespace characters, no special processing is needed.
1528 *
1529 * @param ch
1530 * @return boolean
1531 */
1532 private static boolean isCharacterInC0orC1Range(char ch)
1533 {
1534 if(ch == 0x09 || ch == 0x0A || ch == 0x0D)
1535 return false;
1536 else
1537 return (ch >= 0x7F && ch <= 0x9F)|| (ch >= 0x01 && ch <= 0x1F);
1538 }
1539 /**
1540 * This method checks if a given character either NEL (0x85) or LSEP (0x2028)
1541 * These are new end of line charcters added in XML 1.1. These characters must be
1593 chars,
1594 end,
1595 fromTextNode,
1596 false);
1597 i = startClean - 1;
1598 }
1599 // Return the index of the last character that we just processed
1600 // which is a dirty character.
1601 return i;
1602 }
1603
1604 /**
1605 * Receive notification of character data.
1606 *
1607 * @param s The string of characters to process.
1608 *
1609 * @throws org.xml.sax.SAXException
1610 */
1611 public void characters(String s) throws org.xml.sax.SAXException
1612 {
1613 if (m_inEntityRef && !m_expandDTDEntities)
1614 return;
1615 final int length = s.length();
1616 if (length > m_charsBuff.length)
1617 {
1618 m_charsBuff = new char[length * 2 + 1];
1619 }
1620 s.getChars(0, length, m_charsBuff, 0);
1621 characters(m_charsBuff, 0, length);
1622 }
1623
1624 /**
1625 * Escape and writer.write a character.
1626 *
1627 * @param ch character to be escaped.
1628 * @param i index into character array.
1629 * @param chars non-null reference to character array.
1630 * @param len length of chars.
1631 * @param fromTextNode true if the characters being processed are
1632 * from a text node, false if the characters being processed are from
1633 * an attribute value.
1741 * @param localName The local name (without prefix), or the
1742 * empty string if Namespace processing is not being
1743 * performed.
1744 * @param name The element type name.
1745 * @param atts The attributes attached to the element, if any.
1746 * @throws org.xml.sax.SAXException Any SAX exception, possibly
1747 * wrapping another exception.
1748 * @see org.xml.sax.ContentHandler#startElement
1749 * @see org.xml.sax.ContentHandler#endElement
1750 * @see org.xml.sax.AttributeList
1751 *
1752 * @throws org.xml.sax.SAXException
1753 */
1754 public void startElement(
1755 String namespaceURI,
1756 String localName,
1757 String name,
1758 Attributes atts)
1759 throws org.xml.sax.SAXException
1760 {
1761 if (m_inEntityRef)
1762 return;
1763
1764 if (m_needToCallStartDocument)
1765 {
1766 startDocumentInternal();
1767 m_needToCallStartDocument = false;
1768 }
1769 else if (m_cdataTagOpen)
1770 closeCDATA();
1771 try
1772 {
1773 if ((true == m_needToOutputDocTypeDecl)
1774 && (null != getDoctypeSystem()))
1775 {
1776 outputDocTypeDecl(name, true);
1777 }
1778
1779 m_needToOutputDocTypeDecl = false;
1780
1781 /* before we over-write the current elementLocalName etc.
1782 * lets close out the old one (if we still need to)
1783 */
1784 if (m_elemContext.m_startTagOpen)
1785 {
1786 closeStartTag();
1787 m_elemContext.m_startTagOpen = false;
1788 }
1789
1790 if (namespaceURI != null)
1791 ensurePrefixIsDeclared(namespaceURI, name);
1792
1793 m_ispreserve = false;
1794
1795 if (shouldIndent() && m_startNewLine)
1796 {
1797 indent();
1798 }
1799
1800 m_startNewLine = true;
1801
1802 final Writer writer = m_writer;
1803 writer.write('<');
1804 writer.write(name);
1805 }
1806 catch (IOException e)
1807 {
1808 throw new SAXException(e);
1809 }
1810
1811 // process the attributes now, because after this SAX call they might be gone
1812 if (atts != null)
1813 addAttributes(atts);
1814
1815 m_elemContext = m_elemContext.push(namespaceURI,localName,name);
1816 m_isprevtext = false;
1817
1818 if (m_tracer != null){
1819 firePseudoAttributes();
1820 }
1821
1822 }
1823
1824 /**
1825 * Receive notification of the beginning of an element, additional
1826 * namespace or attribute information can occur before or after this call,
1827 * that is associated with this element.
1828 *
1829 *
1830 * @param elementNamespaceURI The Namespace URI, or the empty string if the
1831 * element has no Namespace URI or if Namespace
1832 * processing is not being performed.
1833 * @param elementLocalName The local name (without prefix), or the
1834 * empty string if Namespace processing is not being
2002 /**
2003 * Receive notification of the end of an element.
2004 *
2005 *
2006 * @param namespaceURI The Namespace URI, or the empty string if the
2007 * element has no Namespace URI or if Namespace
2008 * processing is not being performed.
2009 * @param localName The local name (without prefix), or the
2010 * empty string if Namespace processing is not being
2011 * performed.
2012 * @param name The element type name
2013 * @throws org.xml.sax.SAXException Any SAX exception, possibly
2014 * wrapping another exception.
2015 *
2016 * @throws org.xml.sax.SAXException
2017 */
2018 public void endElement(String namespaceURI, String localName, String name)
2019 throws org.xml.sax.SAXException
2020 {
2021
2022 if (m_inEntityRef)
2023 return;
2024
2025 // namespaces declared at the current depth are no longer valid
2026 // so get rid of them
2027 m_prefixMap.popNamespaces(m_elemContext.m_currentElemDepth, null);
2028
2029 try
2030 {
2031 final Writer writer = m_writer;
2032 if (m_elemContext.m_startTagOpen)
2033 {
2034 if (m_tracer != null)
2035 super.fireStartElem(m_elemContext.m_elementName);
2036 int nAttrs = m_attributes.getLength();
2037 if (nAttrs > 0)
2038 {
2039 processAttributes(m_writer, nAttrs);
2040 // clear attributes object for re-use with next element
2041 m_attributes.clear();
2042 }
2043 if (m_spaceBeforeClose)
2044 writer.write(" />");
2045 else
2046 writer.write("/>");
2047 /* don't need to pop cdataSectionState because
2048 * this element ended so quickly that we didn't get
2049 * to push the state.
2050 */
2051
2052 }
2053 else
2054 {
2055 if (m_cdataTagOpen)
2056 closeCDATA();
2057
2058 if (shouldIndent())
2059 indent(m_elemContext.m_currentElemDepth - 1);
2060 writer.write('<');
2061 writer.write('/');
2062 writer.write(name);
2063 writer.write('>');
2064 }
2065 }
2066 catch (IOException e)
2067 {
2068 throw new SAXException(e);
2069 }
2070
2071 if (!m_elemContext.m_startTagOpen && m_doIndent)
2072 {
2073 m_ispreserve = m_preserves.isEmpty() ? false : m_preserves.pop();
2074 }
2075
2076 m_isprevtext = false;
2077
2078 // fire off the end element event
2079 if (m_tracer != null)
2080 super.fireEndElem(name);
2081 m_elemContext = m_elemContext.m_prev;
2082 }
2083
2084 /**
2085 * Receive notification of the end of an element.
2086 * @param name The element type name
2087 * @throws org.xml.sax.SAXException Any SAX exception, possibly
2088 * wrapping another exception.
2089 */
2090 public void endElement(String name) throws org.xml.sax.SAXException
2091 {
2092 endElement(null, null, name);
2093 }
2094
2095 /**
2191 }
2192 }
2193 }
2194 return pushed;
2195 }
2196
2197 /**
2198 * Receive notification of an XML comment anywhere in the document. This
2199 * callback will be used for comments inside or outside the document
2200 * element, including comments in the external DTD subset (if read).
2201 * @param ch An array holding the characters in the comment.
2202 * @param start The starting position in the array.
2203 * @param length The number of characters to use from the array.
2204 * @throws org.xml.sax.SAXException The application may raise an exception.
2205 */
2206 public void comment(char ch[], int start, int length)
2207 throws org.xml.sax.SAXException
2208 {
2209
2210 int start_old = start;
2211 if (m_inEntityRef)
2212 return;
2213 if (m_elemContext.m_startTagOpen)
2214 {
2215 closeStartTag();
2216 m_elemContext.m_startTagOpen = false;
2217 }
2218 else if (m_needToCallStartDocument)
2219 {
2220 startDocumentInternal();
2221 m_needToCallStartDocument = false;
2222 }
2223
2224 try
2225 {
2226 if (shouldIndent() && m_isStandalone)
2227 indent();
2228
2229 final int limit = start + length;
2230 boolean wasDash = false;
2231 if (m_cdataTagOpen)
2232 closeCDATA();
2372 *
2373 * @param name The name of the skipped entity. If it is a
2374 * parameter entity, the name will begin with '%',
2375 * and if it is the external DTD subset, it will be the string
2376 * "[dtd]".
2377 * @throws org.xml.sax.SAXException Any SAX exception, possibly wrapping
2378 * another exception.
2379 */
2380 public void skippedEntity(String name) throws org.xml.sax.SAXException
2381 { // TODO: Should handle
2382 }
2383
2384 /**
2385 * Report the start of a CDATA section.
2386 *
2387 * @throws org.xml.sax.SAXException The application may raise an exception.
2388 * @see #endCDATA
2389 */
2390 public void startCDATA() throws org.xml.sax.SAXException
2391 {
2392 m_cdataStartCalled = true;
2393 }
2394
2395 /**
2396 * Report the beginning of an entity.
2397 *
2398 * The start and end of the document entity are not reported.
2399 * The start and end of the external DTD subset are reported
2400 * using the pseudo-name "[dtd]". All other events must be
2401 * properly nested within start/end entity events.
2402 *
2403 * @param name The name of the entity. If it is a parameter
2404 * entity, the name will begin with '%'.
2405 * @throws org.xml.sax.SAXException The application may raise an exception.
2406 * @see #endEntity
2407 * @see org.xml.sax.ext.DeclHandler#internalEntityDecl
2408 * @see org.xml.sax.ext.DeclHandler#externalEntityDecl
2409 */
2410 public void startEntity(String name) throws org.xml.sax.SAXException
2411 {
2412 if (name.equals("[dtd]"))
2413 m_inExternalDTD = true;
2414
2415 if (!m_expandDTDEntities && !m_inExternalDTD) {
2416 /* Only leave the entity as-is if
2417 * we've been told not to expand them
2418 * and this is not the magic [dtd] name.
2419 */
2420 startNonEscaping();
2421 characters("&" + name + ';');
2422 endNonEscaping();
2423 }
2424
2425 m_inEntityRef = true;
2426 }
2427
2428 /**
2429 * For the enclosing elements starting tag write out
2430 * out any attributes followed by ">"
2431 *
2432 * @throws org.xml.sax.SAXException
2433 */
2434 protected void closeStartTag() throws SAXException
2435 {
2436 if (m_elemContext.m_startTagOpen)
2437 {
2438
2439 try
2440 {
2441 if (m_tracer != null)
2442 super.fireStartElem(m_elemContext.m_elementName);
2443 int nAttrs = m_attributes.getLength();
2444 if (nAttrs > 0)
2445 {
2506 }
2507
2508 /**
2509 * Sets the m_indentAmount.
2510 *
2511 * @param m_indentAmount The m_indentAmount to set
2512 */
2513 public void setIndentAmount(int m_indentAmount)
2514 {
2515 this.m_indentAmount = m_indentAmount;
2516 }
2517
2518 /**
2519 * Tell if, based on space preservation constraints and the doIndent property,
2520 * if an indent should occur.
2521 *
2522 * @return True if an indent should occur.
2523 */
2524 protected boolean shouldIndent()
2525 {
2526 return m_doIndent && (!m_ispreserve && !m_isprevtext) && (m_elemContext.m_currentElemDepth > 0 || m_isStandalone);
2527 }
2528
2529 /**
2530 * Searches for the list of qname properties with the specified key in the
2531 * property list. If the key is not found in this property list, the default
2532 * property list, and its defaults, recursively, are then checked. The
2533 * method returns <code>null</code> if the property is not found.
2534 *
2535 * @param key the property key.
2536 * @param props the list of properties to search in.
2537 *
2538 * Sets the ArrayList of local-name/URI pairs of the cdata section elements
2539 * specified in the cdata-section-elements property.
2540 *
2541 * This method is essentially a copy of getQNameProperties() from
2542 * OutputProperties. Eventually this method should go away and a call
2543 * to setCdataSectionElements(ArrayList<String> v) should be made directly.
2544 */
2545 private void setCdataSectionElements(String key, Properties props) {
2546 String s = props.getProperty(key);
2798 * stream serializers, not for SAX ones.
2799 *
2800 * @param uri the URI of the attribute
2801 * @param localName the local name of the attribute
2802 * @param rawName the qualified name of the attribute
2803 * @param type the type of the attribute (probably CDATA)
2804 * @param value the value of the attribute
2805 * @param xslAttribute true if this attribute is coming from an xsl:attribute element.
2806 * @return true if the attribute value was added,
2807 * false if the attribute already existed and the value was
2808 * replaced with the new value.
2809 */
2810 public boolean addAttributeAlways(
2811 String uri,
2812 String localName,
2813 String rawName,
2814 String type,
2815 String value,
2816 boolean xslAttribute)
2817 {
2818 boolean was_added;
2819 int index;
2820 //if (uri == null || localName == null || uri.length() == 0)
2821 index = m_attributes.getIndex(rawName);
2822 // Don't use 'localName' as it gives incorrect value, rely only on 'rawName'
2823 /*else {
2824 index = m_attributes.getIndex(uri, localName);
2825 }*/
2826 if (index >= 0)
2827 {
2828 String old_value = null;
2829 if (m_tracer != null)
2830 {
2831 old_value = m_attributes.getValue(index);
2832 if (value.equals(old_value))
2833 old_value = null;
2834 }
2835
2836 /* We've seen the attribute before.
2837 * We may have a null uri or localName, but all we really
2906
2907 try
2908 {
2909 /* This is our last chance to make sure the namespace for this
2910 * attribute is declared, especially if we just generated an alternate
2911 * prefix to avoid a collision (the new prefix/rawName will go out of scope
2912 * soon and be lost ... last chance here.
2913 */
2914 String prefixUsed =
2915 ensureAttributesNamespaceIsDeclared(
2916 uri,
2917 localName,
2918 rawName);
2919 }
2920 catch (SAXException e)
2921 {
2922 // TODO Auto-generated catch block
2923 e.printStackTrace();
2924 }
2925 }
2926 m_attributes.addAttribute(uri, localName, rawName, type, value);
2927 was_added = true;
2928 if (m_tracer != null){
2929 firePseudoAttributes();
2930 }
2931 }
2932 return was_added;
2933 }
2934
2935 /**
2936 * To fire off the pseudo characters of attributes, as they currently
2937 * exist. This method should be called everytime an attribute is added,
2938 * or when an attribute value is changed, or an element is created.
2939 */
2940 protected void firePseudoAttributes() {
2941 if (m_tracer != null) {
2942 try {
2943 // flush out the "<elemName" if not already flushed
2944 m_writer.flush();
2945
2946 // make a StringBuffer to write the name="value" pairs to.
2947 StringBuffer sb = new StringBuffer();
2948 int nAttrs = m_attributes.getLength();
2949 if (nAttrs > 0) {
2950 // make a writer that internally appends to the same
2951 // StringBuffer
3042 /**
3043 * Reset all of the fields owned by ToStream class
3044 *
3045 */
3046 private void resetToStream() {
3047 this.m_cdataStartCalled = false;
3048 /* The stream is being reset. It is one of
3049 * ToXMLStream, ToHTMLStream ... and this type can't be changed
3050 * so neither should m_charInfo which is associated with the
3051 * type of Stream. Just leave m_charInfo as-is for the next re-use.
3052 */
3053 // this.m_charInfo = null; // don't set to null
3054
3055 this.m_disableOutputEscapingStates.clear();
3056
3057 this.m_escaping = true;
3058 // Leave m_format alone for now - Brian M.
3059 // this.m_format = null;
3060 this.m_inDoctype = false;
3061 this.m_ispreserve = false;
3062 this.m_ispreserve = false;
3063 this.m_isprevtext = false;
3064 this.m_isUTF8 = false; // ?? used anywhere ??
3065 this.m_preserves.clear();
3066 this.m_shouldFlush = true;
3067 this.m_spaceBeforeClose = false;
3068 this.m_startNewLine = false;
3069 this.m_lineSepUse = true;
3070 // DON'T SET THE WRITER TO NULL, IT MAY BE REUSED !!
3071 // this.m_writer = null;
3072 this.m_expandDTDEntities = true;
3073
3074 }
3075
3076 /**
3077 * Sets the character encoding coming from the xsl:output encoding stylesheet attribute.
3078 * @param encoding the character encoding
3079 */
3080 public void setEncoding(String encoding)
3081 {
3082 setOutputProperty(OutputKeys.ENCODING,encoding);
3083 }
3084
3085 /**
3221 *
3222 * @return <code>true</code> if this stack is empty;
3223 * <code>false</code> otherwise.
3224 */
3225 public boolean isEmpty() {
3226 return (m_index == -1);
3227 }
3228
3229 /**
3230 * Grows the size of the stack
3231 *
3232 */
3233 private void grow() {
3234 m_allocatedSize *= 2;
3235 boolean newVector[] = new boolean[m_allocatedSize];
3236 System.arraycopy(m_values, 0, newVector, 0, m_index + 1);
3237 m_values = newVector;
3238 }
3239 }
3240
3241 // Implement DTDHandler
3242 /**
3243 * If this method is called, the serializer is used as a
3244 * DTDHandler, which changes behavior how the serializer
3245 * handles document entities.
3246 * @see org.xml.sax.DTDHandler#notationDecl(java.lang.String, java.lang.String, java.lang.String)
3247 */
3248 public void notationDecl(String name, String pubID, String sysID) throws SAXException {
3249 // TODO Auto-generated method stub
3250 try {
3251 DTDprolog();
3252
3253 m_writer.write("<!NOTATION ");
3254 m_writer.write(name);
3255 if (pubID != null) {
3256 m_writer.write(" PUBLIC \"");
3257 m_writer.write(pubID);
3258
3259 }
3260 else {
|
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20
21 package com.sun.org.apache.xml.internal.serializer;
22
23 import com.sun.org.apache.xalan.internal.utils.SecuritySupport;
24 import com.sun.org.apache.xml.internal.serializer.utils.MsgKey;
25 import com.sun.org.apache.xml.internal.serializer.utils.Utils;
26 import com.sun.org.apache.xml.internal.serializer.utils.WrappedRuntimeException;
27 import java.io.IOException;
28 import java.io.OutputStream;
29 import java.io.OutputStreamWriter;
30 import java.io.UnsupportedEncodingException;
31 import java.io.Writer;
32 import java.util.ArrayDeque;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.Deque;
36 import java.util.EmptyStackException;
37 import java.util.Enumeration;
38 import java.util.Properties;
39 import java.util.Queue;
40 import java.util.Set;
41 import java.util.StringTokenizer;
42 import javax.xml.transform.ErrorListener;
43 import javax.xml.transform.OutputKeys;
44 import javax.xml.transform.Transformer;
45 import javax.xml.transform.TransformerException;
46 import org.w3c.dom.Node;
47 import org.xml.sax.Attributes;
48 import org.xml.sax.ContentHandler;
49 import org.xml.sax.SAXException;
50
51 /**
52 * This abstract class is a base class for other stream
53 * serializers (xml, html, text ...) that write output to a stream.
54 *
55 * @xsl.usage internal
56 */
57 abstract public class ToStream extends SerializerBase {
58
59 private static final String COMMENT_BEGIN = "<!--";
60 private static final String COMMENT_END = "-->";
61
76 EncodingInfo m_encodingInfo = new EncodingInfo(null,null);
77
78 /**
79 * Method reference to the sun.io.CharToByteConverter#canConvert method
80 * for this encoding. Invalid if m_charToByteConverter is null.
81 */
82 java.lang.reflect.Method m_canConvertMeth;
83
84 /**
85 * Boolean that tells if we already tried to get the converter.
86 */
87 boolean m_triedToGetConverter = false;
88
89 /**
90 * Opaque reference to the sun.io.CharToByteConverter for this
91 * encoding.
92 */
93 Object m_charToByteConverter = null;
94
95 /**
96 * Used to buffer the text nodes and the entity reference nodes if
97 * indentation is on.
98 */
99 protected CharacterBuffer m_charactersBuffer = new CharacterBuffer();
100
101 /**
102 * Used to decide if a text node is pretty-printed with indentation.
103 * If m_childNodeNum > 1, the text node will be indented.
104 *
105 */
106 protected Deque<Integer> m_childNodeNumStack = new ArrayDeque<>();
107
108 protected int m_childNodeNum = 0;
109
110 /**
111 * Used to handle xml:space attribute
112 *
113 */
114 protected BoolStack m_preserveSpaces = new BoolStack();
115
116 protected boolean m_ispreserveSpace = false;
117
118
119 /**
120 * Stack to keep track of whether or not we need to
121 * preserve whitespace.
122 *
123 * Used to push/pop values used for the field m_ispreserve, but
124 * m_ispreserve is only relevant if m_doIndent is true.
125 * If m_doIndent is false this field has no impact.
126 *
127 */
128 protected BoolStack m_preserves = new BoolStack();
129
130 /**
131 * State flag to tell if preservation of whitespace
132 * is important.
133 *
134 * Used only in shouldIndent() but only if m_doIndent is true.
135 * If m_doIndent is false this flag has no impact.
136 *
137 */
138 protected boolean m_ispreserve = false;
139
778 final boolean temp = m_escaping;
779 m_escaping = escape;
780 return temp;
781
782 }
783
784
785 /**
786 * Might print a newline character and the indentation amount
787 * of the given depth.
788 *
789 * @param depth the indentation depth (element nesting depth)
790 *
791 * @throws org.xml.sax.SAXException if an error occurs during writing.
792 */
793 protected void indent(int depth) throws IOException
794 {
795
796 if (m_startNewLine)
797 outputLineSep();
798 /*
799 * Default value is 4, so printSpace directly.
800 */
801 printSpace(depth * m_indentAmount);
802
803 }
804
805 /**
806 * Indent at the current element nesting depth.
807 * @throws IOException
808 */
809 protected void indent() throws IOException
810 {
811 indent(m_elemContext.m_currentElemDepth);
812 }
813 /**
814 * Prints <var>n</var> spaces.
815 * @param n Number of spaces to print.
816 *
817 * @throws org.xml.sax.SAXException if an error occurs when writing.
818 */
819 private void printSpace(int n) throws IOException
820 {
1243 * <p>The application must not attempt to read from the array
1244 * outside of the specified range.</p>
1245 *
1246 * <p>Note that some parsers will report whitespace using the
1247 * ignorableWhitespace() method rather than this one (validating
1248 * parsers must do so).</p>
1249 *
1250 * @param ch The characters from the XML document.
1251 * @param start The start position in the array.
1252 * @param length The number of characters to read from the array.
1253 * @throws org.xml.sax.SAXException Any SAX exception, possibly
1254 * wrapping another exception.
1255 * @see #ignorableWhitespace
1256 * @see org.xml.sax.Locator
1257 *
1258 * @throws org.xml.sax.SAXException
1259 */
1260 protected void cdata(char ch[], int start, final int length)
1261 throws org.xml.sax.SAXException
1262 {
1263 try
1264 {
1265 final int old_start = start;
1266 if (m_elemContext.m_startTagOpen)
1267 {
1268 closeStartTag();
1269 m_elemContext.m_startTagOpen = false;
1270 }
1271 m_ispreserve = true;
1272
1273 if (shouldIndent())
1274 indent();
1275
1276 boolean writeCDataBrackets =
1277 (((length >= 1) && escapingNotNeeded(ch[start])));
1278
1279 /* Write out the CDATA opening delimiter only if
1280 * we are supposed to, and if we are not already in
1281 * the middle of a CDATA section
1282 */
1331 */
1332 private boolean isEscapingDisabled()
1333 {
1334 return m_disableOutputEscapingStates.peekOrFalse();
1335 }
1336
1337 /**
1338 * If available, when the disable-output-escaping attribute is used,
1339 * output raw text without escaping.
1340 *
1341 * @param ch The characters from the XML document.
1342 * @param start The start position in the array.
1343 * @param length The number of characters to read from the array.
1344 *
1345 * @throws org.xml.sax.SAXException
1346 */
1347 protected void charactersRaw(char ch[], int start, int length)
1348 throws org.xml.sax.SAXException
1349 {
1350
1351 if (isInEntityRef())
1352 return;
1353 try
1354 {
1355 if (m_elemContext.m_startTagOpen)
1356 {
1357 closeStartTag();
1358 m_elemContext.m_startTagOpen = false;
1359 }
1360
1361 m_ispreserve = true;
1362
1363 m_writer.write(ch, start, length);
1364 }
1365 catch (IOException e)
1366 {
1367 throw new SAXException(e);
1368 }
1369
1370 }
1371
1386 * ignorableWhitespace() method rather than this one (validating
1387 * parsers must do so).</p>
1388 *
1389 * @param chars The characters from the XML document.
1390 * @param start The start position in the array.
1391 * @param length The number of characters to read from the array.
1392 * @throws org.xml.sax.SAXException Any SAX exception, possibly
1393 * wrapping another exception.
1394 * @see #ignorableWhitespace
1395 * @see org.xml.sax.Locator
1396 *
1397 * @throws org.xml.sax.SAXException
1398 */
1399 public void characters(final char chars[], final int start, final int length)
1400 throws org.xml.sax.SAXException
1401 {
1402 // It does not make sense to continue with rest of the method if the number of
1403 // characters to read from array is 0.
1404 // Section 7.6.1 of XSLT 1.0 (http://www.w3.org/TR/xslt#value-of) suggest no text node
1405 // is created if string is empty.
1406 if (length == 0 || (isInEntityRef()))
1407 return;
1408
1409 final boolean shouldFormat = shouldFormatOutput();
1410 if (m_elemContext.m_startTagOpen && !shouldFormat)
1411 {
1412 closeStartTag();
1413 m_elemContext.m_startTagOpen = false;
1414 }
1415 else if (m_needToCallStartDocument)
1416 {
1417 startDocumentInternal();
1418 }
1419
1420 if (m_cdataStartCalled || m_elemContext.m_isCdataSection)
1421 {
1422 /* either due to startCDATA() being called or due to
1423 * cdata-section-elements atribute, we need this as cdata
1424 */
1425 cdata(chars, start, length);
1426
1427 return;
1428 }
1429
1430 if (m_cdataTagOpen)
1431 closeCDATA();
1432 // the check with _escaping is a bit of a hack for XLSTC
1433
1434 if (m_disableOutputEscapingStates.peekOrFalse() || (!m_escaping))
1435 {
1436 charactersRaw(chars, start, length);
1437 m_isprevtext = true;
1438 // time to fire off characters generation event
1439 if (m_tracer != null)
1440 super.fireCharEvent(chars, start, length);
1441
1442 return;
1443 }
1444
1445 if (m_elemContext.m_startTagOpen && !shouldFormat)
1446 {
1447 closeStartTag();
1448 m_elemContext.m_startTagOpen = false;
1449 }
1450
1451 if (shouldFormat) {
1452 m_charactersBuffer.addText(chars, start, length);
1453 } else {
1454 outputCharacters(chars, start, length);
1455 }
1456
1457 // time to fire off characters generation event
1458 if (m_tracer != null)
1459 super.fireCharEvent(chars, start, length);
1460 }
1461
1462
1463 /**
1464 * This method checks if the content in current element should be formatted.
1465 *
1466 * @return True if the content should be formatted.
1467 */
1468 protected boolean shouldFormatOutput() {
1469 return !m_ispreserveSpace && m_doIndent;
1470 }
1471
1472 /**
1473 * Write out the characters.
1474 *
1475 * @param chars The characters of the text.
1476 * @param start The start position in the char array.
1477 * @param length The number of characters from the char array.
1478 */
1479 private void outputCharacters(final char chars[], final int start, final int length) throws SAXException {
1480 try
1481 {
1482 int i;
1483 char ch1;
1484 int startClean;
1485
1486 // skip any leading whitspace
1487 // don't go off the end and use a hand inlined version
1488 // of isWhitespace(ch)
1489 final int end = start + length;
1490 int lastDirty = start - 1; // last character that needed processing
1491 for (i = start;
1492 ((i < end)
1493 && ((ch1 = chars[i]) == 0x20
1494 || (ch1 == 0xA && m_lineSepUse)
1495 || ch1 == 0xD
1496 || ch1 == 0x09));
1497 i++)
1498 {
1499 /*
1552 i = lastDirty;
1553 }
1554 }
1555
1556 // we've reached the end. Any clean characters at the
1557 // end of the array than need to be written out?
1558 startClean = lastDirty + 1;
1559 if (i > startClean)
1560 {
1561 int lengthClean = i - startClean;
1562 m_writer.write(chars, startClean, lengthClean);
1563 }
1564
1565 // For indentation purposes, mark that we've just writen text out
1566 m_isprevtext = true;
1567 }
1568 catch (IOException e)
1569 {
1570 throw new SAXException(e);
1571 }
1572 }
1573
1574 /**
1575 * Used to flush the buffered characters when indentation is on, this method
1576 * will be called when the next node is traversed.
1577 *
1578 */
1579 final protected void flushCharactersBuffer() throws SAXException {
1580 try {
1581 if (shouldFormatOutput() && m_charactersBuffer.hasContent()) {
1582 if (m_elemContext.m_startTagOpen) {
1583 closeStartTag();
1584 m_elemContext.m_startTagOpen = false;
1585 }
1586
1587 if (m_elemContext.m_isCdataSection) {
1588 /*
1589 * due to cdata-section-elements atribute, we need this as
1590 * cdata
1591 */
1592 char[] chars = m_charactersBuffer.toChars();
1593 cdata(chars, 0, chars.length);
1594 return;
1595 }
1596
1597 m_childNodeNum++;
1598 if (shouldIndentForText()) {
1599 indent();
1600 m_startNewLine = true;
1601 }
1602 m_charactersBuffer.flush();
1603 }
1604 } catch (IOException e) {
1605 throw new SAXException(e);
1606 } finally {
1607 m_charactersBuffer.clear();
1608 }
1609 }
1610
1611 /**
1612 * True if should indent in flushCharactersBuffer method.
1613 * This method may be overridden in sub-class.
1614 *
1615 */
1616 protected boolean shouldIndentForText() {
1617 return (shouldIndent() && m_childNodeNum > 1);
1618 }
1619
1620 /**
1621 * This method checks if a given character is between C0 or C1 range
1622 * of Control characters.
1623 * This method is added to support Control Characters for XML 1.1
1624 * If a given character is TAB (0x09), LF (0x0A) or CR (0x0D), this method
1625 * return false. Since they are whitespace characters, no special processing is needed.
1626 *
1627 * @param ch
1628 * @return boolean
1629 */
1630 private static boolean isCharacterInC0orC1Range(char ch)
1631 {
1632 if(ch == 0x09 || ch == 0x0A || ch == 0x0D)
1633 return false;
1634 else
1635 return (ch >= 0x7F && ch <= 0x9F)|| (ch >= 0x01 && ch <= 0x1F);
1636 }
1637 /**
1638 * This method checks if a given character either NEL (0x85) or LSEP (0x2028)
1639 * These are new end of line charcters added in XML 1.1. These characters must be
1691 chars,
1692 end,
1693 fromTextNode,
1694 false);
1695 i = startClean - 1;
1696 }
1697 // Return the index of the last character that we just processed
1698 // which is a dirty character.
1699 return i;
1700 }
1701
1702 /**
1703 * Receive notification of character data.
1704 *
1705 * @param s The string of characters to process.
1706 *
1707 * @throws org.xml.sax.SAXException
1708 */
1709 public void characters(String s) throws org.xml.sax.SAXException
1710 {
1711 if (isInEntityRef())
1712 return;
1713 final int length = s.length();
1714 if (length > m_charsBuff.length)
1715 {
1716 m_charsBuff = new char[length * 2 + 1];
1717 }
1718 s.getChars(0, length, m_charsBuff, 0);
1719 characters(m_charsBuff, 0, length);
1720 }
1721
1722 /**
1723 * Escape and writer.write a character.
1724 *
1725 * @param ch character to be escaped.
1726 * @param i index into character array.
1727 * @param chars non-null reference to character array.
1728 * @param len length of chars.
1729 * @param fromTextNode true if the characters being processed are
1730 * from a text node, false if the characters being processed are from
1731 * an attribute value.
1839 * @param localName The local name (without prefix), or the
1840 * empty string if Namespace processing is not being
1841 * performed.
1842 * @param name The element type name.
1843 * @param atts The attributes attached to the element, if any.
1844 * @throws org.xml.sax.SAXException Any SAX exception, possibly
1845 * wrapping another exception.
1846 * @see org.xml.sax.ContentHandler#startElement
1847 * @see org.xml.sax.ContentHandler#endElement
1848 * @see org.xml.sax.AttributeList
1849 *
1850 * @throws org.xml.sax.SAXException
1851 */
1852 public void startElement(
1853 String namespaceURI,
1854 String localName,
1855 String name,
1856 Attributes atts)
1857 throws org.xml.sax.SAXException
1858 {
1859 if (isInEntityRef())
1860 return;
1861
1862 m_childNodeNum++;
1863 flushCharactersBuffer();
1864
1865 if (m_needToCallStartDocument)
1866 {
1867 startDocumentInternal();
1868 m_needToCallStartDocument = false;
1869 }
1870 else if (m_cdataTagOpen)
1871 closeCDATA();
1872 try
1873 {
1874 if ((true == m_needToOutputDocTypeDecl)
1875 && (null != getDoctypeSystem()))
1876 {
1877 outputDocTypeDecl(name, true);
1878 }
1879
1880 m_needToOutputDocTypeDecl = false;
1881
1882 /* before we over-write the current elementLocalName etc.
1883 * lets close out the old one (if we still need to)
1884 */
1885 if (m_elemContext.m_startTagOpen)
1886 {
1887 closeStartTag();
1888 m_elemContext.m_startTagOpen = false;
1889 }
1890
1891 if (namespaceURI != null)
1892 ensurePrefixIsDeclared(namespaceURI, name);
1893
1894 m_ispreserve = false;
1895
1896
1897
1898 if (shouldIndent() && m_startNewLine)
1899 {
1900 indent();
1901 }
1902
1903 m_startNewLine = true;
1904
1905 final Writer writer = m_writer;
1906 writer.write('<');
1907 writer.write(name);
1908 }
1909 catch (IOException e)
1910 {
1911 throw new SAXException(e);
1912 }
1913
1914 // process the attributes now, because after this SAX call they might be gone
1915 if (atts != null)
1916 addAttributes(atts);
1917
1918 m_ispreserveSpace = m_preserveSpaces.peekOrFalse();
1919 m_preserveSpaces.push(m_ispreserveSpace);
1920
1921 m_childNodeNumStack.push(m_childNodeNum);
1922 m_childNodeNum = 0;
1923
1924 m_elemContext = m_elemContext.push(namespaceURI,localName,name);
1925 m_isprevtext = false;
1926
1927 if (m_tracer != null){
1928 firePseudoAttributes();
1929 }
1930
1931 }
1932
1933 /**
1934 * Receive notification of the beginning of an element, additional
1935 * namespace or attribute information can occur before or after this call,
1936 * that is associated with this element.
1937 *
1938 *
1939 * @param elementNamespaceURI The Namespace URI, or the empty string if the
1940 * element has no Namespace URI or if Namespace
1941 * processing is not being performed.
1942 * @param elementLocalName The local name (without prefix), or the
1943 * empty string if Namespace processing is not being
2111 /**
2112 * Receive notification of the end of an element.
2113 *
2114 *
2115 * @param namespaceURI The Namespace URI, or the empty string if the
2116 * element has no Namespace URI or if Namespace
2117 * processing is not being performed.
2118 * @param localName The local name (without prefix), or the
2119 * empty string if Namespace processing is not being
2120 * performed.
2121 * @param name The element type name
2122 * @throws org.xml.sax.SAXException Any SAX exception, possibly
2123 * wrapping another exception.
2124 *
2125 * @throws org.xml.sax.SAXException
2126 */
2127 public void endElement(String namespaceURI, String localName, String name)
2128 throws org.xml.sax.SAXException
2129 {
2130
2131 if (isInEntityRef())
2132 return;
2133
2134 flushCharactersBuffer();
2135 // namespaces declared at the current depth are no longer valid
2136 // so get rid of them
2137 m_prefixMap.popNamespaces(m_elemContext.m_currentElemDepth, null);
2138
2139 try
2140 {
2141 final Writer writer = m_writer;
2142 if (m_elemContext.m_startTagOpen)
2143 {
2144 if (m_tracer != null)
2145 super.fireStartElem(m_elemContext.m_elementName);
2146 int nAttrs = m_attributes.getLength();
2147 if (nAttrs > 0)
2148 {
2149 processAttributes(m_writer, nAttrs);
2150 // clear attributes object for re-use with next element
2151 m_attributes.clear();
2152 }
2153 if (m_spaceBeforeClose)
2154 writer.write(" />");
2155 else
2156 writer.write("/>");
2157 /* don't need to pop cdataSectionState because
2158 * this element ended so quickly that we didn't get
2159 * to push the state.
2160 */
2161
2162 }
2163 else
2164 {
2165 if (m_cdataTagOpen)
2166 closeCDATA();
2167
2168 if (shouldIndent() && (m_childNodeNum > 1 || !m_isprevtext))
2169 indent(m_elemContext.m_currentElemDepth - 1);
2170 writer.write('<');
2171 writer.write('/');
2172 writer.write(name);
2173 writer.write('>');
2174 }
2175 }
2176 catch (IOException e)
2177 {
2178 throw new SAXException(e);
2179 }
2180
2181 if (!m_elemContext.m_startTagOpen && m_doIndent)
2182 {
2183 m_ispreserve = m_preserves.isEmpty() ? false : m_preserves.pop();
2184 }
2185
2186 m_ispreserveSpace = m_preserveSpaces.popAndTop();
2187 m_childNodeNum = m_childNodeNumStack.pop();
2188
2189 m_isprevtext = false;
2190
2191 // fire off the end element event
2192 if (m_tracer != null)
2193 super.fireEndElem(name);
2194 m_elemContext = m_elemContext.m_prev;
2195 }
2196
2197 /**
2198 * Receive notification of the end of an element.
2199 * @param name The element type name
2200 * @throws org.xml.sax.SAXException Any SAX exception, possibly
2201 * wrapping another exception.
2202 */
2203 public void endElement(String name) throws org.xml.sax.SAXException
2204 {
2205 endElement(null, null, name);
2206 }
2207
2208 /**
2304 }
2305 }
2306 }
2307 return pushed;
2308 }
2309
2310 /**
2311 * Receive notification of an XML comment anywhere in the document. This
2312 * callback will be used for comments inside or outside the document
2313 * element, including comments in the external DTD subset (if read).
2314 * @param ch An array holding the characters in the comment.
2315 * @param start The starting position in the array.
2316 * @param length The number of characters to use from the array.
2317 * @throws org.xml.sax.SAXException The application may raise an exception.
2318 */
2319 public void comment(char ch[], int start, int length)
2320 throws org.xml.sax.SAXException
2321 {
2322
2323 int start_old = start;
2324 if (isInEntityRef())
2325 return;
2326 m_childNodeNum++;
2327 flushCharactersBuffer();
2328 if (m_elemContext.m_startTagOpen)
2329 {
2330 closeStartTag();
2331 m_elemContext.m_startTagOpen = false;
2332 }
2333 else if (m_needToCallStartDocument)
2334 {
2335 startDocumentInternal();
2336 m_needToCallStartDocument = false;
2337 }
2338
2339 try
2340 {
2341 if (shouldIndent() && m_isStandalone)
2342 indent();
2343
2344 final int limit = start + length;
2345 boolean wasDash = false;
2346 if (m_cdataTagOpen)
2347 closeCDATA();
2487 *
2488 * @param name The name of the skipped entity. If it is a
2489 * parameter entity, the name will begin with '%',
2490 * and if it is the external DTD subset, it will be the string
2491 * "[dtd]".
2492 * @throws org.xml.sax.SAXException Any SAX exception, possibly wrapping
2493 * another exception.
2494 */
2495 public void skippedEntity(String name) throws org.xml.sax.SAXException
2496 { // TODO: Should handle
2497 }
2498
2499 /**
2500 * Report the start of a CDATA section.
2501 *
2502 * @throws org.xml.sax.SAXException The application may raise an exception.
2503 * @see #endCDATA
2504 */
2505 public void startCDATA() throws org.xml.sax.SAXException
2506 {
2507 m_childNodeNum++;
2508 flushCharactersBuffer();
2509
2510 m_cdataStartCalled = true;
2511 }
2512
2513 /**
2514 * Report the beginning of an entity.
2515 *
2516 * The start and end of the document entity are not reported.
2517 * The start and end of the external DTD subset are reported
2518 * using the pseudo-name "[dtd]". All other events must be
2519 * properly nested within start/end entity events.
2520 *
2521 * @param name The name of the entity. If it is a parameter
2522 * entity, the name will begin with '%'.
2523 * @throws org.xml.sax.SAXException The application may raise an exception.
2524 * @see #endEntity
2525 * @see org.xml.sax.ext.DeclHandler#internalEntityDecl
2526 * @see org.xml.sax.ext.DeclHandler#externalEntityDecl
2527 */
2528 public void startEntity(String name) throws org.xml.sax.SAXException
2529 {
2530 if (name.equals("[dtd]"))
2531 m_inExternalDTD = true;
2532
2533 // if this is not the magic [dtd] name
2534 if (!m_inExternalDTD) {
2535 // if it's not in nested entity reference
2536 if (!isInEntityRef()) {
2537 if (shouldFormatOutput()) {
2538 m_charactersBuffer.addEntityReference(name);
2539 } else {
2540 outputEntityReference(name);
2541 }
2542 }
2543 m_inEntityRef++;
2544 }
2545 }
2546
2547 /**
2548 * Write out the entity reference with the form as "&entityName;".
2549 *
2550 * @param name The name of the entity.
2551 */
2552 private void outputEntityReference(String name) throws SAXException {
2553 startNonEscaping();
2554 characters("&" + name + ';');
2555 endNonEscaping();
2556 m_isprevtext = true;
2557 }
2558
2559 /**
2560 * For the enclosing elements starting tag write out
2561 * out any attributes followed by ">"
2562 *
2563 * @throws org.xml.sax.SAXException
2564 */
2565 protected void closeStartTag() throws SAXException
2566 {
2567 if (m_elemContext.m_startTagOpen)
2568 {
2569
2570 try
2571 {
2572 if (m_tracer != null)
2573 super.fireStartElem(m_elemContext.m_elementName);
2574 int nAttrs = m_attributes.getLength();
2575 if (nAttrs > 0)
2576 {
2637 }
2638
2639 /**
2640 * Sets the m_indentAmount.
2641 *
2642 * @param m_indentAmount The m_indentAmount to set
2643 */
2644 public void setIndentAmount(int m_indentAmount)
2645 {
2646 this.m_indentAmount = m_indentAmount;
2647 }
2648
2649 /**
2650 * Tell if, based on space preservation constraints and the doIndent property,
2651 * if an indent should occur.
2652 *
2653 * @return True if an indent should occur.
2654 */
2655 protected boolean shouldIndent()
2656 {
2657 return shouldFormatOutput() && (m_elemContext.m_currentElemDepth > 0 || m_isStandalone);
2658 }
2659
2660 /**
2661 * Searches for the list of qname properties with the specified key in the
2662 * property list. If the key is not found in this property list, the default
2663 * property list, and its defaults, recursively, are then checked. The
2664 * method returns <code>null</code> if the property is not found.
2665 *
2666 * @param key the property key.
2667 * @param props the list of properties to search in.
2668 *
2669 * Sets the ArrayList of local-name/URI pairs of the cdata section elements
2670 * specified in the cdata-section-elements property.
2671 *
2672 * This method is essentially a copy of getQNameProperties() from
2673 * OutputProperties. Eventually this method should go away and a call
2674 * to setCdataSectionElements(ArrayList<String> v) should be made directly.
2675 */
2676 private void setCdataSectionElements(String key, Properties props) {
2677 String s = props.getProperty(key);
2929 * stream serializers, not for SAX ones.
2930 *
2931 * @param uri the URI of the attribute
2932 * @param localName the local name of the attribute
2933 * @param rawName the qualified name of the attribute
2934 * @param type the type of the attribute (probably CDATA)
2935 * @param value the value of the attribute
2936 * @param xslAttribute true if this attribute is coming from an xsl:attribute element.
2937 * @return true if the attribute value was added,
2938 * false if the attribute already existed and the value was
2939 * replaced with the new value.
2940 */
2941 public boolean addAttributeAlways(
2942 String uri,
2943 String localName,
2944 String rawName,
2945 String type,
2946 String value,
2947 boolean xslAttribute)
2948 {
2949 if (m_charactersBuffer.isAnyCharactersBuffered()) {
2950 /*
2951 * If stylesheet includes xsl:copy-of an attribute node, XSLTC will
2952 * fire an addAttribute event. When a text node is handling in
2953 * ToStream, addAttribute has no effect. But closeStartTag call is
2954 * delayed to flushCharactersBuffer() method if the text node is
2955 * buffered, so here we ignore the attribute to avoid corrupting the
2956 * start tag content.
2957 *
2958 */
2959 return m_attributes.getIndex(rawName) < 0;
2960 } else {
2961 return doAddAttributeAlways(uri, localName, rawName, type, value, xslAttribute);
2962 }
2963 }
2964
2965 /**
2966 * Does really add the attribute to the set of attributes.
2967 */
2968 private boolean doAddAttributeAlways(
2969 String uri,
2970 String localName,
2971 String rawName,
2972 String type,
2973 String value,
2974 boolean xslAttribute)
2975 {
2976 boolean was_added;
2977 int index;
2978 //if (uri == null || localName == null || uri.length() == 0)
2979 index = m_attributes.getIndex(rawName);
2980 // Don't use 'localName' as it gives incorrect value, rely only on 'rawName'
2981 /*else {
2982 index = m_attributes.getIndex(uri, localName);
2983 }*/
2984 if (index >= 0)
2985 {
2986 String old_value = null;
2987 if (m_tracer != null)
2988 {
2989 old_value = m_attributes.getValue(index);
2990 if (value.equals(old_value))
2991 old_value = null;
2992 }
2993
2994 /* We've seen the attribute before.
2995 * We may have a null uri or localName, but all we really
3064
3065 try
3066 {
3067 /* This is our last chance to make sure the namespace for this
3068 * attribute is declared, especially if we just generated an alternate
3069 * prefix to avoid a collision (the new prefix/rawName will go out of scope
3070 * soon and be lost ... last chance here.
3071 */
3072 String prefixUsed =
3073 ensureAttributesNamespaceIsDeclared(
3074 uri,
3075 localName,
3076 rawName);
3077 }
3078 catch (SAXException e)
3079 {
3080 // TODO Auto-generated catch block
3081 e.printStackTrace();
3082 }
3083 }
3084
3085 m_attributes.addAttribute(uri, localName, rawName, type, value);
3086 was_added = true;
3087 if (m_tracer != null){
3088 firePseudoAttributes();
3089 }
3090 }
3091
3092 if (rawName.equals("xml:space")) {
3093 if (value.equals("preserve")) {
3094 m_ispreserveSpace = true;
3095 if (m_preserveSpaces.size() > 0)
3096 m_preserveSpaces.setTop(m_ispreserveSpace);
3097 } else if (value.equals("default")) {
3098 m_ispreserveSpace = false;
3099 if (m_preserveSpaces.size() > 0)
3100 m_preserveSpaces.setTop(m_ispreserveSpace);
3101 }
3102 }
3103
3104 return was_added;
3105 }
3106
3107 /**
3108 * To fire off the pseudo characters of attributes, as they currently
3109 * exist. This method should be called everytime an attribute is added,
3110 * or when an attribute value is changed, or an element is created.
3111 */
3112 protected void firePseudoAttributes() {
3113 if (m_tracer != null) {
3114 try {
3115 // flush out the "<elemName" if not already flushed
3116 m_writer.flush();
3117
3118 // make a StringBuffer to write the name="value" pairs to.
3119 StringBuffer sb = new StringBuffer();
3120 int nAttrs = m_attributes.getLength();
3121 if (nAttrs > 0) {
3122 // make a writer that internally appends to the same
3123 // StringBuffer
3214 /**
3215 * Reset all of the fields owned by ToStream class
3216 *
3217 */
3218 private void resetToStream() {
3219 this.m_cdataStartCalled = false;
3220 /* The stream is being reset. It is one of
3221 * ToXMLStream, ToHTMLStream ... and this type can't be changed
3222 * so neither should m_charInfo which is associated with the
3223 * type of Stream. Just leave m_charInfo as-is for the next re-use.
3224 */
3225 // this.m_charInfo = null; // don't set to null
3226
3227 this.m_disableOutputEscapingStates.clear();
3228
3229 this.m_escaping = true;
3230 // Leave m_format alone for now - Brian M.
3231 // this.m_format = null;
3232 this.m_inDoctype = false;
3233 this.m_ispreserve = false;
3234 this.m_preserves.clear();
3235 this.m_ispreserveSpace = false;
3236 this.m_preserveSpaces.clear();
3237 this.m_childNodeNum = 0;
3238 this.m_childNodeNumStack.clear();
3239 this.m_charactersBuffer.clear();
3240 this.m_isprevtext = false;
3241 this.m_isUTF8 = false; // ?? used anywhere ??
3242 this.m_shouldFlush = true;
3243 this.m_spaceBeforeClose = false;
3244 this.m_startNewLine = false;
3245 this.m_lineSepUse = true;
3246 // DON'T SET THE WRITER TO NULL, IT MAY BE REUSED !!
3247 // this.m_writer = null;
3248 this.m_expandDTDEntities = true;
3249
3250 }
3251
3252 /**
3253 * Sets the character encoding coming from the xsl:output encoding stylesheet attribute.
3254 * @param encoding the character encoding
3255 */
3256 public void setEncoding(String encoding)
3257 {
3258 setOutputProperty(OutputKeys.ENCODING,encoding);
3259 }
3260
3261 /**
3397 *
3398 * @return <code>true</code> if this stack is empty;
3399 * <code>false</code> otherwise.
3400 */
3401 public boolean isEmpty() {
3402 return (m_index == -1);
3403 }
3404
3405 /**
3406 * Grows the size of the stack
3407 *
3408 */
3409 private void grow() {
3410 m_allocatedSize *= 2;
3411 boolean newVector[] = new boolean[m_allocatedSize];
3412 System.arraycopy(m_values, 0, newVector, 0, m_index + 1);
3413 m_values = newVector;
3414 }
3415 }
3416
3417 /**
3418 * This inner class is used to buffer the text nodes and the entity
3419 * reference nodes if indentation is on. There is only one CharacterBuffer
3420 * instance in ToStream, it contains a queue of GenericCharacters,
3421 * GenericCharacters can be a text node or an entity reference node. The
3422 * text nodes and entity reference nodes are joined together and then are
3423 * flushed.
3424 */
3425 private class CharacterBuffer {
3426 /**
3427 * GenericCharacters is immutable.
3428 */
3429 private abstract class GenericCharacters {
3430 /**
3431 * @return True if having any character other than whitespace or
3432 * line feed.
3433 */
3434 abstract boolean hasContent();
3435
3436 abstract void flush() throws SAXException;
3437
3438 /**
3439 * Converts this GenericCharacters to a new character array.
3440 */
3441 abstract char[] toChars();
3442 }
3443
3444 private Queue<GenericCharacters> bufferedCharacters = new ArrayDeque<>();
3445
3446 /**
3447 * Append a text node to the buffer.
3448 */
3449 public void addText(final char chars[], final int start, final int length) {
3450 bufferedCharacters.add(new GenericCharacters() {
3451 char[] text;
3452
3453 {
3454 text = Arrays.copyOfRange(chars, start, start + length);
3455 }
3456
3457 boolean hasContent() {
3458 for (int i = 0; i < text.length; i++) {
3459 if (!isWhiteSpace(text[i])) {
3460 return true;
3461 }
3462 }
3463 return false;
3464 }
3465
3466 void flush() throws SAXException {
3467 outputCharacters(text, 0, text.length);
3468 }
3469
3470 char[] toChars() {
3471 return text;
3472 }
3473
3474 boolean isWhiteSpace(char ch) {
3475 return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
3476 }
3477
3478 });
3479 }
3480
3481 /**
3482 * Append an entity reference to the buffer.
3483 */
3484 public void addEntityReference(String entityName) {
3485 bufferedCharacters.add(new GenericCharacters() {
3486 boolean hasContent() {
3487 return true;
3488 }
3489
3490 void flush() throws SAXException {
3491 outputEntityReference(entityName);
3492 }
3493
3494 char[] toChars() {
3495 return ("&" + entityName + ";").toCharArray();
3496 }
3497 });
3498 }
3499
3500 /**
3501 * @return True if any GenericCharacters is already buffered.
3502 */
3503 public boolean isAnyCharactersBuffered() {
3504 return !bufferedCharacters.isEmpty();
3505 }
3506
3507 /**
3508 * @return True if any buffered GenericCharacters has content.
3509 */
3510 public boolean hasContent() {
3511 return bufferedCharacters.stream().anyMatch(GenericCharacters::hasContent);
3512 }
3513
3514 /**
3515 * Flush all buffered GenericCharacters.
3516 */
3517 public void flush() throws SAXException {
3518 GenericCharacters element;
3519 while ((element = bufferedCharacters.poll()) != null)
3520 element.flush();
3521 }
3522
3523 /**
3524 * Converts all buffered GenericCharacters to a new character array.
3525 */
3526 public char[] toChars() {
3527 return bufferedCharacters.stream().map(GenericCharacters::toChars)
3528 .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append).toString()
3529 .toCharArray();
3530 }
3531
3532 /**
3533 * Clear the buffer.
3534 */
3535 public void clear() {
3536 bufferedCharacters.clear();
3537 }
3538 }
3539
3540 // Implement DTDHandler
3541 /**
3542 * If this method is called, the serializer is used as a
3543 * DTDHandler, which changes behavior how the serializer
3544 * handles document entities.
3545 * @see org.xml.sax.DTDHandler#notationDecl(java.lang.String, java.lang.String, java.lang.String)
3546 */
3547 public void notationDecl(String name, String pubID, String sysID) throws SAXException {
3548 // TODO Auto-generated method stub
3549 try {
3550 DTDprolog();
3551
3552 m_writer.write("<!NOTATION ");
3553 m_writer.write(name);
3554 if (pubID != null) {
3555 m_writer.write(" PUBLIC \"");
3556 m_writer.write(pubID);
3557
3558 }
3559 else {
|