Создать NodeList всех узлов документа вручную - PullRequest
4 голосов
/ 09 августа 2011

В настоящее время я генерирую NodeList всех узлов документа (в порядке документов) вручную. Выражение XPath для получения этого NodeList равно

//. | //@* | //namespace::*

Моя первая попытка обойти DOM вручную и собрать узлы (NodeSet - это примитивная реализация NodeList, делегирующая List):

private static void walkRecursive(Node cur, NodeSet nodes) {
    nodes.add(cur);

    if (cur.hasAttributes()) {
        NamedNodeMap attrs = cur.getAttributes();
        for (int i=0; i < attrs.getLength(); i++) {
            Node child = attrs.item(i);
            walkRecursive(child, nodes);
        }
    }

    int type = cur.getNodeType();
    if (type == Node.ELEMENT_NODE || type == Node.DOCUMENT_NODE) {
        NodeList children = cur.getChildNodes();
        if (children == null)
            return;

        for (int i=0; i < children.getLength(); i++) {
            Node child = children.item(i);
            walkRecursive(child, list);
        }
    }
}

Я бы начал рекурсию с вызова walkRecursive(doc, nodes), где doc - это org.w3c.Document и nodes a (пока пусто) NodeSet.

Я проверил это, используя этот примитивный XML-документ:

<?xml version="1.0"?>
<myns:root xmlns:myns="http://www.my.ns/#">
  <myns:element/>
</myns:root>

Если я, например, канонизирую свои созданные вручную NodeSet и NodeList, сгенерированные первоначально упомянутым выражением XPath, и сравниваю два байта для байта, то результат будет одинаковым и, кажется, будет работать нормально.

Но , если я переберу два NodeList s и выведу отладочную информацию (typeString просто генерирует строковое представление)

for (int i=0; i < nodes.getLength(); i++) {
    Node child = nodes.item(i);
    System.out.println("Type: " + typeString(child.getNodeType()) +
                       " Name:" + child.getNodeName() + 
                       " Local name: " + child.getLocalName() +
                       " NS: " + child.getNamespaceURI());
}

затем я получаю этот вывод для сгенерированного XPath NodeList:

Type: DocumentNode Name:#document Local name: null NS: null
Type: Element Name:myns:root Local name: root NS: http://www.my.ns/#
Type: Attribute Name:xmlns:myns Local name: myns NS: http://www.w3.org/2000/xmlns/
Type: Attribute Name:xmlns:xml Local name: xml NS: http://www.w3.org/2000/xmlns/
Type: Text Name:#text Local name: null NS: null
Type: Element Name:myns:element Local name: element NS: http://www.my.ns/#
Type: Text Name:#text Local name: null NS: null

и это для сгенерированного вручную NodeList:

Type: DocumentNode Name:#document Local name: null NS: null
Type: Element Name:myns:root Local name: root NS: http://www.my.ns/#
Type: Attribute Name:xmlns:myns Local name: myns NS: http://www.w3.org/2000/xmlns/
Type: Text Name:#text Local name: null NS: null
Type: Element Name:myns:element Local name: element NS: http://www.my.ns/#
Type: Text Name:#text Local name: null NS: null

Итак, как вы можете видеть, в первом примере NodeList дополнительно содержит Node для пространства имен XML:

Type: Attribute Name:xmlns:xml Local name: xml NS: http://www.w3.org/2000/xmlns/

Теперь мои вопросы:

a) Если я правильно интерпретирую xml-names11 , тогда мне не нужно объявление xmlns: xml:

Префикс xml по определению связан с именем пространства имен http://www.w3.org/XML/1998/namespace. Он МОЖЕТ, но не обязательно, быть объявленным и НЕ ДОЛЖЕН быть необъявленным или привязанным к любому другому имени пространства имен. Другие префиксы НЕ ДОЛЖНЫ быть связаны с этим именем пространства имен и НЕ ДОЛЖНЫ быть объявлены как пространство имен по умолчанию.

Я прав? (хотя бы с) намекает в этом направлении)

b) Но тогда, в любом случае, почему оценка XPath добавляет его - не должно ли оно просто включать то, что было в первую очередь, вместо автоматического добавления вещей?

c) Это может вызвать проблемы при канонизации XML , хотя не должно - объявления пространства имен xml должны быть опущены во время канонизации. Кто-нибудь знает реализации (Java), которые ошибаются?


Редактировать:

Вот код, который я использовал для оценки выражения XPath, содержащего узел пространства имен «xml»:

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
dbf.setValidating(false);
InputStream in = ...;
try {
    Document doc = dbf.newDocumentBuilder().parse(in);
    XPathFactory fac = XPathFactory.newInstance();
    XPath xp = fac.newXPath();
    XPathExpression exp = xp.compile("//. | //@* | //namespace::*");
    NodeList nodes = (NodeList)exp.evaluate(doc, XPathConstants.NODESET);
} finally {
    in.close();
}

1 Ответ

1 голос
/ 25 августа 2011

Поскольку вы можете написать

<myns:root xml:space="preserve" xmlns:myns="http://www.my.ns/#">
  <myns:element/>
</myns:root>

без объявления префикса "xml", то он должен быть там неявно.Поэтому правильно включить узел пространства имен для этого объявления пространства имен в шаге расположения //namespace:*

Итак,

a) вы ошибаетесь, вам это нужно (ну, в зависимости от целивашего кода)

b) см. выше

c) нет, но я видел другие случаи в углу пространства имен, когда дела пошли не так (например, Проблема с преобразованием org.dom4j.Документ к org.w3c.dom. Документ и подпись XML

...