xsltproc specify search path for input document - xslt

Can I do this with xsltproc:
The main document that is being processed is located in directory src_dirname relative to $PWD
The name of the document to include is specified in the src attribute
If the attribute origin is set to
generated, the document to include should be found in target_dir/src_dirname/#src
anything else, the document to include should be found in src_dirname/#src
Tried:
<xsl:template match="include">
<xsl:choose>
<xsl:when test="#origin='generated'">
<xsl:apply-templates select="document(concat($target_dir, '/', $src_dirname, '/', #src))/content" />
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="document(#src)/content" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
with
xsltproc --path . --stringparam target_dir __targets --stringparam src_dirname doc doc/doc2html.xsl doc/maike.doc.xml
But it wants to load the resource from doc/__targets/doc/cmdline.xml, which is wrong. It should be __targets/doc/cmdline.xml.
Tag in input document:
<include src="cmdline.xml" origin="generated" />

Apparently, if the stylesheet is fed through stdin, document will not try to look in the directory of the source file, thus
xsltproc --path . --stringparam target_dir __targets --stringparam src_dirname doc - doc/maike.doc.xml < doc/doc2html.xsl
Solves the problem

Related

[Wix][Harvest] How to get a Guid of a matched Component and set it to an attribute in Xsl

Hy everyone,
I'm using Wix with Heat and a XslTransform file.
I want to customize my App.config connectionString using a made UI dialog. Therefore, I use Xsl Transformation as below to add a XmlFile copy:
<xsl:template match="wix:Component[wix:File[#Source='$(var.SourceDir)\App.config']]">
<xsl:copy>
<xsl:apply-templates select="node() | #*" />
<util:XmlFile Id="UpdateBaseAddress"
Action="setValue"
File="[I want to put here my auto generated ID for the App.config File Component]"
SelectionLanguage="XPath"
Permanent="yes"
ElementPath="/configuration/connectionStrings/"
Name="connectionString" Value="[DatabaseConnectionString]" >
</util:XmlFile>
</xsl:copy>
</xsl:template>
I tried a lot of ways without a succes. Do you have how to set the matched file id to my 'File' attribute?
Thank you in advance .
You don't need to put the component ID there. The File attribute expects the path to the physical file, not MSI component ID. Here is the WiX snippet of how to achieve the similar thing (although not with XmlFile, but XmlConfig element):
<Component Id="ConnectionStringChanges" Guid="{GUID-GOES-HERE}" Directory="App_Config">
<CreateFolder/>
<Condition>NOT Installed</Condition>
<util:XmlConfig Id="ConnectionStringChange" ElementPath="connectionStrings/add[\[]#name='core'[\]]"
File="[!ConnectionStrings.config]" Name="connectionString" Action="create" Node="value" On="install"
PreserveModifiedDate="yes" Value="user id=[SQL_SERVER_CONFIG_USER];password=[SQL_SERVER_CONFIG_PASSWORD];Data Source=[SQL_SERVER];Database=[DBPREFIX]Core_DB" />
</Component>
Couple of things to note here:
The Directory attribute of the <Component> element can be omitted in case you nest the ConnectionStringChanges component under the right <Directory> element. However, if the directory structure is auto-generated with Heat, you can use this XSL snippet to replace auto-generated ID of the App_Config folder with the custom one:
<xsl:template match="wix:DirectoryRef/wix:Directory[#Name='App_Config']">
<xsl:element name="Directory" xmlns="http://schemas.microsoft.com/wix/2006/wi">
<xsl:attribute name="Id">App_Config</xsl:attribute>
<xsl:attribute name="Name">
<xsl:value-of select="#Name"/>
</xsl:attribute>
<xsl:apply-templates />
</xsl:element>
As long as ConnectionStrings.config file declaration is also auto-generated, you should apply the similar trick to be able to reference the file with \[!ConnectionStrings.config\] syntax:
<xsl:template match="wix:DirectoryRef/wix:Directory[#Name='App_Config']/wix:Component[wix:File[#Source='$(var.Source)\App_Config\ConnectionStrings.config']]">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:attribute name="NeverOverwrite">yes</xsl:attribute>
<xsl:element name="File" xmlns="http://schemas.microsoft.com/wix/2006/wi">
<xsl:attribute name="Id">ConnectionStrings.config</xsl:attribute>
<xsl:copy-of select="wix:File/#KeyPath | wix:File/#Source"/>
</xsl:element>
</xsl:copy>
Hope this helps.

