Weitere Fragen zum Analysieren von XML (com.sun.star.xml.dom.DocumentBuilder)

Programmierung unter AOO/LO (StarBasic, Python, Java, ...)

Moderator: Moderatoren

Stephan
********
Beiträge: 12369
Registriert: Mi, 30.06.2004 19:36
Wohnort: nahe Berlin

Weitere Fragen zum Analysieren von XML (com.sun.star.xml.dom.DocumentBuilder)

Beitrag von Stephan »

Hallo,

in:
viewtopic.php?f=18&t=66795

wurde mir gezeigt wie ich mittels com.sun.star.xml.dom.DocumentBuilder auf ein xml-dokument zugreifen kann:

Code: Alles auswählen

Sub Main

 sURI = "file:///home/axel/content.xml"
 oDocumentBuilder = createUnoService("com.sun.star.xml.dom.DocumentBuilder")
 oDOMDocument = oDocumentBuilder.parseURI(sURI)

 oBody = oDOMDocument.getElementsByTagNameNS("urn:oasis:names:tc:opendocument:xmlns:office:1.0", "body").item(0)
 oText = oBody.getElementsByTagNameNS("urn:oasis:names:tc:opendocument:xmlns:office:1.0", "text").item(0)
 oTextChilds = oText.getChildNodes()
  
 for i = 0 to oTextChilds.getLength()
  oTextChild = oTextChilds.item(i)
  if oTextChild.getPrefix()="draw" and oTextChild.getLocalName()="frame" then
   xray oTextChild
  end if
  if oTextChild.getPrefix()="text" and oTextChild.getLocalName()="p" then exit for
 next

End Sub

Ich habe mich nun durch den Code gearbeitet und auch begriffen wie ich an der Stelle:

Code: Alles auswählen

if oTextChild.getPrefix()="draw" and oTextChild.getLocalName()="frame" then
   '...
end if
weitergehend hierachisch tieferliegende Nodes auflösen kann, nämlich jeweils:

Code: Alles auswählen

oTextChild.getChildNodes
wobei dann:

Code: Alles auswählen

oTextChild.getChildNodes.item(<Index>)
der jeweilige Knoten ist, z.B.:

Code: Alles auswählen

oTextChild.getChildNodes.item(0)
mit den Eigenschaften:

Code: Alles auswählen

Msgbox oTextChild.getChildNodes.item(0).dbg_properties
Auf diese Weise komme ich letztlich durch den gesamten XML-Baum des XML-Dokuments.


ABER:
Wie bekomme ich einen kompletten Node als quasi XML-Quelltext in eine Variable?

z.B. entspricht bei einer konkreten XML-Testdatei, der erste gefundene Node aufgrund des Codes:

Code: Alles auswählen

if oTextChild.getPrefix()="draw" and oTextChild.getLocalName()="frame" then
   '...
end if
diesem 'Stück' des XML-Quelltextes:

Code: Alles auswählen

<draw:frame draw:name="Rahmen1" draw:z-index="0" svg:height="3.006cm" svg:width="4.838cm" svg:y="5.519cm" svg:x="7.86cm" text:anchor-page-number="1" text:anchor-type="page" draw:style-name="fr1">
<draw:text-box>
<text:p text:style-name="Frame_20_contents">
<text:database-display text:database-name="Neue Datenbank3" text:column-name="Anrede" text:table-type="table" text:table-name="Tabelle1"><Anrede></text:database-display>
</text:p>
<text:p text:style-name="Frame_20_contents">
<text:database-display text:database-name="Neue Datenbank3" text:column-name="Vorname" text:table-type="table" text:table-name="Tabelle1"><Vorname></text:database-display>
<text:database-display text:database-name="Neue Datenbank3" text:column-name="Name" text:table-type="table" text:table-name="Tabelle1"><Name></text:database-display>
</text:p>
<text:p text:style-name="Frame_20_contents">
<text:database-display text:database-name="Neue Datenbank3" text:column-name="Strasse" text:table-type="table" text:table-name="Tabelle1"><Strasse></text:database-display>
</text:p>
<text:p text:style-name="Frame_20_contents">
<text:database-display text:database-name="Neue Datenbank3" text:column-name="PLZ" text:table-type="table" text:table-name="Tabelle1"><PLZ></text:database-display>
<text:database-display text:database-name="Neue Datenbank3" text:column-name="Ort" text:table-type="table" text:table-name="Tabelle1"><Ort></text:database-display>
</text:p>
</draw:text-box>
</draw:frame>
und dieses Stück hätte ich gerne genauso als String in einer Textvariable.

