2011-06-01 63 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