LadyCygnus
LadyCygnus

Reputation: 693

XSLT - Parameter as a series of nodes

My goal is to pass in the names of multiple files into an XSLT and process the files using document($myFile). I'm trying to pass the parameter through on the command line using the saxon engine and it keeps throwing errors.

I know I could write out a manifest file, process that in, and then delete it when I'm finished. But that just seems to be a lot of extra work that would potentially slow things down even more.

The XSLT works when the parameter hard coded like this...

<xsl:param name="PnewArticles" as="element()*">
    <file-name>XMLFile.XML</file-name>
    <file-name>XMLFile2.XML</file-name>
</xsl:param>

To assign from the command line looks like this:

XSLT -s:Source.XML -o:outfileTest.xml -xsl:"test.xsl" newArticles='<file-name>XMLFile.XML</file-name>'

<!-- xslt param changed to this: -->
<xsl:param name="newArticles"/>

However, it appears to be reading it as a string value. When printed it comes out looking like this (which, of course, fails):

'&lt;file-name&gt;XMLFile.XML&lt;/file-name&gt;'

I've tried various combinations of quotes on the command line (single/double), but to no avail. Also tried adding in the as="element()*" as with the hard coded example - but then it complains mightily...

  XPTY0004: Required item type of value of variable $newArticles is node(); suplied value has item type xs:untypedAtomic

Any ideas? It seems like this should be possible.


Sample Files

XSLT

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
xmlns:oasis="//OASIS//DTD XML Exchange Table Model 19990315//EN"
xmlns:mml="http://www.w3.org/1998/Math/MathML"     xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
exclude-result-prefixes="mml oasis dc content xsi">

<xsl:output method="xml" encoding="utf8"/>
<xsl:param name="newArticles"/>

<!-- When these are used they work -- the extra letter in front is just to silence -->
<xsl:param name="PnewArticles" as="element()*">
    <file-name>XMLFile.XML</file-name>
</xsl:param>
<xsl:variable name="VnewArticles" as="element()*">
    <file-name>XMLFile.XML</file-name>
</xsl:variable>

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

<xsl:template match="skipDays">
    <xsl:copy-of select="."/>
    <myParam>
        <xsl:value-of select="$newArticles"/>
    </myParam>
    <xsl:apply-templates select="document($newArticles)" mode="addArticle"/>
</xsl:template>

<xsl:template match="front" mode="addArticle">
    <item>
        <xsl:text>NEW XML, Vol. </xsl:text>
        <xsl:value-of select="volume"/>
        <xsl:text>, No. </xsl:text>
        <xsl:value-of select="issue"/>
    </item>
</xsl:template>

<xsl:template match="body" mode="addArticle"/>
</xsl:stylesheet>

Source File

<?xml version="1.0"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
<language>en-us</language>
<skipDays>
 <day>Saturday</day>
 <day>Sunday</day>
</skipDays>
</channel>
</rss>

XMLFile

<?xml version="1.0" encoding="US-ASCII"?>
<!DOCTYPE article>
<article xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" dtd-version="2.2" xml:lang="EN" article-type="abstract">
  <front>
    <volume>17</volume>
    <issue>1</issue>
  </front>
  <body>
    <sec>
      <title>This is my Title</title>
      <p>This is a Paragraph</p>
    </sec>
  </body>
 </article>

Upvotes: 2

Views: 2429

Answers (3)

helderdarocha
helderdarocha

Reputation: 23637

A possible solution is to read a parameter as a string and convert it to a node-set is by loading it via the document() function using a data URI scheme, which allows embedded documents to be read as if they were external documents. That will allow your string to be parsed, and you can apply templates to it.

This is supported by many XSLT processors, but it depends on support by the parser. If your parser doesn't recognize RFC 2397 data URI schemes, it will not work. I tested it in my environment which was configured by Oxygen XML Editor 15.2.

Since you are using XSLT 2.0, you can store the node-set in a variable:

<xsl:variable name="string-as-document">
     <xsl:copy-of select="doc(concat('data:text/xml,',$newArticles))"/>
</xsl:variable>

You can print the result node, as before:

<myParam>
    <xsl:apply-templates select="$string-as-document"/>
</myParam>

And you can read the file name and get the contents of your XMLFile document:

doc($string-as-document)

Here is the full working template:

<xsl:template match="skipDays">
    <xsl:copy-of select="."/>
    <xsl:variable name="string-as-document">
        <xsl:copy-of select="doc(concat('data:text/xml,',$newArticles))"/>
    </xsl:variable>
    <xsl:apply-templates select="doc($string-as-document)" mode="addArticle"/>
</xsl:template>

Running a Saxon XSLT processor with your RSS source and a newArticles parameter containing the string "<file-name>XMLFile.XML</file-name>" will produce:

<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/"
     xmlns:dc="http://purl.org/dc/elements/1.1/"
     version="2.0">
   <channel>
      <language>en-us</language>
      <skipDays>
         <day>Saturday</day>
         <day>Sunday</day>
      </skipDays>
      <item>NEW XML, Vol. 17, No. 1</item>
   </channel>
</rss>

Upvotes: 1

Michael Kay
Michael Kay

Reputation: 163458

If you really want to supply lexical XML on the command line, you will have to parse it within the stylesheet using a call on saxon:parse() or the XPath 3.0 function parse-xml(), both of which need Saxon-PE or higher. But to me it seems an odd thing to do.

I would have thought the most obvious solution is to supply a string-valued parameter containing the list of filenames, separated by something like colon or semicolon, and then use tokenize() within the stylesheet to separate out the individual filenames, which can then be passed to document(). In fact document() accepts a list of URIs, so you could directly do document(tokenize($param, ';')).

Upvotes: 2

Daniel Haley
Daniel Haley

Reputation: 52878

Trying for 5 minutes, I could only get it to work by putting the XML in a separate file and then referencing that file in the param (by adding + to the param name on the command line).

Note: The XML has to be well formed so if you wanted multiple file-name elements, you'd have to wrap them in a root element like:

<param>
    <file-name>XMLFile.XML</file-name>
    <file-name>other</file-name>
</param>

(I referenced http://www.saxonica.com/documentation/using-xsl/commandline.html)

Example:

XSLT 2.0

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

    <xsl:param name="newArticles"/>

    <xsl:template match="/*">
        <test>
            <xsl:copy-of select="$newArticles"/>            
        </test>
    </xsl:template>

</xsl:stylesheet>

Command Line (Windows)

java -cp "C:\apps\saxon\saxon9he.jar" net.sf.saxon.Transform ^
-s:"so_test.xsl" ^
-xsl:"so_test.xsl" ^
+newArticles="newArticlesParam.xml"

newArticlesParam.xml

<file-name>XMLFile.XML</file-name>

Output

<test>
   <file-name>XMLFile.XML</file-name>
</test>

Upvotes: 3

Related Questions