Geht das direkt?


Oder muss ich bei einem solchen, bereits im Code 'isolierten' Node, durch alle Child-Nodes (aller weiteren Ebenen) interieren dann auch alle Attribute auslesen um am Ende quasi umgekehrt alles wieder zusammenzusetzen um den gewünschten String zu erhalten?



Gruß
Stephan
Axel Richter
****
Beiträge: 159
Registriert: So, 17.10.2010 16:54

Re: Weitere Fragen zum Analysieren von XML (com.sun.star.xml.dom.DocumentBuilder)

Beitrag von Axel Richter »

Hallo Stephan,

man darf XML nicht als Text sehen. XML ist die serialisierte Form eines Dokument-Objekts bestehend aus Teil-Objekten, die auch wieder als XML serialisiert werden können. Auf XML mit Text-Funktionen loszugehen ist in etwa so, wie mit einem Hex-Editor auf kompilierten Programmcode loszugehen. Deshalb würde ich XML auch nie als String weiterbearbeiten, sondern immer nur geparst in Objektform.

Nichts desto trotz kann man natürlich XML als String darstellen. Es muss ja schließlich in eine Textdatei geschrieben werden. Allerdings bietet die OpenOffice API da nicht sehr komfortable Möglichkeiten. Man muss quasi einen String von einem TextInputStream lesen:

Code: Alles auswählen

function getXMLString(oXMLElement as object) as string
  oDocumentBuilder = createUnoService("com.sun.star.xml.dom.DocumentBuilder")
  oDOMDocumentNew = oDocumentBuilder.newDocument()
  oXMLElementNew = oDOMDocumentNew.importNode(oXMLElement, true)
  oDOMDocumentNew.appendChild(oXMLElementNew)

  oPipe = createUnoService("com.sun.star.io.Pipe")
  oTextInputStream = createUnoService("com.sun.star.io.TextInputStream")
  oTextInputStream.setInputStream(oPipe)
      
  oDOMDocumentNew.setOutputStream(oPipe) 
  oDOMDocumentNew.start()
  oPipe.closeOutput()
  
  sXML =  oTextInputStream.readString(array(), true)
   
  getXMLString = sXML
end function
Das würde ich aber nur als Debugging-Werkzeug nutzen und nicht zur Weiterverarbeitung. Bearbeiten würde ich das XML immer mit den Node-Methoden.

Beispiel1: Separiere alle DrawFrames je in eine eigene XML-Datei:

Code: Alles auswählen

sub separatePageDrawFrames
 sURI = "file:///home/axel/content.xml"
 oDocumentBuilder = createUnoService("com.sun.star.xml.dom.DocumentBuilder")
 
 oDOMDocument = oDocumentBuilder.parseURI(sURI) '= ursprüngliche content.xml
 
 'hole alle DrawFrames  
 oDrawFrames = oDOMDocument.getElementsByTagNameNS("urn:oasis:names:tc:opendocument:xmlns:drawing:1.0", "frame")
 'laufe durch alle DrawFrames
 for i = 0 to oDrawFrames.getLength()-1
  oDrawFrame = oDrawFrames.item(i)
  if oDrawFrame.getAttributes().getNamedItem("anchor-type").getNodeValue() = "page" then
 
   msgbox getXMLString(oDrawFrame)
   
   oDOMDocumentNew = oDocumentBuilder.newDocument() '=je DrawFrame ein Ziel-Dokument
   
   'importiere den DrawFrame in das Ziel-Dokument
   oDrawFrameNew = oDOMDocumentNew.importNode(oDrawFrame, true)
   oDOMDocumentNew.appendChild(oDrawFrameNew)
   
   'schreibe das Ziel-Dokument     
   sURI = "file:///home/axel/drawFrame" & i & ".xml"
   oSimpleFileAccess = createUnoService("com.sun.star.ucb.SimpleFileAccess") 
   if oSimpleFileAccess.exists(sURI) then oSimpleFileAccess.kill(sURI)
   oFile = oSimpleFileAccess.openFileWrite(sURI)
   oDOMDocumentNew.setOutputStream(oFile) 
   oDOMDocumentNew.start()
   oFile.closeOutput()
  endif
 next
end sub
Beispiel2: Erstelle eine neue content.xml nur mit DrawFrames und geändertem z-index:

