XSLT 1.0 for-each 'starts-with' (with variable length)

心不动则不痛 提交于 2021-01-29 09:35:09

问题


thinking aloud, re approach(es 🤔)

  • Input collection (xml) records being transformed eg for-each Order.

  • With an xml 'exclude' (reference-collection) param, to filter out records, eg only transform (for-each Order) where (start of) current order/reference is not equal to any Reference in the References collection.

For simplicity, Order input example

<Orders>
 <Order number="1"> <!-- Include -->
  <Reference>AB123</Reference>
 </Order>
 <Order number="2"> <!-- Exclude -->
  <Reference>C3PO</Reference>
 </Order>
</Orders>

and the exclude references (of variable length)

<References>
 <Reference>ABC</Reference>
 <Reference>BC</Reference>
 <Reference>AC</Reference>
 <Reference>Z7</Reference>
</References>
  • I think there's probably several ways to do this, but maybe one / two that are recommended, for performance...
  • thinking about variables (don't think a key will help), not sure if a for-each in a for-each is an option, or how that would work
  • the rub (I think) is a starts-with() approach, where the numberofcharacters to compare varies... so maybe some c# may be best (from a performance perspective)

assume 1000 orders, 200 references... if that impacts decisions on approach, in XSLT 1.0

Any recommended/preferred|optimised approach?

many thanks in advance 😎


回答1:


The key point to understand here, IMHO, is that the starts-with() function takes two strings as it arguments - not node-sets. This means that while it's very easy to select all orders that do not start with a specific string, using:

Order[not(starts-with(Reference, $string)]

it is not so easy* to reverse the point-of-view and ask "does this Order start with any one of the given strings?".

This then determines the strategy to take here, which I have already outlined in the comment to your question: loop over the reference strings, removing the orders that start with the current reference string at each iteration. Then output the remaining orders.

The other important point to remember here is that xsl:for-each is not a loop. It does not allow for an exit condition and - much more importantly for our case - it does not allow passing the result of one iteration to the next. Since we want to "dwindle" the node-set of orders at each iteration, we must a use a truly recursive tool:

XSLT 1.0

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

<xsl:param name="path-to-references">References.xml</xsl:param>

<xsl:template match="/Orders">
    <output>
        <xsl:call-template name="exclude-orders">
            <xsl:with-param name="orders" select="Order"/>
            <xsl:with-param name="references" select="document($path-to-references)/References/Reference"/>
        </xsl:call-template>
    </output>
</xsl:template>

<xsl:template name="exclude-orders">
    <xsl:param name="orders"/>
    <xsl:param name="references"/>
    <xsl:choose>
        <xsl:when test="$references">
            <!-- recursive call -->
            <xsl:call-template name="exclude-orders">
                <xsl:with-param name="orders" select="$orders[not(starts-with(Reference, $references[1]))]" />
                <xsl:with-param name="references" select="$references[position() > 1]" />
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:copy-of select="$orders"/>
        </xsl:otherwise>
  </xsl:choose>
</xsl:template>

</xsl:stylesheet>

Added:

(*) On second thought, you could do:

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

<xsl:param name="path-to-references">References.xml</xsl:param>

<xsl:template match="/Orders">
    <output>
        <xsl:for-each select="Order">
            <xsl:if test="not(document($path-to-references)/References/Reference[starts-with(current()/Reference, .)])">
                <xsl:copy-of select="."/>
            </xsl:if>
        </xsl:for-each>
    </output>
</xsl:template>

</xsl:stylesheet>

It's a rather awkward code, but it is short and it works.



来源:https://stackoverflow.com/questions/65349120/xslt-1-0-for-each-starts-with-with-variable-length

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!