All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.saxon.event.StreamWriterToReceiver Maven / Gradle / Ivy

There is a newer version: 10.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2013 Saxonica Limited.
// 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.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package net.sf.saxon.event;

import net.sf.saxon.lib.StandardURIChecker;
import net.sf.saxon.om.*;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.BuiltInAtomicType;
import net.sf.saxon.type.Untyped;

import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * This class implements the XmlStreamWriter interface, translating the events into Saxon
 * Receiver events. The Receiver can be anything: a serializer, a schema validator, a tree builder.
 *
 * 

The class will attempt to generate namespace prefixes where none have been supplied, unless the * inventPrefixes option is set to false. The preferred mode of use is to call the versions * of writeStartElement and writeAttribute that supply the prefix, URI, and * local name in full. If the prefix is omitted, the class attempts to invent a prefix. If the URI is * omitted, the name is assumed to be in no namespace. The writeNamespace

method should be * called only if there is a need to declare a namespace prefix that is not used on any element or * attribute name.

* *

The class will check all names, URIs, and character content for conformance against XML well-formedness * rules unless the checkValues option is set to false.

* * @since 9.3 */ public class StreamWriterToReceiver implements XMLStreamWriter { /** * The receiver to which events will be passed */ private Receiver receiver; /** * The Saxon NamePool */ private NamePool namePool; /** * The Name Checker */ private NameChecker nameChecker; /** * Flag to indicate whether names etc are to be checked for well-formedness */ private boolean isChecking = false; /** * The current depth of element nesting. -1 indicates outside startDocument/endDocument; non-negative * values indicate the number of open start element tags */ private int depth = -1; /** * The namespace context used to determine the namespace-prefix mappings * in scope. */ //protected NamespaceContext namespaceContext; /** * Flag set to true during processing of a start tag. */ private boolean inStartTag; /** * Flag indicating that an empty element has been requested. */ private boolean isEmptyElement; /** * Flag indicating that default prefixes should be allocated if the user does not declare them explicitly */ private boolean inventPrefixes = true; /** * inScopeNamespaces represents namespaces that have been declared in the XML stream */ private NamespaceReducer inScopeNamespaces; /** * declaredNamespaces represents prefix-to-uri bindings that have been set using setPrefix. These * do not necessarily correspond to namespace declarations appearing in the XML stream. Note that * this is a map from URIs to prefixes, not the other way around! */ private Map declaredNamespaces = new HashMap(10); /** * rootNamespaceContext is the namespace context supplied at the start, is the final fallback * for allocating a prefix to a URI */ private javax.xml.namespace.NamespaceContext rootNamespaceContext = null; /** * Constructor. Creates a StreamWriter as a front-end to a given Receiver. * @param receiver the Receiver that is to receive the events generated * by this StreamWriter. */ public StreamWriterToReceiver(Receiver receiver) { // Events are passed through a NamespaceReducer which maintains the namespace context // It also eliminates duplicate namespace declarations, and creates extra namespace declarations // where needed to support prefix-uri mappings used on elements and attributes PipelineConfiguration pipe = receiver.getPipelineConfiguration(); this.inScopeNamespaces = new NamespaceReducer(receiver); this.receiver = inScopeNamespaces; this.nameChecker = pipe.getConfiguration().getNameChecker(); this.namePool = pipe.getConfiguration().getNamePool(); } /** * Say whether prefixes are to be invented when none is specified by the user * @param invent true if prefixes are to be invented. Default is true; */ public void setInventPrefixes(boolean invent) { this.inventPrefixes = invent; } /** * Ask whether prefixes are to be invented when none is specified by the user * @return true if prefixes are to be invented. Default is true; */ public boolean isInventPrefixes() { return this.inventPrefixes; } /** * Say whether names and values are to be checked for conformance with XML rules * @param check true if names and values are to be checked. Default is true; */ public void setCheckValues(boolean check) { this.isChecking = check; } /** * Ask whether names and values are to be checked for conformance with XML rules * @return true if names and values are to be checked. Default is true; */ public boolean isCheckValues() { return this.isChecking; } public void writeStartElement(/*@NotNull*/ String localName) throws XMLStreamException { String uri = inScopeNamespaces.getURIForPrefix("", true); assert uri != null; writeStartElement("", localName, uri); } public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException { String prefix = getPrefix(namespaceURI); boolean isDeclared = (prefix != null); if (!isDeclared) { if (inventPrefixes) { prefix = inventPrefix(namespaceURI); } else { throw new XMLStreamException("namespace " + namespaceURI + " has not been declared"); } } writeStartElement(prefix, localName, namespaceURI); } public void writeStartElement(/*@NotNull*/ String prefix, /*@NotNull*/ String localName, /*@NotNull*/ String namespaceURI) throws XMLStreamException { if (depth == -1) { writeStartDocument(); } try { if (!isValidURI(namespaceURI)) { throw new IllegalArgumentException("Invalid namespace URI: " + namespaceURI); } if (!isValidNCName(prefix)) { throw new IllegalArgumentException("Invalid prefix: " + prefix); } if (!isValidNCName(localName)) { throw new IllegalArgumentException("Invalid local name: " + localName); } startContent(); inStartTag = true; depth++; NodeName nc; if (namespaceURI.length()==0) { nc = new NoNamespaceName(localName); } else { nc = new FingerprintedQName(prefix, namespaceURI, localName); } receiver.startElement(nc, Untyped.getInstance(), 0, 0); inStartTag = true; } catch (XPathException err) { throw new XMLStreamException(err); } } /** * Creates a prefix that is not currently in use. * @param uri the URI for which a prefix is required * @return the chosen prefix */ private String inventPrefix(String uri) { String prefix = getPrefix(uri); if (prefix != null) { return prefix; } prefix = namePool.suggestPrefixForURI(uri); if (prefix != null) { return prefix; } int count = 0; while (true) { prefix = "ns" + count; if (inScopeNamespaces.getURIForPrefix(prefix, false) == null) { setPrefix(prefix, uri); return prefix; } count++; } } public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException { writeStartElement(namespaceURI, localName); isEmptyElement = true; } public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { writeStartElement(prefix, localName, namespaceURI); isEmptyElement = true; } public void writeEmptyElement(String localName) throws XMLStreamException { writeStartElement(localName); isEmptyElement = true; } /** * Indicate the end of a start tag if one is open (no action otherwise). * This will also write an end tag if the element was opened as an empty element. * @throws javax.xml.stream.XMLStreamException if an error occurs writing to the output stream */ private void startContent() throws XMLStreamException { if (inStartTag) { try { receiver.startContent(); } catch (XPathException err) { throw new XMLStreamException(err); } inStartTag = false; if (isEmptyElement) { isEmptyElement = false; writeEndElement(); } } } public void writeEndElement() throws XMLStreamException { if (depth <= 0) { throw new IllegalStateException("writeEndElement with no matching writeStartElement"); } // if (isEmptyElement) { // throw new IllegalStateException("writeEndElement called for an empty element"); // } try { startContent(); receiver.endElement(); depth--; } catch (XPathException err) { throw new XMLStreamException(err); } } public void writeEndDocument() throws XMLStreamException { if (depth == -1) { throw new IllegalStateException("writeEndDocument with no matching writeStartDocument"); } try { if (isEmptyElement) { startContent(); // which also ends the element and decrements depth } while (depth > 0) { writeEndElement(); } receiver.endDocument(); depth = -1; } catch (XPathException err) { throw new XMLStreamException(err); } } public void close() throws XMLStreamException { if (depth >= 0) { writeEndDocument(); } try { receiver.close(); } catch (XPathException err) { throw new XMLStreamException(err); } } public void flush() throws XMLStreamException { // no action } public void writeAttribute(String localName, String value) throws XMLStreamException { writeAttribute("", "", localName, value); } public void writeAttribute(/*@Nullable*/ String prefix, /*@Nullable*/ String namespaceURI, String localName, String value) throws XMLStreamException { if (!inStartTag) { throw new IllegalStateException("Cannot write attribute when not in a start tag"); } if (prefix == null) { prefix = ""; } if (namespaceURI == null) { namespaceURI = ""; } if (namespaceURI.length() != 0 && !isValidURI(namespaceURI)) { throw new IllegalArgumentException("Invalid attribute namespace URI: " + namespaceURI); } if (prefix.length() != 0 && !isValidNCName(prefix)) { throw new IllegalArgumentException("Invalid attribute prefix: " + prefix); } if (!isValidNCName(localName)) { throw new IllegalArgumentException("Invalid attribute local name: " + localName); } if (!isValidChars(value)) { throw new IllegalArgumentException("Invalid characters in attribute content: " + value); } try { NodeName nn = new FingerprintedQName(prefix, namespaceURI, localName); receiver.attribute(nn, BuiltInAtomicType.UNTYPED_ATOMIC, value, -1, 0); } catch (XPathException err) { throw new XMLStreamException(err); } } public void writeAttribute(String namespaceURI, String localName, String value) throws XMLStreamException { String prefix = getPrefix(namespaceURI); if (prefix == null) { if (inventPrefixes) { prefix = inventPrefix(namespaceURI); } else { throw new XMLStreamException("Namespace " + namespaceURI + " has not been declared"); } } writeAttribute(prefix, namespaceURI, localName, value); } public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException { if (!inStartTag) { throw new IllegalStateException("Cannot write namespace when not in a start tag"); } if (prefix == null || "".equals(prefix) || "xmlns".equals(prefix)) { writeDefaultNamespace(namespaceURI); return; } if (!isValidURI(namespaceURI)) { throw new IllegalArgumentException("Invalid namespace URI: " + namespaceURI); } if (!isValidNCName(prefix)) { throw new IllegalArgumentException("Invalid namespace prefix: " + prefix); } outputNamespaceDeclaration(prefix, namespaceURI); } public void writeDefaultNamespace(String namespaceURI) throws XMLStreamException { if (!inStartTag) { throw new IllegalStateException(); } if (!isValidURI(namespaceURI)) { throw new IllegalArgumentException("Invalid namespace URI: " + namespaceURI); } outputNamespaceDeclaration("", namespaceURI); } private void outputNamespaceDeclaration(String prefix, String namespaceURI) throws XMLStreamException { try { if (prefix == null) { prefix = ""; } receiver.namespace(new NamespaceBinding(prefix, namespaceURI), 0); } catch (XPathException err) { throw new XMLStreamException(err); } } public void writeComment(/*@Nullable*/ String data) throws XMLStreamException { if (data == null) { data = ""; } try { if (!isValidChars(data)) { throw new IllegalArgumentException("Invalid XML character in comment: " + data); } if (isChecking && data.contains("--")) { throw new IllegalArgumentException("Comment contains '--'"); } startContent(); receiver.comment(data, 0, 0); } catch (XPathException err) { throw new XMLStreamException(err); } } public void writeProcessingInstruction(String target) throws XMLStreamException { writeProcessingInstruction(target, ""); } public void writeProcessingInstruction(/*@NotNull*/ String target, /*@NotNull*/ String data) throws XMLStreamException { try { if (isChecking) { if (!isValidNCName(target) || "xml".equalsIgnoreCase(target)) { throw new IllegalArgumentException("Invalid PITarget: " + target); } if (!isValidChars(data)) { throw new IllegalArgumentException("Invalid character in PI data: " + data); } } startContent(); receiver.processingInstruction(target, data, 0, 0); } catch (XPathException err) { throw new XMLStreamException(err); } } public void writeCData(/*@NotNull*/ String data) throws XMLStreamException { writeCharacters(data); } public void writeDTD(String dtd) throws XMLStreamException { // no-op } public void writeEntityRef(String name) throws XMLStreamException { throw new UnsupportedOperationException("writeEntityRef"); } public void writeStartDocument() throws XMLStreamException { writeStartDocument(null, null); } public void writeStartDocument(/*@Nullable*/ String version) throws XMLStreamException { writeStartDocument(null, version); } public void writeStartDocument(/*@Nullable*/ String encoding, /*@Nullable*/ String version) throws XMLStreamException { if ("1.1".equals(version)) { nameChecker = Name11Checker.getInstance(); } if (depth != -1) { throw new IllegalStateException("writeStartDocument must be the first call"); } try { receiver.startDocument(0); } catch (XPathException err) { throw new XMLStreamException(err); } depth = 0; } public void writeCharacters(/*@Nullable*/ String text) throws XMLStreamException { if (text != null) { if (!isValidChars(text)) { throw new IllegalArgumentException("illegal XML character: " + text); } startContent(); try { receiver.characters(text, 0, 0); } catch (XPathException err) { throw new XMLStreamException(err); } } } public void writeCharacters(char[] text, int start, int len) throws XMLStreamException { writeCharacters(new String(text, start, len)); } public String getPrefix(String uri) { String prefix = declaredNamespaces.get(uri); if (prefix == null && rootNamespaceContext != null) { prefix = rootNamespaceContext.getPrefix(uri); } return prefix; } public void setPrefix(String prefix, String uri) { if (!isValidURI(uri)) { throw new IllegalArgumentException("Invalid namespace URI: " + uri); } if (!"".equals(prefix) && !isValidNCName(prefix)) { throw new IllegalArgumentException("Invalid namespace prefix: " + prefix); } declaredNamespaces.put(uri, prefix); //sic } public void setDefaultNamespace(String uri) throws XMLStreamException { setPrefix("", uri); } public void setNamespaceContext(javax.xml.namespace.NamespaceContext context) throws XMLStreamException { if (depth > 0) { throw new IllegalStateException("setNamespaceContext may only be called at the start of the document"); } // Unfortunately the JAXP NamespaceContext class does not allow us to discover all the namespaces // that were declared, nor to declare new ones. So we have to retain it separately rootNamespaceContext = context; } /*@Nullable*/ public javax.xml.namespace.NamespaceContext getNamespaceContext() { // Note: the spec is unclear. We return the namespace context that was supplied to setNamespaceContext, // regardless of any other subsequent additions return rootNamespaceContext; } public Object getProperty(String name) throws IllegalArgumentException { throw new IllegalArgumentException(name); } /** * Test whether a supplied name is a valid NCName * @param name the name to be tested * @return true if the name is valid or if checking is disabled */ private boolean isValidNCName(String name) { return !isChecking || nameChecker.isValidNCName(name); } /** * Test whether a supplied character string is valid in XML * @param text the string to be tested * @return true if the string is valid or if checking is disabled */ private boolean isValidChars(String text) { return !isChecking || (nameChecker.firstInvalidChar(text) == -1); } /** * Test whether a supplied namespace URI is a valid URI * @param uri the namespace URI to be tested * @return true if the name is valid or if checking is disabled */ private boolean isValidURI(String uri) { return !isChecking || StandardURIChecker.getInstance().isValidURI(uri); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy