net.sf.saxon.event.StreamWriterToReceiver Maven / Gradle / Ivy
Show all versions of Saxon-HE Show documentation
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2018-2022 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.expr.parser.Loc;
import net.sf.saxon.lib.StandardURIChecker;
import net.sf.saxon.om.*;
import net.sf.saxon.pull.NamespaceContextImpl;
import net.sf.saxon.serialize.charcode.UTF16CharacterSet;
import net.sf.saxon.str.StringView;
import net.sf.saxon.str.UnicodeString;
import net.sf.saxon.trans.Err;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.BuiltInAtomicType;
import net.sf.saxon.type.Untyped;
import net.sf.saxon.z.IntPredicateProxy;
import javax.xml.namespace.NamespaceContext;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import java.util.*;
/**
* 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.
*
* This class does not itself perform "namespace repairing" as defined in the interface Javadoc
* (also referred to as "prefix defaulting" in the StaX JSR specification). In normal use, however,
* the events emitted by this class are piped into a {@link NamespaceReducer} which performs a function
* very similar to namespace repairing; specifically, it ensures that when elements and attribute are
* generated with a given namespace URI and local name, then namespace declarations are generated
* automatically without any explicit need to call the {@link #writeNamespace(String, String)} method.
*
* 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.
*
* The implementation of this class is influenced not only by the Javadoc documentation of the
* XMLStreamWriter
interface (which is woefully inadequate), but also by the helpful
* but unofficial interpretation of the spec to be found at
* http://veithen.github.io/2009/11/01/understanding-stax.html
*
* Provided that the sequence of events sent to this class is legitimate, the events
* sent to the supplied {@code Receiver} should constitute a regular sequence
* as defined in the documentation of class {@link RegularSequenceChecker}.
*
* @since 9.3. Rewritten May 2015 to fix bug 2357. Further modified in 9.7.0.2 in light of the discussion
* of bug 2398, and the interpretation of the spec cited above.
*/
public class StreamWriterToReceiver implements XMLStreamWriter {
private static final boolean DEBUG = false;
private static class Triple {
public String prefix;
public String uri;
public String local;
public String value;
}
private static class StartTag {
public Triple elementName;
public List attributes;
public List namespaces;
public StartTag() {
elementName = new Triple();
attributes = new ArrayList<>();
namespaces = new ArrayList<>();
}
}
private StartTag pendingTag = null;
private final Stack namespaceStack = new Stack<>();
/**
* The receiver to which events will be passed
*/
private final Receiver receiver;
/**
* The Checker used for testing valid characters
*/
private final IntPredicateProxy charChecker;
/**
* 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;
/**
* Flag indicating that an empty element has been requested.
*/
private boolean isEmptyElement;
/**
* inScopeNamespaces represents namespaces that have been declared in the XML stream
*/
private final NamespaceReducer inScopeNamespaces;
/**
* setPrefixes represents the namespace bindings that have been set, at each level of the stack,
* using {@link #setPrefix}. There is one entry for each level of element nesting, and the entry is a list
* of NamespaceBinding objects, that is, prefix/uri pairs
*/
private final Stack> setPrefixes = new Stack<>();
/**
* 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.namespaceStack.push(NamespaceMap.emptyMap());
this.receiver = inScopeNamespaces;
this.charChecker = pipe.getConfiguration().getValidCharacterChecker();
this.setPrefixes.push(new ArrayList<>());
this.rootNamespaceContext = new NamespaceContextImpl(NamespaceMap.emptyMap());
// See bug 2902; initialise rootNamespaceContext to an empty set of namespaces
}
/**
* Get the Receiver to which this StreamWriterToReceiver is writing events
*
* @return the destination Receiver
*/
public Receiver getReceiver() {
return receiver;
}
/**
* 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 false;
*/
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 false;
*/
public boolean isCheckValues() {
return this.isChecking;
}
private void flushStartTag() throws XMLStreamException {
if (depth == -1) {
writeStartDocument();
}
if (pendingTag != null) {
try {
completeTriple(pendingTag.elementName, false);
for (Triple t : pendingTag.attributes) {
completeTriple(t, true);
}
NodeName elemName;
if (pendingTag.elementName.uri.isEmpty()) {
elemName = new NoNamespaceName(pendingTag.elementName.local);
} else {
elemName = new FingerprintedQName(pendingTag.elementName.prefix, pendingTag.elementName.uri, pendingTag.elementName.local);
}
NamespaceMap nsMap = namespaceStack.peek();
if (!pendingTag.elementName.uri.isEmpty()) {
nsMap = nsMap.put(pendingTag.elementName.prefix, pendingTag.elementName.uri);
}
for (Triple t : pendingTag.namespaces) {
if (t.prefix == null) {
t.prefix = "";
}
if (t.uri == null) {
t.uri = "";
}
if (!t.uri.isEmpty()) {
nsMap = nsMap.put(t.prefix, t.uri);
}
}
AttributeMap attributes = EmptyAttributeMap.getInstance();
for (Triple t : pendingTag.attributes) {
NodeName attName;
if (t.uri.isEmpty()) {
attName = new NoNamespaceName(t.local);
} else {
attName = new FingerprintedQName(t.prefix, t.uri, t.local);
nsMap = nsMap.put(t.prefix, t.uri);
}
attributes = attributes.put(new AttributeInfo(attName, BuiltInAtomicType.UNTYPED_ATOMIC, t.value,
Loc.NONE, ReceiverOption.NONE));
}
receiver.startElement(elemName, Untyped.getInstance(), attributes, nsMap, Loc.NONE, ReceiverOption.NONE);
pendingTag = null;
if (isEmptyElement) {
isEmptyElement = false;
depth--;
setPrefixes.pop();
receiver.endElement();
} else {
namespaceStack.push(nsMap);
}
} catch (XPathException e) {
throw new XMLStreamException(e);
}
}
}
/**
* Fill in the unknown parts of a (prefix, uri, localname) triple by reference to the namespace
* context.
* For details see the table in the XMLStreamWriter
javadoc.
* @param t the (prefix, uri, localname) triple. Note that the prefix will be null if not supplied
* in the call, and will be "" if an empty string or null was supplied in the call; these
* cases are handled differently.
* @param isAttribute true if this is an attribute name rather than an element name
* @throws XMLStreamException if the name is invalid or incomplete
*/
private void completeTriple(Triple t, boolean isAttribute) throws XMLStreamException {
if (t.local == null) {
throw new XMLStreamException("Local name of " + (isAttribute ? "Attribute" : "Element") + " is missing");
}
if (isChecking && !isValidNCName(t.local)) {
throw new XMLStreamException("Local name of " + (isAttribute ? "Attribute" : "Element") +
Err.wrap(t.local) + " is invalid");
}
if (t.prefix == null) {
t.prefix = "";
}
if (t.uri == null) {
t.uri = "";
}
if (isChecking && !t.uri.isEmpty() && isInvalidURI(t.uri)) {
throw new XMLStreamException("Namespace URI " + Err.wrap(t.local) + " is invalid");
}
if (t.prefix.isEmpty() && !t.uri.isEmpty()) {
t.prefix = getPrefixForUri(t.uri);
}
}
private String getPrefixForUri(String uri) {
for (Triple t : pendingTag.namespaces) {
if (uri.equals(t.uri)) {
return t.prefix == null ? "" : t.prefix;
}
}
String setPrefix = getPrefix(uri);
if (setPrefix != null) {
return setPrefix;
}
Iterator prefixes = inScopeNamespaces.iteratePrefixes();
while (prefixes.hasNext()) {
String p = prefixes.next();
if (inScopeNamespaces.getURIForPrefix(p, false).equals(uri)) {
return p;
}
}
return "";
}
/**
* Generate a start element event for an element in no namespace. Note: the element
* will be in no namespace, even if {@link #setDefaultNamespace(String)} has been called;
* this is Saxon's interpretation of the intended effect of the StAX specification.
*
* @param localName local name of the tag, may not be null
* @throws XMLStreamException if names are being checked and the name is invalid, or if an error occurs downstream
* @throws NullPointerException if the supplied local name is null
*/
@Override
public void writeStartElement(String localName) throws XMLStreamException {
if (DEBUG) {
System.err.println("StartElement " + localName);
}
checkNonNull(localName);
setPrefixes.push(new ArrayList<>());
flushStartTag();
depth++;
pendingTag = new StartTag();
pendingTag.elementName.local = localName;
}
/**
* Generate a start element event. The name of the element is determined by the supplied
* namespace URI and local name. The prefix used for the element is determined by the in-scope
* prefixes established using {@link #setPrefix(String, String)} and/or {@link #setDefaultNamespace(String)}
* if these include the specified namespace URI; otherwise the namespace will become the default namespace
* and there will therefore be no prefix.
*
* @param namespaceURI the namespace URI of the element name. Must not be null. A zero-length
* string means the element is in no namespace.
* @param localName local part of the element name. Must not be null
* @throws XMLStreamException if names are being checked and are found to be invalid, or if an
* error occurs downstream in the pipeline.
* @throws NullPointerException if either argument is null
*/
@Override
public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException {
if (DEBUG) {
System.err.println("StartElement Q{" + namespaceURI + "}" + localName);
}
checkNonNull(namespaceURI);
checkNonNull(localName);
setPrefixes.push(new ArrayList<>());
flushStartTag();
depth++;
pendingTag = new StartTag();
pendingTag.elementName.local = localName;
pendingTag.elementName.uri = namespaceURI;
}
/**
* Generate a start element event. The name of the element is determined by the supplied
* namespace URI and local name, and the prefix will be as supplied in the call.
*
* @param prefix the prefix of the element, must not be null. If the prefix is supplied as a zero-length
* string, the element will nave no prefix (that is, the namespace URI will become the default
* namespace).
* @param localName local name of the element, must not be null
* @param namespaceURI the uri to bind the prefix to, must not be null. If the value is a zero-length string,
* the element will be in no namespace; in this case any prefix is ignored.
* @throws NullPointerException if any of the arguments is null.
* @throws XMLStreamException if names are being checked and are found to be invalid, or if an
* error occurs downstream in the pipeline.
*/
@Override
public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
if (DEBUG) {
System.err.println("StartElement " + prefix + "=Q{" + namespaceURI + "}" + localName);
}
checkNonNull(prefix);
checkNonNull(localName);
checkNonNull(namespaceURI);
setPrefixes.push(new ArrayList<>());
flushStartTag();
depth++;
pendingTag = new StartTag();
pendingTag.elementName.local = localName;
pendingTag.elementName.uri = namespaceURI;
pendingTag.elementName.prefix = prefix;
}
@Override
public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException {
checkNonNull(namespaceURI);
checkNonNull(localName);
flushStartTag();
writeStartElement(namespaceURI, localName);
isEmptyElement = true;
}
@Override
public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
checkNonNull(prefix);
checkNonNull(localName);
checkNonNull(namespaceURI);
flushStartTag();
writeStartElement(prefix, localName, namespaceURI);
isEmptyElement = true;
}
@Override
public void writeEmptyElement(String localName) throws XMLStreamException {
checkNonNull(localName);
flushStartTag();
writeStartElement(localName);
isEmptyElement = true;
}
@Override
public void writeEndElement() throws XMLStreamException {
if (DEBUG) {
System.err.println("EndElement" + depth);
}
if (depth <= 0) {
throw new IllegalStateException("writeEndElement with no matching writeStartElement");
}
// if (isEmptyElement) {
// throw new IllegalStateException("writeEndElement called for an empty element");
// }
try {
flushStartTag();
setPrefixes.pop();
namespaceStack.pop();
receiver.endElement();
depth--;
} catch (XPathException err) {
throw new XMLStreamException(err);
}
}
@Override
public void writeEndDocument() throws XMLStreamException {
if (depth == -1) {
throw new IllegalStateException("writeEndDocument with no matching writeStartDocument");
}
try {
flushStartTag();
while (depth > 0) {
writeEndElement();
}
receiver.endDocument();
depth = -1;
} catch (XPathException err) {
throw new XMLStreamException(err);
}
}
@Override
public void close() throws XMLStreamException {
if (depth >= 0) {
writeEndDocument();
}
try {
receiver.close();
} catch (XPathException err) {
throw new XMLStreamException(err);
}
}
@Override
public void flush() {
// no action
}
@Override
public void writeAttribute(String localName, String value) {
checkNonNull(localName);
checkNonNull(value);
if (pendingTag == null) {
throw new IllegalStateException("Cannot write attribute when not in a start tag");
}
Triple t = new Triple();
t.local = localName;
t.value = value;
pendingTag.attributes.add(t);
}
@Override
public void writeAttribute(String prefix, String namespaceURI, String localName, String value) {
checkNonNull(prefix);
checkNonNull(namespaceURI);
checkNonNull(localName);
checkNonNull(value);
if (pendingTag == null) {
throw new IllegalStateException("Cannot write attribute when not in a start tag");
}
Triple t = new Triple();
t.prefix = prefix;
t.uri = namespaceURI;
t.local = localName;
t.value = value;
pendingTag.attributes.add(t);
}
@Override
public void writeAttribute(String namespaceURI, String localName, String value) {
checkNonNull(namespaceURI);
checkNonNull(localName);
checkNonNull(value);
Triple t = new Triple();
t.uri = namespaceURI;
t.local = localName;
t.value = value;
pendingTag.attributes.add(t);
}
/**
* Emits a namespace declaration event.
*
* If the prefix argument to this method is the empty string,
* "xmlns", or null this method will delegate to writeDefaultNamespace.
*
* This method does not change the name of any element or attribute; its only use is to write
* additional or redundant namespace declarations. With this implementation of XMLStreamWriter,
* this method is needed only to generate namespace declarations for prefixes that do not appear
* in element or attribute names. If an attempt is made to generate a namespace declaration that
* conflicts with the prefix-uri bindings in scope for element and attribute names, an exception
* occurs.
*
* @param prefix the prefix to bind this namespace to
* @param namespaceURI the uri to bind the prefix to
* @throws IllegalStateException if the current state does not allow Namespace writing
* @throws XMLStreamException if things go wrong
*/
@Override
public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException {
if (prefix == null || prefix.equals("") || prefix.equals("xmlns")) {
writeDefaultNamespace(namespaceURI);
} else {
checkNonNull(namespaceURI);
if (pendingTag == null) {
throw new IllegalStateException("Cannot write namespace when not in a start tag");
}
Triple t = new Triple();
t.uri = namespaceURI;
t.prefix = prefix;
pendingTag.namespaces.add(t);
}
}
/**
* Emits a default namespace declaration
*
* This method does not change the name of any element or attribute; its only use is to write
* additional or redundant namespace declarations. With this implementation of XMLStreamWriter,
* this method is needed only to generate namespace declarations for prefixes that do not appear
* in element or attribute names. If an attempt is made to generate a namespace declaration that
* conflicts with the prefix-uri bindings in scope for element and attribute names, an exception
* occurs.
*
* @param namespaceURI the uri to bind the default namespace to
* @throws IllegalStateException if the current state does not allow Namespace writing
*/
@Override
public void writeDefaultNamespace(String namespaceURI) {
checkNonNull(namespaceURI);
if (pendingTag == null) {
throw new IllegalStateException("Cannot write namespace when not in a start tag");
}
Triple t = new Triple();
t.uri = namespaceURI;
pendingTag.namespaces.add(t);
}
@Override
public void writeComment(String data) throws XMLStreamException {
flushStartTag();
if (data == null) {
data = "";
}
UnicodeString uData = StringView.of(data);
try {
if (!isValidChars(uData)) {
throw new IllegalArgumentException("Invalid XML character in comment: " + data);
}
if (isChecking && data.contains("--")) {
throw new IllegalArgumentException("Comment contains '--'");
}
receiver.comment(uData, Loc.NONE, ReceiverOption.NONE);
} catch (XPathException err) {
throw new XMLStreamException(err);
}
}
@Override
public void writeProcessingInstruction(String target) throws XMLStreamException {
writeProcessingInstruction(target, "");
}
@Override
public void writeProcessingInstruction(/*@NotNull*/ String target, /*@NotNull*/ String data) throws XMLStreamException {
checkNonNull(target);
checkNonNull(data);
flushStartTag();
final UnicodeString uData = StringView.of(data);
try {
if (isChecking) {
if (!isValidNCName(target) || "xml".equalsIgnoreCase(target)) {
throw new IllegalArgumentException("Invalid PITarget: " + target);
}
if (!isValidChars(uData)) {
throw new IllegalArgumentException("Invalid character in PI data: " + data);
}
}
receiver.processingInstruction(target, uData, Loc.NONE, ReceiverOption.NONE);
} catch (XPathException err) {
throw new XMLStreamException(err);
}
}
@Override
public void writeCData(/*@NotNull*/ String data) throws XMLStreamException {
checkNonNull(data);
flushStartTag();
writeCharacters(data);
}
@Override
public void writeDTD(String dtd) throws XMLStreamException {
// no-op
}
@Override
public void writeEntityRef(String name) {
throw new UnsupportedOperationException("writeEntityRef");
}
@Override
public void writeStartDocument() throws XMLStreamException {
writeStartDocument("utf-8", "1.0");
}
@Override
public void writeStartDocument(/*@Nullable*/ String version) throws XMLStreamException {
writeStartDocument("utf-8", version);
}
@Override
public void writeStartDocument(/*@Nullable*/ String encoding, /*@Nullable*/ String version) throws XMLStreamException {
if (encoding == null) {
encoding = "utf-8";
}
if (version == null) {
version = "1.0";
}
if (depth != -1) {
throw new IllegalStateException("writeStartDocument must be the first call");
}
try {
receiver.open();
receiver.startDocument(ReceiverOption.NONE);
} catch (XPathException err) {
throw new XMLStreamException(err);
}
depth = 0;
}
@Override
public void writeCharacters(String text)
throws XMLStreamException {
checkNonNull(text);
flushStartTag();
final UnicodeString uData = StringView.of(text);
if (!isValidChars(uData)) {
throw new IllegalArgumentException("illegal XML character: " + text);
}
try {
receiver.characters(uData, Loc.NONE, ReceiverOption.NONE);
} catch (XPathException err) {
throw new XMLStreamException(err);
}
}
@Override
public void writeCharacters(char[] text, int start, int len)
throws XMLStreamException {
checkNonNull(text);
writeCharacters(new String(text, start, len));
}
@Override
public String getPrefix(String uri) {
for (int i=setPrefixes.size()-1; i>=0; i--) {
List bindings = setPrefixes.get(i);
for (int j=bindings.size()-1; j>=0; j--) {
NamespaceBinding binding = bindings.get(j);
if (binding.getURI().equals(uri)) {
return binding.getPrefix();
}
}
}
if (rootNamespaceContext != null) {
return rootNamespaceContext.getPrefix(uri);
}
return null;
}
@Override
public void setPrefix(String prefix, String uri) {
// See Saxon bug 2398: this should have stack-like effect
checkNonNull(prefix);
if (uri == null) {
uri = "";
}
if (isInvalidURI(uri)) {
throw new IllegalArgumentException("Invalid namespace URI: " + uri);
}
if (!"".equals(prefix) && !isValidNCName(prefix)) {
throw new IllegalArgumentException("Invalid namespace prefix: " + prefix);
}
setPrefixes.peek().add(new NamespaceBinding(prefix, uri));
}
@Override
public void setDefaultNamespace(String uri) {
setPrefix("", uri);
}
@Override
public void setNamespaceContext(javax.xml.namespace.NamespaceContext context) {
// Note, we do not enforce the rule that this can only be called once, because the spec is self-contradictory
// on this point.
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;
}
/**
* Return the current namespace context.
*
* The specification of this method is hopelessly vague. This method returns a namespace context
* that contains the namespaces declared using {@link #setPrefix(String, String)} calls that are
* in-scope at the time, overlaid on the root namespace context that was defined using
* {@link #setNamespaceContext(NamespaceContext)}. The namespaces bound using {@link #setPrefix(String, String)}
* are copied, and are therefore unaffected by subsequent changes, but the root namespace context
* is not copied, because the {@link NamespaceContext} interface provides no way of doing so.
* @return a copy of the current namespace context.
*/
@Override
public javax.xml.namespace.NamespaceContext getNamespaceContext() {
return new StreamWriterNamespaceContext(this);
}
private static class StreamWriterNamespaceContext implements javax.xml.namespace.NamespaceContext {
final NamespaceContext rootNamespaceContext;
final Map bindings = new HashMap<>();
public StreamWriterNamespaceContext(StreamWriterToReceiver streamWriter) {
rootNamespaceContext = streamWriter.rootNamespaceContext;
for (List list : streamWriter.setPrefixes) {
for (NamespaceBinding binding : list) {
bindings.put(binding.getPrefix(), binding.getURI());
}
}
}
@Override
public String getNamespaceURI(String prefix) {
String uri = bindings.get(prefix);
if (uri != null) {
return uri;
}
return rootNamespaceContext.getNamespaceURI(prefix);
}
@Override
public String getPrefix(String namespaceURI) {
for (Map.Entry entry : bindings.entrySet()) {
if (entry.getValue().equals(namespaceURI)) {
return entry.getKey();
}
}
return rootNamespaceContext.getPrefix(namespaceURI);
}
@Override
public Iterator getPrefixes(String namespaceURI) {
List prefixes = new ArrayList<>();
for (Map.Entry entry : bindings.entrySet()) {
if (entry.getValue().equals(namespaceURI)) {
prefixes.add(entry.getKey());
}
}
@SuppressWarnings("unchecked")
Iterator root = (Iterator)rootNamespaceContext.getPrefixes(namespaceURI);
while (root.hasNext()) {
prefixes.add(root.next());
}
return prefixes.iterator();
}
}
@Override
public Object getProperty(String name) throws IllegalArgumentException {
if (name.equals("javax.xml.stream.isRepairingNamespaces")) {
return receiver instanceof NamespaceReducer;
} else {
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(UnicodeString text) {
return !isChecking || (UTF16CharacterSet.firstInvalidChar(text.codePoints(), charChecker) == -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 invalid (unless checking is disabled)
*/
private boolean isInvalidURI(String uri) {
return isChecking && !StandardURIChecker.getInstance().isValidURI(uri);
}
private void checkNonNull(Object value) {
if (value == null) {
throw new NullPointerException();
}
}
}