Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

xml

xslt

xpath

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>
like image 245
delliottg Avatar asked Nov 09 '12 22:11

delliottg


2 Answers

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>
like image 113
Daniel Haley Avatar answered Sep 25 '22 13:09

Daniel Haley


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".

like image 21
Dimitre Novatchev Avatar answered Sep 23 '22 13:09

Dimitre Novatchev