Alex Schneider: Abarbeitung externer Dokumente möglich?

Hallo,

ich habe ein delikates Problem:
Ich würde gerne zwei oder auch mehr XML-Dokumente in eines überführen. Konkret möchte ich zwei gleichstrukturierte (gleiche DTD) XML-Dokumente zusammenführen UND sie per XSLT in eine andere XML-Form bringen (ein Dokument).

Ich probiere nun seit einiger Zeit mit der Funktion 'document()' herum, aber das hat wohl keinen Zweck.

Anscheinend eignet sich auch 'xsl.template' nicht für externe Daten, d.h. zum Abprüfen.

Ich denke, dass ich nicht der Einzige bin, der an diesem Problem arbeitet, mehrere Ressourcen zusammenzufassen.

Gruß
Alex

  1. Hallo Alex,

    ich habe ein delikates Problem:

    huch ;-)

    Ich würde gerne zwei oder auch mehr XML-Dokumente in eines überführen. Konkret möchte ich zwei gleichstrukturierte (gleiche DTD) XML-Dokumente zusammenführen UND sie per XSLT in eine andere XML-Form bringen (ein Dokument).

    Ich probiere nun seit einiger Zeit mit der Funktion 'document()' herum, aber das hat wohl keinen Zweck.

    Doch, das ist genau das Werkzeug, was Du benötigst. Also, was hast Du probiert, was klappt nicht, wie sieht/sehen dein(e) Quelldokument(e) aus, wie soll das Zieldokument aussehen?

    Gruß
    Franz

    1. Hallo Franz,

      ich habe ein Szenario erstellt, um Dir zu zeigen, was mein Problem ist:

      Dokument 1:
      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE GRUPPE SYSTEM "C:\gruppe.dtd">
      <GRUPPE>
       <PERSON>
        <VORNAME>Peter</VORNAME>
        <NAME>Müller</NAME>
       </PERSON>
       <PERSON>
        <VORNAME>Daniel</VORNAME>
        <NAME>Meyer</NAME>
       </PERSON>
       <PERSON>
        <VORNAME>Stefan</VORNAME>
        <NAME>Schneider</NAME>
       </PERSON>
      </GRUPPE>

      Dokument 2:
      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE GRUPPE SYSTEM "C:\gruppe.dtd">
      <GRUPPE>
       <PERSON>
        <VORNAME>Thomas</VORNAME>
        <NAME>Kaiser</NAME>
       </PERSON>
       <PERSON>
        <VORNAME>Andreas</VORNAME>
        <NAME>Schulz</NAME>
       </PERSON>
      </GRUPPE>

      DTD C:\gruppe.dtd:
      <?xml version="1.0" encoding="UTF-8"?>
      <!ELEMENT GRUPPE (PERSON+)>
      <!ELEMENT PERSON (VORNAME, NAME)>
      <!ELEMENT VORNAME (#PCDATA)>
      <!ELEMENT NAME (#PCDATA)>

      ----------------------------------------------
      Das Ausgangsdokument sollte so aussehen:
      (die Elementknoten heißen anders und Vorname und Name werden vertauscht)

      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE TEAM SYSTEM "C:\team.dtd">
      <TEAM>
       <MEMBER>
        <SURNAME>Müller</SURNAME>
        <FIRSTNAME>Peter</FIRSTNAME>
       </MEMBER>
       <MEMBER>
        <SURNAME>Meyer</SURNAME>
        <FIRSTNAME>Daniel</FIRSTNAME>
       </MEMBER>
       <MEMBER>
        <SURNAME>Schneider</SURNAME>
        <FIRSTNAME>Stefan</FIRSTNAME>
       </MEMBER>
       <MEMBER>
        <SURNAME>Kaiser</SURNAME>
        <FIRSTNAME>Thomas</FIRSTNAME>
       </MEMBER>
       <MEMBER>
        <SURNAME>Schulz</SURNAME>
        <FIRSTNAME>Andreas</FIRSTNAME>
       </MEMBER>
      </TEAM>

      DTD C:\team.dtd
      <?xml version="1.0" encoding="UTF-8"?>
      <!ELEMENT TEAM (MEMBER+)>
      <!ELEMENT MEMBER (SURNAME, FIRSTNAME)>
      <!ELEMENT SURNAME (#PCDATA)>
      <!ELEMENT FIRSTNAME (#PCDATA)>

      Das XSL - OHNE Zusammenführung - könnte so aussehen:
      <?xml version="1.0" ?>
      <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
       <xsl:template match="/">
        <TEAM>
         <MEMBER>
          <xsl:for-each select="GRUPPE/PERSON/NAME">
           <SURNAME>
            <xsl:value-of select="."/>
           </SURNAME>
          </xsl:for-each>
          <xsl:for-each select="GRUPPE/PERSON/VORNAME">
           <FIRSTNAME>
            <xsl:value-of select="."/>
           </FIRSTNAME>
          </xsl:for-each>
         </MEMBER>
        </TEAM>
       </xsl:template>
      </xsl:stylesheet>

      Es stellen sich nach einigen unerfolgreichen Tests folgende Fragen:
      a) ist das mit XSL und Co. überhaupt möglich?
       b) Wenn ja, womit?
       c) Wenn nein, womit dann?

      Gruß
      Alex

      1. Hallo Alex,

        DTD C:\gruppe.dtd:
        <?xml version="1.0" encoding="UTF-8"?>
        <!ELEMENT GRUPPE (PERSON+)>
        <!ELEMENT PERSON (VORNAME, NAME)>
        <!ELEMENT VORNAME (#PCDATA)>
        <!ELEMENT NAME (#PCDATA)>

        Die DTD habe ich unten weggelassen, da sie für das Problem unerheblich ist.


        Das Ausgangsdokument sollte so aussehen:
        (die Elementknoten heißen anders und Vorname und Name werden vertauscht)

        <?xml version="1.0" encoding="UTF-8"?>
        <!DOCTYPE TEAM SYSTEM "C:\team.dtd">
        <TEAM>
        <MEMBER>
          <SURNAME>Müller</SURNAME>
          <FIRSTNAME>Peter</FIRSTNAME>
        </MEMBER>
        <MEMBER>
          <SURNAME>Meyer</SURNAME>
          <FIRSTNAME>Daniel</FIRSTNAME>
        </MEMBER>
        <MEMBER>
          <SURNAME>Schneider</SURNAME>
          <FIRSTNAME>Stefan</FIRSTNAME>
        </MEMBER>
        <MEMBER>
          <SURNAME>Kaiser</SURNAME>
          <FIRSTNAME>Thomas</FIRSTNAME>
        </MEMBER>
        <MEMBER>
          <SURNAME>Schulz</SURNAME>
          <FIRSTNAME>Andreas</FIRSTNAME>
        </MEMBER>
        </TEAM>

        Das soll wohl nicht das _Ausgangs_dokument, sondern das _Ergebnis_dokument sein - zusammengeführt aus den Inhalten der oberen beiden Ausgangsdokumente.

        Das XSL - OHNE Zusammenführung - könnte so aussehen:
        <?xml version="1.0" ?>
        <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:template match="/">
          <TEAM>
           <MEMBER>
            <xsl:for-each select="GRUPPE/PERSON/NAME">
             <SURNAME>
              <xsl:value-of select="."/>
             </SURNAME>
            </xsl:for-each>
            <xsl:for-each select="GRUPPE/PERSON/VORNAME">
             <FIRSTNAME>
              <xsl:value-of select="."/>
             </FIRSTNAME>
            </xsl:for-each>
           </MEMBER>
          </TEAM>
        </xsl:template>
        </xsl:stylesheet>

        Das ist schonmal, auch ohne Zusmamenführung, nicht das adäquate Stylesheet.

        OHNE Einbeziehung der zweiten Datei wäre ein Stylesheet, das die gewünschte Transformation ausführt, das folgende:

        <?xml version="1.0" ?>
        <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output method="xml" encoding="iso-8859-1" indent="yes"/>

        <xsl:template match="/">
          <TEAM>
            xsl:apply-templates/
          </TEAM>
         </xsl:template>

        <xsl:template match="PERSON">
           <MEMBER>
               <SURNAME>
                 <xsl:value-of select="NAME"/>
               </SURNAME>
               <FIRSTNAME>
                 <xsl:value-of select="VORNAME"/>
               </FIRSTNAME>
           </MEMBER>
         </xsl:template>

        </xsl:stylesheet>

        So, wie nun das zweite Dokument reinbekommen? Über die document()-Funktion. Ein kleines Problem dabei ist, dass die Elemente im zweiten Dokument die gleichen Namen haben wie im ersten Dokument. D.h.

        <xsl:template match="PERSON">
           <MEMBER>
               <SURNAME>
                 <xsl:value-of select="NAME"/>
               </SURNAME>
               <FIRSTNAME>
                 <xsl:value-of select="VORNAME"/>
               </FIRSTNAME>
           </MEMBER>
           <xsl:apply-templates select="document('../xml/xsl43b.xml')/GRUPPE/PERSON"/>
         </xsl:template>

        führt zu einer Endlosschleife.

        Du musst den Aufruf also hochziehen in das Template für das Wurzelelement:

        <xsl:template match="/">
          <TEAM>
            xsl:apply-templates/
            <xsl:apply-templates select="document('../xml/xsl43b.xml')/GRUPPE/PERSON"/>
          </TEAM>
         </xsl:template>

        Das Stylesheet musst du natürlich mit dem ersten Dokument verknüpfen bzw. es auf das erste Dokument anwenden.

        Gruß
        Franz

        1. Hallo Franz,

          So, wie nun das zweite Dokument reinbekommen?
          Über die document()-Funktion.
          Ein kleines Problem dabei ist, dass die Elemente
          im zweiten Dokument die gleichen Namen haben wie
          im ersten Dokument.
          ...
          führt zu einer Endlosschleife.
          Du musst den Aufruf also hochziehen in das Template
          für das Wurzelelement:

          Hm ... ich habe ja nun gar keine Ahnung von XML & Co.
          Aber von der reinen Semantik her würde ich anstreben,
          die beiden Operationen "verschmelzen" und "übersetzen" in zwei verschiedenen Phasen durchzuführen, also nacheinander.

          Kannst Du mal ein paar allgemeine Bemerkungen dazu schreiben? (Ob das überhaupt geht mit XSL, ob es überhaupt bzw. in diesem Falle sinnvoll wäre usw.)

          Viele Grüße
                Michael

          (der sich fragt, ob diese <team> nicht auch irgend eine Identität/Teamname brauchen und ob ein <member> nur Mitglied in genau einem <team> sein kann - von wegen redundante Datenhaltung und so ...)

          1. Hallo Michael,

            Hm ... ich habe ja nun gar keine Ahnung von XML & Co.
            Aber von der reinen Semantik her würde ich anstreben,
            die beiden Operationen "verschmelzen" und "übersetzen" in zwei verschiedenen Phasen durchzuführen, also nacheinander.

            Ja, von der Semantik der Wörter mag das so erscheinen, aber es ist aus meiner Sicht nicht sinnvoll. XSLT stellt die document()-Funktion zur Verfügung, mit der man andere Dokumente, als die eigentliche Quelldatei der Transformation ansteuern kann. Die Template Rules des Stylesheets gelten dann auch für die Elemente dieses Dokuments. Das ansteuern der externen Datei ist dann nur eine Zeile. Üblicherweise nutzt man das z.B., um irgendwelche zusätlichen Informationen aus einer zweiten XML-Datei ins Ergebnisdokument mit reinzunehmen. Z.B. zu einer Literaturliste, die jeweiligen Rezensionen, die in einer Extra-XML-Datenquelle stehen.

            Alex wollte nun aber zwei von der Struktur her vollkommen gleiche Dokumente verschmelzen. Etwas eigenartiger Anwendungsfall, aber es geht eben auch. Allerdings hast du in diesem Falle recht: Ich habe mich auch gefragt, warum es überhaupt zwei Dateien gibt. Falls die eigentliche Datenquelle z.B. eine DB ist, dann sollte man tatsächlich erst versuchen EINE XML-Datei zu erzeugen. Naja, irgendeinen Grund wird es schon haben.

            Kannst Du mal ein paar allgemeine Bemerkungen dazu schreiben? (Ob das überhaupt geht mit XSL, ob es überhaupt bzw. in diesem Falle sinnvoll wäre usw.)

            Zweistufige Transformationen sind durchaus möglich und auch üblich mit XSLT, v.a. wenns um Gruppierung geht, was ja mit XSLT/XPath 1.0 nicht so ohne weiteres funktioniert. Dann kann man z.B. im ersten Schritt sortieren und dann in einem zweiten Schritt Elemente nach den sortierten Kriterien gruppieren.

            Cocoon unterstützt solche verketteten Transformationen. Bei der ersten Transformation muss man dann eine entsprechende Processing-Instruction generieren, die dann eine weitere Transformation auslöst. Mit anderen Parsern muss man die Ausgabe als DOM gleich wieder als Input für eine neue Transformation nehmen. Dazu ist ja XSLT eben auch selbst XML, erwartet XML und spuckt XML aus. Das kannst Du also vom Prinzip her endlos "pipen".

            Viele Grüße
                  Michael

            (der sich fragt, ob diese <team> nicht auch irgend eine Identität/Teamname brauchen und ob ein <member> nur Mitglied in genau einem <team> sein kann - von wegen redundante Datenhaltung und so ...)

            Hm, es gibt doch nur EIN Team, am Ende. Oder meinst Du, das eine <person> nur genau einer <gruppe> angehören kann, also es keine doppelten in den zwei Eingangsdokumenten geben kann? Das könnte man ja dann bei der Transformation abfragen, wobei man dann den Personen tatsächlich eine id verpassen sollte. Naja, aber wer weiss, wozu Alex das nun alles braucht. Kann er ja vielleicht mal kundtun, falls er nicht bei der CIA arbeitet ;-)

            Gruß
            Franz