2011-06-01 28 views
5

我有2个包含类似XML的文档对象。例如:在保留xsi的同时合并文档:类型

<tt:root xmlns:tt="http://myurl.com/"> 
    <tt:child/> 
    <tt:child/> 
</tt:root> 

而另外一个:

<ns1:root xmlns:ns1="http://myurl.com/" xmlns:ns2="http://myotherurl.com/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
    <ns1:child/> 
    <ns1:child xsi:type="ns2:SomeType"/> 
</ns1:root> 

我需要将它们与1个元素和4个个子元素合并到1号文件。 问题是,如果我使用document.importNode函数进行合并,那么它正确地处理名称空间到处都是,但是xsi:type元素。那么我得到的结果是这样的:

<tt:root xmlns:tt="http://myurl.com/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
    <tt:child/> 
    <tt:child/> 
    <ns1:child xmlns:ns1="http://myurl.com/"/> 
    <ns1:child xmlns:ns1="http://myurl.com/" xsi:type="ns2:SomeType"/> 
</tt:root> 

正如你可以看到,NS2是在xsi:type使用,但不被任何定义。有没有自动化的方法来解决这个问题?

谢谢。

新增:

如果这个任务是不可能使用默认的Java DOM库来完成的,也许有我可以用它来完成我的任务了一些其他图书馆?

+0

你有没有用'deep'参数玩过这个工作对我来说,当我的合并文档是失踪的xmlns in'importNode'已经?也许你需要'deep = true'来正确地导入属性节点。 – 2011-06-01 08:56:55

+0

是的,我使用deep = true,但这没有帮助。它似乎只是简单地将xsi:type属性解析为一个简单的字符串参数,而不是一个类型参数。 – bezmax 2011-06-01 10:07:00

+0

您的第二个文档不是名称空间,因为它使用的前缀“xsi”尚未绑定。 – alexbrn 2011-06-01 10:56:10

回答

1

XQuery单行可以做的工作:构建命名为背景的新节点根元素,然后将它的子项与其他文档的子项一起导入:

declare variable $other external; element {node-name(*)} {*/*, $other/*/*} 

尽管在XQuery您不能完全控制命名空间节点(至少在XQuery 1.0中),它具有copy-namespaces模式设置,可用于请求保持命名空间上下文完整,以防默认情况下实现保留它。

如果XQuery是一个可行的选项,那么saxon9he.jar可能是你所追求的“magic xml库”。

下面是示例代码暴露出一些情况下,使用s9api API

import javax.xml.parsers.DocumentBuilderFactory; 
import net.sf.saxon.s9api.*; 
import org.w3c.dom.Document; 

... 

    Document merge(Document context, Document other) throws Exception 
    { 
    Processor processor = new Processor(false); 
    XQueryExecutable executable = processor.newXQueryCompiler().compile(
     "declare variable $other external; element {node-name(*)} {*/*, $other/*/*}"); 
    XQueryEvaluator evaluator = executable.load();  
    DocumentBuilder db = processor.newDocumentBuilder(); 
    evaluator.setContextItem(db.wrap(context)); 
    evaluator.setExternalVariable(new QName("other"), db.wrap(other)); 
    Document doc = 
     DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); 
    processor.writeXdmValue(evaluator.evaluate(), new DOMDestination(doc)); 
    return doc; 
    } 
+0

谢谢。正是我在找什么。 – bezmax 2011-06-09 16:13:43

2

如果我修复第二个文件中的名称空间问题(通过绑定“xsi”前缀),并使用下面的代码进行合并,则命名空间绑定将保留在输出中;或者至少他们在这里(在Windows版本1.6.0_24上的香草Java 64位)。

String s1 = "<!-- 1st XML document here -->"; 
String s2 = "<!-- 2nd XML document here -->"; 

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 
factory.setNamespaceAware(true); 
DocumentBuilder builder = factory.newDocumentBuilder(); 

Document doc1 = builder.parse(new ByteArrayInputStream(s1.getBytes())); 
Document doc2 = builder.parse(new ByteArrayInputStream(s2.getBytes())); 

Element doc1root = (Element)doc1.getDocumentElement(); 
Element doc2root = (Element)doc2.getDocumentElement(); 

NamedNodeMap atts1 = doc1root.getAttributes(); 
NamedNodeMap atts2 = doc2root.getAttributes(); 

for(int i = 0; i < atts1.getLength(); i++) 
{ 
    String name = atts1.item(i).getNodeName(); 
    if(name.startsWith("xmlns:")) 
    { 
     if(atts2.getNamedItem(name) == null) 
     { 
      doc2root.setAttribute(name, atts1.item(i).getNodeValue()); 
     }  
    }  
} 

