package client.net.sf.saxon.ce.event;
import client.net.sf.saxon.ce.om.*;
import client.net.sf.saxon.ce.trans.XPathException;
import client.net.sf.saxon.ce.tree.iter.EmptyIterator;
import client.net.sf.saxon.ce.tree.iter.ListIterator;
import client.net.sf.saxon.ce.tree.util.Orphan;
import client.net.sf.saxon.ce.type.Type;
import client.net.sf.saxon.ce.value.AtomicValue;
import client.net.sf.saxon.ce.value.EmptySequence;
import client.net.sf.saxon.ce.value.SequenceExtent;
import client.net.sf.saxon.ce.Controller;
import java.util.ArrayList;
/**
* This outputter is used when writing a sequence of atomic values and nodes, that
* is, when xsl:variable is used with content and an "as" attribute. The outputter
* builds the sequence and provides access to it. (It isn't really an outputter at all,
* it doesn't pass the events to anyone, it merely constructs the sequence in memory
* and provides access to it). Note that the event sequence can include calls such as
* startElement and endElement that require trees to be built. If nodes such as attributes
* and text nodes are received while an element is being constructed, the nodes are added
* to the tree. Otherwise, "orphan" nodes (nodes with no parent) are created and added
* directly to the sequence.
*
* <p>This class is not used to build temporary trees. For that, the ComplexContentOutputter
* is used.</p>
*
*
* @author Michael H. Kay
*/
public final class SequenceOutputter extends SequenceReceiver {
private ArrayList list;
private Controller controller; // enables the SequenceOutputter to be reused
private Receiver outputter = null;
private Builder builder = null;
private int level = 0;
private boolean inStartTag = false;
public SequenceOutputter(Controller controller, int estimatedSize) {
this.list = new ArrayList(estimatedSize);
this.controller = controller;
}
/**
* Clear the contents of the SequenceOutputter and make it available for reuse
*/
public void reset() {
list = new ArrayList(Math.max(list.size()+10, 50));
if (controller != null && adviseReuse()) {
controller.reuseSequenceOutputter(this);
}
}
/**
* Method to be supplied by subclasses: output one item in the sequence.
*/
public void write(Item item) {
list.add(item);
}
/**
* Get the sequence that has been built
*/
public ValueRepresentation getSequence() {
switch (list.size()) {
case 0:
return EmptySequence.getInstance();
case 1:
return (Item)list.get(0);
default:
return new SequenceExtent(list);
}
}
/**
* Get an iterator over the sequence of items that has been constructed
*/
public SequenceIterator iterate() {
if (list.isEmpty()) {
return EmptyIterator.getInstance();
} else {
return new ListIterator(list);
}
}
/**
* Get the list containing the sequence of items
*/
public ArrayList getList() {
return list;
}
/**
* Get the first item in the sequence that has been built
*/
public Item getFirstItem() {
if (list.isEmpty()) {
return null;
} else {
return (Item)list.get(0);
}
}
/**
* Get the last item in the sequence that has been built, and remove it
*/
public Item popLastItem() {
if (list.isEmpty()) {
return null;
} else {
return (Item)list.remove(list.size()-1);
}
}
/**
* Start of a document node.
*/
public void startDocument() throws XPathException {
if (outputter==null) {
createTree();
}
if (level++ == 0) {
outputter.startDocument();
}
}
/**
* Create a tree to hold a document or element node.
* @throws client.net.sf.saxon.ce.trans.XPathException
*/
private void createTree() throws XPathException {
PipelineConfiguration pipe = getPipelineConfiguration();
builder = pipe.getController().makeBuilder();
builder.setPipelineConfiguration(pipe);
builder.setSystemId(getSystemId());
NamespaceReducer reducer = new NamespaceReducer();
reducer.setUnderlyingReceiver(builder);
reducer.setPipelineConfiguration(getPipelineConfiguration());
ComplexContentOutputter cco = new ComplexContentOutputter();
cco.setPipelineConfiguration(getPipelineConfiguration());
cco.setReceiver(reducer);
outputter = cco;
outputter.setSystemId(systemId);
outputter.setPipelineConfiguration(getPipelineConfiguration());
outputter.open();
}
/**
* Decide whether reuse of the SequenceWriter is advisable
* @return true if reuse is considered advisable
*/
protected boolean adviseReuse() {
return false;
//return builder instanceof TinyBuilder && ((TinyBuilder)builder).getTree().getNumberOfNodes() < 20000;
}
/**
* Notify the end of a document node
*/
public void endDocument() throws XPathException {
if (--level == 0) {
outputter.endDocument();
DocumentInfo doc = (DocumentInfo)builder.getCurrentRoot();
// add the constructed document to the result sequence
append(doc, NodeInfo.ALL_NAMESPACES);
}
previousAtomic = false;
}
/**
* Output an element start tag.
* @param nameCode The element name code - a code held in the Name Pool
* @param properties bit-significant flags indicating any special information
*/
public void startElement(int nameCode, int properties) throws XPathException {
if (inStartTag) {
startContent();
}
if (outputter==null) {
createTree();
}
outputter.startElement(nameCode, properties);
level++;
inStartTag = true;
previousAtomic = false;
}
/**
* Output an element end tag.
*/
public void endElement() throws XPathException {
if (inStartTag) {
startContent();
}
outputter.endElement();
if (--level == 0) {
outputter.close();
NodeInfo element = builder.getCurrentRoot();
append(element, NodeInfo.ALL_NAMESPACES);
}
previousAtomic = false;
}
/**
* Output a namespace declaration. <br>
* This is added to a list of pending namespaces for the current start tag.
* If there is already another declaration of the same prefix, this one is
* ignored.
* Note that unlike SAX2 startPrefixMapping(), this call is made AFTER writing the start tag.
* @param nsBinding The namespace code
* @param properties Allows special properties to be passed if required
* @throws client.net.sf.saxon.ce.trans.XPathException if there is no start tag to write to (created using writeStartTag),
* or if character content has been written since the start tag was written.
*/
public void namespace(NamespaceBinding nsBinding, int properties)
throws XPathException {
if (level == 0) {
NamePool namePool = getNamePool();
Orphan o = new Orphan(getConfiguration());
o.setNodeKind(Type.NAMESPACE);
o.setNameCode(namePool.allocate("", "", nsBinding.getPrefix()));
o.setStringValue(nsBinding.getURI());
append(o, NodeInfo.ALL_NAMESPACES);
} else {
outputter.namespace(nsBinding, properties);
}
previousAtomic = false;
}
/**
* Output an attribute value. <br>
* @param nameCode An integer code representing the name of the attribute, as held in the Name Pool
* @param value The value of the attribute
* @throws client.net.sf.saxon.ce.trans.XPathException if there is no start tag to write to (created using writeStartTag),
* or if character content has been written since the start tag was written.
*/
public void attribute(int nameCode, CharSequence value)
throws XPathException {
if (level == 0) {
Orphan o = new Orphan(getConfiguration());
o.setNodeKind(Type.ATTRIBUTE);
o.setNameCode(nameCode);
o.setStringValue(value);
append(o, NodeInfo.ALL_NAMESPACES);
} else {
outputter.attribute(nameCode, value);
}
previousAtomic = false;
}
/**
* The startContent() event is notified after all namespaces and attributes of an element
* have been notified, and before any child nodes are notified.
* @throws client.net.sf.saxon.ce.trans.XPathException for any failure
*/
public void startContent() throws XPathException {
inStartTag = false;
outputter.startContent();
previousAtomic = false;
}
/**
* Produce text content output. <BR>
* @param s The String to be output
* @throws client.net.sf.saxon.ce.trans.XPathException for any failure
*/
public void characters(CharSequence s) throws XPathException {
if (level == 0) {
Orphan o = new Orphan(getConfiguration());
o.setNodeKind(Type.TEXT);
o.setStringValue(s.toString());
append(o, NodeInfo.ALL_NAMESPACES);
} else {
if (s.length() > 0) {
if (inStartTag) {
startContent();
}
outputter.characters(s);
}
}
previousAtomic = false;
}
/**
* Write a comment.
*/
public void comment(CharSequence comment) throws XPathException {
if (inStartTag) {
startContent();
}
if (level == 0) {
Orphan o = new Orphan(getConfiguration());
o.setNodeKind(Type.COMMENT);
o.setStringValue(comment);
append(o, NodeInfo.ALL_NAMESPACES);
} else {
outputter.comment(comment);
}
previousAtomic = false;
}
/**
* Write a processing instruction
* No-op in this implementation
*/
public void processingInstruction(String target, CharSequence data) throws XPathException {
if (inStartTag) {
startContent();
}
if (level == 0) {
Orphan o = new Orphan(getConfiguration());
o.setNameCode(getNamePool().allocate("", "", target));
o.setNodeKind(Type.PROCESSING_INSTRUCTION);
o.setStringValue(data);
append(o, NodeInfo.ALL_NAMESPACES);
} else {
outputter.processingInstruction(target, data);
}
previousAtomic = false;
}
/**
* Close the output
*/
public void close() throws XPathException {
previousAtomic = false;
if (outputter != null) {
outputter.close();
}
}
/**
* Append an item to the sequence, performing any necessary type-checking and conversion
*/
public void append(Item item, int copyNamespaces) throws XPathException {
if (item==null) {
return;
}
if (level==0) {
write(item);
previousAtomic = false;
} else {
if (item instanceof AtomicValue) {
// If an atomic value is written to a tree, and the previous item was also
// an atomic value, then add a single space to separate them
if (previousAtomic) {
outputter.characters(" ");
}
outputter.characters(item.getStringValueCS());
previousAtomic = true;
} else {
((NodeInfo)item).copy(outputter, (CopyOptions.ALL_NAMESPACES | CopyOptions.TYPE_ANNOTATIONS));
previousAtomic = false;
}
}
}
}
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0.