0

I want to select the next sibling of an attribute for outputting the period. This template is selecting the required attributes.

The only solution that works is the one where i match the name of the attribute and selecting the one i need. How can i make it more general, using few line? I've tried with 'following-sibling' but that works just for elements.

The piece of the functional code;

             ...
                <Period >
                    <xsl:attribute name="Unit">
                        <xsl:choose>
                            <xsl:when test="local-name()='BonusAmount'">
                                <xsl:value-of select="../@BonusFrequency"/>
                            </xsl:when>
                            <xsl:when test="local-name()='CommissionAmount'">
                                <xsl:value-of select="../@CommissionFrequency"/>
                            </xsl:when>
                            <xsl:when test="local-name()='GrossRegularOvertimeAmount'">
                                <xsl:value-of select="../@GrossRegularOvertimeFrequency"/>
                            </xsl:when>
                            <xsl:when test="local-name()='GrossSalaryAmount'">
                                <xsl:value-of select="../@GrossSalaryFrequency"/>
                            </xsl:when>
                            <xsl:when test="local-name()='CarAllowanceAmount'">
                                <xsl:value-of select="../@CarAllowanceFrequency"/>
                            </xsl:when>
                            <xsl:when test="local-name()='WorkAllowanceAmount'">
                                <xsl:value-of select="../@WorkAllowanceFrequency"/>
                            </xsl:when>
                            <xsl:when test="local-name()='WorkersCompensationAmount'">
                                <xsl:value-of select="../@WorkersCompensationFrequency"/>
                            </xsl:when>

                        </xsl:choose>
                    </xsl:attribute>
                </Period>
           ...

XML sample:

 <Employment>
      <PAYG Basis="Temporary" Industry="Oil and Gas Extraction" IndustryCode="0700" Occupation="General Waiter" OccupationCode="6323-11" OnProbation="Yes" ProbationDateEnds="2019-03-27" StartDate="2014-05-05" Status="Secondary" UniqueID="c8492d8c-34fc-419b-93f4-f1f3" x_Employer="c46c9077-31ef-4daa-b8cc-c9e3">
        <Income BonusAmount="89898985" BonusFrequency="Monthly" CommissionAmount="4488" CommissionFrequency="Yearly" GrossRegularOvertimeAmount="365" GrossRegularOvertimeFrequency="Fortnightly" GrossSalaryAmount="4798" GrossSalaryFrequency="Weekly"  WorkAllowanceAmount="10101010" WorkAllowanceFrequency="Monthly"/>
      </PAYG>
    </Employment>

The output:

<ValueItem Value="89898985">
        <Identifier UniqueID="c8492d8c-34fc-419b-93f4-f1f3-Income-PAYG-BonusAmount"/>
        <PercentOwned Percent="100">
           <RelatedEntityRef RelatedID="baaef85e-3793-4fe8-8c62-8cc766fa490b"/>
        </PercentOwned>
        <Income Type="Bonus">
           <Period Unit="Monthly"/>
           <RelatedEntityRef RelatedID="c46c9077-31ef-4daa-b8cc-c9e3"/>
        </Income>
     </ValueItem>
     <ValueItem Value="4488">
        <Identifier UniqueID="c8492d8c-34fc-419b-93f4-f1f3-Income-PAYG-CommissionAmount"/>
        <PercentOwned Percent="100">
           <RelatedEntityRef RelatedID="baaef85e-3793-4fe8-8c62-8cc766fa490b"/>
        </PercentOwned>
        <Income Type="Commission">
           <Period Unit="Yearly"/>
           <RelatedEntityRef RelatedID="c46c9077-31ef-4daa-b8cc-c9e3"/>
        </Income>
     </ValueItem>
     <ValueItem Value="365">
        <Identifier UniqueID="c8492d8c-34fc-419b-93f4-f1f3-Income-PAYG-GrossRegularOvertimeAmount"/>
        <PercentOwned Percent="100">
           <RelatedEntityRef RelatedID="baaef85e-3793-4fe8-8c62-8cc766fa490b"/>
        </PercentOwned>
        <Income Type="GrossRegularOvertime">
           <Period Unit="Fortnightly"/>
           <RelatedEntityRef RelatedID="c46c9077-31ef-4daa-b8cc-c9e3"/>
        </Income>
     </ValueItem>
     <ValueItem Value="4798">
        <Identifier UniqueID="c8492d8c-34fc-419b-93f4-f1f3-Income-PAYG-GrossSalaryAmount"/>
        <PercentOwned Percent="100">
           <RelatedEntityRef RelatedID="baaef85e-3793-4fe8-8c62-8cc766fa490b"/>
        </PercentOwned>
        <Income Type="GrossSalary">
           <Period Unit="Weekly"/>
           <RelatedEntityRef RelatedID="c46c9077-31ef-4daa-b8cc-c9e3"/>
        </Income>
     </ValueItem>

etc.

DanielCSD
  • 73
  • 1
  • 8
  • 1
    It isn't clear without sample XML to see where the starting attribute and the next sibling that you were talking about are actually located... – har07 Mar 30 '16 at 12:55
  • 2
    Can you show a sample of your XML? Do note that the concept of "siblings" does not apply to attributes as the XML DOM does not care about attribute order. However, in your particular case, it looks like you are looking for an attribute ending in "Frequency" in place of one ending in "Amount". If so, it should be possibly to make your XSLT more generic but some string-handling functions. – Tim C Mar 30 '16 at 12:56
  • @ har07 After an Amount need to be a Frequency. – DanielCSD Mar 30 '16 at 13:18

