How do I get a new DITA tag to carry over to my output using XSLT? - xslt

I had successfully updated my XSLT to script to add the audience tag to my output but I was asked for the output to display slightly differently.
My original input looked like this:
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE bookmap PUBLIC "-//OASIS//DTD DITA BookMap//EN" "bookmap.dtd" []>
<bookmap>
<topicgroup>
<topichead navtitle="Arkansas Property and Casualty Adjuster Law" locktitle="yes" audience="Arkansas">
<topicgroup>
<mapref href="Qbank/maps/Adj_Unit_AR.ditamap" format="ditamap"/>
</topicgroup>
</topichead>
</topicgroup>
</bookmap>
The portion of my original script looked like this:
<xsl:template match="*[contains(#class,' mapgroup-d/topichead ')]">
<topichead>
<xsl:attribute name="navtitle">
<xsl:value-of select="#navtitle"/>
</xsl:attribute>
<xsl:choose>
<xsl:when test="#audience">
<xsl:attribute name="audience">
<xsl:value-of select="#audience"/>
</xsl:attribute>
</xsl:when>
<xsl:otherwise />
</xsl:choose>
</topichead>
</template>
But now I'm being asked to move the audience tag to a different spot and I'm having trouble getting the transform to grab the tag.
My new input looks like this:
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE bookmap PUBLIC "-//OASIS//DTD DITA BookMap//EN" "bookmap.dtd" []>
<bookmap>
<chapter navtitle="Alabama Property and Casualty Adjuster Law" locktitle="yes" audience="Alabama">
<topicgroup
<topicgroup>
<mapref href="Qbank/maps/Adj_Unit_AL.ditamap" format="ditamap"/>
</topicgroup>
</topicgroup>
</chapter>
</bookmap>
This is the script I tried but it's not pulling over the audience tag.
<xsl:template match="*[contains(#class,' mapgroup-d/topichead ')]">
<topichead>
<xsl:attribute name="navtitle">
<xsl:value-of select="#navtitle"/>
</xsl:attribute>
<xsl:choose>
<xsl:when test="bookmap/chapter/#audience">
<xsl:attribute name="audience">
<xsl:value-of select="#audience"/>
</xsl:attribute>
</xsl:when>
<xsl:otherwise />
</xsl:choose>
</topichead>
</template>
What am I doing wrong?

This xsl:when expression:
<xsl:when test="bookmap/chapter/#audience">
is a relative xpath, meaning that the XSLT processor searches for an element "bookmap" which is a child of the current "topichead" element matched by the xsl:message. You need to search upwards, something like:
<xsl:when test="ancestor::chapter/#audience">
Then this value-of:
<xsl:value-of select="#audience"/>
it's a relative xpath expression again, relative to the current "topichead" element matched by the xsl:template. So you would probably need to replace it with:
<xsl:value-of select="ancestor::chapter/#audience">

In addition to the answer of #Radu Coravu:
A bit shorter would be
instead of:
<xsl:choose>
<xsl:when test="ancestor::chapter/#audience">
<xsl:attribute name="audience">
<xsl:value-of select="ancestor::chapter/#audience"/>
</xsl:attribute>
</xsl:when>
<xsl:otherwise />
</xsl:choose>
just use:
<xsl:copy-of select="ancestor::chapter/#audience"/>

Related

Concate after Choose XSLT1.0 or 2.0

Still new to this forum and XSLT, already searched but could not find the correct solution.
i am trying to get the right concat after the choose, if i match the condition 208 or 906 is still get LX1234 as combination, it always ends up on the otherwise as solution. the XSLT is build in 2.0 so both solutions will work for me.
<xsl:for-each select="XXX/Envelope/PickingList/Line/Lot">
<articleLine type="tag">
<articleCode>
<xsl:choose>
<xsl:when test="XXX/Envelope/GLN_Supplier =208">
<xsl:value-of select="concat('AV',../ArticleCodeSupplier)"/>
</xsl:when>
<xsl:when test="XXX/Envelope/GLN_Supplier =906">
<xsl:value-of select="concat('KR',../ArticleCodeSupplier)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat('LX',../ArticleCodeSupplier)"/>
</xsl:otherwise>
</xsl:choose>
</articleCode>
Edit XML input, thank you for pointing that out #michael.hor257k
<?xml version='1.0' encoding='ISO-8859-1'?>
<XXX>
<Envelope>
<InterchangeControlReference>995566</InterchangeControlReference>
<GLN_Supplier>906</GLN_Supplier>
<GLN_Customer>541</GLN_Customer>
<PickingList>
<PickingListNumber>9</PickingListNumber>
<PickingDate>2019-08-01</PickingDate>
<OrderNumberSupplier>12345</OrderNumberSupplier>
<OrderNumberCustomer>EDI Test 1</OrderNumberCustomer>
<EarliestDeliveryDate>2019-10-09</EarliestDeliveryDate>
<EarliestDeliveryTime>14:13:39</EarliestDeliveryTime>
<LastDeliveryDate>2019-10-09</LastDeliveryDate>
<LastDeliveryTime>14:13:39</LastDeliveryTime>
<GLN_Supplier>215</GLN_Supplier>
<GLN_WarehouseAddressSupplier>541</GLN_WarehouseAddressSupplier>
<GLN_Customer>000</GLN_Customer>
<GLN_DeliveryAddressCustomer>000</GLN_DeliveryAddressCustomer>
<GLN_CarrierAddressCustomer></GLN_CarrierAddressCustomer>
<DeliveryName>CLIENTNAME</DeliveryName>
<DeliveryNameContactperson></DeliveryNameContactperson>
<DeliveryTelephoneNumber></DeliveryTelephoneNumber>
<DeliveryAddress>CLIENTADDRESS</DeliveryAddress>
<DeliveryAddress2></DeliveryAddress2>
<DeliveryHouseNumber>0</DeliveryHouseNumber>
<DeliveryHouseNumberExtra></DeliveryHouseNumberExtra>
<DeliveryPostalCode>1111 aa</DeliveryPostalCode>
<DeliveryCity>PLACE</DeliveryCity>
<DeliveryCountryISO>NL</DeliveryCountryISO>
<DeliveryCustomsExcise></DeliveryCustomsExcise>
<DeliveryMethod>DDP</DeliveryMethod>
<DeliveryIncoterm>DDP</DeliveryIncoterm>
<DeliveryIncotermCity></DeliveryIncotermCity>
<Remarks_Pickinglist>REMARKS WAREHOUSE</Remarks_Pickinglist>
<Remarks_Packinglist>REMARKS FREIGHTBILL</Remarks_Packinglist>
<Remarks_Delivery></Remarks_Delivery>
<CustomsExcise>true</CustomsExcise>
<Test>false</Test>
<Line>
<LineNumber>1</LineNumber>
<EAN_Article>528</EAN_Article>
<ArticleCodeSupplier>1234</ArticleCodeSupplier>
<ArticleCodeCustomer></ArticleCodeCustomer>
<UnitToPick>Fles</UnitToPick>
<ArticleDescription>VINE WINE</ArticleDescription>
<QuantityToPick>21</QuantityToPick>
<QuantityToPickInOrderUnit>21</QuantityToPickInOrderUnit>
<UnitOrdered>Fles</UnitOrdered>
<Lot>
<LotCode>72</LotCode>
<BatchCode>3</BatchCode>
<YearOfProduction>2017</YearOfProduction>
<QuantityToPick>21</QuantityToPick>
</Lot>
</Line>
<Line>
<LineNumber>2</LineNumber>
<EAN_Article>081</EAN_Article>
<ArticleCodeSupplier>5678</ArticleCodeSupplier>
<ArticleCodeCustomer></ArticleCodeCustomer>
<UnitToPick>Fles</UnitToPick>
<ArticleDescription>CHAMPAGNE</ArticleDescription>
<QuantityToPick>10</QuantityToPick>
<CountryOfOriginISO>ES</CountryOfOriginISO>
<QuantityToPickInOrderUnit>10</QuantityToPickInOrderUnit>
<UnitOrdered>Fles</UnitOrdered>
<Lot>
<LotCode>5</LotCode>
<BatchCode>4</BatchCode>
<YearOfProduction>2019</YearOfProduction>
<QuantityToPick>10</QuantityToPick>
<CountryOfOriginISO>ES</CountryOfOriginISO>
</Lot>
</Line>
</PickingList>
</Envelope>
</XXX>
The instruction:
<xsl:for-each select="XXX/Envelope/PickingList/Line/Lot">
puts you in the context of Lot. From this context, the test in:
<xsl:when test="XXX/Envelope/GLN_Supplier =208">
will return false, because the relative path XXX/Envelope/GLN_Supplierselects nothing. You will get a different result if you make the path an absolute one:
<xsl:when test="/XXX/Envelope/GLN_Supplier =208">
Of course, it would be more efficient to do this test once outside the xsl:for-each block and make the result available in a variable - for example:
<xsl:template match="/XXX">
<output>
<xsl:variable name="articleCodePrefix">
<xsl:choose>
<xsl:when test="Envelope/GLN_Supplier=208">AV</xsl:when>
<xsl:when test="Envelope/GLN_Supplier=906">KR</xsl:when>
<xsl:otherwise>LX</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:for-each select="Envelope/PickingList/Line/Lot">
<articleLine type="tag">
<articleCode>
<xsl:value-of select="$articleCodePrefix"/>
<xsl:value-of select="../ArticleCodeSupplier"/>
</articleCode>
</articleLine>
</xsl:for-each>
</output>
</xsl:template>

