Determining files in a directory using xslt - 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.

Related

XSL copy without values is it possible?

I want to compare two xmls.
1. First compare XML strucutre/schema.
2. Compare values.
I am using beyond compare tool to compare. Since these two xmls are different values, there are lot many differences in comparison report, for which I am not interested. Since, my focus now is to only compare structure/schema.
I tried to copy the xmls by following template, and other as well. But every time it is with values.
I surfed on google, xsl-copy command itself copies everything for selected node/element..
Is there any ways with which I can filter out values and only schema is copied ?
My Data :
<root>
<Child1>xxxx</Child1>
<Child2>yyy</Child2>
<Child3>
<GrandChild1>dddd<GrandChild1>
<GrandChild2>erer<GrandChild2>
</Child3>
</root>
Template used :
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<!-- for all elements (tags) -->
<xsl:template match="*">
<!-- create a copy of the tag (without attributes and children) in the output -->
<xsl:copy>
<!-- For all attributes of the current tag -->
<xsl:for-each select="#*">
<xsl:sort select="name( . )" order="ascending" case-order="lower-first" />
<xsl:copy/>
</xsl:for-each>
<!-- recurse through all child tags -->
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()|comment()|processing-instruction()">
<xsl:copy/>
</xsl:template>
OutPut Required :
Something like..
<root>
<Child1></Child1>
<Child2></Child2>
<Child3>
<GrandChild1><GrandChild1>
<GrandChild2><GrandChild2>
</Child3>
</root>
At the moment, you have a template matching text() to copy it. What you need to do is remove this match from that template, and have a separate template match, that matches only non-whitespace text, and remove it.
<xsl:template match="comment()|processing-instruction()">
<xsl:copy/>
</xsl:template>
<xsl:template match="text()[normalize-space()]" />
For white-space only text (as used in indentation), these will be matched by XSLT'S built-in templates.
For attributes, use xsl:attribute to create a new attribute, without a value, rather than using xsl:copy which will copy the whole attribute.
<xsl:attribute name="{name()}" />
Note the use of Attribute Value Templates (the curly braces) to indicate the expression is to be evaluated to get the string to use.
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<!-- for all elements (tags) -->
<xsl:template match="*">
<!-- create a copy of the tag (without attributes and children) in the output -->
<xsl:copy>
<!-- For all attributes of the current tag -->
<xsl:for-each select="#*">
<xsl:sort select="name( . )" order="ascending" case-order="lower-first" />
<xsl:attribute name="{name()}" />
</xsl:for-each>
<!-- recurse through all child tags -->
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="comment()|processing-instruction()">
<xsl:copy/>
</xsl:template>
<xsl:template match="text()[normalize-space()]" />
</xsl:stylesheet>
Also note that attributes are considered to be unordered in XML, so although you have code to sort the attributes, and they probably will appear in the right order, you can't guarantee it.

Using string in xslt apply-template select attribute

I have a string that contains a xpath query and it will be passed as a parameter or included from external source i don't know right now but it is dynamic that's sure.
I want to use this string in select attribute of apply-templates command.
<!--this is external xml file to be queried.-->
<xsl:variable name="v" select="document('foo.xml')"></xsl:variable>
<xsl:template match="/Selector">
<!--this is our variable = query-->
<xsl:variable name="xpathquery" select="string(#select)" />
<!--i want to say this!-->
<xsl:apply-templates select="$v/$xpathquery" mode="ui" />
</xsl:template>
<xsl:template mode="ui" match="*">
<foo></foo>
</xsl:template>
I hope i could explain it. Thanks.

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

How to read a .properties file inside a .xsl file?

