10

I have created a XSLT file that can transform a single XML file. However, I have several hundred directories with multiple xml files. Is there a way in XSLT to transform all these files. I am using the collection function to get a list of all files. But, not sure how to apply the transform now.

Here is my example XSLT file. Basically, I want to loop through all the xml files and apply the template table on the individual file. The output of all these transforms needs to be in one single flat text file.

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:foo="http://whatever">

<xsl:output method="text" encoding="ISO-8859-1"/>
    <xsl:template name="process">
        <xsl:variable name="files" select="collection('file:///C:/files/testResults?select=*.xml;recurse=yes')"/>
        <xsl:for-each select="$files">
            <xsl:if test="(not(contains(document-uri(.), 'SuiteSetUp'))) and (not(contains(document-uri(.), 'SuiteTearDown')))">
                <xsl:value-of select="tokenize(document-uri(.), '/')[last()]"></xsl:value-of>
                <xsl:apply-templates select="/testResults/result/tables/table[14]">
                    <xsl:with-param name="title" select="/testResults/rootPath"></xsl:with-param>
                </xsl:apply-templates>
                <xsl:apply-templates select="/testResults/result/tables/table[15]"/>
            </xsl:if>
        </xsl:for-each>
    </xsl:template>

    <xsl:template match="table">
    <xsl:param name="testName"></xsl:param>
        <xsl:for-each select="row">
            <xsl:if test="position() > 2">
                <xsl:variable name="choices" select="col[2]"></xsl:variable>
                <xsl:if test="contains($choices, 'fail')">
                    <xsl:value-of  select="$testName"></xsl:value-of>
                    <xsl:text>|</xsl:text>
                    <xsl:value-of select="col[1]"></xsl:value-of>
                    <xsl:text>|</xsl:text>
                    <xsl:value-of select="foo:getCorrectChoices(col[2])"></xsl:value-of>
                    <xsl:text>|</xsl:text>
                    <xsl:value-of select="foo:getExpectedChoices(col[2])"></xsl:value-of>
                    <xsl:text>|</xsl:text>
                    <xsl:call-template name="NewLine"/>
                </xsl:if>
            </xsl:if>
        </xsl:for-each>
    </xsl:template>

    <xsl:template name="NewLine">
        <xsl:text>&#xA;</xsl:text>
    </xsl:template>

    <xsl:function name="foo:getCorrectChoices">
        <xsl:param name="content"></xsl:param>
        <xsl:analyze-string select="$content" regex="\[(\[.+?\])\] fail">
            <xsl:matching-substring>
                <xsl:value-of select="regex-group(1)"></xsl:value-of>
            </xsl:matching-substring>
        </xsl:analyze-string>
    </xsl:function>
    <xsl:function name="foo:getExpectedChoices">
        <xsl:param name="content"></xsl:param>
        <xsl:analyze-string select="$content" regex="fail\(expected \[(\[.+?\])\]">
            <xsl:matching-substring>
                <xsl:value-of select="regex-group(1)"></xsl:value-of>
            </xsl:matching-substring>
        </xsl:analyze-string>
    </xsl:function>
</xsl:stylesheet>
Grzegorz Oledzki
  • 23,614
  • 16
  • 68
  • 106
user320587
  • 1,347
  • 7
  • 29
  • 57
  • What kind of XSLT processor are you using? If you are using Saxon, you can do it from the command line directly. Saxon is able to transform batch of files for which you want apply the same transform. – Emiliano Poggi May 25 '11 at 18:49
  • I was using a XSLT editor called EditX to build my XSLT and thought maybe I can run it from there. But looks like I need to run this from somewhere else. I saw an ant task for XSLT. Will that work? – user320587 May 25 '11 at 18:53
  • What operating system are you running on? – Jim Garrison May 25 '11 at 19:07

2 Answers2

9

Here is probably the simplest example how to process all the xml files in a file system subtree (using the collection() function as implemented in Saxon):

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>

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

 <xsl:template match="/">
   <xsl:apply-templates mode="inFile" select=
   "collection('file:///C:/temp?select=*.xml;recurse=yes')"/>
 </xsl:template>
</xsl:stylesheet>

When applied on any XML document (not used, ignored), this transformation applies the identity rule to every XML document contained in any of the *.xml files in the C:/Temp subtree of the file system.

To do more neaningful processing, one needs to override the identity template -- in the inFile mode.

In your specific case I believe you can simply replace:

            <xsl:apply-templates select="/testResults/result/tables/table[14]">     

with

            <xsl:apply-templates select="./testResults/result/tables/table[14]">    

and this applies the desired templates on the nodes selected off the current (document) node.

Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
  • I am getting error "Collection is an unknown xslt function" when I Transform this in C#. Please help me – shanmugharaj Jun 26 '16 at 13:41
  • @shansfk, Yes, the XSLT engines that come with .NET don't support XSLT 2.0. The question is how to do this in XSLT 2.0 -- as the provided by the OP code suggests. You can install and use Saxon.NET and then this transformation will produce the expected results. – Dimitre Novatchev Jun 26 '16 at 16:06
0

I just wanted to add a lightweight version of Dimitre's excellent answer. If you have <foo> documents (root node) sitting in a directory, and a working XSLT program for <foo>, simply make your top-level template like this:

<xsl:template match="/">
    <xsl:apply-templates select="collection($my_url)/foo"/>
</xsl:template>

That's assuming you want the URL as a parameter, <xsl:param name="my_url"/>, specified on the command line.

Pete
  • 16,534
  • 9
  • 40
  • 54