使用dom4j清理命名空间处理

时间:2009-09-14 15:48:31

标签: java namespaces dom4j

我们正在使用dom4j 1.6.1来解析来自某个地方的XML。有时,应答器提到了命名空间(例如:),有时候没有()。而且调用Element.selectSingleNode(String s)会失败。

目前我们有3种解决方案,我们对它们不满意

1 - 在对xml文档执行任何操作之前删除所有名称空间出现

xml = xml .replaceAll("xmlns=\"[^\"]*\"","");
xml = xml .replaceAll("ds:","");
xml = xml .replaceAll("etm:","");
[...] // and so on for each kind of namespace

2 - 在获取节点之前删除命名空间 致电

Element.remove(Namespace ns)

但它仅适用于节点和子级的第一级

3 - 通过

混乱代码
node = rootElement.selectSingleNode(NameWithoutNameSpace)
if ( node == null )
    node = rootElement.selectSingleNode(NameWithNameSpace)

那么......你怎么看?女巫一个是不是更糟糕?你有其他解决方案吗?

5 个答案:

答案 0 :(得分:5)

我想删除任何名称空间信息(声明和标记)以简化xpath评估。我最终得到了这个解决方案:

String xml = ...
SAXReader reader = new SAXReader();
Document document = reader.read(new ByteArrayInputStream(xml.getBytes()));
document.accept(new NameSpaceCleaner());
return document.asXML();

其中NameSpaceCleaner是dom4j访问者:

private static final class NameSpaceCleaner extends VisitorSupport {
    public void visit(Document document) {
        ((DefaultElement) document.getRootElement())
                .setNamespace(Namespace.NO_NAMESPACE);
        document.getRootElement().additionalNamespaces().clear();
    }
    public void visit(Namespace namespace) {
        namespace.detach();
    }
    public void visit(Attribute node) {
       if (node.toString().contains("xmlns")
        || node.toString().contains("xsi:")) {
        node.detach();
      }
    }

    public void visit(Element node) {
        if (node instanceof DefaultElement) {
        ((DefaultElement) node).setNamespace(Namespace.NO_NAMESPACE);
        }
         }
 }

答案 1 :(得分:4)

以下是我找到并现在使用的一些代码。如果寻找通用方法,可能会从dom4j文档中删除所有名称空间。

    public static void removeAllNamespaces(Document doc) {
        Element root = doc.getRootElement();
        if (root.getNamespace() !=
                Namespace.NO_NAMESPACE) {            
                removeNamespaces(root.content());
        }
    }

    public static void unfixNamespaces(Document doc, Namespace original) {
        Element root = doc.getRootElement();
        if (original != null) {
            setNamespaces(root.content(), original);
        }
    }

    public static void setNamespace(Element elem, Namespace ns) {

        elem.setQName(QName.get(elem.getName(), ns,
                elem.getQualifiedName()));
    }

    /**
     *Recursively removes the namespace of the element and all its
    children: sets to Namespace.NO_NAMESPACE
     */
    public static void removeNamespaces(Element elem) {
        setNamespaces(elem, Namespace.NO_NAMESPACE);
    }

    /**
     *Recursively removes the namespace of the list and all its
    children: sets to Namespace.NO_NAMESPACE
     */
    public static void removeNamespaces(List l) {
        setNamespaces(l, Namespace.NO_NAMESPACE);
    }

    /**
     *Recursively sets the namespace of the element and all its children.
     */
    public static void setNamespaces(Element elem, Namespace ns) {
        setNamespace(elem, ns);
        setNamespaces(elem.content(), ns);
    }

    /**
     *Recursively sets the namespace of the List and all children if the
    current namespace is match
     */
    public static void setNamespaces(List l, Namespace ns) {
        Node n = null;
        for (int i = 0; i < l.size(); i++) {
            n = (Node) l.get(i);

            if (n.getNodeType() == Node.ATTRIBUTE_NODE) {
                ((Attribute) n).setNamespace(ns);
            }
            if (n.getNodeType() == Node.ELEMENT_NODE) {
                setNamespaces((Element) n, ns);
            }            
        }
    }

