Xpath2.0 selecting first and last occurance of str

2019-09-17 06:43发布

问题:

I have this tree:

<Events>
 <Properties>
   <Property Descriptor=100>1378314022</Property>
   <Property Descriptor=200>ABC1234</Property>
 </Properties>
 <Properties>
   <Property Descriptor=100>1378314023</Property>
   <Property Descriptor=200>ABC1234</Property>
 </Properties>
 <Properties>
   <Property Descriptor=100>1378314024</Property>
   <Property Descriptor=200>ABC1234</Property>
 </Properties>
 <Properties>
   <Property Descriptor=100>1378314022</Property>
   <Property Descriptor=200>123456</Property>
 </Properties>
 <Properties>
   <Property Descriptor=100>1378314023</Property>
   <Property Descriptor=200>123456</Property>
 </Properties>
 <Properties>
   <Property Descriptor=100>1378314024</Property>
   <Property Descriptor=200>123456</Property>
 </Properties>

</Events>

I'm iterating at this level: Events/Properties

How can I have only the FIRST and LAST occurrence of each Property Descriptor = 200 and its respective Property Descriptor = 100?

I've tried so far:

  • Iteration: Events/Properties
  • Select: Property[@Descriptor=200])[last()] or Property[@Descriptor=200])[first()]

but with no success.