Code: Alles auswählen

sub concatenateNewDocumentOnlyWithDrawFrames
 sURI = "file:///home/axel/content.xml"
 oDocumentBuilder = createUnoService("com.sun.star.xml.dom.DocumentBuilder")
 
 oDOMDocument1 = oDocumentBuilder.parseURI(sURI) '= ursprüngliche content.xml
 oDOMDocument2 = oDocumentBuilder.parseURI(sURI) '= Ziel-Dokument, 
 'kein neues wegen der ganzen Namespaces, die sonst erst wieder aufwändig implementiert werden müssten

 'Ziel-Dokument leersen bis auf das <office:text ...> Element
 oText = oDOMDocument2.getElementsByTagNameNS("urn:oasis:names:tc:opendocument:xmlns:office:1.0", "text").item(0)
 oTextChilds = oText.getChildNodes()
 for i = oTextChilds.getLength()-1 to 0 step -1
  oChild = oText.removeChild(oTextChilds.item(i))
 next
 
 'hole alle DrawFrames  
 oDrawFrames = oDOMDocument1.getElementsByTagNameNS("urn:oasis:names:tc:opendocument:xmlns:drawing:1.0", "frame")
 for i = 0 to oDrawFrames.getLength()-1
  oDrawFrame = oDrawFrames.item(i)
  if oDrawFrame.getAttributes().getNamedItem("anchor-type").getNodeValue() = "page" then
   'stelle den z-index neu ein
   oDrawFrame.getAttributes().getNamedItem("z-index").setNodeValue(100+i)
   'importiere den DrawFrame in das Ziel-Dokument
   oDrawFrameNew = oDOMDocument2.importNode(oDrawFrame, true)
   oText.appendChild(oDrawFrameNew)
  endif
 next
 'schreibe das Ziel-Dokument
 sURI = "file:///home/axel/contentNew.xml"
 oSimpleFileAccess = createUnoService("com.sun.star.ucb.SimpleFileAccess") 
 if oSimpleFileAccess.exists(sURI) then oSimpleFileAccess.kill(sURI)
 oFile = oSimpleFileAccess.openFileWrite(sURI)
 oDOMDocument2.setOutputStream(oFile) 
 oDOMDocument2.start()
 oFile.closeOutput()
end sub
Stephan
********
Beiträge: 12369
Registriert: Mi, 30.06.2004 19:36
Wohnort: nahe Berlin

Re: Weitere Fragen zum Analysieren von XML (com.sun.star.xml.dom.DocumentBuilder)

Beitrag von Stephan »

Hallo Axel,
man darf XML nicht als Text sehen. XML ist die serialisierte Form eines Dokument-Objekts bestehend aus Teil-Objekten, die auch wieder als XML serialisiert werden können. Auf XML mit Text-Funktionen loszugehen ist in etwa so, wie mit einem Hex-Editor auf kompilierten Programmcode loszugehen.
Deshalb würde ich XML auch nie als String weiterbearbeiten, sondern immer nur geparst in Objektform.
Mir ist das schon klar, aber wenn es um komplexes hierrachisches XML geht flüchten sich meine Gedanken immer wieder in Strings (mit nichthirachischem XML wie der Addon.xcu einer Extension, habe ich hingegen keine Schwierigkeiten).

Ich schweife jetzt ab, aber man ein Beispiel das mir schon vor einigen Tagen bei Experimenten aufgefallen ist. Für den folgenden Knoten (draw:frame) gilt nach 'Ansicht' von "com.sun.star.xml.dom.DocumentBuilder" das der enthaltene "draw:image"-Teil kein Unterknoten wäre:

Code: Alles auswählen

<draw:frame draw:name="Grafik2" draw:z-index="3" svg:height="1.143cm" svg:width="1.789cm" svg:y="3.625cm" svg:x="12.966cm" text:anchor-page-number="3" text:anchor-type="page" draw:style-name="fr3">
<draw:image xlink:actuate="onLoad" xlink:show="embed" xlink:type="simple" xlink:href="Pictures/10000000000004000000030010393590.jpg"/>
</draw:frame>
Und das ist wieder etwas was mir nicht einleuchtet, denn die inhaltliche Aussagen von XML ist doch wohl nicht von der Schreibweise abhängig und da ich obigen Ausdruck doch auch schreiben könnte als:

Code: Alles auswählen