I have an XSL file which uses a a static website link as shown below:
<xsl:template match="my_match">
<xsl:variable name="variable1">
<xsl:value-of select="sel1/Label = 'Variable1'"/>
</xsl:variable>
<xsl:copy-of select="sites:testPath('http://testsite.com/services/testService/v1.0', $fname, $lname,
$email , $zip, $phone, $comments, $jps, boolean($myvar), string(cust/#custID), string(#paID))"/>
</xsl:template>
My question is that how to read a properties file(key value pair) in the xsl file. so in my properties file (e.g. site.properties) I have a key called site i.e. site=testsite.com/services/testService/v1.0
I want to use this site key in place of specifying url value in the xsl i.e. http://testsite.com/services/testService/v1.0. The reason for doing this is that this link changes depending on the various environments.
Is this possible?
Please give your suggestions or a sample code if possible...Also if this is not possible...is there any work-around?
As a proof of concept:
Input .properties file:
# You are reading the ".properties" entry.
! The exclamation mark can also mark text as comments.
website = http://example.com
language = English
key\ with\ spaces = This is the value that could be looked up with the key "key with spaces".
Stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:f="Functions"
version="2.0">
<xsl:variable name="properties" select="unparsed-text('.properties')" as="xs:string"/>
<xsl:template match="/" name="main">
<xsl:value-of select="f:getProperty('language')"/>
</xsl:template>
<xsl:function name="f:getProperty" as="xs:string?">
<xsl:param name="key" as="xs:string"/>
<xsl:variable name="lines" as="xs:string*" select="
for $x in
for $i in tokenize($properties, '\n')[matches(., '^[^!#]')] return
tokenize($i, '=')
return translate(normalize-space($x), '\', '')"/>
<xsl:sequence select="$lines[index-of($lines, $key)+1]"/>
</xsl:function>
</xsl:stylesheet>
The f:getProperty('language') will return 'English'.
See this as a proof of concept, this needs to be improved in many ways since it does not handle many of the different ways a .properties file can be authored.
I belive Alejandro or Dimitrie probably could improve this many times.
For an XSLT 1.0 solution, you could use an external (parsed) general entity in an XML file that will load the properties file as part of the XML content.
For example, if you had a properties file like this, named site.properties:
foo=x
site=http://testsite.com/services/testService/v1.0
bar=y
You could create a simple XML file, named properties.xml that "wraps" the content of the properties file and loads it using an external parsed general entity:
<!DOCTYPE properties [
<!ENTITY props SYSTEM "site.properties">
]>
<properties>
&props;
</properties>
Then, within your XSLT you can load that properties.xml using the document() function and obtain the value for a given key:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:variable name="props" select="document('properties.xml')" />
<xsl:template match="/">
<output>
<example1>
<!--simple one-liner -->
<xsl:value-of select="substring-before(
substring-after($props,
concat('site','=')),
'
')" />
</example1>
<example2>
<!--using a template to retrieve the value
of the "site" property -->
<xsl:call-template name="getProperty">
<xsl:with-param name="propertiesFile" select="$props"/>
<xsl:with-param name="key" select="'site'"/>
</xsl:call-template>
</example2>
<example3>
<!--Another example using the template to retrieve
the value of the "foo" property,
leveraging default param value for properties -->
<xsl:call-template name="getProperty">
<!--default $propertiesFile defined in the template,
so no need to specify -->
<xsl:with-param name="key" select="'foo'"/>
</xsl:call-template>
</example3>
</output>
</xsl:template>
<!--Retrieve a property from a properties file by specifying the key -->
<xsl:template name="getProperty">
<xsl:param name="propertiesFile" select="$props"/>
<xsl:param name="key" />
<xsl:value-of select="substring-before(
substring-after($propertiesFile,
concat($key,'=')),
'
')" />
</xsl:template>
</xsl:stylesheet>
When applied to any XML input the stylesheet above will produce the following output:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<example1>http://testsite.com/services/testService/v1.0</example1>
<example2>http://testsite.com/services/testService/v1.0</example2>
<example3>x</example3>
</output>
Note: this strategy will only work if the content of the properties file is "XML safe". If it were to contain characters, like & or < it would result in an XML parsing error when the properties.xml file is loaded.

Producing a new line in XSLT

I want to produce a newline for text output in XSLT. Any ideas?
The following XSL code will produce a newline (line feed) character:
<xsl:text>
</xsl:text>
For a carriage return, use:
<xsl:text>
</xsl:text>
My favoured method for doing this looks something like:
<xsl:stylesheet>
<xsl:output method='text'/>
<xsl:variable name='newline'><xsl:text>
</xsl:text></xsl:variable>
<!-- note that the layout there is deliberate -->
...
</xsl:stylesheet>
Then, whenever you want to output a newline (perhaps in csv) you can output something like the following:
<xsl:value-of select="concat(elem1,elem2,elem3,$newline)" />
I've used this technique when outputting sql from xml input. In fact, I tend to create variables for commas, quotes and newlines.
Include the attribute Method="text" on the xsl:output tag and include newlines in your literal content in the XSL at the appropriate points. If you prefer to keep the source code of your XSL tidy use the entity
where you want a new line.
You can use: <xsl:text>
</xsl:text>
see the example
<xsl:variable name="module-info">
<xsl:value-of select="#name" /> = <xsl:value-of select="#rev" />
<xsl:text>
</xsl:text>
</xsl:variable>
if you write this in file e.g.
<redirect:write file="temp.prop" append="true">
<xsl:value-of select="$module-info" />
</redirect:write>
this variable will produce a new line infile as:
commons-dbcp_commons-dbcp = 1.2.2
junit_junit = 4.4
org.easymock_easymock = 2.4
IMHO no more info than #Florjon gave is needed. Maybe some small details are left to understand why it might not work for us sometimes.
First of all, the &#xa (hex) or &#10 (dec) inside a <xsl:text/> will always work, but you may not see it.
There is no newline in a HTML markup. Using a simple <br/> will do fine. Otherwise you'll see a white space. Viewing the source from the browser will tell you what really happened. However, there are cases you expect this behaviour, especially if the consumer is not directly a browser. For instance, you want to create an HTML page and view its structure formatted nicely with empty lines and idents before serving it to the browser.
Remember where you need to use disable-output-escaping and where you don't. Take the following example where I had to create an xml from another and declare its DTD from a stylesheet.
The first version does escape the characters (default for xsl:text)
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" encoding="utf-8"/>
<xsl:template match="/">
<xsl:text><!DOCTYPE Subscriptions SYSTEM "Subscriptions.dtd">
</xsl:text>
<xsl:copy>
<xsl:apply-templates select="*" mode="copy"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()" mode="copy">
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="copy"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
and here is the result:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE Subscriptions SYSTEM "Subscriptions.dtd">
<Subscriptions>
<User id="1"/>
</Subscriptions>
Ok, it does what we expect, escaping is done so that the characters we used are displayed properly. The XML part formatting inside the root node is handled by ident="yes". But with a closer look we see that the newline character &#xa was not escaped and translated as is, performing a double linefeed! I don't have an explanation on this, will be good to know. Anyone?
The second version does not escape the characters so they're producing what they're meant for. The change made was:
<xsl:text disable-output-escaping="yes"><!DOCTYPE Subscriptions SYSTEM "Subscriptions.dtd">
</xsl:text>
and here is the result:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE Subscriptions SYSTEM "Subscriptions.dtd">
<Subscriptions>
<User id="1"/>
</Subscriptions>
and that will be ok. Both cr and lf are properly rendered.
Don't forget we're talking about nl, not crlf (nl=lf). My first attempt was to use only cr:&#xd and while the output xml was validated by DOM properly.
I was viewing a corrupted xml:
<?xml version="1.0" encoding="utf-8"?>
<Subscriptions>riptions SYSTEM "Subscriptions.dtd">
<User id="1"/>
</Subscriptions>
DOM parser disregarded control characters but the rendered didn't. I spent quite some time bumping my head before I realised how silly I was not seeing this!
For the record, I do use a variable inside the body with both CRLF just to be 100% sure it will work everywhere.
You can try,
<xsl:text>
</xsl:text>
It will work.
I added the DOCTYPE directive you see here:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet [
<!ENTITY nl "
">
]>
<xsl:stylesheet xmlns:x="http://www.w3.org/2005/02/query-test-XQTSCatalog"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
This allows me to use &nl; instead of
to produce a newline in the output. Like other solutions, this is typically placed inside a <xsl:text> tag.
I second Nic Gibson's method, this was
always my favorite:
<xsl:variable name='nl'><xsl:text>
</xsl:text></xsl:variable>
However I have been using the Ant task <echoxml> to
create stylesheets and run them against files. The
task will do attribute value templates, e.g. ${DSTAMP} ,
but is also will reformat your xml, so in some
cases, the entity reference is preferable.
<xsl:variable name='nl'><xsl:text>
</xsl:text></xsl:variable>
I have found a difference between literal newlines in <xsl:text> and literal newlines using
.
While literal newlines worked fine in my environment (using both Saxon and the default Java XSLT processor) my code failed when it was executed by another group running in a .NET environment.
Changing to entities (
) got my file generation code running consistently on both Java and .NET.
Also, literal newlines are vulnerable to being reformatted by IDEs and can inadvertently get lost when the file is maintained by someone 'not in the know'.
I've noticed from my experience that producing a new line INSIDE a <xsl:variable> clause doesn't work.
I was trying to do something like:
<xsl:variable name="myVar">
<xsl:choose>
<xsl:when test="#myValue != ''">
<xsl:text>My value: </xsl:text>
<xsl:value-of select="#myValue" />
<xsl:text></xsl:text> <!--NEW LINE-->
<xsl:text>My other value: </xsl:text>
<xsl:value-of select="#myOtherValue" />
</xsl:when>
</xsl:choose>
<xsl:variable>
<div>
<xsl:value-of select="$myVar"/>
</div>
Anything I tried to put in that "new line" (the empty <xsl:text> node) just didn't work (including most of the simpler suggestions in this page), not to mention the fact that HTML just won't work there, so eventually I had to split it to 2 variables, call them outside the <xsl:variable> scope and put a simple <br/> between them, i.e:
<xsl:variable name="myVar1">
<xsl:choose>
<xsl:when test="#myValue != ''">
<xsl:text>My value: </xsl:text>
<xsl:value-of select="#myValue" />
</xsl:when>
</xsl:choose>
<xsl:variable>
<xsl:variable name="myVar2">
<xsl:choose>
<xsl:when test="#myValue != ''">
<xsl:text>My other value: </xsl:text>
<xsl:value-of select="#myOtherValue" />
</xsl:when>
</xsl:choose>
<xsl:variable>
<div>
<xsl:value-of select="$myVar1"/>
<br/>
<xsl:value-of select="$myVar2"/>
</div>
Yeah, I know, it's not the most sophisticated solution but it works, just sharing my frustration experience with XSLs ;)
I couldn't just use the <xsl:text>
</xsl:text> approach because if I format the XML file using XSLT the entity will disappear. So I had to use a slightly more round about approach using variables
<xsl:variable name="nl" select="'
'"/>
<xsl:template match="/">
<xsl:value-of select="$nl" disable-output-escaping="no"/>
<xsl:apply-templates select="*"/>
</xsl:template>
<xsl:text xml:space="preserve">
</xsl:text>
just add this tag:
<br/>
it works for me ;) .