How to put a field coming from <xsl: value-of select = "#" /> inside a tag?

I can not add within a tag a field with the (<xsl:value-of select=)
I'm creating an XSLT template that transforms data from an Excel into XML. Everything is working fine but now I wanted to add to the userdata tag the username of excel (#User_Name).
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<xsl:template match="/">
<tXML>
<Header>
<Source>TESTE</Source>
<Action_Type>Upgrade</Action_Type>
<Message_Type>User</Message_Type>
<Company_ID>200000002</Company_ID>
<Msg_Locale>English (United Kingdom)</Msg_Locale>
<Internal_Date_Time_Stamp>08/09/2017 17:35:00</Internal_Date_Time_Stamp>
</Header>
<Message>
<xsl:for-each select="//rs:data">
<xsl:apply-templates/>
</xsl:for-each>
</Message>
</tXML>
</xsl:template>
<xsl:template match="//z:row">
<UserData User_Name="{#User_Name}">
<xsl:value-of select="#User_Name"/>
<User>
<Company_Name>
<xsl:value-of select="#Company_Name"/>
</Company_Name>
<Location_Name>
<xsl:value-of select="#Location_Name"/>
</Location_Name>
<Role_Name>
<xsl:value-of select="#Role_Name"/>
</Role_Name>
<User_Name>
<xsl:value-of select="#User_Name"/>
</User_Name>
<First_Name>
<xsl:value-of select="#First_Name"/>
</First_Name>
<Last_Name>
<xsl:value-of select="#Last_Name"/>
</Last_Name>
<Solutions>
<RestrictedSolution>TESTE</RestrictedSolution>
</Solutions>
</User>
</UserData>
</xsl:template>
</xsl:stylesheet>

Generic XSLT to do XML to CSV - almost there, but stuck

