< prev index next >
src/java.xml/share/classes/com/sun/org/apache/xml/internal/serializer/ToStream.java
Print this page
*** 27,42 ****
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.Enumeration;
- import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
- import java.util.ArrayList;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import org.w3c.dom.Node;
--- 27,46 ----
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
+ import java.util.ArrayDeque;
+ import java.util.ArrayList;
+ import java.util.Arrays;
+ import java.util.Deque;
+ import java.util.EmptyStackException;
import java.util.Enumeration;
import java.util.Properties;
+ import java.util.Queue;
import java.util.Set;
import java.util.StringTokenizer;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import org.w3c.dom.Node;
*** 87,96 ****
--- 91,123 ----
* encoding.
*/
Object m_charToByteConverter = null;
/**
+ * Used to buffer the text nodes and the entity reference nodes if
+ * indentation is on.
+ */
+ protected CharacterBuffer m_charactersBuffer = new CharacterBuffer();
+
+ /**
+ * Used to decide if a text node is pretty-printed with indentation.
+ * If m_childNodeNum > 1, the text node will be indented.
+ *
+ */
+ protected Deque<Integer> m_childNodeNumStack = new ArrayDeque<>();
+
+ protected int m_childNodeNum = 0;
+
+ /**
+ * Used to handle xml:space attribute
+ *
+ */
+ protected BoolStack m_preserveSpaces = new BoolStack();
+
+ protected boolean m_ispreserveSpace = false;
+
+ /**
* Stack to keep track of whether or not we need to
* preserve whitespace.
*
* Used to push/pop values used for the field m_ispreserve, but
* m_ispreserve is only relevant if m_doIndent is true.
*** 765,779 ****
protected void indent(int depth) throws IOException
{
if (m_startNewLine)
outputLineSep();
! /* For m_indentAmount > 0 this extra test might be slower
! * but Xalan's default value is 0, so this extra test
! * will run faster in that situation.
*/
- if (m_indentAmount > 0)
printSpace(depth * m_indentAmount);
}
/**
--- 792,804 ----
protected void indent(int depth) throws IOException
{
if (m_startNewLine)
outputLineSep();
! /*
! * Default value is 4, so printSpace directly.
*/
printSpace(depth * m_indentAmount);
}
/**
*** 1232,1242 ****
* @throws org.xml.sax.SAXException
*/
protected void cdata(char ch[], int start, final int length)
throws org.xml.sax.SAXException
{
-
try
{
final int old_start = start;
if (m_elemContext.m_startTagOpen)
{
--- 1257,1266 ----
*** 1321,1331 ****
*/
protected void charactersRaw(char ch[], int start, int length)
throws org.xml.sax.SAXException
{
! if (m_inEntityRef)
return;
try
{
if (m_elemContext.m_startTagOpen)
{
--- 1345,1355 ----
*/
protected void charactersRaw(char ch[], int start, int length)
throws org.xml.sax.SAXException
{
! if (isInEntityRef())
return;
try
{
if (m_elemContext.m_startTagOpen)
{
*** 1376,1388 ****
{
// It does not make sense to continue with rest of the method if the number of
// characters to read from array is 0.
// Section 7.6.1 of XSLT 1.0 (http://www.w3.org/TR/xslt#value-of) suggest no text node
// is created if string is empty.
! if (length == 0 || (m_inEntityRef && !m_expandDTDEntities))
return;
! if (m_elemContext.m_startTagOpen)
{
closeStartTag();
m_elemContext.m_startTagOpen = false;
}
else if (m_needToCallStartDocument)
--- 1400,1414 ----
{
// It does not make sense to continue with rest of the method if the number of
// characters to read from array is 0.
// Section 7.6.1 of XSLT 1.0 (http://www.w3.org/TR/xslt#value-of) suggest no text node
// is created if string is empty.
! if (length == 0 || (isInEntityRef()))
return;
!
! final boolean shouldFormat = shouldFormatOutput();
! if (m_elemContext.m_startTagOpen && !shouldFormat)
{
closeStartTag();
m_elemContext.m_startTagOpen = false;
}
else if (m_needToCallStartDocument)
*** 1405,1429 ****
// the check with _escaping is a bit of a hack for XLSTC
if (m_disableOutputEscapingStates.peekOrFalse() || (!m_escaping))
{
charactersRaw(chars, start, length);
!
// time to fire off characters generation event
if (m_tracer != null)
super.fireCharEvent(chars, start, length);
return;
}
! if (m_elemContext.m_startTagOpen)
{
closeStartTag();
m_elemContext.m_startTagOpen = false;
}
try
{
int i;
char ch1;
int startClean;
--- 1431,1483 ----
// the check with _escaping is a bit of a hack for XLSTC
if (m_disableOutputEscapingStates.peekOrFalse() || (!m_escaping))
{
charactersRaw(chars, start, length);
! m_isprevtext = true;
// time to fire off characters generation event
if (m_tracer != null)
super.fireCharEvent(chars, start, length);
return;
}
! if (m_elemContext.m_startTagOpen && !shouldFormat)
{
closeStartTag();
m_elemContext.m_startTagOpen = false;
}
+ if (shouldFormat) {
+ m_charactersBuffer.addText(chars, start, length);
+ } else {
+ outputCharacters(chars, start, length);
+ }
+ // time to fire off characters generation event
+ if (m_tracer != null)
+ super.fireCharEvent(chars, start, length);
+ }
+
+
+ /**
+ * This method checks if the content in current element should be formatted.
+ *
+ * @return True if the content should be formatted.
+ */
+ protected boolean shouldFormatOutput() {
+ return !m_ispreserveSpace && m_doIndent;
+ }
+
+ /**
+ * Write out the characters.
+ *
+ * @param chars The characters of the text.
+ * @param start The start position in the char array.
+ * @param length The number of characters from the char array.
+ */
+ private void outputCharacters(final char chars[], final int start, final int length) throws SAXException {
try
{
int i;
char ch1;
int startClean;
*** 1512,1526 ****
}
catch (IOException e)
{
throw new SAXException(e);
}
! // time to fire off characters generation event
! if (m_tracer != null)
! super.fireCharEvent(chars, start, length);
}
/**
* This method checks if a given character is between C0 or C1 range
* of Control characters.
* This method is added to support Control Characters for XML 1.1
* If a given character is TAB (0x09), LF (0x0A) or CR (0x0D), this method
--- 1566,1623 ----
}
catch (IOException e)
{
throw new SAXException(e);
}
+ }
! /**
! * Used to flush the buffered characters when indentation is on, this method
! * will be called when the next node is traversed.
! *
! */
! final protected void flushCharactersBuffer() throws SAXException {
! try {
! if (shouldFormatOutput() && m_charactersBuffer.hasContent()) {
! if (m_elemContext.m_startTagOpen) {
! closeStartTag();
! m_elemContext.m_startTagOpen = false;
! }
!
! if (m_elemContext.m_isCdataSection) {
! /*
! * due to cdata-section-elements atribute, we need this as
! * cdata
! */
! char[] chars = m_charactersBuffer.toChars();
! cdata(chars, 0, chars.length);
! return;
! }
!
! m_childNodeNum++;
! if (shouldIndentForText()) {
! indent();
! m_startNewLine = true;
}
+ m_charactersBuffer.flush();
+ }
+ } catch (IOException e) {
+ throw new SAXException(e);
+ } finally {
+ m_charactersBuffer.clear();
+ }
+ }
+
+ /**
+ * True if should indent in flushCharactersBuffer method.
+ * This method may be overridden in sub-class.
+ *
+ */
+ protected boolean shouldIndentForText() {
+ return (shouldIndent() && m_childNodeNum > 1);
+ }
+
/**
* This method checks if a given character is between C0 or C1 range
* of Control characters.
* This method is added to support Control Characters for XML 1.1
* If a given character is TAB (0x09), LF (0x0A) or CR (0x0D), this method
*** 1608,1618 ****
*
* @throws org.xml.sax.SAXException
*/
public void characters(String s) throws org.xml.sax.SAXException
{
! if (m_inEntityRef && !m_expandDTDEntities)
return;
final int length = s.length();
if (length > m_charsBuff.length)
{
m_charsBuff = new char[length * 2 + 1];
--- 1705,1715 ----
*
* @throws org.xml.sax.SAXException
*/
public void characters(String s) throws org.xml.sax.SAXException
{
! if (isInEntityRef())
return;
final int length = s.length();
if (length > m_charsBuff.length)
{
m_charsBuff = new char[length * 2 + 1];
*** 1756,1768 ****
String localName,
String name,
Attributes atts)
throws org.xml.sax.SAXException
{
! if (m_inEntityRef)
return;
if (m_needToCallStartDocument)
{
startDocumentInternal();
m_needToCallStartDocument = false;
}
--- 1853,1868 ----
String localName,
String name,
Attributes atts)
throws org.xml.sax.SAXException
{
! if (isInEntityRef())
return;
+ m_childNodeNum++;
+ flushCharactersBuffer();
+
if (m_needToCallStartDocument)
{
startDocumentInternal();
m_needToCallStartDocument = false;
}
*** 1810,1819 ****
--- 1910,1925 ----
// process the attributes now, because after this SAX call they might be gone
if (atts != null)
addAttributes(atts);
+ m_ispreserveSpace = m_preserveSpaces.peekOrFalse();
+ m_preserveSpaces.push(m_ispreserveSpace);
+
+ m_childNodeNumStack.push(m_childNodeNum);
+ m_childNodeNum = 0;
+
m_elemContext = m_elemContext.push(namespaceURI,localName,name);
m_isprevtext = false;
if (m_tracer != null){
firePseudoAttributes();
*** 2017,2029 ****
*/
public void endElement(String namespaceURI, String localName, String name)
throws org.xml.sax.SAXException
{
! if (m_inEntityRef)
return;
// namespaces declared at the current depth are no longer valid
// so get rid of them
m_prefixMap.popNamespaces(m_elemContext.m_currentElemDepth, null);
try
--- 2123,2136 ----
*/
public void endElement(String namespaceURI, String localName, String name)
throws org.xml.sax.SAXException
{
! if (isInEntityRef())
return;
+ flushCharactersBuffer();
// namespaces declared at the current depth are no longer valid
// so get rid of them
m_prefixMap.popNamespaces(m_elemContext.m_currentElemDepth, null);
try
*** 2053,2063 ****
else
{
if (m_cdataTagOpen)
closeCDATA();
! if (shouldIndent())
indent(m_elemContext.m_currentElemDepth - 1);
writer.write('<');
writer.write('/');
writer.write(name);
writer.write('>');
--- 2160,2170 ----
else
{
if (m_cdataTagOpen)
closeCDATA();
! if (shouldIndent() && (m_childNodeNum > 1 || !m_isprevtext))
indent(m_elemContext.m_currentElemDepth - 1);
writer.write('<');
writer.write('/');
writer.write(name);
writer.write('>');
*** 2071,2080 ****
--- 2178,2190 ----
if (!m_elemContext.m_startTagOpen && m_doIndent)
{
m_ispreserve = m_preserves.isEmpty() ? false : m_preserves.pop();
}
+ m_ispreserveSpace = m_preserveSpaces.popAndTop();
+ m_childNodeNum = m_childNodeNumStack.pop();
+
m_isprevtext = false;
// fire off the end element event
if (m_tracer != null)
super.fireEndElem(name);
*** 2206,2217 ****
public void comment(char ch[], int start, int length)
throws org.xml.sax.SAXException
{
int start_old = start;
! if (m_inEntityRef)
return;
if (m_elemContext.m_startTagOpen)
{
closeStartTag();
m_elemContext.m_startTagOpen = false;
}
--- 2316,2329 ----
public void comment(char ch[], int start, int length)
throws org.xml.sax.SAXException
{
int start_old = start;
! if (isInEntityRef())
return;
+ m_childNodeNum++;
+ flushCharactersBuffer();
if (m_elemContext.m_startTagOpen)
{
closeStartTag();
m_elemContext.m_startTagOpen = false;
}
*** 2387,2396 ****
--- 2499,2511 ----
* @throws org.xml.sax.SAXException The application may raise an exception.
* @see #endCDATA
*/
public void startCDATA() throws org.xml.sax.SAXException
{
+ m_childNodeNum++;
+ flushCharactersBuffer();
+
m_cdataStartCalled = true;
}
/**
* Report the beginning of an entity.
*** 2410,2430 ****
public void startEntity(String name) throws org.xml.sax.SAXException
{
if (name.equals("[dtd]"))
m_inExternalDTD = true;
! if (!m_expandDTDEntities && !m_inExternalDTD) {
! /* Only leave the entity as-is if
! * we've been told not to expand them
! * and this is not the magic [dtd] name.
*/
startNonEscaping();
characters("&" + name + ';');
endNonEscaping();
! }
!
! m_inEntityRef = true;
}
/**
* For the enclosing elements starting tag write out
* out any attributes followed by ">"
--- 2525,2558 ----
public void startEntity(String name) throws org.xml.sax.SAXException
{
if (name.equals("[dtd]"))
m_inExternalDTD = true;
! // if this is not the magic [dtd] name
! if (!m_inExternalDTD) {
! // if it's not in nested entity reference
! if (!isInEntityRef()) {
! if (shouldFormatOutput()) {
! m_charactersBuffer.addEntityReference(name);
! } else {
! outputEntityReference(name);
! }
! }
! m_inEntityRef++;
! }
! }
!
! /**
! * Write out the entity reference with the form as "&entityName;".
! *
! * @param name The name of the entity.
*/
+ private void outputEntityReference(String name) throws SAXException {
startNonEscaping();
characters("&" + name + ';');
endNonEscaping();
! m_isprevtext = true;
}
/**
* For the enclosing elements starting tag write out
* out any attributes followed by ">"
*** 2521,2531 ****
*
* @return True if an indent should occur.
*/
protected boolean shouldIndent()
{
! return m_doIndent && (!m_ispreserve && !m_isprevtext) && (m_elemContext.m_currentElemDepth > 0 || m_isStandalone);
}
/**
* Searches for the list of qname properties with the specified key in the
* property list. If the key is not found in this property list, the default
--- 2649,2659 ----
*
* @return True if an indent should occur.
*/
protected boolean shouldIndent()
{
! return shouldFormatOutput() && (m_elemContext.m_currentElemDepth > 0 || m_isStandalone);
}
/**
* Searches for the list of qname properties with the specified key in the
* property list. If the key is not found in this property list, the default
*** 2813,2822 ****
--- 2941,2977 ----
String rawName,
String type,
String value,
boolean xslAttribute)
{
+ if (m_charactersBuffer.isAnyCharactersBuffered()) {
+ /*
+ * If stylesheet includes xsl:copy-of an attribute node, XSLTC will
+ * fire an addAttribute event. When a text node is handling in
+ * ToStream, addAttribute has no effect. But closeStartTag call is
+ * delayed to flushCharactersBuffer() method if the text node is
+ * buffered, so here we ignore the attribute to avoid corrupting the
+ * start tag content.
+ *
+ */
+ return m_attributes.getIndex(rawName) < 0;
+ } else {
+ return doAddAttributeAlways(uri, localName, rawName, type, value, xslAttribute);
+ }
+ }
+
+ /**
+ * Does really add the attribute to the set of attributes.
+ */
+ private boolean doAddAttributeAlways(
+ String uri,
+ String localName,
+ String rawName,
+ String type,
+ String value,
+ boolean xslAttribute)
+ {
boolean was_added;
int index;
//if (uri == null || localName == null || uri.length() == 0)
index = m_attributes.getIndex(rawName);
// Don't use 'localName' as it gives incorrect value, rely only on 'rawName'
*** 2921,2936 ****
--- 3076,3105 ----
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
+
m_attributes.addAttribute(uri, localName, rawName, type, value);
was_added = true;
if (m_tracer != null){
firePseudoAttributes();
}
}
+
+ if (rawName.equals("xml:space")) {
+ if (value.equals("preserve")) {
+ m_ispreserveSpace = true;
+ if (m_preserveSpaces.size() > 0)
+ m_preserveSpaces.setTop(m_ispreserveSpace);
+ } else if (value.equals("default")) {
+ m_ispreserveSpace = false;
+ if (m_preserveSpaces.size() > 0)
+ m_preserveSpaces.setTop(m_ispreserveSpace);
+ }
+ }
+
return was_added;
}
/**
* To fire off the pseudo characters of attributes, as they currently
*** 3057,3070 ****
this.m_escaping = true;
// Leave m_format alone for now - Brian M.
// this.m_format = null;
this.m_inDoctype = false;
this.m_ispreserve = false;
! this.m_ispreserve = false;
this.m_isprevtext = false;
this.m_isUTF8 = false; // ?? used anywhere ??
- this.m_preserves.clear();
this.m_shouldFlush = true;
this.m_spaceBeforeClose = false;
this.m_startNewLine = false;
this.m_lineSepUse = true;
// DON'T SET THE WRITER TO NULL, IT MAY BE REUSED !!
--- 3226,3243 ----
this.m_escaping = true;
// Leave m_format alone for now - Brian M.
// this.m_format = null;
this.m_inDoctype = false;
this.m_ispreserve = false;
! this.m_preserves.clear();
! this.m_ispreserveSpace = false;
! this.m_preserveSpaces.clear();
! this.m_childNodeNum = 0;
! this.m_childNodeNumStack.clear();
! this.m_charactersBuffer.clear();
this.m_isprevtext = false;
this.m_isUTF8 = false; // ?? used anywhere ??
this.m_shouldFlush = true;
this.m_spaceBeforeClose = false;
this.m_startNewLine = false;
this.m_lineSepUse = true;
// DON'T SET THE WRITER TO NULL, IT MAY BE REUSED !!
*** 3236,3245 ****
--- 3409,3541 ----
System.arraycopy(m_values, 0, newVector, 0, m_index + 1);
m_values = newVector;
}
}
+ /**
+ * This inner class is used to buffer the text nodes and the entity
+ * reference nodes if indentation is on. There is only one CharacterBuffer
+ * instance in ToStream, it contains a queue of GenericCharacters,
+ * GenericCharacters can be a text node or an entity reference node. The
+ * text nodes and entity reference nodes are joined together and then are
+ * flushed.
+ */
+ private class CharacterBuffer {
+ /**
+ * GenericCharacters is immutable.
+ */
+ private abstract class GenericCharacters {
+ /**
+ * @return True if having any character other than whitespace or
+ * line feed.
+ */
+ abstract boolean hasContent();
+
+ abstract void flush() throws SAXException;
+
+ /**
+ * Converts this GenericCharacters to a new character array.
+ */
+ abstract char[] toChars();
+ }
+
+ private Queue<GenericCharacters> bufferedCharacters = new ArrayDeque<>();
+
+ /**
+ * Append a text node to the buffer.
+ */
+ public void addText(final char chars[], final int start, final int length) {
+ bufferedCharacters.add(new GenericCharacters() {
+ char[] text;
+
+ {
+ text = Arrays.copyOfRange(chars, start, start + length);
+ }
+
+ boolean hasContent() {
+ for (int i = 0; i < text.length; i++) {
+ if (!isWhiteSpace(text[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void flush() throws SAXException {
+ outputCharacters(text, 0, text.length);
+ }
+
+ char[] toChars() {
+ return text;
+ }
+
+ boolean isWhiteSpace(char ch) {
+ return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
+ }
+
+ });
+ }
+
+ /**
+ * Append an entity reference to the buffer.
+ */
+ public void addEntityReference(String entityName) {
+ bufferedCharacters.add(new GenericCharacters() {
+ boolean hasContent() {
+ return true;
+ }
+
+ void flush() throws SAXException {
+ outputEntityReference(entityName);
+ }
+
+ char[] toChars() {
+ return ("&" + entityName + ";").toCharArray();
+ }
+ });
+ }
+
+ /**
+ * @return True if any GenericCharacters is already buffered.
+ */
+ public boolean isAnyCharactersBuffered() {
+ return !bufferedCharacters.isEmpty();
+ }
+
+ /**
+ * @return True if any buffered GenericCharacters has content.
+ */
+ public boolean hasContent() {
+ return bufferedCharacters.stream().anyMatch(GenericCharacters::hasContent);
+ }
+
+ /**
+ * Flush all buffered GenericCharacters.
+ */
+ public void flush() throws SAXException {
+ GenericCharacters element;
+ while ((element = bufferedCharacters.poll()) != null)
+ element.flush();
+ }
+
+ /**
+ * Converts all buffered GenericCharacters to a new character array.
+ */
+ public char[] toChars() {
+ return bufferedCharacters.stream().map(GenericCharacters::toChars)
+ .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append).toString()
+ .toCharArray();
+ }
+
+ /**
+ * Clear the buffer.
+ */
+ public void clear() {
+ bufferedCharacters.clear();
+ }
+ }
+
// Implement DTDHandler
/**
* If this method is called, the serializer is used as a
* DTDHandler, which changes behavior how the serializer
* handles document entities.
< prev index next >