blob: ac00833f3a52a57699f905d7d945c16cfdf1d1b1 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.vysper.xml.fragment;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
/**
* an immutable xml element specialized for XMPP.
*
* @author The Apache MINA Project (dev@mina.apache.org)
*/
public class XMLElement implements XMLFragment {
private String name;
/**
* example element: <a:b xmlns:a="http://ns.org" >
* element's namespace. the prefix for the element name is taken from the corresponding attribute.
* if the namespace is 'http://ns.org', then element name b is prefixed with a.
* NOTE: the namespace value must NOT be "b"!
*/
private String namespaceURI;
private String namespacePrefix;
private List<Attribute> attributes;
private Map<String, String> namespaces;
private List<XMLFragment> innerFragments;
protected XMLElementVerifier xmlElementVerifier;
public XMLElement(String namespaceURI, String name, String namespacePrefix, Attribute[] attributes,
XMLFragment[] innerFragments) {
this(namespaceURI, name, namespacePrefix, attributes, innerFragments, null);
}
public XMLElement(String namespaceURI, String name, String namespacePrefix, Attribute[] attributes,
XMLFragment[] innerFragments, Map<String, String> namespaces) {
this(namespaceURI, name, namespacePrefix, FragmentFactory.asList(attributes), FragmentFactory
.asList(innerFragments), namespaces);
}
public XMLElement(String namespaceURI, String name, String namespacePrefix, List<Attribute> attributes,
List<XMLFragment> innerFragments) {
this(namespaceURI, name, namespacePrefix, attributes, innerFragments, null);
}
public XMLElement(String namespaceURI, String name, String namespacePrefix, List<Attribute> attributes,
List<XMLFragment> innerFragments, Map<String, String> namespaces) {
this.namespaceURI = namespaceURI == null ? Namespaces.DEFAULT_NAMESPACE_URI : namespaceURI;
if(namespacePrefix != null && namespacePrefix.length() > 0) {
if(!isValidName(namespacePrefix) || namespacePrefix.contains(":")) throw new IllegalArgumentException("Invalid XML element namespace prefix");
}
this.namespacePrefix = namespacePrefix == null ? Namespaces.DEFAULT_NAMESPACE_PREFIX : namespacePrefix;
if(name == null || !isValidName(name)) throw new IllegalArgumentException("Invalid XML element name");
this.name = name;
this.attributes = (attributes == null) ? Collections.EMPTY_LIST : Collections.unmodifiableList(attributes);
this.namespaces = (namespaces == null) ? Collections.EMPTY_MAP : Collections.unmodifiableMap(namespaces);
this.innerFragments = (innerFragments == null) ? Collections.EMPTY_LIST : Collections
.unmodifiableList(innerFragments);
}
private static final String NAME_START_CHAR = "A-Za-z\\_\\:";
private static final String NAME_CHAR = NAME_START_CHAR + "\\-\\.0-9";
private static final Pattern namePattern = Pattern.compile("[" + NAME_START_CHAR + "][" + NAME_CHAR + "]*");
private boolean isValidName(String name) {
// TODO add additional char ranges
return namePattern.matcher(name).matches();
}
public String getName() {
return name;
}
/**
* Return the XML namespace prefix.
* @return The namespace prefix. If the element does not have a prefix
* , thus being part of the default namespace, this method will return
* an empty string.
*/
public String getNamespacePrefix() {
return namespacePrefix;
}
/**
* Return the namespace URI.
* @return The namespace URI for the element.
*/
public String getNamespaceURI() {
return namespaceURI;
}
public List<Attribute> getAttributes() {
return attributes;
}
public Attribute getAttribute(String name) {
return getAttribute("", name);
}
public Attribute getAttribute(String namespaceUri, String name) {
for (Attribute attribute : attributes) {
// name must match and must be in empty namespace
if (attribute.getName().equals(name) && attribute.getNamespaceUri().equals(namespaceUri))
return attribute;
}
return null;
}
public String getAttributeValue(String name) {
return getAttributeValue("", name);
}
public String getAttributeValue(String namespaceUri, String name) {
Attribute attribute = getAttribute(namespaceUri, name);
if (attribute == null)
return null;
else
return attribute.getValue();
}
public Map<String, String> getDeclaredNamespaces() {
return namespaces;
}
public String getXMLLang() {
return getAttributeValue(Namespaces.XML, "lang");
}
public List<XMLFragment> getInnerFragments() {
return Collections.unmodifiableList(innerFragments);
}
public XMLElement getFirstInnerElement() {
if (innerFragments == null || innerFragments.size() < 1)
return null;
for (XMLFragment xmlFragment : innerFragments) {
if (xmlFragment instanceof XMLElement)
return (XMLElement) xmlFragment;
}
return null;
}
public List<XMLElement> getInnerElements() {
if (innerFragments == null || innerFragments.size() < 1)
return Collections.emptyList();
List<XMLElement> innerElements = new ArrayList<XMLElement>();
for (XMLFragment xmlFragment : innerFragments) {
if (xmlFragment instanceof XMLElement)
innerElements.add((XMLElement) xmlFragment);
}
return innerElements;
}
public List<XMLText> getInnerTexts() {
if (innerFragments == null || innerFragments.size() < 1)
return Collections.emptyList();
List<XMLText> innerTexts = new ArrayList<XMLText>();
for (XMLFragment xmlFragment : innerFragments) {
if (xmlFragment instanceof XMLText)
innerTexts.add((XMLText) xmlFragment);
}
return innerTexts;
}
public XMLText getFirstInnerText() {
if (innerFragments == null || innerFragments.size() < 1)
return null;
for (XMLFragment xmlFragment : innerFragments) {
if (xmlFragment instanceof XMLText)
return (XMLText) xmlFragment;
}
return null;
}
public XMLText getSingleInnerText() throws XMLSemanticError {
List<XMLText> innerTexts = getInnerTexts();
if (innerTexts == null || innerTexts.isEmpty())
return null;
if (innerTexts.size() > 1)
throw new XMLSemanticError("element has more than one inner text fragment");
return innerTexts.get(0);
}
/**
* Get the complete inner text
* @return The concatenated inner text or null if no text fragments exist
*/
public XMLText getInnerText() {
boolean hadText = false;
StringBuffer sb = new StringBuffer();
for (XMLText text : getInnerTexts()) {
sb.append(text.getText());
hadText = true;
}
if (hadText) {
return new XMLText(sb.toString());
} else {
return null;
}
}
/**
* collects all inner elements named as given parameter. Namespace UIR is ignored.
* @param name - must not be NULL
*/
public List<XMLElement> getInnerElementsNamed(String name) {
return getInnerElementsNamed(name, null);
}
/**
* collects all inner elements named as given parameter
* @param name - must not be NULL
* @param namespaceUri The namespace URI used for matching. Null if namespace URIs should not be considered
*/
public List<XMLElement> getInnerElementsNamed(String name, String namespaceUri) {
if (name == null)
return null;
List<XMLElement> innerElements = getInnerElements();
if (innerElements == null)
return null;
if (innerElements.size() == 0)
return innerElements;
Iterator<XMLElement> elementIterator = innerElements.iterator(); // this List will be modified now!
while (elementIterator.hasNext()) {
XMLElement xmlElement = elementIterator.next();
if (!name.equals(xmlElement.getName())
|| (namespaceUri != null && !namespaceUri.equals(xmlElement.getNamespaceURI()))) {
elementIterator.remove();
}
}
return innerElements;
}
public XMLElement getSingleInnerElementsNamed(String name) throws XMLSemanticError {
return getSingleInnerElementsNamed(name, null);
}
public XMLElement getSingleInnerElementsNamed(String name, String namespaceUri) throws XMLSemanticError {
List<XMLElement> innerElements = getInnerElementsNamed(name, namespaceUri);
if (innerElements == null)
return null;
if (innerElements.isEmpty())
return null;
if (innerElements.size() > 1)
throw new XMLSemanticError("element has more than one inner element named: " + name);
return innerElements.get(0);
}
/**
* collects all inner elements with given name and puts them in a map indexed by
* @param name
* @return Map from String language to XMLElement
* @throws XMLSemanticError no language attribute may occur more than once for the same element
*/
public Map<String, XMLElement> getInnerElementsByXMLLangNamed(String name) throws XMLSemanticError {
if (name == null) return null;
List<XMLElement> innerElements = getInnerElementsNamed(name);
Map<String, XMLElement> langMap = new HashMap<String, XMLElement>();
Iterator<XMLElement> elementIterator = innerElements.iterator(); // this List will be modified now!
while (elementIterator.hasNext()) {
XMLElement xmlElement = elementIterator.next();
String xmlLang = xmlElement.getXMLLang();
if (langMap.containsKey(xmlLang)) {
throw new XMLSemanticError("two inner elements '" + name + "' with same language attribute " + xmlLang);
}
langMap.put(xmlLang, xmlElement);
}
return langMap;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || !(o instanceof XMLElement))
return false;
final XMLElement that = (XMLElement) o;
// attributes are allowed to be in any order
if(attributes != null && that.attributes != null) {
if(attributes.size() != that.attributes.size()) return false;
for(Attribute attribute : attributes) {
boolean found = false;
for(Attribute thatAttribute : that.attributes) {
if(thatAttribute.equals(attribute)) {
found = true;
break;
}
}
if(!found) return false;
}
} else if(attributes == null && that.attributes == null) {
// ok
} else {
return false;
}
if (innerFragments != null ? !innerFragments.equals(that.innerFragments) : that.innerFragments != null) {
return false;
}
if (name != null ? !name.equals(that.name) : that.name != null)
return false;
// TODO the namespace prefix should not matter for equality, only the URI
if (namespacePrefix != null ? !namespacePrefix.equals(that.namespacePrefix) : that.namespacePrefix != null)
return false;
return true;
}
@Override
public int hashCode() {
int result;
result = (name != null ? name.hashCode() : 0);
result = 29 * result + (namespacePrefix != null ? namespacePrefix.hashCode() : 0);
result = 29 * result + (attributes != null ? attributes.hashCode() : 0);
result = 29 * result + (innerFragments != null ? innerFragments.hashCode() : 0);
return result;
}
public XMLElementVerifier getVerifier() {
if (xmlElementVerifier == null)
xmlElementVerifier = new XMLElementVerifier(this);
return xmlElementVerifier;
}
}