EDIT: I have corrected the code I had shared previously as there were some errors. I have tried the below suggestions Set xmldoc = CreateObject("MSXML2.DOMDocument.3.0") and Set xmldoc = CreateObject("MSXML2.DOMDocument.6.0")) with no success.
To give some context, the SVG is generated, through an Excel file I prepare, from a third party software I feed the Excel file with, so the word Item is the keyword I use to mark those paths for which I want the text to appear, this removal is to clean up the resulting SVG.
I would like to remove the string Item inside tspan, so from <tspan id="Item1-tspan" x="" y="">Item1</tspan> to <tspan id="Item1-tspan" x="" y="">1</tspan>.
I have tried all possible solutions, yet I am not able to replace text with XSLT through VBA. I would like to remove the word "Item" and I went through every single answer I found on StackOverflow and in othtr websites. I either do not get the wanted result or I get errors.
I call it with this simple macro:
VBA
Sub AddTextToSVGReplace()
Dim StrFileName As String
Dim StrFolder As String
Dim StrFolderTarget As String
Dim xmldoc As Object
Dim xsldoc As Object
Dim newdoc As Object
With Application.FileDialog(msoFileDialogFolderPicker)
.Title = "Select the folder where the vector file is stored"
If .Show = -1 Then
StrFolder = .SelectedItems(1) & "\"
End If
End With
With Application.FileDialog(msoFileDialogFolderPicker)
.Title = "Select the folder where the edited vector file should be stored"
If .Show = -1 Then
StrFolderTarget = .SelectedItems(1) & "\"
End If
End With
Set xmldoc = CreateObject("MSXML2.DOMDocument")
Set xsldoc = CreateObject("MSXML2.DOMDocument")
Set newdoc = CreateObject("MSXML2.DOMDocument")
StrFileName = Dir(StrFolder & "*.svg")
'Load XML
xmldoc.async = False
xmldoc.Load StrFileName
'Load XSL
xsldoc.async = False
xsldoc.Load StrFolder & "\" & "TextAdditionReplace.xsl"
'Transform
xmldoc.transformNodeToObject xsldoc, newdoc
newdoc.Save StrFolderTarget & "WithNames" & StrFileName
End Sub
This is the SVG file I would like to transform
SVG (extract, only relevant part)
<g id="symbols-svg">
<g id="Item1-svg" transform="translate(105, 210)">
<path d="M-5 0a5 5 0 1 0 10 0 5 5 0 1 0-10 0Z" stroke="rgb(200, 200, 200)" id="Item1" style="fill: rgb(0, 15, 60); stroke-width: 1; fill-opacity: 0.9; stroke-opacity: 0.5; stroke-linejoin: miter; stroke-linecap: butt; stroke: rgb(0, 50, 100);">
</path>
<text x="" y="" id="Item1-text" style="-inkscape-font-specification:'Calibri, Normal';font-family:Calibri;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:20px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000">
<tspan id="Item1-tspan" x="" y="">Item1</tspan>
</text>
</g>
<g id="Item2-svg" transform="translate(250, 90)">
<path d="M-5 0a5 5 0 1 0 10 0 5 5 0 1 0-10 0Z" stroke="rgb(200, 200, 200)" id="Item2" style="fill: rgb(0, 15, 60); stroke-width: 1; fill-opacity: 0.9; stroke-opacity: 0.5; stroke-linejoin: miter; stroke-linecap: butt; stroke: rgb(0, 50, 100);">
</path>
<text x="" y="" id="Item2-text" style="-inkscape-font-specification:'Calibri, Normal';font-family:Calibri;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:20px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000">
<tspan id="Item2-tspan" x="" y="">Item2</tspan>
</text>
</g>
</g>
This is the XSLT I am using
XSLT
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
exclude-result-prefixes="svg"
version="1.0">
<xsl:output method="xml" encoding="utf-8" omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="svg:g[#id[starts-with(., 'Item')]]">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
<xsl:variable name="id" select="substring-before(#id, '-')"/>
<text x="" y="" id="{$id}-text" style="-inkscape-font-specification:'Calibri, Normal';font-family:Calibri;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:20px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000">
<tspan id="{$id}-tspan" x="" y="">
<xsl:value-of select="$id"/>
</tspan>
</text>
</xsl:copy>
</xsl:template>
<xsl:template name="string-replace-all">
<xsl:param name="text" />
<xsl:param name="replace" />
<xsl:param name="by" />
<xsl:choose>
<xsl:when test="contains($text, $replace)">
<xsl:value-of select="substring-before($text,$replace)" />
<xsl:value-of select="$by" />
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text"
select="substring-after($text,$replace)" />
<xsl:with-param name="replace" select="$replace" />
<xsl:with-param name="by" select="$by" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- template call -->
<xsl:variable name="result">
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="$text" />
<xsl:with-param name="replace" select="'Item'" />
<xsl:with-param name="by" select="''" />
</xsl:call-template>
</xsl:variable>
<xsl:template match="processing-instruction('xml-stylesheet')"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Finally, this is the result I would like to have:
Wanted SVG
<g id="symbols-svg">
<g id="Item1-svg" transform="translate(105, 210)">
<path d="M-5 0a5 5 0 1 0 10 0 5 5 0 1 0-10 0Z" stroke="rgb(200, 200, 200)" id="Item1" style="fill: rgb(0, 15, 60); stroke-width: 1; fill-opacity: 0.9; stroke-opacity: 0.5; stroke-linejoin: miter; stroke-linecap: butt; stroke: rgb(0, 50, 100);">
</path>
<text x="" y="" id="Item1-text" style="-inkscape-font-specification:'Calibri, Normal';font-family:Calibri;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:20px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000">
<tspan id="Item1-tspan" x="" y="">1</tspan>
</text>
</g>
<g id="Item2-svg" transform="translate(250, 90)">
<path d="M-5 0a5 5 0 1 0 10 0 5 5 0 1 0-10 0Z" stroke="rgb(200, 200, 200)" id="Item2" style="fill: rgb(0, 15, 60); stroke-width: 1; fill-opacity: 0.9; stroke-opacity: 0.5; stroke-linejoin: miter; stroke-linecap: butt; stroke: rgb(0, 50, 100);">
</path>
<text x="" y="" id="Item2-text" style="-inkscape-font-specification:'Calibri, Normal';font-family:Calibri;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:20px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000">
<tspan id="Item2-tspan" x="" y="">2</tspan>
</text>
</g>
</g>
With <xsl:with-param name="text" select="$text" />(both CreateObject("MSXML2.DOMDocument.3.0") and "MSXML2.DOMDocument.6.0") I get error '-2147467259 (80004005)' A reference to variable or parameter 'text' cannot be resolved. The variable or parameter may not be defined, or it may not be in scope.
With <xsl:with-param name="text" select="'Item'" /> nothing happens in both cases. Nor does it with <xsl:with-param name="text" select="'{Item}'" />.
I also tried nesting as per below (it may look like blasphemy to experts)
<xsl:template match="svg:tspan[#id[starts-with(., 'Item')]]">
<xsl:variable name="result">
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="'Item'" />
<xsl:with-param name="replace" select="'Item'" />
<xsl:with-param name="by" select="''" />
</xsl:call-template>
</xsl:variable>
</xsl:template>
I cannot think of any more combinations (apart of course from the correct one...).
To remove a single instance of the text Item from a string $s, you can use this expression:
concat(substring-before($s, 'Item'), substring-after($s, 'Item'))
That assumes that $s does contain the string Item (substring-before and substring-after will return an empty string if the substring is not found), but if computation takes place in a template whose match expression checks that the text Item is indeed there, then that's a safe assumption.
In your example the text Item is always at the start of the string, and your template's match checks for that case. So you could just use:
substring-after(., 'Item')
EDIT: adding a full stylesheet:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<!-- template to trim "Item" from the start of every text node -->
<xsl:template match="text()[starts-with(., 'Item')]">
<xsl:value-of select="substring-after(., 'Item')"/>
</xsl:template>
<!-- identity template copies everything else unchanged -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
View as XSLT Fiddle
I'm adding this as another "answer" although it's not a proposed solution to your problem (unlike my other answer which is a proposed solution).
I just want to point out a couple of problems with the example code you've posted, because I think they point to a misunderstanding of how XSLT works, and I think they show you need to read up on the basics of XSLT, otherwise you're reduced to copy-and-paste of other people's code, without understanding it, in the hope that you might get lucky with some random combination.
I hope this is helpful!
This variable assignment has two problems:
<xsl:variable name="result">
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="$text" />
<xsl:with-param name="replace" select="'Item'" />
<xsl:with-param name="by" select="''" />
</xsl:call-template>
</xsl:variable>
Firstly, there's an illegal reference to a variable called $text. Because the <xsl:variable name="result"> is a child of the xsl:stylesheet element, that makes it a "top-level" or "global" variable. Effectively, such variables are calculated first (and once only!) when the stylesheet is processed, and within such a variable assignment, the only variables which can be referred to are other top-level variables or stylesheet parameters. You've referred to a variable called $text but there's no top-level variable $text.
Secondly, because $result is a top-level variable, that means it's visible to code anywhere else in the stylesheet; its value can be accessed anywhere by referring to the variable name $result. But in fact there are no references to $result anywhere in the stylesheet. That means this variable assignment is "dead code"; it does nothing. Typically, a stylesheet processor would ignore this code altogether, and should probably even warn you, because "dead code" while harmless in itself is generally a sign that the programmer has made a mistake.
You also mention another attempt:
<xsl:template match="svg:tspan[#id[starts-with(., 'Item')]]">
<xsl:variable name="result">
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="'Item'" />
<xsl:with-param name="replace" select="'Item'" />
<xsl:with-param name="by" select="''" />
</xsl:call-template>
</xsl:variable>
</xsl:template>
This template will match tspan elements whose id starts with 'Item', but what effect will the template have? It does not produce any output, so its effect will be to "eat" those tspan elements. Note that again, the $result variable is "dead code"; a variable has a value assigned but then the variable is not actually used for anything. Here, because the variable is defined within a template, the variable is visible only to following siblings (i.e. it can be referred to only within XSLT elements which follow it at the same level). But this variable assignment is the last statement in the template, so there's no way that anything can refer to it.
Related
I have XML form data coming in from a vendor app that allows me to select values for fields on the form by using this format.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsl:stylesheet version="1.0" xmlns="http://www.w3.org/1999/XSL/Transform" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:output method="text" /><xsl:template match="/"><xsl:apply-templates/></xsl:template>
<xsl:template match="DataSet/diffgr:diffgram/Forms">
<xsl:for-each select="Form">
<xsl:value-of select="FieldName1"/>
<xsl:value-of select="FieldName2"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I can't see the XML, but I can surmise from this that all of the fields are siblings within the <form> and looks something like this:
<form>
<CosignerName1 value="John Doe"/>
<CosignerDate1 value="1/1/1970"/>
<CosignerTime1 value="8:46 PM"/>
<CosignerName2 value="Jane Smith"/>
<CosignerDate2 value="2/2/1972"/>
<CosignerTime2 value="11:46 AM"/>
...
<CosignerName12 value="Will Hunting"/>
<CosignerDate12 value="12/12/1982"/>
<CosignerTime12 value="1:00 AM"/>
</form>
<form>
<CosignerName1 value="Bill Thomas"/>
<CosignerDate1 value="5/5/2020"/>
<CosignerTime1 value="8:46 PM"/>
<CosignerName2 value="Bev Poole"/>
<CosignerDate2 value="6/6/2022"/>
<CosignerTime2 value="11:46 AM"/>
...
<CosignerName12 value="Bob Ross"/>
<CosignerDate12 value="12/12/1982"/>
<CosignerTime12 value="1:00 AM"/>
</form>
Known factors:
Each form has 12 cosigners, each with numbered field names i.e. CosignerName1, CosignerName2, etc.
Each named cosigner also has a CosignerDate and a CosignerTime field associated by number. i.e. CosignerName1 has CosignerDate1 and CosignerTime1 fields that, when combined, show when that user signed the form.
I am using the word 'idnum' in my code and in this question to refer to the number at the end of each label that shows they are part of a set i.e CosignerName1, CosignerDate1, and CosignerTime1 all share idnum=1
Goal: Identify and select only the most recent CosignerName, based on CosignerDate and CosignerTime.
To do this, I figure I need to:
Find the most recent CosignerDate/CosignerTime combination
Read the label of either of the identified fields to extract the idnum from the end
Use the idnum to to identify the correct CosignerName and output it
I have some pieces of the solution, but no way to bring them together:
Create a Timestamp
I can combine a set of CosignerDate[idnum] and CosignerTime[idnum] fields, then translate them into a timestamp. If I could create an array of these timestamps, then I could sort and find the most recent one, but as I understand it XSLT 1.0 doesn't have arrays. So I am not sure what to do with this.
<xsl:variable name="mm" select="substring-before(CosignerDate1,'/')" />
<xsl:variable name="mmyyyy" select="substring-after(CosignerDate1,'/')"/>
<xsl:variable name="dd" select="substring-before($mmyyyy,'/')" />
<xsl:variable name="yyyy" select="substring-after($mmyyyy,'/')" />
<xsl:variable name="ampm" select="substring-after(CosignerTime1,' ')" />
<xsl:variable name="time">
<xsl:choose>
<xsl:when test="$ampm = 'AM'">
<xsl:value-of select="number(translate(substring-before(CosignerTime1,' '),':',''))" />
</xsl:when>
<xsl:when test="$ampm = 'PM'">
<xsl:value-of select="number(translate(substring-before(CosignerTime1,' '),':',''))+number('1200')" />
</xsl:when>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="concat('Timestamp:',$yyyy,$mm,$dd,$time)"/>
The code above outputs something like: Timestamp:202209191128
Get idnums
I can loop through all of the Cosigners and extract the identifying number shared by their labels.
<xsl:for-each select="*[starts-with(local-name(), 'CosignerName')]">
<xsl:variable name="idnum">
<xsl:value-of select="translate(local-name(),'CosignerName','')" />
</xsl:variable>
<xsl:value-of select="$idnum"/>
<xsl:value-of select="','"/>
</xsl:for-each>
This code above outputs something like: 1,4,7,12
But, this is where I get lost.
Failed Attempt
I tried something like the code below. The logic was to loop through each Cosigner name (12 iterations), get the idnum from the label, use that to get the CosignerDate and CosignerTime, create a timestamp, then sort the timestamps, if the timestamp we are looking at is the top one (most recent) then use the idnum to select the correct CosignerName and output it.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsl:stylesheet version="1.0" xmlns="http://www.w3.org/1999/XSL/Transform" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:output method="text" /><xsl:template match="/"><xsl:apply-templates/></xsl:template>
<xsl:template match="DataSet/diffgr:diffgram/Forms">
<xsl:for-each select="Form">
<xsl:for-each select="*[starts-with(local-name(), 'CosignerName')]">
<xsl:variable name="idnum">
<xsl:value-of select="translate(local-name(),'CosignerName','')" />
</xsl:variable>
<xsl:variable name="mm" select="substring-before(concat(CosignerDate,$idnum),'/')" />
<xsl:variable name="mmyyyy" select="substring-after(concat(CosignerDate,$idnum),'/')"/>
<xsl:variable name="dd" select="substring-before($mmyyyy,'/')" />
<xsl:variable name="yyyy" select="substring-after($mmyyyy,'/')" />
<xsl:variable name="ampm" select="substring-after(concat(CosignerTime,$idnum),' ')" />
<xsl:variable name="time">
<xsl:choose>
<xsl:when test="$ampm = 'AM'">
<xsl:value-of select="number(translate(substring-before(concat(CosignerTime,$idnum),' '),':',''))" />
</xsl:when>
<xsl:when test="$ampm = 'PM'">
<xsl:value-of select="number(translate(substring-before(concat(CosignerTime,$idnum),' '),':',''))+number('1200')" />
</xsl:when>
</xsl:choose>
</xsl:variable>
<xsl:variable name="timestamp" select="concat('Timestamp: ',$yyyy,$mm,$dd,$time)"/>
<xsl:sort select="$timestamp" data-type="number" order="descending"/>
<xsl:if test="position() = 1"><xsl:value-of select="."/></xsl:if>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
This of course fails for several reasons:
the concat with the $idnum doesn't actually pull values from fields
the sort has to be the first thing in the for-each
the sort has to happen after all of the timestamps are created
the sorted list of timestamps still needs to reference which idnum they were generated from
Without being able to create an array of timestamps and idnums, I don't know how you can accomplish this.
Does anyone know how can I get at the CosignerName[idnum] that is correlated with the most recent CosignerDate[idnum]/CosignerTime[idnum]?
I believe you could just sort the CosignerNameX nodes by the individual components of their associated dates and times, as shown in the two other answers I linked to:
https://stackoverflow.com/a/59288030/3016153
https://stackoverflow.com/a/30631073/3016153
However, it might be more efficient - and certainly more readable - to do this in two steps:
Construct a dateTime value for each cosigner;
Find the cosigner with the most recent value.
Consider the following example:
XML
<DataSet xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1">
<diffgr:diffgram>
<Forms>
<form>
<CosignerName1 value="John Doe"/>
<CosignerDate1 value="1/1/1970"/>
<CosignerTime1 value="8:46 PM"/>
<CosignerName2 value="Jane Smith"/>
<CosignerDate2 value="2/2/1972"/>
<CosignerTime2 value="11:46 AM"/>
<CosignerName12 value="Will Hunting"/>
<CosignerDate12 value="12/12/1982"/>
<CosignerTime12 value="1:00 AM"/>
</form>
<form>
<CosignerName1 value="Bill Thomas"/>
<CosignerDate1 value="5/5/2020"/>
<CosignerTime1 value="8:46 PM"/>
<CosignerName2 value="Bev Poole"/>
<CosignerDate2 value="6/6/2022"/>
<CosignerTime2 value="11:46 AM"/>
<CosignerName12 value="Bob Ross"/>
<CosignerDate12 value="12/12/1982"/>
<CosignerTime12 value="1:00 PM"/>
</form>
</Forms>
</diffgr:diffgram>
</DataSet>
XSLT 1.0 + EXSLT node-set()
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1"
xmlns:exsl="http://exslt.org/common"
exclude-result-prefixes="diffgr exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/DataSet">
<output>
<xsl:for-each select="diffgr:diffgram/Forms/form">
<xsl:variable name="datetimes">
<xsl:for-each select="*[starts-with(local-name(), 'CosignerName')]">
<!-- identify values -->
<xsl:variable name="i" select="substring-after(local-name(), 'CosignerName')" />
<xsl:variable name="date" select="../*[local-name()=concat('CosignerDate', $i)]/#value" />
<xsl:variable name="time" select="../*[local-name()=concat('CosignerTime', $i)]/#value" />
<!-- extract date components -->
<xsl:variable name="year" select="substring-after(substring-after($date, '/'), '/')" />
<xsl:variable name="month" select="substring-before($date, '/')" />
<xsl:variable name="day" select="substring-before(substring-after($date, '/'), '/')" />
<!-- extract time components -->
<xsl:variable name="hour12" select="substring-before($time, ':')" />
<xsl:variable name="minute" select="substring-before(substring-after($time, ':'), ' ')" />
<xsl:variable name="pm" select="contains($time,'PM')" />
<xsl:variable name="hour" select="$hour12 mod 12 + 12*$pm"/>
<!-- construct dateTime -->
<datetime cosigner="{#value}" index="{$i}">
<xsl:value-of select="$year"/>
<xsl:value-of select="format-number($month, '-00')"/>
<xsl:value-of select="format-number($day, '-00')"/>
<xsl:value-of select="format-number($hour, 'T00')"/>
<xsl:value-of select="format-number($minute, ':00')"/>
<xsl:text>:00</xsl:text>
</datetime>
</xsl:for-each>
</xsl:variable>
<!-- output -->
<form>
<xsl:for-each select="exsl:node-set($datetimes)/datetime">
<xsl:sort select="." data-type="text" order="descending"/>
<xsl:if test="position()=1">
<last-cosigner index="{#index}" dateTime="{.}" >
<xsl:value-of select="#cosigner"/>
</last-cosigner>
</xsl:if>
</xsl:for-each>
</form>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<output>
<form>
<last-cosigner index="12" dateTime="1982-12-12T01:00:00">Will Hunting</last-cosigner>
</form>
<form>
<last-cosigner index="2" dateTime="2022-06-06T11:46:00">Bev Poole</last-cosigner>
</form>
</output>
P.S. With a Microsoft processor you may need to change the namespace declaration:
xmlns:exsl="http://exslt.org/common"
to:
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
and then change the exsl prefix in lines #5 and #39 to msxsl.
I have a value in an element as 433554567643.
I would like to change it to 43 35545 67643. The grouping should start from right side of the value.
Is it possible to use subtring from end to start of the value?
Thanks.
You could do this with a recursive template.
<xsl:template name="add-spaces">
<xsl:param name="group" select="5" />
<xsl:param name="text" />
<xsl:if test="string-length($text) > $group">
<xsl:call-template name="add-spaces">
<xsl:with-param name="group" select="$group" />
<xsl:with-param name="text"
select="substring($text, 1, string-length($text) - $group)" />
</xsl:call-template>
<xsl:text> </xsl:text>
</xsl:if>
<xsl:value-of select="substring($text, string-length($text) - $group + 1)" />
</xsl:template>
You would call this when required using
<xsl:call-template name="add-spaces">
<xsl:with-param name="text" select="'433554567643'" />
<!-- or select="path/to/element" as appropriate -->
</xsl:call-template>
If your value is always a number, you could use format-number() with a pattern that groups the numbers by 5 digits and then translate() the "," into " ":
<xsl:value-of select="translate(format-number('433554567643', '#,#####'),
',', ' ')" />
Is it possible to have a better solution for the following:
Input xml
<product>
<text>
<languageId>en-us</languageId>
<description>some text en us</description>
</text>
<text>
<languageId>en-gb</languageId>
<description>some text en gb</description>
</text>
<text>
<languageId>en-us</languageId>
</text>
<product>
Output xml
<specifications>some text en us</specifications>
So, if there is a description with languageId = en-us and there is text present, then this text will be placed in the output xml, otherwise the element receives the attribute value xsi:nil=true
xslt version must be 1.0
XSLT
<ns0:specifications>
<!-- First loop, check if en-us is present, if so, check if there is a text! -->
<!-- If the 2 requirements are met, then this Txt element is used -->
<xsl:for-each select="s0:text">
<xsl:if test="translate(s0:LanguageId/text(),$smallcase,$uppercase)=translate('en-us',$smallcase,$uppercase)">
<xsl:if test="s0:Txt!=''">
<xsl:value-of select="s0:Txt/text()" />
</xsl:if>
</xsl:if>
</xsl:for-each>
<!-- Second loop, checks are the same. This loop is needed because xsl variables are immutable. If there is a better solution, just change the code!! -->
<!-- If the 2 requirements are met, then the variable is marked as true, else it's empty -->
<xsl:variable name="isEnUsPresent">
<xsl:for-each select="s0:text">
<xsl:if test="translate(s0:LanguageId/text(),$smallcase,$uppercase)=translate('en-us',$smallcase,$uppercase)">
<xsl:if test="s0:Txt!=''">
<xsl:value-of select="1" />
</xsl:if>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<!-- if the variable is empty, set the attribute value xsi:nil=true like below -->
<xsl:choose>
<xsl:when test="$isEnUsPresent=''">
<xsl:attribute name="xsi:nil">
<xsl:value-of select="'true'" />
</xsl:attribute>
</xsl:when>
</xsl:choose>
</ns0:specifications>
Regards
As simple as this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
exclude-result-prefixes="xsi">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<specifications>
<xsl:apply-templates select="*"/>
</specifications>
</xsl:template>
<xsl:template match="text[languageId='en-us']/description">
<xsl:value-of select="."/>
</xsl:template>
<xsl:template match="/*[not(text[languageId='en-us']/description)]">
<xsl:attribute name="xsi:nil">true</xsl:attribute>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<product>
<text>
<languageId>en-us</languageId>
<description>some text en us</description>
</text>
<text>
<languageId>en-gb</languageId>
<description>some text en gb</description>
</text>
<text>
<languageId>en-us</languageId>
</text>
</product>
the wanted, correct result is produced:
<specifications>some text en us</specifications>
When the same transformation is applied to this XML document:
<product>
<text>
<languageId>en-us-XXX</languageId>
<description>some text en us</description>
</text>
<text>
<languageId>en-gb</languageId>
<description>some text en gb</description>
</text>
<text>
<languageId>en-us</languageId>
</text>
</product>
again the wanted, correct result is produced:
<specifications xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
This works with static data:
<xsl:variable name="ExtensionData3">
<images>
<image id="10842" width="2450" height="2200" default="1" />
<image id="10543" width="2450" height="2200" default="0" />
<image id="10544" width="2450" height="2200" default="0" />
</images>
</xsl:variable>
<xsl:value-of select="msxsl:node-set($ExtensionData3)/images/image[#default='1']/#id" />
How do I get this to work with dynamic data?:
<xsl:variable name="ExtensionData3">
<xsl:value-of select="XMLData" />
</xsl:variable>
<xsl:value-of select="msxsl:node-set($ExtensionData3)/images/image[#default='1']/#id" />
How do I get this to work with dynamic data?:
<xsl:variable name="ExtensionData3">
<xsl:value-of select="XMLData" />
</xsl:variable>
<xsl:value-of select="msxsl:node-set($ExtensionData3)/images/image[#default='1']/#id"
/>
The error is in this line:
<xsl:value-of select="XMLData" />
This outputs (creates a text node with) the string value of the first XMLData child of the current node.
But you don't want string -- you want nodes to be copied.
The correct way to do this is:
<xsl:copy-of select="XMLData" />
Even better, in this case you don't need at all to create an RTF and then to convert it to a temporary tree -- just use:
<xsl:variable name="ExtensionData3" select="XMLData"/>
I am trying to loop through a node, which in itselt is not a problem. I want to check to see if the selectedLetter matches the first letter of the node, which is working as intended (see code below). The difficulties arise in that I would like to present some alternative text if the conditional "" is not met at least once during the loop, along the lines of "Sorry, no documents begin with that letter". I was thinking about setting a variable i.e. flag in the conditional and check later on if this variable has been set, but from what I have read here, the scope of the variable is restricted and only available within the loop? Can anyone offer any assistance?
Cheers,
Taff
<xsl:param name="selectedLetter" select="''"/>
<xsl:key name="docLetter" match="docs" use="substring(text()),1,1)"/>
<xsl:for-each select="//ad-documents/item">
<xsl:sort select="documentName"/>
<xsl:variable name="firstLetter" select="upper-case(substring(documentName, 1, 1))"/>
<xsl:choose>
<xsl:when test="$firstLetter = $selectedLetter">
<div class="doc-date">
<xsl:if test="upload-date/day < 10">0</xsl:if>
<xsl:value-of select="upload-date/day"/>
<xsl:text>.</xsl:text>
<xsl:if test="upload-date/month < 10">0</xsl:if>
<xsl:value-of select="upload-date/month"/>
<xsl:text>.</xsl:text>
<xsl:value-of select="upload-date/year"/>
</div>
<div class="doc-link">
<a>
<xsl:attribute name="href">
<xsl:choose>
<xsl:when test="isEditableFormDoc/box = 'true'">
#
</xsl:when>
<xsl:otherwise>
<xsl:text>/img/ejbfile/</xsl:text>
<xsl:value-of select="documents/document/#documentName"/>
<xsl:text>?id=</xsl:text>
<xsl:value-of select="documents/document/#src"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:if test="isEditableFormDoc/box = 'true'">
<xsl:attribute name="target">
<xsl:text>_blank</xsl:text>
</xsl:attribute>
</xsl:if>
<xsl:value-of select="documentName"/>
</a>
</div>
<div class="doc-short-desc">
<xsl:apply-templates select="document-short-desc" mode="format"/>
</div>
<!--<xsl:apply-templates select="//ad-documents/item" mode="filteredDocuments"/>-->
</xsl:when>
</xsl:choose>
</xsl:for-each>
EDIT:
XML Example
<ad-documents name="ad-documents" label="Dokumente (limitiert auf 20 pro Seite)">
<item description="Doc%203" id="1" timestamp="1328525592205">
<documentName name="documentName" description="Doc%203">Doc 3</documentName>
<optionalLetter name="optionalLetter" description="c">c</optionalLetter>
<document-short-desc name="document-short-desc" description="">
<p>yxf</p>
</document-short-desc>
<upload-date name="upload-date" description="">
<day>24</day>
<month>2</month>
<year>2012</year>
</upload-date>
<isEditableFormDoc name="isEditableFormDoc" description="">
<box/>
</isEditableFormDoc>
<documents name="documents" description=""/>
</item>
<item description="Doc%204" id="2" timestamp="1328525624889">
<documentName name="documentName" description="Doc%204">Doc 4</documentName>
<optionalLetter name="optionalLetter" description="z%2Ci%2Cg">z,i,g</optionalLetter>
<document-short-desc name="document-short-desc" description="">
<p>asff</p>
</document-short-desc>
<upload-date name="upload-date" description="">
<day>25</day>
<month>2</month>
<year>2012</year>
</upload-date>
<isEditableFormDoc name="isEditableFormDoc" description="">
<box/>
</isEditableFormDoc>
<documents name="documents" description=""/>
</item>
<item description="Doc%201" id="1" timestamp="1328523551639">
<documentName name="documentName" description="Doc%201">Doc 1</documentName>
<optionalLetter name="optionalLetter" description="b%2Cc">b,c</optionalLetter>
<document-short-desc name="document-short-desc" description="">
<p>Short Desc 1</p>
</document-short-desc>
<upload-date name="upload-date" description="">
<day>9</day>
<month>2</month>
<year>2012</year>
</upload-date>
<isEditableFormDoc name="isEditableFormDoc" description="">
<box/>
</isEditableFormDoc>
<documents name="documents" description=""/>
</item>
</ad-documents>
The solution is simple:
Just replace:
<xsl:for-each select="//ad-documents/item">
with
<xsl:for-each select=
"//ad-documents/item
[$selectedLetter eq upper-case(substring(documentName, 1, 1) ]">
You can add after this xsl:for-each
<xsl:sequence select=
"'YourErrorMessage'
[not(//ad-documents/item
[$selectedLetter eq upper-case(substring(documentName, 1, 1) ]
)
]"
/>
Thanks to Dimitre, I managed to solve the problem with count()
<xsl:variable name="foundDocs" select="count(//ad-documents/item[$selectedLetter eq upper-case(substring(documentName, 1, 1))])"/>
<xsl:if test="$foundDocs = 0">
<div class="no-documents">
<xsl:text>Leider keine Dokumente gefunden</xsl:text>
</div>
</xsl:if>