Selecting nodes with conditions

I am trying to convert XML files, but I am blocked. The idea is to concatenate all elements from <ore:Aggregation>

node to the next. This is some kind of detail. But I can't get more than 1 edm:WebResource

per created dc:item

.

XML:

<rdf:RDF>
    <ore:Aggregation rdf:about="id1">
        <some:crap/>
    </ore:Aggregation>
    <edm:ProvidedCHO rdf:about="id1">
        <some:crap/>
    </edm:ProvidedCHO>
    <edm:WebResource rdf:about="some/random/url"> 
        <some:crap/>
    </edm:WebResource>
            ...
               (n 'edm:WebResource' nodes)
            ...
    <edm:WebResource rdf:about="some/random/url">
        <some:crap/>    
    </edm:WebResource>

    <ore:Aggregation rdf:about="id2">
        <some:crap/>
    </ore:Aggregation>
    <edm:ProvidedCHO rdf:about="id2">
        <some:crap/>
    </edm:ProvidedCHO>
    <edm:WebResource rdf:about="some/random/url"> 
        <some:crap/>
    </edm:WebResource>
            ...
               (n 'edm:WebResource' nodes)
            ...
    <edm:WebResource rdf:about="some/random/url">
        <some:crap/>    
    </edm:WebResource>

        ... and on and on ...
</rdf:RDF>

      

XSL

<xsl:template match="/">
    <xsl:apply-templates select="/rdf:RDF/ore:Aggregation"/>
</xsl:template>

<xsl:template match="/rdf:RDF/ore:Aggregation">
    <rdf:RDF>
    <xsl:for-each select=".">
            <dc:item>
                <xsl:attribute name="rdf:about">
                    <xsl:value-of select="concat($fileName, '_item', position())"/>
                </xsl:attribute>

                <xsl:copy-of select="."/>
                <xsl:copy-of select="following-sibling::edm:ProvidedCHO[1]"/>
                <xsl:copy-of select="following-sibling::edm:WebResource[1]"/>

                <!-- WHERE IT SUCKS -->
                <xsl:if test="local-name(following-sibling::*[3]) = 'edm:WebResource'">
                    <xsl:copy-of select="following-sibling::*[3]"/>
                </xsl:if>                    
                <!-- ./WHERE IT SUCKS -->


            </dc:item>
    </xsl:for-each>
    </rdf:RDF>
</xsl:template>

      

Another attempt that brings too many nodes:

<!-- WHERE IT SUCKS -->
<xsl:copy-of select="following-sibling::*[local-name (preceding::*[1]) = 'ore:Aggregation']"/>
<!-- ./WHERE IT SUCKS -->

      

Expected Result

<!-- ITEM N1 -->
<rdf:RDF>
    <dc:item rdf:about="some.concat.string"/>
    <ore:Aggregation rdf:about="id1">
        <some:crap/>
    </ore:Aggregation>
    <edm:ProvidedCHO rdf:about="id1">
        <some:crap/>
    </edm:ProvidedCHO>
    <edm:WebResource rdf:about="some/random/url"> 
        <some:crap/>
    </edm:WebResource>
</rdf:RDF>

<!-- ITEM N2 -->
<rdf:RDF>
     <dc:item rdf:about="some.concat.string"/>
     <ore:Aggregation rdf:about="id1">
     <etc/>

      

+3


source to share


2 answers


In XSLT 2.0, this looks like a job for xsl:for-each-group

(see http://www.xml.com/pub/a/2003/11/05/tr.html ). Specifically, using it withgroup-starting-with

 <xsl:for-each-group select="*" group-starting-with="ore:Aggregation">

      

This will be done when placed on the parent element rdf:RDF

and will arrange all the children into groups, ore:Aggregration

starting at the start of each group. Then the code inside xsl:for-each-group

is called once for each element ore:Aggregation

, and then you can use a function current-group()

to access all the elements in the group.

Try this XSLT to run



<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" xmlns:rdf="rdf" xmlns:ore="ore" xmlns:dc="dc">
    <xsl:output method="xml" encoding="UTF-8" indent="yes" />

    <xsl:template match="rdf:RDF">
        <xsl:for-each-group select="*" group-starting-with="ore:Aggregation">
            <rdf:RDF xmlns:edm="edm" xmlns:ore="ore" xmlns:some="some">
                <dc:item rdf:about="{concat('item', position())}" />
                <xsl:apply-templates select="current-group()" />
            </rdf:RDF>
        </xsl:for-each-group>
    </xsl:template>

    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet> 

      

Note that the generated output XML is not valid because it is missing one root element. It would be much better if someone was added, not just to make it well-formed, but then the namespace declarations would be placed in the same place as well:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" xmlns:rdf="rdf" xmlns:ore="ore" xmlns:dc="dc">
    <xsl:output method="xml" encoding="UTF-8" indent="yes" />

    <xsl:template match="rdf:RDF">
        <rdf:root xmlns:edm="edm" xmlns:ore="ore" xmlns:some="some">
            <xsl:for-each-group select="*" group-starting-with="ore:Aggregation">
                <rdf:RDF>
                    <dc:item rdf:about="{concat('item', position())}" />
                    <xsl:apply-templates select="current-group()" />
                </rdf:RDF>
            </xsl:for-each-group>
        </rdf:root>
    </xsl:template>

    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet> 

      

Also note the use of Attribute Value Templates when creating rdf:about

, which further reduces the amount of code required.

+2


source


I have encountered this case several times before and the best solution I could come up with involves the manipulations given.

In a sense, this makes sense, since you want all nodes after each ore: Aggregation before the next ore: Aggregation.

In other words, you need all of the following nodes except for the next ore: aggregation and everything that follows.

Fortunately, in XSLT 2.0 we set up manipulation operators, so we don't have to jump over the tangled hoops that we would have to do in XSLT 1.0.

Try this XPATH

following-sibling::node() except 
  (following-sibling::ore:Aggregation | 
   following-sibling::ore:Aggregation/following-sibling::node())

      



This should give you the node-set you are expecting.

This pattern usually works every time you try to sort a flat list of items into some sort of result structure. For example, the problem was that I found all the elements that had a certain attribute and then grouped them into one.

So the general solution (in pseudocode)

It would be a little easier if we could specify both the terminal tag and the following siblings at the same time, but it really isn't that bad.

following-sibling::node() except 
  (following-sibling::{next-node-selection-criteria} | 
   following-sibling::{next-node-selection-criteria}/following-sibling::node())

      

+1


source







All Articles