Determining files in a directory using xslt

I am replacing the Distribution section in my many metadata records to include optional references to other non-xml files. Each xml metadata record is in a separate folder, and in each such folder there may or may not be a PDF document and perhaps an Excel file. These three files have independent names. I basically need a directory list, which I can access and manipulate.
The following is a subset of what I have been attempting, although as it stands it does compile using Kernow-Saxon 9PE. The first part seems to construct the correct path to the desired subdirectory, albeit with a “file:/” prefix.
My problems are:
Do I need to go to the effort I am making to determine the path? (It seems “un-xsl-ish”). Kernow easily finds the metadata.xml file in each subdirectory.
I don’t think I have the “for-each” statement correct. In versions where it does execute, I temporarily limit “select=.” as “select=*.xml” (and it only does the metadata.xml files).
Without the override mentioned, I get “Variable filename has not been declared (or its declaration is not in scope)”
<xsl:strip-space elements="*" />
<xsl:template match="gmd:distributionInfo" >
<gmd:distributionInfo>
<gmd:distributorTransferOptions>
<!-- construct path to directory containing xml metadata and non-xml files -->
<xsl:variable name="pathParts" select="tokenize(base-uri(),'/') " />
<!-- remove filename.extension, reconstruct path -->
<xsl:variable name="directory" select="string-join((remove($pathParts,count($pathParts))),'/')" />
<gmd:dir>
<gco:CharacterString>
<xsl:value-of select = "$directory" />
</gco:CharacterString>
</gmd:dir>
<!-- loop thru all files to obtain filenames -->
<xsl:for-each select="for $filename in collection(concat($directory, select='*.*')) return $filename " >
<!-- temporary? override to enable xslt compilation -->
<xsl:variable name="filename" select= "base-uri()" />
<gmd:name>
<gco:CharacterString>
<xsl:value-of select= "$filename" />
</gco:CharacterString>
</gmd:name>
</xsl:for-each>
</gmd:distributorTransferOptions>
</gmd:distributionInfo>
</xsl:template>
<xsl:template match="*" >
<xsl:copy>
<xsl:copy-of select="#*" />
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
</xsl:transform>
A dummy metadata record would as attached.
<?xml version="1.0" encoding="UTF-8"?>
<gmd:MD_Metadata xmlns:gco="http://www.isotc211.org/2005/gco" xmlns:gmd="http://www.isotc211.org/2005/gmd">
<gmd:fileIdentifier>
<gco:CharacterString>56400C76-E5E3-44D7-904C-90B97858F7CE</gco:CharacterString>
</gmd:fileIdentifier>
<gmd:identificationInfo>
<gmd:MD_DataIdentification>
</gmd:MD_DataIdentification>
</gmd:identificationInfo>
<gmd:distributionInfo>
<gmd:MD_Distribution>
</gmd:MD_Distribution>
</gmd:distributionInfo>
</gmd:MD_Metadata>
You cannot access the defined $filename outside of your xpath for $i in x statement.
Try making the following changes to your code:
<xsl:for-each select="for $filename in collection(concat($directory, select='*.*')) return $filename " >
<gmd:name>
<gco:CharacterString>
<xsl:value-of select= "base-uri(.)" />
</gco:CharacterString>
</gmd:name>
</xsl:for-each>
The select statement in the for-each should return a document node for each item in your collection, base-uri(.) will give you the filename for each document node.
Also if you only have xml files in your collection I would use select='*.xml' because if there were other files in your directory that statement would fail.

Print parameter or variable depending on a file's path only once