NodeList nl = doc1.getDocumentElement().getChildNodes(); 
for(int i = 0; i < nl.getLength(); i++) 
{ 
    Node n = nl.item(i); 
    doc2root.appendChild(doc2.importNode(n, true)); 

} 

TransformerFactory transformerFactory = TransformerFactory.newInstance(); 
Transformer transformer = transformerFactory.newTransformer(); 
StreamResult streamResult = new StreamResult(System.out); 
transformer.transform(new DOMSource(doc2), streamResult); 
+0

不适合我。试试这个XML例如:http://pastebin.com/qmu8vVUV。结果我得到的名称空间ns2没有声明。 – bezmax 2011-06-01 11:22:39

+0

啊,是的,我只是幸运地按照合并文件的顺序。我现在修改了代码,以便从要合并的文档中复制不在目标文档中的前缀绑定。这只是对根元素进行绑定:如果你有更难处理的情况,你可以在树的更深层次应用相同的技术...... – alexbrn 2011-06-01 13:07:40

+0

是的,这是解决问题的方法之一,但它太恶心了。主要问题是2个消息的命名空间在我的情况下可能重叠。所以第一条消息将有'xmlns:ns1 ='aaa''和第二个'xmlns:ns1 ='bbb''。此外,我猜想一个好的DOM库应该能够自己完美地处理它,而不需要使用像你所提议的那样的各种黑客。 – bezmax 2011-06-02 06:30:58

2

这里的问题是在属性值中使用名称空间前缀;创建命名空间标准时从未考虑过的东西,以及常见Java DOM/XML工具无法轻松处理的东西。但是,您可以通过

  1. 解决它合并前,用xsi:type="{namespace}value"取代xsi:type="prefix:value"每个实例。通过这样做,您不依赖前缀映射。在你的例子中,<xsi:type="ns2:SomeType"将变成xsi:type="{http://myotherurl.com/}SomeType"
  2. 合并文件。
  3. 在结果文档上,反转步骤1中的替换。必须仔细管理前缀映射以避免冲突;可能需要创建一个新的映射。
1

我会让JAXB和Mergeable plugin在架构派生类中生成mergeFrom方法。然后:

  • 解组O1,O2
  • 玛吉O1,使用所生成的方法分为O3
  • 元帅O2 O3

JAXB通常处理xsi:type相当好吧。

+0

我认为这将是唯一没有可怕黑客的全自动方式......好吧,如果没有人提出一些自动合并的magic-xml-library,我想我会坚持JAXB。 – bezmax 2011-06-07 12:00:03

1

UPDATE

这不会对其中 两个文件具有冲突 空间前缀(从 第二个文档会从第一更换 映射的映射)的情况下工作。

您可以将名称空间声明从第二个文档复制到导入的节点。由于子节点可以覆盖父节点的前缀,这是有效的:

<foo:root xmlns:foo="urn:ROOT"> 
    <foo:child xmlns:foo="urn:CHILD" xsi:type="foo:child-type"> 
     ... 
    </foo:child> 
</foo:root> 

在上面的XML绑定到前缀“foo”的命名空间的子元素的范围覆盖。您可以通过执行以下操作做到这一点为您的使用情况:

import java.io.File; 

import javax.xml.parsers.DocumentBuilder; 
import javax.xml.parsers.DocumentBuilderFactory; 
import javax.xml.transform.Transformer; 
import javax.xml.transform.TransformerFactory; 
import javax.xml.transform.dom.DOMSource; 
import javax.xml.transform.stream.StreamResult; 

import org.w3c.dom.Attr; 
import org.w3c.dom.Document; 
import org.w3c.dom.Element; 
import org.w3c.dom.NamedNodeMap; 
import org.w3c.dom.Node; 
import org.w3c.dom.NodeList; 

public class Demo { 