I have gathered bits and pieces of this XSLT from these forums. I'm trying to put them altogether to create a single, generic XSLT that can be used to convert XML to CSV by specifying the path to the nodes that should be included in the CSV file.
I have three things that I still can't figure out after about 10 hours of messing with it.
I want to iterate over each column named in csv:columns. During each iteration, I need to extract and store the text() of the column. I think this is the way to iterate, but want to make sure:
<xsl:for-each select="document('')/*/csv:columns/*">
Once I have the text() from the column, I need to put that into the columnname variable in such a way that it works when it is used with getNodeValue.
I was unable to set columnname using variable. If I didn't hard-code the value (surrounded by apostrophes), I could not get it to work. This is why I have the following line in the code:
<xsl:variable name="columnname" select="'location/city'" />
I want to pass the result of getNodeValue into quotevalue so that the result is properly quoted.
The XSLT:
<?xml version="1.0"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:csv="csv:csv" xpath-default-namespace="http://nowhere/" >
<xsl:output method="text" encoding="utf-8" />
<xsl:strip-space elements="*" />
<xsl:variable name="delimiter" select="','" />
<csv:columns>
<column>title</column>
<column>location/city</column>
</csv:columns>
<xsl:template match="job">
<xsl:value-of select="concat(#id, ',')"/>
<!-- #1 I WANT TO LOOP THROUGH ALL OF THE CSV COLUMNS HERE -->
<!-- #2 How do I put the text into the variable 'columnname' variable so that it works with getNodeValue? -->
<xsl:variable name="columnname" select="'location/city'" />
<xsl:variable name="vXpathExpression" select="$columnname"/>
<xsl:call-template name="getNodeValue">
<xsl:with-param name="pExpression" select="$vXpathExpression"/>
</xsl:call-template>
<!-- #3 After getNodeValue gets the value, I want to send that value into 'quotevalue' -->
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template name="getNodeValue">
<xsl:param name="pExpression"/>
<xsl:param name="pCurrentNode" select="."/>
<xsl:choose>
<xsl:when test="not(contains($pExpression, '/'))">
<xsl:value-of select="$pCurrentNode/*[name()=$pExpression]"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="getNodeValue">
<xsl:with-param name="pExpression"
select="substring-after($pExpression, '/')"/>
<xsl:with-param name="pCurrentNode" select=
"$pCurrentNode/*[name()=substring-before($pExpression, '/')]"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="quotevalue">
<xsl:param name="value"/>
<xsl:choose>
<!-- Quote the value if required -->
<xsl:when test="contains($value, '"')">
<xsl:variable name="x" select="replace($value, '"', '""')"/>
<xsl:value-of select="concat('"', $x, '"')"/>
</xsl:when>
<xsl:when test="contains($value, $delimiter)">
<xsl:value-of select="concat('"', $value, '"')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$value"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Sample XML
<?xml version="1.0" encoding="utf-8"?>
<positionfeed
xmlns="http://nowhere/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
version="2006-04">
<job id="2830302">
<employer>Acme</employer>
<title>Manager</title>
<description>Full time</description>
<postingdate>2016-09-15T23:12:13Z</postingdate>
<location>
<city>Los Angeles</city>
<state>California</state>
</location>
</job>
<job id="2830303">
<employer>Acme</employer>
<title>Clerk, evenings</title>
<description>Part time</description>
<postingdate>2016-09-15T23:12:13Z</postingdate>
<location>
<city>Albany</city>
<state>New York</state>
</location>
</job>
</positionfeed>
The current output using the XSLT I provided
2830302,Los Angeles
2830303,Albany
The output if the XSLT works as desired
2830302,Manager,Los Angeles
2830303,"Clerk, evenings",Albany
Solution (many thanks to Tim's help below)
<?xml version="1.0"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:csv="csv:csv" xpath-default-namespace="http://www.job-search-engine.com/add-jobs/positionfeed-namespace/" >
<xsl:output method="text" encoding="utf-8" />
<xsl:strip-space elements="*" />
<!-- Set the value of the delimiter character -->
<xsl:variable name="delimiter" select="','" />
<!-- The name of the node that contains the column values -->
<xsl:param name="containerNodeName" select="'job'"/>
<!-- All nodes that should be ignored during processing -->
<xsl:template match="source|feeddate"/>
<!-- The names of the nodes to be included in the CSV file -->
<xsl:variable name="columns" as="element()*">
<column header="Title">title</column>
<column header="Category">category</column>
<column header="Description">description</column>
<column header="PostingDate">postingdate</column>
<column header="URL">joburl</column>
<column header="City">location/city</column>
<column header="State">location/state</column>
</xsl:variable>
<!-- ************** DO NOT TOUCH BELOW **************** -->
<!-- ************** DO NOT TOUCH BELOW **************** -->
<!-- ************** DO NOT TOUCH BELOW **************** -->
<!-- ************** DO NOT TOUCH BELOW **************** -->
<!-- ************** DO NOT TOUCH BELOW **************** -->
<!-- Warn about unmatched nodes -->
<xsl:template match="*">
<xsl:message terminate="no">
<xsl:text>WARNING: Unmatched element: </xsl:text>
<xsl:value-of select="name()"/>
</xsl:message>
<xsl:apply-templates/>
</xsl:template>
<!-- Generate the column headers -->
<xsl:template match="//*[*[local-name()=$containerNodeName]]">
<xsl:value-of select="'Id'"/>
<xsl:value-of select="$delimiter"/>
<xsl:for-each select="$columns/#header">
<xsl:variable name="colname" select="." />
<xsl:value-of select="$colname"/>
<xsl:if test="position() != last()">
<xsl:value-of select="$delimiter"/>
</xsl:if>
</xsl:for-each>
<xsl:text>
</xsl:text>
<xsl:apply-templates />
</xsl:template>
<!-- Generate the rows of column data -->
<xsl:template match="//*[local-name()=$containerNodeName]">
<!-- TODO: Handle attributes generically -->
<xsl:value-of select="#id"/>
<xsl:variable name="container" select="." />
<xsl:for-each select="$columns">
<xsl:value-of select="$delimiter"/>
<xsl:variable name="vXpathExpression" select="."/>
<xsl:call-template name="getQuotedNodeValue">
<xsl:with-param name="pCurrentNode" select="$container"/>
<xsl:with-param name="pExpression" select="$vXpathExpression"/>
</xsl:call-template>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template name="getQuotedNodeValue">
<xsl:param name="pExpression"/>
<xsl:param name="pCurrentNode" select="."/>
<xsl:choose>
<xsl:when test="not(contains($pExpression, '/'))">
<xsl:variable name="result" select="$pCurrentNode/*[name()=$pExpression]"/>
<xsl:call-template name="quotevalue">
<xsl:with-param name="value" select="$result"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="getQuotedNodeValue">
<xsl:with-param name="pExpression" select="substring-after($pExpression, '/')"/>
<xsl:with-param name="pCurrentNode" select= "$pCurrentNode/*[name()=substring-before($pExpression, '/')]"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="quotevalue">
<xsl:param name="value"/>
<xsl:choose>
<xsl:when test="contains($value, '"')">
<!-- Quote the value and escape the double-quotes -->
<xsl:variable name="x" select="replace($value, '"', '""')"/>
<xsl:value-of select="concat('"', $x, '"')"/>
</xsl:when>
<xsl:otherwise>
<!-- Quote the value -->
<xsl:value-of select="concat('"', $value, '"')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Sample data to demonstrate solution
<?xml version="1.0" encoding="utf-8"?>
<positionfeed
xmlns="http://www.job-search-engine.com/add-jobs/positionfeed-namespace/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.job-search-engine.com/add-jobs/positionfeed-namespace/ http://www.job-search-engine.com/add-jobs/positionfeed.xsd"
version="2006-04">
<source>Casting360</source>
<feeddate>2016-11-11T21:48:34Z</feeddate><job id="1363612">
<employer>Casting360</employer>
<title>The Robert Irvine Show Is Seeking Guests</title>
<category>Reality TV</category>
<description>TV personality ROBERT IRVINE (Restaurant Impossible) is seeking guests looking for solutions to their unique problems to share their stories on his show!
Our next show is Thursday, September 22nd in LA. If you're not in LA we will provide your airfare, hotel, car service, and per diem.
Please note: WE ARE NOT LOOKING FOR RESUMES; THIS IS NOT AN ACTING GIG. We are looking for real people to share their stories!
*appearance fee (TBD)
If you or someone you know has a conflict that they need help resolving, WE WANT TO HEAR FROM YOU.
Please email tvgal.ri#gmail.com the following information:
Name
Phone number
Your story in 2-3 paragraphs
1-3 photos of yourself.</description>
<postingdate>2016-09-15T23:12:13Z</postingdate>
<joburl>http://casting360.com/lgj/8886644624?jobid=1363612&city=Los+Angeles&state=CA</joburl>
<location>
<nation>USA</nation>
<city>Los Angeles</city>
<state>California</state>
</location>
<jobsource>Casting360</jobsource>
</job><job id="1370302">
<employer>Casting360</employer>
<title>Photoshoot for Publication</title>
<category>Modeling</category>
<description>6 FEMALE Models are wanted for publication photoshoot.
If you're not in the NYC Vicinity (NY, Pa, Ct,) DO NOT REPLY because your response will be summarily ignored.
Chosen models will be given a 5 look photo shoot. The shoot will occur on location (outdoors) in highly public locations chosen both for it's convenience and scenery.
The 5 looks (outfits) will be pre-determined by our staff of items most outfits within a model's wardrobe.
THIS IS A TF (UNPAID) SHOOT. After the release of the magazine, the photos agreed upon from the shoot shall be given to the model (in digital format) for her to build her portfolio.
Chosen models will receive a 5 outfit photo shoot at no cost to them by a NY Fashion Photographer.As a result, chosen models not only receive a free photo shoot, but also become PUBLISHED MODELS featured in a magazine.
The model (Janeykay) centered in the photo attached (Please look at the attached photo) is a Casting360 member who not only received her photo shoot, not only is being featured in a magazine, but also made the cover becoming a Cover Model from her shoot with us.</description>
<postingdate>2016-10-03T00:34:43Z</postingdate>
<joburl>http://casting360.com/lgj/8886644624?jobid=1370302&city=New+York&state=NY</joburl>
<location>
<nation>USA</nation>
<city>New York</city>
<state>New York</state>
</location>
<jobsource>Casting360</jobsource>
</job><job id="1370962">
<employer>Casting360</employer>
<title>Actresses Needed for "Red Shore", Action Film</title>
<category>Acting</category>
<description>CASTING (non-union)
We are a New Independent company looking to shoot our first feature. We are currently looking to fill two Major roles.
Female/African American, Hispanic, Asian, Pacific Islander/ 5'5-5'10/ Age Late 30's-Early 40's.
Project description: A long standing feud between two best friends turned enemies escalates over a valuable Diamond on display in a New York City Museum. With the stakes high they each seek the help of both friends and strangers to settle their feud once and for all.
Please note this is a non-paid project.
Fight training will be provided for free.
Please email including age and height in your e-mail.
Those selected will be invited to our audition.</description>
<postingdate>2016-10-03T14:18:20Z</postingdate>
<joburl>http://casting360.com/lgj/8886644624?jobid=1370962&city=New+York&state=NY</joburl>
<location>
<nation>USA</nation>
<city>New York</city>
<state>New York</state>
</location>
<jobsource>Casting360</jobsource>
</job>
</positionfeed>
As you are using XSLT 2.0, you could define your columns in a variable like so:
<xsl:variable name="columns" as="element()*">
<column>title</column>
<column>location/city</column>
</xsl:variable>
Then you can just iterate over them with a simple statement
<xsl:for-each select="$columns">
But the problem you may be having is that within this xsl:for-each you have changed context. You are no longer positioned on a job element, but the column element, and you don't want your expression to be relative to that. You really need to swap back to being on the job element, which you can do simply by setting a variable reference to the job element before the xsl:for-each and then using that as a parameter to the named template:
<xsl:template match="job">
<xsl:value-of select="#id"/>
<xsl:variable name="job" select="." />
<xsl:for-each select="$columns">
<xsl:value-of select="$delimiter"/>
<xsl:variable name="vXpathExpression" select="."/>
<xsl:call-template name="getNodeValue">
<xsl:with-param name="pCurrentNode" select="$job"/>
<xsl:with-param name="pExpression" select="$vXpathExpression"/>
</xsl:call-template>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:template>
As for quoting the result; instead of doing just xsl:value-of simply call the quote template with the value as a parameter
<xsl:when test="not(contains($pExpression, '/'))">
<xsl:call-template name="quotevalue">
<xsl:with-param name="value" select="$pCurrentNode/*[name()=$pExpression]" />
</xsl:call-template>
</xsl:when>
EDIT: If you want a header row of column names, you would have to match the parent of the job node, and then just output the values of the $column variable
<xsl:template match="*[job]">
<xsl:value-of select="$columns" separator="," />
<xsl:text>
</xsl:text>
<xsl:apply-templates />
</xsl:template>
Or maybe this if you didn't want the full path
<xsl:value-of select="$columns/(tokenize(., '/')[last()])" separator="," />
Or you could extend your columns variable to have the header text
<xsl:variable name="columns" as="element()*">
<column header="Title">title</column>
<column header="City">location/city</column>
</xsl:variable>
Then you would do this...
<xsl:value-of select="$columns/#header" separator="," />

Why do I end up with 8 extra spaces when applying the transform from within MatLab rather than letting Firefox handle it?

I have the following structure:
<suite>
<faults>
<fault componentName="comp1">
<introduceWhen>Time1</introduceWhen>
<signals>
<signal name="sig11" value="1"/>
<signal name="sig22" value="1"/>
</signals>
</fault>
<fault componentName="comp2">
<introduceWhen>Time2</introduceWhen>
<signals>
<signal name="sig44" value="0"/>
</signals>
</fault>
</faults>
</suite>
And using the follwing template I extract some data and put it in a table cell that has white-space: pre set, in order to allow me to put each fault on a new line.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:output doctype-system="http://www.w3.org/TR/html4/strict.dtd"/>
<xsl:output doctype-public="-//W3C//DTD HTML 4.01//EN"/>
<xsl:template match="/suite">
<html>
<head>
<title>Space Demo</title>
<style>
td.faults {text-align: left; white-space: pre;}
</style>
</head>
<body>
<table>
<tr>
<td class="faults">
<xsl:apply-templates select="faults"/>
</td>
</tr>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="faults">
<xsl:for-each select="fault">
<xsl:value-of select="concat(#componentName, ' ', introduceWhen, ' ')"/>
<xsl:for-each select="signals/signal">
<xsl:value-of select="concat(#value, ' ')"/>
</xsl:for-each>
<xsl:if test="position()!=last()">
<xsl:text>
</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
It works exactly as I want it to if I only display the XML in Firefox, with the XSLT as a stylesheet. But if I apply the transform in MatLab, the resulting HTML gets generated with an extra 8 spaces after the $1
. MatLab uses Saxon 6.5.5 for XSLT processing.
I tried using the following template to trim the string, but it made no difference:
<xsl:template name="trim">
<xsl:param name="str"/>
<xsl:choose>
<xsl:when test="string-length($str) > 0 and substring($str, 1, 1) = ' '">
<xsl:call-template name="trim">
<xsl:with-param name="str">
<xsl:value-of select="substring($str, 2)"/>
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:when test="string-length($str) > 0 and substring($str, string-length($str)) = ' '">
<xsl:call-template name="trim">
<xsl:with-param name="str">
<xsl:value-of select="substring($str, 1, string-length($str)-1)"/>
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$str"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
What can I do to get rid of the extra 8 spaces? There is a working example of this behavior here: http://xsltransform.net/jz1PuP4
The indent is apparently an attempt of the Saxon processor to make the HTML code more pretty. Neither Xalan nor libxslt produce a similar indent.
Since HTML browsers ignore superfluous white space characters, the addition is harmless and I see no reason why you should be concerned.
Changing the output method to xml will remove the indent, because unlike with HTML, white space can be critical.

Display different xsl:attribute depending on the ending of a string

I have the following xsl code in an xsl document
<A target="_blank" style="text-decoration=none">
<xsl:attribute name="href">viewdoc.aspx?doc=<xsl:value-of select="URLFilePath"/>&mode=inline</xsl:attribute>
<xsl:attribute name="prefix"><xsl:value-of select="FileName"/>: </xsl:attribute>
<IMG src="images/word_small.gif" border="0"/>
</A>
and in the code-behind I am doing this
newItemNode = xmlDocument.CreateElement("URLFilePath")
newItemNode.InnerText = correctedPath
xmlItemNode.ParentNode.AppendChild(newItemNode)
Now that works fine for word documents. However I need a way in code to check the extension of the file, and display the correct Image and xsl:attribute depending on the If statement.
So the If statement will be like this:-
If correctedPath.ToLower.Contains(".doc") Then
//display the word icon and attributes
Else
//display the excel icon and attributes
End If
Can you please give me some tips and help on how I can achieve this?
Thanks
Just using contains() may generally produce the wrong results (see the test XML document).
What is necessary is a ends-with() function, which is standard in XPath 2.0 and can be implemented in XSLT 1.0 as in the following transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="URLFilePath">
<xsl:variable name="visDoc">
<xsl:call-template name="ends-with">
<xsl:with-param name="pEnding" select="'.doc'"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="visXls">
<xsl:call-template name="ends-with">
<xsl:with-param name="pEnding" select="'.xls'"/>
</xsl:call-template>
</xsl:variable>
<xsl:choose>
<xsl:when test="$visDoc=1">word_small.gif</xsl:when>
<xsl:when test="$visXls=1">xls_small.gif</xsl:when>
<xsl:otherwise>unknown_small.gif</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="ends-with">
<xsl:param name="pEnding"/>
<xsl:value-of select=
"number(substring(.,
string-length() -string-length($pEnding) +1
)
=
$pEnding
)
"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following test XML document:
<files>
<URLFilePath>myFile.doc</URLFilePath>
<URLFilePath>myFile.xls</URLFilePath>
<URLFilePath>myFile.xls.doc</URLFilePath>
<URLFilePath>myFile.doc.xls</URLFilePath>
</files>
the correct result is produced:
word_small.gif
xls_small.gif
word_small.gif
xls_small.gif
Do note that just using contains() produces incorrect results.
I managed to come up with a solution! Sorry for the late reply but had to work on something else
Here is the code:-
<A target="_blank" style="text-decoration=none">
<xsl:choose>
<xsl:when test="contains(., '.doc')">
<xsl:attribute name="href">viewdoc.aspx?doc=<xsl:value-of select="URLFilePath"/>&mode=inline
</xsl:attribute>
<xsl:attribute name="prefix">
<xsl:value-of select="FileName"/>:
</xsl:attribute>
<IMG src="images/word_small.gif" border="0"/>
</xsl:when>
<xsl:when test="contains(., '.xls')">
<xsl:attribute name="href">viewxls.aspx?doc=<xsl:value-of select="URLFilePath"/>&mode=inline
</xsl:attribute>
<xsl:attribute name="prefix">
<xsl:value-of select="FileName"/>:
</xsl:attribute>
<IMG src="images/excel_small.gif" border="0"/>
</xsl:when>
</xsl:choose>
</A>
Thanks for all your help guys, really very much appreciated!
A late reply but I hit to two answers that deal with matching end of string with XSLT 1.0 and are very elegant:
https://stackoverflow.com/a/3081205/520567
https://stackoverflow.com/a/3081866/520567
go give them +1
This can be done purely in your XSLT document if you require. For displaying the image, you could use a xsl:choose statement, that tests the URLFilePath element
<xsl:choose>
<xsl:when test="contains(., '.doc')">
<IMG src="images/word_small.gif" border="0"/>
</xsl:when>
<xsl:when test="contains(., '.xls')">
<IMG src="images/excel_small.gif" border="0"/>
</xsl:when>
</xsl:choose>
If you wanted to do this check in the code behind, you could always add extra attributes to your URLFilePath element.
imageAttr = xmlDocument.CreateAttr("image")
If correctedPath.ToLower.Contains(".doc") Then
imageAttr.value = "images/word_small.gif"
Else
imageAttr.value = "images/excel_small.gif"
End If
newItemNode.AppendChild(imageAttr)
And then, in your xls, you can simply use this attribute to set the source attribute of the image
<IMG border="0">
<xsl:attribute name="src"><xsl:value-of select='#image' /></xsl:attribute>
</IMG>