<draw:frame draw:name="Grafik2" draw:z-index="3" svg:height="1.143cm" svg:width="1.789cm" svg:y="3.625cm" svg:x="12.966cm" text:anchor-page-number="3" text:anchor-type="page" draw:style-name="fr3">
<draw:image xlink:actuate="onLoad" xlink:show="embed" xlink:type="simple" xlink:href="Pictures/10000000000004000000030010393590.jpg">
</draw:image>
</draw:frame>
IST dort, für mein Verständnis, ein Unterknoten.

Ich gehe nicht davon aus das "com.sun.star.xml.dom.DocumentBuilder" hier einen Fehler macht und ich kann auch nicht ausschliessen das ich selbst etwas falsch gemacht habe, nur solcherart Probleme halten mich auf und machen mich unsicher.[/size]

Das ich im Generellen hingegen überhaupt keine Zweifel habe das es im Grundsatz eigentlich falsch ist hier mit Strings arbeiten zu wollen ist doch überhaupt der Anlass für meinen ursprünglichen Post gewesen (wobei mein spezieller Grund der war das ich annehme das die Bearbeitung mit "com.sun.star.xml.dom.DocumentBuilder" schlichtweg schneller ist (Verarbeitungsdauer zur Laufzeit) als wenn ich auf Stringsbasis arbeite).


Mit Deinem Code muss ich mich jetzt erst einmal auseinandersetzen und das dauert etwas weil ich es nur in meiner Freizeit tun kann, also Antwort wohl erst morgen.

Schon jetzt scheinen mir aber Deine Beispiele 1 und 2 viel anregender als die eigentliche Antwort auf meine Frage (also Dein erster Code-Schnipsel).



Gruß
Stephan
Axel Richter
****
Beiträge: 159
Registriert: So, 17.10.2010 16:54

Re: Weitere Fragen zum Analysieren von XML (com.sun.star.xml.dom.DocumentBuilder)

Beitrag von Axel Richter »

Hallo Stephan,
Für den folgenden Knoten (draw:frame) gilt nach 'Ansicht' von "com.sun.star.xml.dom.DocumentBuilder" das der enthaltene "draw:image"-Teil kein Unterknoten wäre:

Code: Alles auswählen

<draw:frame draw:name="Grafik2" draw:z-index="3" svg:height="1.143cm" svg:width="1.789cm" svg:y="3.625cm" svg:x="12.966cm" text:anchor-page-number="3" text:anchor-type="page" draw:style-name="fr3">
<draw:image xlink:actuate="onLoad" xlink:show="embed" xlink:type="simple" xlink:href="Pictures/10000000000004000000030010393590.jpg"/>
</draw:frame>
Doch, der draw:image-Knoten ist ein Kind-knoten von draw:frame. Allerdings hat, wenn dieses XML der einzige Inhalt eines XML-Dokuments ist, das Dokument selbst nur einen Knoten, nämlich den root-Knoten oder das DocumentElement draw:frame.

XML-Datei drawFrame1.xml:

Code: Alles auswählen

<?xml version="1.0"?>
<draw:frame draw:name="Grafik2" draw:z-index="3" svg:height="1.143cm" svg:width="1.789cm" svg:y="3.625cm" svg:x="12.966cm" text:anchor-page-number="3" text:anchor-type="page" draw:style-name="fr3">
<draw:image xlink:actuate="onLoad" xlink:show="embed" xlink:type="simple" xlink:href="Pictures/10000000000004000000030010393590.jpg"/>
</draw:frame>

Code: Alles auswählen

Sub Main
 sURI = "file:///home/axel/drawFrame1.xml"
 oDocumentBuilder = createUnoService("com.sun.star.xml.dom.DocumentBuilder")
 oDOMDocument = oDocumentBuilder.parseURI(sURI)
 
 oChilds = oDOMDocument.getDocumentElement().getChildNodes()
 for i = 0 to oChilds.getLength()-1
  oChild = oChilds.item(i)
  xray  oChild
 next
End Sub
Das Document selbst hat nur einen Knoten. Dieser aber hat drei Kind-Knoten ;-). Nämlich den Text-Knoten [Linefeed], den Element-Knoten draw:image und dann noch einen Text-Knoten [Linefeed].

Musstest Du Dich nie mit HTML-DOM, CSS und JavaScript beschäftigen? Dann solltest Du nämlich da schon leidvolle Erfahrungen mit solchen Knotenproblemen gesammelt haben ;-).

viele Grüße

Axel
Antworten