singleton = null;
+ /**
+ * {@code NameStartChar} without colon.
+ *
+ * NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]
+ *
+ * @see XML 1.0 – 2.3 Common Syntactic Constructs
+ * @see XML 1.1 – 2.3 Common Syntactic Constructs
+ */
+ private static final String NAME_START_CHAR = "_A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD";
+
+ /**
+ * {@code NameChar} without colon.
+ *
+ * NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]
+ *
+ * @see XML 1.0 – 2.3 Common Syntactic Constructs
+ * @see XML 1.1 – 2.3 Common Syntactic Constructs
+ */
+ private static final String NAME_CHAR = NAME_START_CHAR + "-.0-9\u00B7\u0300-\u036F\u203F-\u2040";
+
+ /**
+ * {@code NCName}
+ *
+ *
+ * NCName ::= NCNameStartChar NCNameChar* (An XML Name, minus the ":")
+ * NCNameChar ::= NameChar -':'
+ * NCNameStartChar ::= NameStartChar -':'
+ *
+ *
+ * @see Namespaces in XML 1.0 – 4 Qualified Names
+ * @see Namespaces in XML 1.1 – 4 Qualified Names
+ */
+ private static final String NCNAME = "["+NAME_START_CHAR+"]["+NAME_CHAR+"]*";
+
+ /**
+ * Regular expression for {@code Name} (with colon).
+ *
+ * Name ::= NameStartChar (NameChar)*
+ *
+ * @see XML 1.0 – 2.3 Common Syntactic Constructs
+ * @see XML 1.1 – 2.3 Common Syntactic Constructs
+ */
+ private static final Pattern RE_NAME = Pattern.compile("[:"+NAME_START_CHAR+"][:"+NAME_CHAR+"]*");
+
+ /**
+ * Regular expression for {@code NCName}.
+ *
+ *
+ * NCName ::= NCNameStartChar NCNameChar* (An XML Name, minus the ":")
+ * NCNameChar ::= NameChar -':'
+ * NCNameStartChar ::= NameStartChar -':'
+ *
+ *
+ * @see Namespaces in XML 1.0 – 4 Qualified Names
+ * @see Namespaces in XML 1.1 – 4 Qualified Names
+ */
+ private static final Pattern RE_NCNAME = Pattern.compile(NCNAME);
+
+ /**
+ * Regular expression for {@code QName}.
+ *
+ *
+ * QName ::= PrefixedName | UnprefixedName
+ * PrefixedName ::= Prefix ':' LocalPart
+ * UnprefixedName ::= LocalPart
+ * Prefix ::= NCName
+ * LocalPart ::= NCName
+ *
+ *
+ * @see Namespaces in XML 1.0 – 4 Qualified Names
+ * @see Namespaces in XML 1.1 – 4 Qualified Names
+ */
+ private static final Pattern RE_QNAME = Pattern.compile("(?:"+NCNAME+":)?"+NCNAME);
+
static {
try {
String defaultSingletonClass = "org.dom4j.util.SimpleSingleton";
@@ -71,6 +147,11 @@ public QName(String name, Namespace namespace) {
this.name = (name == null) ? "" : name;
this.namespace = (namespace == null) ? Namespace.NO_NAMESPACE
: namespace;
+ if (this.namespace.equals(Namespace.NO_NAMESPACE)) {
+ validateName(this.name);
+ } else {
+ validateNCName(this.name);
+ }
}
public QName(String name, Namespace namespace, String qualifiedName) {
@@ -78,6 +159,8 @@ public QName(String name, Namespace namespace, String qualifiedName) {
this.qualifiedName = qualifiedName;
this.namespace = (namespace == null) ? Namespace.NO_NAMESPACE
: namespace;
+ validateNCName(this.name);
+ validateQName(this.qualifiedName);
}
public static QName get(String name) {
@@ -251,6 +334,24 @@ private static QNameCache getCache() {
QNameCache cache = singleton.instance();
return cache;
}
+
+ private static void validateName(String name) {
+ if (!RE_NAME.matcher(name).matches()) {
+ throw new IllegalArgumentException(String.format("Illegal character in name: '%s'.", name));
+ }
+ }
+
+ protected static void validateNCName(String ncname) {
+ if (!RE_NCNAME.matcher(ncname).matches()) {
+ throw new IllegalArgumentException(String.format("Illegal character in local name: '%s'.", ncname));
+ }
+ }
+
+ private static void validateQName(String qname) {
+ if (!RE_QNAME.matcher(qname).matches()) {
+ throw new IllegalArgumentException(String.format("Illegal character in qualified name: '%s'.", qname));
+ }
+ }
}
diff --git a/src/main/java/org/dom4j/tree/QNameCache.java b/src/main/java/org/dom4j/tree/QNameCache.java
index 330f3794..d37e8aaa 100644
--- a/src/main/java/org/dom4j/tree/QNameCache.java
+++ b/src/main/java/org/dom4j/tree/QNameCache.java
@@ -152,6 +152,8 @@ public QName get(String qualifiedName, String uri) {
if (index < 0) {
return get(qualifiedName, Namespace.get(uri));
+ } else if (index == 0){
+ throw new IllegalArgumentException("Qualified name cannot start with ':'.");
} else {
String name = qualifiedName.substring(index + 1);
String prefix = qualifiedName.substring(0, index);
diff --git a/src/test/java/org/dom4j/AllowedCharsTest.java b/src/test/java/org/dom4j/AllowedCharsTest.java
new file mode 100644
index 00000000..20c1de0b
--- /dev/null
+++ b/src/test/java/org/dom4j/AllowedCharsTest.java
@@ -0,0 +1,78 @@
+package org.dom4j;
+
+import org.testng.annotations.Test;
+
+/**
+ * @author Filip Jirsák
+ */
+public class AllowedCharsTest {
+ @Test
+ public void localName() {
+ QName.get("element");
+ QName.get(":element");
+ QName.get("elem:ent");
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void localNameFail() {
+ QName.get("!element");
+ }
+
+ @Test
+ public void qname() {
+ QName.get("element", "http://example.com/namespace");
+ QName.get("ns:element", "http://example.com/namespace");
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void qnameFail1() {
+ QName.get("ns:elem:ent", "http://example.com/namespace");
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void qnameFail2() {
+ QName.get(":nselement", "http://example.com/namespace");
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void createElementLT() {
+ DocumentHelper.createElement("elementname");
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void createElementAmpersand() {
+ DocumentHelper.createElement("element&name");
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void addElement() {
+ Element root = DocumentHelper.createElement("root");
+ root.addElement("element>name");
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void addElementQualified() {
+ Element root = DocumentHelper.createElement("root");
+ root.addElement("element>name", "http://example.com/namespace");
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void addElementQualifiedPrefix() {
+ Element root = DocumentHelper.createElement("root");
+ root.addElement("ns:element>name", "http://example.com/namespace");
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void addElementPrefix() {
+ Element root = DocumentHelper.createElement("root");
+ root.addElement("ns>:element", "http://example.com/namespace");
+ }
+
+ //TODO It is illegal to create element or attribute with namespace prefix and empty namespace IRI.
+ //See https://www.w3.org/TR/2006/REC-xml-names11-20060816/#scoping
+}