OUTPUT should look like this [I'm showing it in HTML, iterating in the ROW level]:

P100        | P200
1378314022  | ABC1234
1378314024  | ABC1234
1378314022  | 123456
1378314024  | 123456

回答1:

In XSLT 2.0 this would be easy with for-each-group, grouping by the 200 value and taking the first and last members of each group. But in pure XPath (not XSLT) you need to think laterally.

If the groups are always contiguous as you've shown here (i.e. all the ABC1234 entries are adjacent to one another, and all the 123456 entries are adjacent to one another) then this boils down to wanting every Properties element P that does not have an immediately preceding and an immediately following sibling Properties element with the same 200 value as P. I.e. you want to iterate over

Events/Properties[not(
  (
    Property[@Descriptor="200"] =
    preceding-sibling::Properties[1]/Property[@Descriptor="200"]
  ) and (
    Property[@Descriptor="200"] =
    following-sibling::Properties[1]/Property[@Descriptor="200"]
  )
)]

and then select the Property[@Descriptor="100"] and Property[@Descriptor="200"] from each of the resulting Properties elements.

You've tagged your question "xpath-2.0" but this expression is also valid in XPath 1.0.



回答2:

I am not completely sure which answer you are looking for, but here I will give you two examples. One of them should probably fit your needs.

I used the following input XML:

<?xml version="1.0" encoding="UTF-8"?>
<Events>
    <Properties>
        <Property Descriptor="100">1378314022</Property>
        <Property Descriptor="200">ABC1234</Property>
        <Property Descriptor="100">MD2356</Property>
        <Property Descriptor="200">25689</Property>
        <Property Descriptor="100">MD75632</Property>
        <Property Descriptor="200">5632</Property>
    </Properties>
    <Properties>
        <Property Descriptor="100">1378314023</Property>
        <Property Descriptor="200">ABC1234</Property>
        <Property Descriptor="100">MD2356</Property>
        <Property Descriptor="200">25689</Property>
        <Property Descriptor="100">MD75632</Property>
        <Property Descriptor="200">5632</Property>
    </Properties>
    <Properties>
        <Property Descriptor="100">1378314024</Property>
        <Property Descriptor="200">ABC1234</Property>
        <Property Descriptor="100">MD2356</Property>
        <Property Descriptor="200">25689</Property>
        <Property Descriptor="100">MD75632</Property>
        <Property Descriptor="200">5632</Property>
    </Properties>
    <Properties>
        <Property Descriptor="100">1378314022</Property>
        <Property Descriptor="200">123456</Property>
        <Property Descriptor="100">MD2356</Property>
        <Property Descriptor="200">25689</Property>
        <Property Descriptor="100">MD75632</Property>
        <Property Descriptor="200">5632</Property>
    </Properties>
    <Properties>
        <Property Descriptor="100">1378314023</Property>
        <Property Descriptor="200">123456</Property>
        <Property Descriptor="100">MD2356</Property>
        <Property Descriptor="200">25689</Property>
        <Property Descriptor="100">MD75632</Property>
        <Property Descriptor="200">5632</Property>
    </Properties>
    <Properties>
        <Property Descriptor="100">1378314024</Property>
        <Property Descriptor="200">123456</Property>
        <Property Descriptor="100">MD2356</Property>
        <Property Descriptor="200">25689</Property>
        <Property Descriptor="100">MD75632</Property>
        <Property Descriptor="200">5632</Property>
    </Properties>
</Events>

When I use the next XSLT:

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

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

    <xsl:template match="Properties">
        <xsl:copy>
            <xsl:apply-templates select="Property[@Descriptor = '100'][1]" />
            <xsl:apply-templates select="Property[@Descriptor = '100'][last()]" />
            <xsl:apply-templates select="Property[@Descriptor = '200'][1]" />
            <xsl:apply-templates select="Property[@Descriptor = '200'][last()]" />
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

This XSLT also has a iteration over the Properties element, within that Properties element it gets the first and the last occurence of each provided Descriptor. The result would be:

<?xml version="1.0" encoding="UTF-8"?>
<Events>
    <Properties>
        <Property Descriptor="100">1378314022</Property>
        <Property Descriptor="100">MD75632</Property>
        <Property Descriptor="200">ABC1234</Property>
        <Property Descriptor="200">5632</Property>
    </Properties>
    <Properties>
        <Property Descriptor="100">1378314023</Property>
        <Property Descriptor="100">MD75632</Property>
        <Property Descriptor="200">ABC1234</Property>
        <Property Descriptor="200">5632</Property>
    </Properties>
    <Properties>
        <Property Descriptor="100">1378314024</Property>
        <Property Descriptor="100">MD75632</Property>
        <Property Descriptor="200">ABC1234</Property>
        <Property Descriptor="200">5632</Property>
    </Properties>
    <Properties>
        <Property Descriptor="100">1378314022</Property>
        <Property Descriptor="100">MD75632</Property>
        <Property Descriptor="200">123456</Property>
        <Property Descriptor="200">5632</Property>
    </Properties>
    <Properties>
        <Property Descriptor="100">1378314023</Property>
        <Property Descriptor="100">MD75632</Property>
        <Property Descriptor="200">123456</Property>
        <Property Descriptor="200">5632</Property>
    </Properties>
    <Properties>
        <Property Descriptor="100">1378314024</Property>
        <Property Descriptor="100">MD75632</Property>
        <Property Descriptor="200">123456</Property>
        <Property Descriptor="200">5632</Property>
    </Properties>
</Events>

If you want to get the last en first occurence over all the Properties the XSLT should be slightly different:

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

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

    <xsl:template match="Events">
        <xsl:copy>
            <Properties>
                <xsl:apply-templates select="(Properties/Property[@Descriptor = '100'])[1]" />
                <xsl:apply-templates select="(Properties/Property[@Descriptor = '100'])[last()]" />
                <xsl:apply-templates select="(Properties/Property[@Descriptor = '200'])[1]" />
                <xsl:apply-templates select="(Properties/Property[@Descriptor = '200'])[last()]" />
            </Properties>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

The result would then be:

<?xml version="1.0" encoding="UTF-8"?>
<Events>
    <Properties>
        <Property Descriptor="100">1378314022</Property>
        <Property Descriptor="100">MD75632</Property>
        <Property Descriptor="200">ABC1234</Property>
        <Property Descriptor="200">5632</Property>
    </Properties>
</Events>