foreign gruppieren (Arbeitstitel)

Beispiel IST Beispiel IST Beispiel SOLL Beispiel SOLL

Kürzlich habe ich einen Schwatz mit Wolfgang aus dem Lüne-Projekt über ein paar Herausforderungen in der Aufbereitung der Editionsdaten gehalten. Das Projekt arbeitet in einem XML First-Workflow, in dem die Kolleginnen und Kollegen aus Oxford bzw. Düsseldorf die Briefe aus den Briefbüchern der Benediktinerinnen des Klosters Lüne in Word-Dateien edieren und fertig bearbeitete Texte in Wolfenbüttel in das Zielformat TEI überführt werden.

Im Projekt werden fremdsprachige Stellen markiert, die sich auch über Seiten- und Zeilenumbrüche erstrecken können. In den für die Verarbeitung vorliegenden Ausgangsdaten sind Seiten- und Zeilenumbrüche jedoch stets außerhalb einer fremdsprachigen Stelle positioniert, auch wenn sie innerhalb der Textstelle auftreten.

Da die Ausgangsdaten bis auf weiteres nicht geändert werden können, haben wir nach einer Lösung gesucht, mit der wir aufeinanderfolgende fremdsprachige Stellen zwischen denen nur Seiten- oder Zeilenumbrüche stehen zusammenfassen können.

Grundsätzlich kommt für die Lösung der Aufgabe die Anweisung for-each-group in Frage. Sie erlaubt es, eine Knotenmenge so zu verarbeiten, dass die Knoten in Gruppen zusammengefasst werden. Ausschlaggebend für die Zugehörigkeit zu einer Gruppe ist ein für jeden Knoten berechneter Gruppierungsschlüssel. Die Formel, nach der der Schlüssel berechnet wird, kann über das groupy-by-Attribute gewählt werden.

[Definition: The xsl:for-each-group instruction allocates the items in an input sequence into groups of items (that is, it establishes a collection of sequences) based either on common values of a grouping key, or on a pattern that the initial or final node in a group must match.] The sequence constructor that forms the content of the xsl:for-each-group instruction is evaluated once for each of these groups.

...

If the group-by attribute is present, the items in the population are examined, in population order. For each item J, the expression in the group-by attribute is evaluated to produce a sequence of zero or more grouping key values. For each one of these grouping keys, if there is already a group created to hold items having that grouping key value, J is added to that group; otherwise a new group is created for items with that grouping key value, and J becomes its first member.

XSL Transformations (XSLT) Version 2.0, Section 14.3

Wir beginnen mit einer Transformationsregel, die auf alle Elemente angewendet wird, die ein oder mehrere foreign-Elemente enthalten. Wir erstellen eine Kopie des Elements und seiner Attribute im Zieldokument und verarbeiten alle Kindknoten mit for-each-group.

Auch wenn wir noch nicht wissen, wie genau die Gruppierungsfunktionen arbeiten wird, können wir uns bereits dem Inneren der Schleife zuwenden. Die noch zu schreibende Gruppierungsfunktion wird uns Gruppen zusammengehörender Knoten liefern. Für jede Gruppe können wir zwei Fälle unterscheiden:

<xsl:template match="*[foreign]">   <xsl:copy>     <xsl:sequence select="@*"/>     <xsl:for-each-group select="node()" group-by="fun:foreign-block-grouping-key(.)">       <xsl:choose>         <xsl:when test="count(current-group()) eq 1">           <xsl:sequence select="current-group()"/>         </xsl:when>         <xsl:otherwise>           <xsl:copy>             <xsl:sequence select="@*"/>             <xsl:for-each select="current-group()">               <xsl:sequence select="if (self::foreign) then ./node() else ."/>             </xsl:for-each>           </xsl:copy>         </xsl:otherwise>       </xsl:choose>     </xsl:for-each-group>   </xsl:copy> </xsl:template>

Und das ist ersteinmal alles!

Verbleibt die Frage nach der Gruppierungsfunktion. Die Funktion muss für Knoten, die Teil einer gruppierungsfähigen Spanne sind den selben Gruppierungsschlüssel liefern und für Knoten, die nicht Teil einer solchen Spanne sind einen jeweils verschiedenen. Für diese Aufgabe können wir die Funktion generate-id() wie folgt verwenden. Wenn der an die Gruppierungsfunktion übergebene Knoten Teil einer gruppierungsfähigen Spanne ist, dann wenden wir die Funktion auf den das foreign-Element an, dass die Spanne beginnt. In allen anderen Fällen wenden wir sie auf den übergebenen Knoten an.

Um festzustellen, ob ein Knoten Teil einer gruppierungsfähigen Spanne ist, gehen wir wie folgt vor.

<xsl:function name="fun:foreign-block-grouping-key" as="xs:string">   <xsl:param name="node" as="node()"/>   <xsl:variable name="block-start" select="$node/preceding-sibling::foreign[fun:valid-block(fun:siblings-between(., $node))][last()]"/>   <xsl:variable name="block-end" select="($node/following-sibling::foreign[fun:valid-block(fun:siblings-between($node, .))][last()], if ($node/self::foreign) then $node else ())[1]"/>   <xsl:value-of select="if ($block-start and $block-end) then generate-id($block-start) else generate-id($node)"/> </xsl:function>

Beide Abfragen Verwenden die Hilfsfunktionen fun:siblings-between und fun:valid-block. Erstere liefert alle auf der selben Hierarchieebene liegende Knoten liefert, die sich zwischen zwei als Argument übergebenen Knoten befinden. Letztere stellt fest, ob es sich bei einer als Argument übergebenen Menge an Knoten um Knoten einer gruppierungsfähigen Spanne handelt. Dies ist immer dann der Fall, wenn die Menge aller Knoten, die weder ein lb-, pb-, oder foreign-Element, noch ein nicht-leerer Textknoten sind, leer ist.

<xsl:function name="fun:valid-block" as="xs:boolean">   <xsl:param name="nodes" as="node()*"/>   <xsl:value-of select="empty($nodes[not(self::lb or self::pb or self::foreign or (self::text() and normalize-space() eq ''))])"/> </xsl:function> <xsl:function name="fun:siblings-between" as="node()*">   <xsl:param name="start" as="node()"/>   <xsl:param name="end" as="node()"/>   <xsl:sequence select="$start/following-sibling::node() intersect $end/preceding-sibling::node()"/> </xsl:function>

Ein Knoten ist genau dann Teil einer gruppierungsfähigen Spanne, wenn sowohl ein Anfang als auch ein Ende einer solchen Spanne ermittelt werden kann.

Anhang