2 Answers2

0

For XSLT 2.0 and up, you can use the fn:replace() XPath function, like this:

<Period Unit="{../@*[name()= replace(name(current()),'Amount','Frequency')]}"/>

XSLT 1.0 does not have this function. One of the ways to work around that looks like follows (this maps arbitrary input strings to arbitrary output strings, which is a bit more flexible than what replace() can do):

<xsl:transform 
    version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:my="http://tempuri.org/"
    exclude-result-prefixes="my"
>
    <xsl:output method="xml" encoding="UTF-8" indent="yes" />

    <my:config>
        <map value="BonusAmount">BonusFrequency</map>
        <map value="CommissionAmount">CommissionFrequency</map>
        <!-- ... -->
    </my:config>
    <xsl:variable name="config" select="document('')/*/my:config" />

    <xsl:template match="@*" mode="create-period">
        <xsl:variable name="mapping" select="$config/map[@value = name(current())]" />
        <xsl:if test="$mapping">
            <Period Unit="{../@*[name() = $mapping]}" />
        </xsl:if>
    </xsl:template>

</xsl:transform>

Notes:

  • document('') can be used to access the current XSLT document
  • custom namespaces allow embedding data into stylesheets
  • exclude-result-prefixes prevents namespaces from leaking into the output document
  • Attribute value templates (curly braces) can be used instead of <xsl:attribute> (as long as the attribute name is fixed, like in your case).
  • Note the template mode, to distinguish it from other templates that might match @*. Needs to be set while applying the template.

Another possibility would be including a string-replace template or using the EXSLT replace extension function - many XSLT 1.0 processors have support for EXSLT.

Community
  • 1
  • 1
Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • This will not add to the attribute Unit the strings and not the values from the BonusFrequency, CommissionFrequenc etc ? – DanielCSD Mar 30 '16 at 13:55
  • Oh, of course. Your sample XML made it a bit clearer, check updated answer. – Tomalak Mar 30 '16 at 14:08
  • I wrote "in the spirit of this" above my code, so of course you must make the appropriate changes. – Tomalak Mar 30 '16 at 15:04
  • i've meant the output. Exemple: Attribute Unit from the Period i don't think will display the Frequency because the function name() will be in my case located in the Amount attributes[I'm applying template to the Amount attributes]. But thank for the help i've learned new things that i will use in the future. – DanielCSD Mar 30 '16 at 15:16
  • D'oh, it has to be `name(current())`, not `name()`. Try again now. – Tomalak Mar 30 '16 at 15:28
  • That fixed the problem. On my one would have been difficult to spot or resolve this error. Did not thought to use 'current()'. Thanks again. – DanielCSD Mar 30 '16 at 15:36
  • What XSLT processor are you using? (exact name / version) – Tomalak Mar 30 '16 at 15:38
  • I'm using Oxygen with Saxon-PE 9.6.0.5 – DanielCSD Mar 30 '16 at 15:42
  • 1
    That XSLT processor implements XSLT 3.0, you don't need to bother with the XSLT 1.0-specific approach my answer suggests. Just use `fn:replace(name(current()), 'Amount', 'Frequency')`. See https://www.w3.org/TR/xpath-functions/#func-replace. – Tomalak Mar 30 '16 at 15:49
  • 1
    Thank you Tomalak, this approach is even better and easier. Just what I wanted. For doing that i've used variable and substring() but you approach is much better. The final code is: – DanielCSD Mar 31 '16 at 06:51
  • 1
    Exactly. Take a few moments and read through the list of XPath functions available to you. If you did not know about replace() then chances are that you are missing out on quite a few others, too. – Tomalak Mar 31 '16 at 07:28
  • @DanielCSD Answer updated (BTW, the hint about XSLT 2.0 and `replace` was in there quite a while, you could have spotted it without me probing it a second time in the comments.) – Tomalak Mar 31 '16 at 07:37
0

I'm not sure if I understood this completely.
Buf if you need to look for each attribute which ends with Amoun an attribute which ends with Frequency You may try a hack like this:

    <xsl:template match="PAYG">
        <test>
            <xsl:apply-templates select="Income/@*" />
        </test>
    </xsl:template>

    <xsl:template match="Income/@*">
        <xsl:variable name="a" select="local-name()" />
        <xsl:if test="'Amount' =  substring( $a, string-length($a) - string-length('Amount') + 1 )">
            <xsl:variable name="f"  select="concat( substring-before( $a, 'Amount' ), 'Frequency' )"/>
            <Period >
                <xsl:attribute name="Unit">
                    <xsl:value-of select="../@*[local-name() = $f]"/>
                </xsl:attribute>
                    <xsl:value-of select="local-name()"/>
            </Period>
        </xsl:if>
    </xsl:template>

Which will output:

<test>
 <Period Unit="Monthly">BonusAmount</Period>
 <Period Unit="Yearly">CommissionAmount</Period>
 <Period Unit="Fortnightly">GrossRegularOvertimeAmount</Period>
 <Period Unit="Weekly">GrossSalaryAmount</Period>
 <Period Unit="Monthly">WorkAllowanceAmount</Period>
</test>
hr_117
  • 9,589
  • 1
  • 18
  • 23
  • Thank you hr_117. After some tests i've implemented something similar but Tomalak solution is even better. – DanielCSD Mar 31 '16 at 07:29