net.sf.saxon.om.NamespaceMap Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of Saxon-HE Show documentation
Show all versions of Saxon-HE Show documentation
The XSLT and XQuery Processor
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2018-2023 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.om;
import net.sf.saxon.lib.NamespaceConstant;
import net.sf.saxon.transpile.CSharpReplaceBody;
import net.sf.saxon.transpile.CSharpSuppressWarnings;
import java.util.*;
/**
* Holds a set of namespace bindings as a simple immutable map from prefixes to URIs.
*
* A NamespaceMap never physically contains a binding for the XML namespace,
* but some interfaces behave as if it did.
*
* The map may or may not contain a binding for the default namespace, represented
* by the prefix "" (zero-length string)
*
* The map must not contain any namespace undeclarations: that is, the namespace will
* never be "" (zero-length string)
*/
public class NamespaceMap implements NamespaceBindingSet, NamespaceResolver {
protected String[] prefixes; // always sorted, for binary search
protected NamespaceUri[] uris;
private static final String[] emptyArray = new String[]{};
private static final NamespaceUri[] emptyUriArray = new NamespaceUri[]{};
private static final NamespaceMap EMPTY_MAP = new NamespaceMap();
/**
* Get a namespace map containing no namespace bindings
* @return an empty namespace map
*/
public static NamespaceMap emptyMap() {
return EMPTY_MAP;
}
/**
* Get a namespace map containing a single namespace binding
* @param prefix the namespace prefix
* @param uri the namespace uri
* @return a map containing the single binding; or an empty map if the binding is
* the standard binding of the XML namespace
* @throws IllegalArgumentException for an invalid mapping or if the namespace URI is empty
*/
public static NamespaceMap of(String prefix, NamespaceUri uri) {
NamespaceMap map = new NamespaceMap();
if (map.isPointlessMapping(prefix, uri)) {
return EMPTY_MAP;
}
map.prefixes = new String[]{prefix};
map.uris = new NamespaceUri[]{uri};
return map;
}
protected NamespaceMap() {
prefixes = emptyArray;
uris = emptyUriArray;
}
protected NamespaceMap makeNamespaceMap() {
return new NamespaceMap();
}
/**
* Create a namespace map from a list of namespace bindings
* @param bindings the list of namespace bindings. If there is more that one
* binding for the same prefix, the last one wins. Any binding
* of the prefix "xml" to the XML namespace is ignored, but
* an incorrect binding of the XML namespace causes an exception.
* @throws IllegalArgumentException if the "xml" prefix is bound to the wrong
* namespace, or if any other prefix is bound to the XML namespace
*/
public NamespaceMap(List bindings) {
NamespaceBinding[] bindingArray = bindings.toArray(NamespaceBinding.EMPTY_ARRAY);
sortByPrefix(bindingArray);
boolean bindsXmlNamespace = false;
prefixes = new String[bindingArray.length];
uris = new NamespaceUri[bindingArray.length];
for (int i=0; i System.String.Compare(x.getPrefix(), y.getPrefix(), System.StringComparison.Ordinal));")
private void sortByPrefix(NamespaceBinding[] bindingArray) {
Arrays.sort(bindingArray, Comparator.comparing(NamespaceBinding::getPrefix));
}
/**
* Create a NamespaceMap that captures all the information in a given NamespaceResolver
*
* @param resolver the NamespaceResolver
* @return the new NamespaceMap
*/
@CSharpSuppressWarnings("UnsafeIteratorConversion")
public static NamespaceMap fromNamespaceResolver(NamespaceResolver resolver) {
Iterator iter = resolver.iteratePrefixes();
List bindings = new ArrayList<>();
while (iter.hasNext()) {
String prefix = iter.next();
NamespaceUri uri = resolver.getURIForPrefix(prefix, true);
bindings.add(new NamespaceBinding(prefix, uri));
}
return new NamespaceMap(bindings);
}
public boolean allowsNamespaceUndeclarations() {
return false;
}
/**
* Get the number of entries in the map
* @return the number of prefix-uri bindings (excluding any binding for the XML namespace)
*/
public int size() {
return prefixes.length;
}
/**
* Ask if the map contains only the binding
* @return true if the map contains no bindings
*/
public boolean isEmpty() {
return prefixes.length == 0;
}
/**
* Get the URI associated with a given prefix. If the supplied prefix is "xml", the
* XML namespace {@link NamespaceConstant#XML} is returned, even if the map is empty.
* @param prefix the required prefix (may be an empty string to get the default namespace)
* @return the associated URI, or null if no mapping is present. Note that we return null
* when no mapping is present, even for the case where the prefix is the empty string.
*/
@Override
public NamespaceUri getNamespaceUri(String prefix) {
if (prefix.equals("xml")) {
return NamespaceUri.XML;
}
int position = Arrays.binarySearch(prefixes, prefix);
return position >= 0 ? uris[position] : null;
}
/**
* Get the default namespace
* @return the namespace bound to the prefix "" if there is one, otherwise {@link NamespaceUri#NULL}.
*/
public NamespaceUri getDefaultNamespace() {
// If the prefix "" is present, it will be the first in alphabetical order
if (prefixes.length > 0 && prefixes[0].isEmpty()) {
return uris[0];
} else {
return NamespaceUri.NULL;
}
}
/**
* Add a new entry to the map, or replace an existing entry. An attempt
* to add a binding of the "xml" prefix to the XML namespace is silently ignored.
* @param prefix the prefix whose entry is to be added or replaced. May be zero-length
* to represent the default namespace
* @param uri the URI to be associated with this prefix; if zero-length or null, any
* existing mapping for the prefix is removed.
* @return a new map containing the added or replaced entry (or this map, unchanged,
* if the prefix-uri mapping was already present in the old map).
* @throws IllegalArgumentException if an attempt is made to create an incorrect
* mapping for the "xml" prefix or URI.
*/
public NamespaceMap put(String prefix, NamespaceUri uri) {
if (uri == null) {
uri = NamespaceUri.NULL;
}
if (isPointlessMapping(prefix, uri)) {
return this;
}
int position = Arrays.binarySearch(prefixes, prefix);
if (position >= 0) {
// An entry for this prefix already exists
if (uris[position].equals(uri)) {
// No change
return this;
} else if (uri == NamespaceUri.NULL) {
// Delete the entry for the prefix
NamespaceMap n2 = makeNamespaceMap();
if (prefixes.length > 1) {
n2.prefixes = new String[prefixes.length - 1];
System.arraycopy(prefixes, 0, n2.prefixes, 0, position);
System.arraycopy(prefixes, position + 1, n2.prefixes, position, prefixes.length - position - 1);
n2.uris = new NamespaceUri[uris.length - 1];
System.arraycopy(uris, 0, n2.uris, 0, position);
System.arraycopy(uris, position + 1, n2.uris, position, uris.length - position - 1);
}
return n2;
} else {
// Replace the entry for the prefix
NamespaceMap n2 = makeNamespaceMap();
n2.prefixes = Arrays.copyOf(prefixes, prefixes.length);
n2.uris = Arrays.copyOf(uris, uris.length);
n2.uris[position] = uri;
return n2;
}
} else {
return putNoExistingEntry(position, prefix, uri);
}
}
private NamespaceMap putNoExistingEntry(int position, String prefix, NamespaceUri uri)
{
if (prefixes.length == 0)
{
NamespaceMap n2 = makeNamespaceMap();
n2.prefixes = new String[]{prefix};
n2.uris = new NamespaceUri[]{uri};
return n2;
}
else
{
// No existing entry for the prefix exists
int insertionPoint = -position - 1;
String[] p2 = new String[prefixes.length + 1];
NamespaceUri[] u2 = new NamespaceUri[uris.length + 1];
System.arraycopy(prefixes, 0, p2, 0, insertionPoint);
System.arraycopy(uris, 0, u2, 0, insertionPoint);
p2[insertionPoint] = prefix;
u2[insertionPoint] = uri;
System.arraycopy(prefixes, insertionPoint, p2, insertionPoint + 1, prefixes.length - insertionPoint);
System.arraycopy(uris, insertionPoint, u2, insertionPoint + 1, prefixes.length - insertionPoint);
NamespaceMap n2 = makeNamespaceMap();
n2.prefixes = p2;
n2.uris = u2;
return n2;
}
}
private boolean isPointlessMapping(String prefix, NamespaceUri uri) {
if (prefix.equals("xml")) {
if (!uri.equals(NamespaceUri.XML)) {
throw new IllegalArgumentException("Invalid URI for xml prefix");
}
return true;
} else if (uri.equals(NamespaceUri.XML)) {
throw new IllegalArgumentException("Invalid prefix for XML namespace");
}
return false;
}
/**
* Add or remove a namespace binding
* @param prefix the namespace prefix ("" for the default namespace)
* @param uri the namespace URI to which the prefix is bound; or {@link NamespaceUri#NULL} to indicate that an existing
* binding for the prefix is to be removed
* @return a new map with the entry added or removed as appropriate (or this map, unchanged, as appropriate).
*/
public NamespaceMap bind(String prefix, NamespaceUri uri) {
if (uri == NamespaceUri.NULL) {
return remove(prefix);
} else {
return put(prefix, uri);
}
}
/**
* Remove an entry from the map
* @param prefix the entry to be removed from the map
* @return a new map in which the relevant entry has been removed, or this map (unchanged)
* if the requested entry was not present
*/
public NamespaceMap remove(String prefix) {
int position = Arrays.binarySearch(prefixes, prefix);
if (position >= 0) {
String[] p2 = new String[prefixes.length - 1];
NamespaceUri[] u2 = new NamespaceUri[uris.length - 1];
System.arraycopy(prefixes, 0, p2, 0, position);
System.arraycopy(uris, 0, u2, 0, position);
System.arraycopy(prefixes, position+1, p2, position, prefixes.length - position - 1);
System.arraycopy(uris, position+1, u2, position, uris.length - position - 1);
NamespaceMap n2 = makeNamespaceMap();
n2.prefixes = p2;
n2.uris = u2;
return n2;
} else {
return this;
}
}
/**
* Merge the prefix/uri pairs in the supplied delta with the prefix/uri pairs
* in this namespace map, to create a new namespace map. If a prefix is present
* in both maps, then the one in delta takes precedence
* @param delta prefix/uri pairs to be merged into this map
* @return a new map, the result of the merge
*/
public NamespaceMap putAll(NamespaceMap delta)
{
if (this == delta)
{
return this;
}
else if (isEmpty())
{
return delta;
}
else if (delta.isEmpty())
{
return this;
}
else
{
return mergePutAll(delta);
}
}
private NamespaceMap mergePutAll(NamespaceMap delta)
{
// Merge of two sorted arrays to produce a sorted array
String[] p1 = prefixes;
NamespaceUri[] u1 = uris;
String[] p2 = delta.prefixes;
NamespaceUri[] u2 = delta.uris;
int lengthSum = p1.length + p2.length;
String[] p3 = new String[lengthSum];
NamespaceUri[] u3 = new NamespaceUri[lengthSum];
int i1 = 0;
int i2 = 0;
int writePos = 0;
while (true)
{
int c = p1[i1].compareTo(p2[i2]);
if (c < 0)
{
p3[writePos] = p1[i1];
u3[writePos++] = u1[i1];
if (++i1 >= p1.length)
{
break;
}
}
else if (c > 0)
{
p3[writePos] = p2[i2];
u3[writePos++] = u2[i2];
if (++i2 >= p2.length)
{
break;
}
}
else
{ // c == 0
p3[writePos] = p2[i2];
u3[writePos++] = u2[i2];
i1++;
i2++;
if (i1 >= p1.length || i2 >= p2.length)
{
break;
}
}
}
while (i1 < p1.length)
{
p3[writePos] = p1[i1];
u3[writePos++] = u1[i1];
i1++;
}
while (i2 < p2.length)
{
p3[writePos] = p2[i2];
u3[writePos++] = u2[i2];
i2++;
}
return createNewNamespaceMap(p3, u3, lengthSum, writePos);
}
private NamespaceMap createNewNamespaceMap(String[] p3, NamespaceUri[] u3, int lengthSum, int writePos)
{
NamespaceMap n2 = new NamespaceMap();
n2.prefixes = writePos == lengthSum ? p3 : Arrays.copyOf(p3, writePos);
n2.uris = writePos == lengthSum ? u3 : Arrays.copyOf(u3, writePos);
return n2;
}
public NamespaceMap addAll(NamespaceBindingSet namespaces) {
if (namespaces instanceof NamespaceMap) {
return putAll((NamespaceMap)namespaces);
} else {
NamespaceMap map = this;
for (NamespaceBinding nb : namespaces) {
map = map.put(nb.getPrefix(), nb.getNamespaceUri());
}
return map;
}
}
/**
* Create a map containing all namespace declarations in this map, plus any namespace
* declarations and minus any namespace undeclarations in the delta map
* @param delta a map of namespace declarations and undeclarations to be applied
* @return a map combining the namespace declarations in this map with the declarations
* and undeclarations in the {@code delta} map.
*/
public NamespaceMap applyDifferences(NamespaceDeltaMap delta) {
if (delta.isEmpty()) {
return this;
} else {
// Merge of two sorted arrays to produce a sorted array
String[] p1 = prefixes;
NamespaceUri[] u1 = uris;
String[] p2 = delta.prefixes;
NamespaceUri[] u2 = delta.uris;
List prefixList = new ArrayList<>(p1.length + p2.length);
List uriList = new ArrayList<>(p1.length + p2.length);
int i1 = 0;
int i2 = 0;
while (i1 < p1.length && i2 < p2.length) {
int c = p1[i1].compareTo(p2[i2]);
if (c < 0) {
prefixList.add(p1[i1]);
uriList.add(u1[i1]);
i1++;
} else if (c > 0) {
if (u2[i2] != NamespaceUri.NULL) {
prefixList.add(p2[i2]);
uriList.add(u2[i2]);
}
i2++;
} else { // c == 0
if (u2[i2] != NamespaceUri.NULL || p2[i2].isEmpty()) {
prefixList.add(p2[i2]);
uriList.add(u2[i2]);
}
i1++;
i2++;
}
}
while (i1 < p1.length) {
prefixList.add(p1[i1]);
uriList.add(u1[i1]);
i1++;
}
while (i2 < p2.length) {
if (u2[i2] != NamespaceUri.NULL) {
prefixList.add(p2[i2]);
uriList.add(u2[i2]);
}
i2++;
}
NamespaceMap n2 = new NamespaceMap();
n2.prefixes = prefixList.toArray(new String[]{});
n2.uris = uriList.toArray(new NamespaceUri[]{});
return n2;
}
}
/**
* Get an iterator over the namespace bindings defined in this namespace map
* @return an iterator over the namespace bindings. (In the current implementation
* they will be in alphabetical order of namespace prefix.)
*/
@Override
public Iterator iterator() {
return new Iterator() {
int i = 0;
@Override
public boolean hasNext() {
return i < prefixes.length;
}
@Override
public NamespaceBinding next() {
NamespaceBinding nb = new NamespaceBinding(prefixes[i], uris[i]);
i++;
return nb;
}
};
}
/**
* Get all the namespace bindings defined in this namespace map as an array
* @return the array of namespace bindings
*/
public NamespaceBinding[] getNamespaceBindings() {
NamespaceBinding[] result = new NamespaceBinding[prefixes.length];
for (int i=0; i result = new ArrayList<>();
int i = 0, j = 0;
while (true) {
// Merge and combine the two sorted lists of prefix/uri pairs
if (i < prefixes.length && j < other.prefixes.length) {
int c = prefixes[i].compareTo(other.prefixes[j]);
if (c < 0) {
// prefix in this namespace map, absent from other
result.add(new NamespaceBinding(prefixes[i], uris[i]));
i++;
} else if (c == 0) {
// prefix present in both maps
if (uris[i].equals(other.uris[j])) {
// URI is the same; this declaration is redundant, so omit it from the result
} else {
// URI is different; use the URI appearing in this map in preference
result.add(new NamespaceBinding(prefixes[i], uris[i]));
}
i++;
j++;
} else {
// prefix present in other map, absent from this: maybe add an undeclaration
if (addUndeclarations || other.prefixes[j].isEmpty()) {
result.add(new NamespaceBinding(other.prefixes[j], NamespaceUri.NULL));
}
j++;
}
} else if (i < prefixes.length) {
// prefix in this namespace map, absent from other
result.add(new NamespaceBinding(prefixes[i], uris[i]));
i++;
} else if (j < other.prefixes.length) {
// prefix present in other map, absent from this: add an undeclaration
result.add(new NamespaceBinding(other.prefixes[j], NamespaceUri.NULL));
j++;
} else {
return result.toArray(NamespaceBinding.EMPTY_ARRAY);
}
}
}
/**
* Get the namespace URI corresponding to a given prefix. Return null
* if the prefix is not in scope.
*
* @param prefix the namespace prefix. May be the zero-length string, indicating
* that there is no prefix. This indicates either the default namespace or the
* null namespace, depending on the value of useDefault. The prefix "xml" is always
* recognized as corresponding to the XML namespace {@link NamespaceConstant#XML}
* @param useDefault true if the default namespace is to be used when the
* prefix is "". If false, the method returns "" when the prefix is "". The default
* namespace is a property of the NamespaceResolver; in general it corresponds to
* the "default namespace for elements and types", but that cannot be assumed.
* @return the uri for the namespace, or null if the prefix is not in scope.
* The "null namespace" is represented by the pseudo-URI "".
*/
@Override
public NamespaceUri getURIForPrefix(String prefix, boolean useDefault) {
if (prefix.equals("xml")) {
return NamespaceUri.XML;
}
if (prefix.equals("")) {
if (useDefault) {
return getDefaultNamespace();
} else {
return NamespaceUri.NULL;
}
}
return getNamespaceUri(prefix);
}
/**
* Get an iterator over the prefixes defined in this namespace map, including
* the "xml" prefix.
*
* @return an iterator over the prefixes. (In the current implementation
* they will be in alphabetical order, except that the "xml" prefix will always come last.)
*/
@Override
public Iterator iteratePrefixes() {
List prefixList = new ArrayList<>(Arrays.asList(prefixes));
prefixList.add("xml");
return prefixList.iterator();
}
/**
* Get the prefixes present in the NamespaceMap, as an array, excluding the "xml" prefix
* @return the prefixes present in the map, not including the "xml" prefix
*/
public String[] getPrefixArray() {
return prefixes;
}
public NamespaceUri[] getURIsAsArray() {
return uris;
}
public String toString() {
StringBuilder sb = new StringBuilder();
for (NamespaceBinding nb : this) {
sb.append(nb.getPrefix()).append("=").append(nb.getNamespaceUri()).append(" ");
}
return sb.toString();
}
@Override
public int hashCode() {
return Arrays.hashCode(prefixes) ^ Arrays.hashCode(uris);
}
@Override
public boolean equals(Object obj) {
return this == obj
|| (obj instanceof NamespaceMap
&& Arrays.equals(prefixes, ((NamespaceMap)obj).prefixes)
&& Arrays.equals(uris, ((NamespaceMap)obj).uris));
}
}