Sorry in advance if the title is too vague.
I have to process several XML files referenced by each other with XSLT and search for certain errors.
My XMLs typically look like this:
<topic>
... some elements ...
<topicref #href="path-to-another-file"/>
... some other elements ...
<figure> ... </figure>
</topic>
And my desired output is:
path-to-a-file:
Errors found
path-to-another-file:
Other errors found
I get the paths from href attributes and I'd like to print a path if there's an error in the corresponding file.
The important parts of my XSLT:
<!-- ingress referenced files -->
<xsl:template match="//*[#href]">
<xsl:apply-templates select="document(#href)/*">
<xsl:with-param name="path" select="#href"/>
</xsl:apply-templates>
<xsl:apply-templates select="./*[#href]">
</xsl:apply-templates>
</xsl:template>
<!-- matching topic elements to check -->
<xsl:template match="topic">
<xsl:param name="path"/>
<xsl:if test=".//figure">
<!-- Right now this is where I print the path of the current file -->
<xsl:value-of select="$path"/>
</xsl:if>
<xsl:apply-templates select="figure">
<xsl:with-param name="path" select="$path"/>
</xsl:apply-templates>
</xsl:template>
<!-- check if there's any error -->
<xsl:template match="figure">
<xsl:param name="path"/>
<xsl:if test="...">
<xsl:call-template name="printError">
<xsl:with-param name="errorText" select="'...'"/>
<xsl:with-param name="filePath" select="..."/>
<xsl:with-param name="elementId" select="..."/>
</xsl:call-template>
</xsl:if>
<xsl:if test="...">
<xsl:call-template name="printError">
<xsl:with-param name="errorText" select="'...'"/>
<xsl:with-param name="filePath" select="..."/>
<xsl:with-param name="elementId" select="..."/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<!-- print error message -->
<xsl:template name="printError">
<xsl:param name="errorText"/>
<xsl:param name="filePath"/>
<xsl:param name="elementId"/>
... print out some stuff ...
</xsl:template>
Where should I print out the path of a file? With this transformation I always write it out if the file has a figure element even when it doesn't contain any mistakes.
Like this:
path-to-a-file:
Errors found
path-to-file-with-no-errors:
path-to-another-file:
Other errors found
If I put the part in question elsewhere (i.e. in the error checking or printing template) it gets printed after every figure elements examined or errors printed.
I assume the problem could be solved with variables but I'm new to XSLT so I'm not sure how to go about it.
EDIT:
I need to display the path of the file when I find an error in it but only once, after the first error was found.
Errors can be found only in files with figure elements.
I hope this clarifies my problem.
You can test if the current element matches a previous element in the XML, using something like:
<variable name='currentlocation' select="#href"/>
<xsl:if test="not(preceding-sibling::topicref[contains(#href, $currentlocation)])">
(process this only if there is no preceding sibling with the same #href as the current location)
edit: this test would solve only half the problem. You need another test to check if there is an error in the other file.

Read remote csv file in xsl

I'm trying to convert a csv file into a xml file with Saxon 9.
But I have a problem when I try to check existence and read a csv file with the xslt functions:
unparsed-text-available(),
unparsed-text()
They work fine with a local file, but when I pass a remote file as parameter, unparsed-text-available() return false.
For example,
when I pass "D:\test\test.csv", it works.
when I pass "\\remote-computer\test\test.csv", it cannot find it.
Here is a part of my xsl file:
<xsl:template match="/" name="main">
<xsl:choose>
<xsl:when test="unparsed-text-available($pathToCSV)">
<xsl:variable name="csv" select="unparsed-text($pathToCSV)"/>
....
</xsl:when>
<xsl:otherwise>
<xsl:text>Cannot locate : </xsl:text><xsl:value-of select="$pathToCSV"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
It seems that document() can read remote file, but only for xml files.
Do you know some other functions that I can use for this case? In xslt or saxon?
You'll have to turn that path into a valid URI.
Something like:
unparsed-text(concat('file://',translate($pathToCSV,'\','/')))

Exists function in XSL When Statement

I am using an XSL when clause to transform one XML file to another XML file. I need to use something like an "exists" function in my when test.
Here's an example source XML:
<People>
<Person personid="1" location="US" fullname="John Doe"/>
<Person personid="2" location="US" fullname="Jane Doe"/>
</People>
<Nicknames>
<Nickname personid="1" nname="Johnny D"/>
</Nicknames>
Here's my example XSL:
<xsl:element name="HASNICKNAME">
<xsl:choose>
<!-- If nickname exists in source XML, return true -->
<xsl:when test="boolean exists function"
<xsl:text>TRUE</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>FALSE</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:element>
Can someone help with the exists part?
Suppose the variable $personid contains the #personid of the Person you are checking, then this only checks existence:
<xsl:when test="boolean(//Nickname[#personid=$personid]/#nname)">
For similar issues I normally prefer to check for a non-empty/non-whitespace value:
<xsl:when test="normalize-space(//Nickname[#personid=$personid]/#nname)!=''">
If you use a key like <xsl:key name="nn" match="//Nickname" use="#personid"/>, the following is also possible:
<xsl:when test="normalize-space(key('nn',$personid)/#nname)!=''">
The latter does not need the $personid variable but can directly work with the #personid of the Person you are checking…