Bug fixed: XPath queries that has only contained characters that are valid in XML element names and has also contained :: (which is valid in names in namespace-unware documents), like e['following-sibling::foo'], were interpreted as literal element names (giving 0 hits) rather than as XPath expressions. Note that there were no such problem with e['following-sibling::*'] for example, as it's not a valid XML element name according the XML specification. This fix can actually break applications that has processed namespace unaware XML that use :: as part of element or attribute names, but such an application is highly unlikely, unlike running into the fixed problem. (Unfortunately, using incompatible_improvements wasn't technically possible here.)
diff --git a/src/main/java/freemarker/ext/dom/NodeListModel.java b/src/main/java/freemarker/ext/dom/NodeListModel.java
index c0e85b9..22e555a 100644
--- a/src/main/java/freemarker/ext/dom/NodeListModel.java
+++ b/src/main/java/freemarker/ext/dom/NodeListModel.java
@@ -42,6 +42,11 @@
 import freemarker.template.TemplateSequenceModel;
 import freemarker.template.utility.StringUtil;
 
+/**
+ * Used when the result set contains 0 or multiple nodes; shouldn't be used when you have exactly 1 node. For exactly 1
+ * node, use {@link NodeModel#wrap(Node)}, because {@link NodeModel} subclasses can have extra features building on that
+ * restriction, like single elements with text content can be used as FTL string-s.
+ */
 class NodeListModel extends SimpleSequence implements TemplateHashModel, _UnexpectedTypeErrorExplainerTemplateModel {
     
     NodeModel contextNode;
diff --git a/src/main/java/freemarker/ext/dom/NodeModel.java b/src/main/java/freemarker/ext/dom/NodeModel.java
index c0ade05..c338664 100644
--- a/src/main/java/freemarker/ext/dom/NodeModel.java
+++ b/src/main/java/freemarker/ext/dom/NodeModel.java
@@ -63,12 +63,16 @@
 import freemarker.template.TemplateSequenceModel;
 
 /**
- * A base class for wrapping a W3C DOM Node as a FreeMarker template model.
+ * A base class for wrapping a single W3C DOM Node as a FreeMarker template model.
  * 
  * <p>
  * Note that {@link DefaultObjectWrapper} automatically wraps W3C DOM {@link Node}-s into this, so you may not need to
  * do that with this class manually. Though, before dropping the {@link Node}-s into the data-model, you may want to
  * apply {@link NodeModel#simplify(Node)} on them.
+ * 
+ * <p>
+ * Note that this class shouldn't be used to represent a result set of 0 or multiple nodes (we use {@link NodeListModel}
+ * then), but should be used to represent a node set of exactly 1 node.
  */
 abstract public class NodeModel
 implements TemplateNodeModel, TemplateHashModel, TemplateSequenceModel,
diff --git a/src/main/java/freemarker/template/utility/StringUtil.java b/src/main/java/freemarker/template/utility/StringUtil.java
index 881bb3b..973c1f4 100644
--- a/src/main/java/freemarker/template/utility/StringUtil.java
+++ b/src/main/java/freemarker/template/utility/StringUtil.java
@@ -1653,18 +1653,31 @@
     }
     
     /**
-     * @return whether the name is a valid XML tagname.
-     * (This routine might only be 99% accurate. Should maybe REVISIT) 
+     * Used internally by the XML DOM wrapper to check if the subvariable name is just an element name, or a more
+     * complex XPath expression.
+     * 
+     * @return whether the name is a valid XML tagname. (This routine might only be 99% accurate. Should maybe REVISIT)
+     * 
+     * @deprecated Don't use this outside FreeMarker; it's name if misleading, and it doesn't follow the XML specs.
      */