希望这对需要它的人有用!

答案 2 :(得分:1)

选项1是危险的,因为如果不预先解析文档而无法保证给定命名空间的前缀,并且因为最终可能会发生命名空间冲突。如果您正在使用文档而不输出任何内容,则可能没问题,具体取决于文档的来源,但是否则会丢失太多信息。

选项2可以递归应用,但它与选项1有许多相同的问题。

选项3听起来像是最好的方法,但不是混乱你的代码,而是制作一个静态方法来执行两种检查,而不是在整个代码库中放置相同的if语句。

最好的方法是让任何向您发送错误XML的人来修复它。当然这引出了一个问题,它实际上已经破裂了。具体来说,您是否获得XML,其中默认命名空间被定义为X,然后代表X的命名空间被赋予前缀'es'?如果是这种情况,则XML格式正确,您只需要与前缀无关的代码,但仍使用限定名称来获取元素。我对Dom4j不太熟悉,知道创建一个带有空前缀的命名空间是否会使所有元素与匹配的URI匹配,或者只匹配那些没有前缀的元素,但是值得尝试。

答案 3 :(得分:0)

作为Abhishek,我需要从XML中剥离命名空间以简化系统测试脚本中的XPath查询。 (XML首先经过XSD验证)

以下是我遇到的问题:

  1. 我需要处理深度结构化的XML,这种XML往往会炸毁堆栈。
  2. 在大多数复杂的XML上,由于我没有完全调查的原因,剥离所有命名空间只能在首先遍历DOM树深度时可靠地工作。这样就排除了访问者,或者获得了document.selectNodes("//*")
  3. 的节点列表

    我最终得到了以下内容(不是最优雅的,但如果这有助于解决某人的问题......):

    public static String normaliseXml(final String message) {
        org.dom4j.Document document;
        document = DocumentHelper.parseText(message);
    
        Queue stack = new LinkedList();
    
        Object current = document.getRootElement();
    
        while (current != null) {
            if (current instanceof Element) {
                Element element = (Element) current;
    
                Iterator iterator = element.elementIterator();
    
                if (iterator.hasNext()) {
                    stack.offer(element);
                    current = iterator;
                } else {
                    stripNamespace(element);
    
                    current = stack.poll();
                }
            } else {
                Iterator iterator = (Iterator) current;
    
                if (iterator.hasNext()) {
                    stack.offer(iterator);
                    current = iterator.next();
                } else {
                    current = stack.poll();
    
                    if (current instanceof Element) {
                        stripNamespace((Element) current);
    
                        current = stack.poll();
                    }
                }
            }
        }
    
        return document.asXML();
    }
    
    private static void stripNamespace(Element element) {
        QName name = new QName(element.getName(), Namespace.NO_NAMESPACE, element.getName());
        element.setQName(name);
    
        for (Object o : element.attributes()) {
            Attribute attribute = (Attribute) o;
    
            QName attributeName = new QName(attribute.getName(), Namespace.NO_NAMESPACE, attribute.getName());
            String attributeValue = attribute.getValue();
    
            element.remove(attribute);
    
            element.addAttribute(attributeName, attributeValue);
        }
    
        for (Object o : element.declaredNamespaces()) {
            Namespace namespace = (Namespace) o;
            element.remove(namespace);
        }
    }
    

答案 4 :(得分:0)

此代码实际上有效:

public void visit(Document document) {
    ((DefaultElement) document.getRootElement())
            .setNamespace(Namespace.NO_NAMESPACE);
    document.getRootElement().additionalNamespaces().clear();
}

public void visit(Namespace namespace) {
    if (namespace.getParent() != null) {
        namespace.getParent().remove(namespace);
    }
}

public void visit(Attribute node) {
    if (node.toString().contains("xmlns")
            || node.toString().contains("xsi:")) {
        node.getParent().remove(node);
    }
}

public void visit(Element node) {
    if (node instanceof DefaultElement) {
        ((DefaultElement) node).setNamespace(Namespace.NO_NAMESPACE);
        node.additionalNamespaces().clear();
    }
}