    public static void main(String[] args) throws Exception { 
     DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 
     dbf.setNamespaceAware(true); 
     DocumentBuilder db = dbf.newDocumentBuilder(); 

     File file1 = new File("src/forum231/input1.xml"); 
     Document doc1 = db.parse(file1); 
     Element rootElement1 = doc1.getDocumentElement(); 

     File file2 = new File("src/forum231/input2.xml"); 
     Document doc2 = db.parse(file2); 
     Element rootElement2 = doc2.getDocumentElement(); 

     // Copy Child Nodes 
     NodeList childNodes2 = rootElement2.getChildNodes(); 
     for(int x=0; x<childNodes2.getLength(); x++) { 
      Node importedNode = doc1.importNode(childNodes2.item(x), true); 
      if(importedNode.getNodeType() == Node.ELEMENT_NODE) { 
       Element importedElement = (Element) importedNode; 
       // Copy Attributes 
       NamedNodeMap namedNodeMap2 = rootElement2.getAttributes(); 
       for(int y=0; y<namedNodeMap2.getLength(); y++) { 
        Attr importedAttr = (Attr) doc1.importNode(namedNodeMap2.item(y), true); 
        importedElement.setAttributeNodeNS(importedAttr); 
       } 
      } 
      rootElement1.appendChild(importedNode); 
     } 

     // Output Document 
     TransformerFactory tf = TransformerFactory.newInstance(); 
     Transformer t = tf.newTransformer(); 
     DOMSource source = new DOMSource(doc1); 
     StreamResult result = new StreamResult(System.out); 
     t.transform(source, result); 
    } 

} 

输出

<?xml version="1.0" encoding="UTF-8" standalone="no"?><tt:root xmlns:tt="http://myurl.com/"> 
    <tt:child/> 
    <tt:child/> 

    <ns1:child xmlns:ns1="http://myurl.com/" xmlns:ns2="http://myotherurl.com/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> 
    <ns1:child xmlns:ns1="http://myurl.com/" xmlns:ns2="http://myotherurl.com/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ns2:SomeType"/> 
</tt:root> 

原来的答案

除了复制的元素,你可以复制属性。这将确保所产生的文件包含必要的命名空间声明:

import java.io.File; 

import javax.xml.parsers.DocumentBuilder; 
import javax.xml.parsers.DocumentBuilderFactory; 
import javax.xml.transform.Transformer; 
import javax.xml.transform.TransformerFactory; 
import javax.xml.transform.dom.DOMSource; 
import javax.xml.transform.stream.StreamResult; 

import org.w3c.dom.Attr; 
import org.w3c.dom.Document; 
import org.w3c.dom.Element; 
import org.w3c.dom.NamedNodeMap; 
import org.w3c.dom.Node; 
import org.w3c.dom.NodeList; 

public class Demo { 

    public static void main(String[] args) throws Exception { 
     DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 
     dbf.setNamespaceAware(true); 
     DocumentBuilder db = dbf.newDocumentBuilder(); 

     File file1 = new File("input1.xml"); 
     Document doc1 = db.parse(file1); 
     Element rootElement1 = doc1.getDocumentElement(); 

     File file2 = new File("input2.xml"); 
     Document doc2 = db.parse(file2); 
     Element rootElement2 = doc2.getDocumentElement(); 

     // Copy Attributes 
     NamedNodeMap namedNodeMap2 = rootElement2.getAttributes(); 
     for(int x=0; x<namedNodeMap2.getLength(); x++) { 
      Attr importedNode = (Attr) doc1.importNode(namedNodeMap2.item(x), true); 
      rootElement1.setAttributeNodeNS(importedNode); 
     } 

     // Copy Child Nodes 
     NodeList childNodes2 = rootElement2.getChildNodes(); 
     for(int x=0; x<childNodes2.getLength(); x++) { 
      Node importedNode = doc1.importNode(childNodes2.item(x), true); 
      rootElement1.appendChild(importedNode); 
     } 

     // Output Document 
     TransformerFactory tf = TransformerFactory.newInstance(); 
     Transformer t = tf.newTransformer(); 
     DOMSource source = new DOMSource(doc1); 
     StreamResult result = new StreamResult(System.out); 
     t.transform(source, result); 
    } 

} 

输出:

<?xml version="1.0" encoding="UTF-8" standalone="no"?> 
<tt:root xmlns:tt="http://myurl.com/" xmlns:ns1="http://myurl.com/" xmlns:ns2="http://myotherurl.com/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
    <tt:child/> 
    <tt:child/> 

    <ns1:child/> 
    <ns1:child xsi:type="ns2:SomeType"/> 
</tt:root> 
+1

这对于两个文档具有冲突的命名空间前缀(来自第二个文档的映射将替换来自第一个文档的映射)的情况不起作用。 – 2011-06-06 22:02:52

+0

@Per Norrman - 我已经更新了我的答案,以说明冲突的命名空间前缀。 – 2011-06-08 20:57:33

0

如果你知道你要添加它可以作为简单的命名空间URI和前缀URI简单地添加属性的元素。载我的导入文档中的xsd =“http://www.w3.org/2001/XMLSchema”:

myDocument.getDocumentElement.setAttribute("xmlns:xsd", "http://www.w3.org/2001/XMLSchema"); 
+0

是的,我可以,但我希望XML框架能够自己处理它。 – bezmax 2012-04-23 06:42:27

相关问题