+    @Deprecated
     static public boolean isXMLID(String name) {
-        for (int i = 0; i < name.length(); i++) {
+        int ln = name.length();
+        for (int i = 0; i < ln; i++) {
             char c = name.charAt(i);
-            if (i == 0) {
-                if (c == '-' || c == '.' || Character.isDigit(c))
+            if (i == 0 && (c == '-' || c == '.' || Character.isDigit(c))) {
                     return false;
             }
-            if (!Character.isLetterOrDigit(c) && c != ':' && c != '_' && c != '-' && c != '.') {
-                return false;
+            if (!Character.isLetterOrDigit(c) && c != '_' && c != '-' && c != '.') {
+                if (c == ':') {
+                    if (i + 1 < ln && name.charAt(i + 1) == ':') {
+                        // "::" is used in XPath
+                        return false;
+                    }
+                    // We don't return here, as a lonely ":" is allowed.
+                } else {
+                    return false;
+                }
             }
         }
         return true;
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index a08d339..ba8c1b1 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -7250,7 +7250,7 @@
           nested calls of the same directive, or directive objects used as
           shared variables accessed by multiple threads concurrently.</para>
 
-          <para>Unfortunatelly, <literal>TemplateDirectiveModel</literal>-s
+          <para>Unfortunately, <literal>TemplateDirectiveModel</literal>-s
           don't support passing parameters by position (rather than by name).
           This is fixed starting from FreeMarker 2.4.</para>
         </section>
@@ -26928,6 +26928,25 @@
             </listitem>
 
             <listitem>
+              <para>Bug fixed: XPath queries that has only contained
+              characters that are valid in XML element names and has also
+              contained <literal>::</literal> (which is valid in names in
+              namespace-unware documents), like
+              <literal>e['following-sibling::foo']</literal>, were interpreted
+              as literal element names (giving 0 hits) rather than as XPath
+              expressions. Note that there were no such problem with
+              <literal>e['following-sibling::*']</literal> for example, as
+              it's not a valid XML element name according the XML
+              specification. This fix can actually break applications that has
+              processed namespace unaware XML that use <literal>::</literal>
+              as part of element or attribute names, but such an application
+              is highly unlikely, unlike running into the fixed problem.
+              (Unfortunately, using
+              <literal>incompatible_improvements</literal> wasn't technically
+              possible here.)</para>
+            </listitem>
+
+            <listitem>
               <para>Internal code cleanup: Mostly source code formatting, also
               many parser construction/setup cleanup</para>
             </listitem>
@@ -27264,6 +27283,25 @@
               the imported template was already imported earlier in another
               namespace.</para>
             </listitem>
+
+            <listitem>
+              <para>Bug fixed: XPath queries that has only contained
+              characters that are valid in XML element names and has also
+              contained <literal>::</literal> (which is valid in names in
+              namespace-unware documents), like
+              <literal>e['following-sibling::foo']</literal>, were interpreted
+              as literal element names (giving 0 hits) rather than as XPath
+              expressions. Note that there were no such problem with
+              <literal>e['following-sibling::*']</literal> for example, as
+              it's not a valid XML element name according the XML
+              specification. This fix can actually break applications that has
+              processed namespace unaware XML that use <literal>::</literal>
+              as part of element or attribute names, but such an application
+              is highly unlikely, unlike running into the fixed problem.
+              (Unfortunately, using
+              <literal>incompatible_improvements</literal> wasn't technically
+              possible here.)</para>
+            </listitem>
           </itemizedlist>
         </section>
       </section>
diff --git a/src/test/java/freemarker/ext/dom/DOMTest.java b/src/test/java/freemarker/ext/dom/DOMTest.java
new file mode 100644
index 0000000..182ac31
--- /dev/null
+++ b/src/test/java/freemarker/ext/dom/DOMTest.java
@@ -0,0 +1,55 @@
+package freemarker.ext.dom;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import freemarker.template.TemplateException;
+import freemarker.test.TemplateTest;
+
+public class DOMTest extends TemplateTest {
+
+    @Test
+    public void xpathDetectionBugfix() throws Exception {
+        addDocToDataModel("<root><a>A</a><b>B</b><c>C</c></root>");
+        assertOutput("${doc.root.b['following-sibling::c']}", "C");
+        assertOutput("${doc.root.b['following-sibling::*']}", "C");
+    }
+
+    @Test
+    public void namespaceUnaware() throws Exception {
+        addNSUnawareDocToDataModel("<root><x:a>A</x:a><:>B</:><xyz::c>C</xyz::c></root>");
+        assertOutput("${doc.root['x:a']}", "A");
+        assertOutput("${doc.root[':']}", "B");
+        try {
+            assertOutput("${doc.root['xyz::c']}", "C");
+            fail();
+        } catch (TemplateException e) {
+            assertThat(e.getMessage(), containsString("xyz"));
+        }
+    }
+    
+    private void addDocToDataModel(String xml) throws SAXException, IOException, ParserConfigurationException {
+        addToDataModel("doc", NodeModel.parse(new InputSource(new StringReader(xml))));
+    }
+
+    private void addNSUnawareDocToDataModel(String xml) throws ParserConfigurationException, SAXException, IOException {
+        DocumentBuilderFactory newFactory = DocumentBuilderFactory.newInstance();
+        newFactory.setNamespaceAware(false);
+        DocumentBuilder builder = newFactory.newDocumentBuilder();
+        Document doc = builder.parse(new InputSource(new StringReader(xml)));
+        addToDataModel("doc", doc);
+    }
+
+}