XSLT: use multiple or'd template matches to apply-templates

32,503

Solution 1

Try changing your template to this:

<xsl:template match="Creation|Test|Assignment|Modification|Repair|Termination">
    <xsl:copy>
        <xsl:apply-templates select="@*[not(name()='User')]|node()"/>
    </xsl:copy>
</xsl:template>

You'll notice it looks a lot like your identity template with a predicate added to @*.

Also, if you wanted to strip all User attributes no matter what the element was, you could use this template instead:

<xsl:template match="@User"/>

Here's one more way (only stripping from Creation and Test for brevity)

<xsl:template match="@User[..[self::Creation or self::Test]]"/>

Answer to comment

Use this template instead:

<xsl:template match="@User">
    <xsl:attribute name="User">
        <xsl:value-of select="substring-before(.,':')"/>
    </xsl:attribute>
</xsl:template>

Solution 2

I would use what I consider even a better solution:

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

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

 <xsl:template match=
 "*[contains('|Creation|Test|Assignment|Modification|Repair|Termination|',
             concat('|', name(), '|'))
   ]/@user"/>
</xsl:stylesheet>

Do note:

  1. We only use a single template overriding the identity rule. Its body is empty.

  2. The list of element names is presented as a pipe-delimited string and for long lists this saves significant space -- also, such a string can be passed as an external parameter to the transformation, thus making it maximum generic.

  3. This transformation is in completely "push style".

Share:
32,503
delliottg
Author by

delliottg

Senior developer for oceanographic sensor manufacturer writing internal tools. Current stack, JavaScript, Node, CSS, PHP, MySQL. Nearly everything we do is done with serial ports to talk to our instruments, and sockets to talk between our JS front end, a client side server widget that lets us talk to hardware via Node, and a PHP backend that talks to MySQL. We are porting our legacy calibration and device certification software (C, C++, C#, VB6) over to web applications. Eventually we'll be able to calibrate all our devices, and provide certificates via web apps. Previously: Test engineer, budding iPhone dev, know a bit about QT localization. Good with shell scripts in Linux &amp; Windows. Learning HTML, CSS, JS + PhoneGap / Cordova, jQuery-UI, jQTouch, jQuery Mobile, etc. Goal: get an app into the app store. Currently working as a dev doing XML &amp; MySQL stuff in C# and learning about tweaking schemas, LINQ to XML &amp; database migration.

Updated on November 13, 2020

Comments

  • delliottg
    delliottg over 3 years

    I have an XML with multiple nodes with similar data in each. I want to delete a specific attribute from each node (USER:IPADDRESS). I've figured out how to chain together a number of elements using ors, simply leaving out the User="{@User}" match so it doesn't show up in the results:

    XSL Snippet:

    <xsl:template match="Creation | Test | Assignment | Modification | Repair | Termination">
    <Creation CommitID="{@CommitID}" Date="{@Date}" BoardID="{@BoardID}">
    <xsl:apply-templates/>
    </Creation>
    </xsl:template>
    

    Unsurprisingly, all of the node names after "Creation" get re-named to Creation because that's what I'm telling it to do. How do I pass in the various matches so they're applied in the proper order in the results? I know I can do a brute force way using identical XSL statements for each of the various matches (that's how I did it the first time), but there must be a more elegant method, it's just evading me. I have millions & millions of lines of XML to process and this is just the first of many transforms I'm going to have to make.

    I'm using msxsl V4.0 on a Win7 box to do my transforms if that's of any consequence.

    XML:

    <?xml version="1.0"?>
    <BoardDatabase>
    <Board_Data BoardID="1035">
        <Creation CommitID="12b" Date="2007-12-07T15:43:51" BoardID="1035" User="CSAGAN:192.168.1.177">
            <BoardDrawing>3B</BoardDrawing>
            <AssemblyDrawing>2010F</AssemblyDrawing>
            <Notes>PO Num 1959</Notes>
        </Creation>
        <Test CommitID="117" Date="2007-12-10T10:39:43" BoardID="1035" User="CSAGAN:192.168.1.183">
            <ElectricalTestData Result="FAIL" Version="IMM STD REVF">
                <AutomatedTest ReportVersion="1.0">
                    <TestSetup>
                        <TestAppBuildDate>Dec 07 2007</TestAppBuildDate>
                        <VersionPath>c:\tests\versions\v12.txt</VersionPath>
                        <VersionNumber>1.2</VersionNumber>
                        <OperatorName>CSAGAN</OperatorName>
                        <StationID>PC-191-NDGrasse</StationID>
                        <JigSN>12345</JigSN>
                        <JigAssembly>42</JigAssembly>
                        <TestStartTime>2007-12-10 10:34:17</TestStartTime>
                    </TestSetup>
                </AutomatedTest>
            </ElectricalTestData>
        </Test>
        <Assignment CommitID="1c1f" User="JRandi:192.168.1.162" Date="2008-09-30T07:36:52" BoardID="1035">
            <Notes>Boardset failed etest twice, no problem log entry/repair attempts made.</Notes>
        </Assignment>
        <Modification CommitID="2bb7" User="JRandi:192.168.1.162" Date="2009-03-11T13:31:21" BoardID="1035">
            <AssemblyDrawing>2001G</AssemblyDrawing>
            <Notes>Cornelius upgraded boardset to rev. G</Notes>
        </Modification>
    </Board_Data>
    </BoardDatabase>
    

    XSL:

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    
    <xsl:template match="Creation | Test | Assignment | Modification | Repair | Termination">
    <Creation CommitID="{@CommitID}" Date="{@Date}" BoardID="{@BoardID}">
    <xsl:apply-templates/>
    </Creation>
    </xsl:template>
    
    <xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
    </xsl:template>
    </xsl:stylesheet>
    

    Latest XSL using @DevNull's solution that doubles size of original file:

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    
    <!-- Answer from Stack Overflow that only strips out the IP Address from the User attribute. -->
    <xsl:template match="@User">
      <xsl:attribute name="User">
        <xsl:value-of select="substring-before(.,':')"/>
      </xsl:attribute>
    </xsl:template>
    
    <xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
    </xsl:template>
    </xsl:stylesheet>
    

    Latest XSL from @Dimitre's solution that takes a very long time to process (still running after more than 30 minutes, but file is still growing):

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:output omit-xml-declaration="yes" indent="yes"/>
      <xsl:strip-space elements="*"/>
    
    <xsl:template match="node()|@*">
      <xsl:copy>
         <xsl:apply-templates select="node()|@*"/>
       </xsl:copy>
     </xsl:template>
    
     <xsl:template match=
      "*[contains('|Creation|Test|Assignment|Modification|Repair|Termination|',concat('|', name(), '|'))
    ]/@user"/>
    </xsl:stylesheet>