How do I rename root 'channel' and children 'item' to 'Records' and 'Record' respectively?
Input
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<item>
<title>A</title>
<link>/news/view/25857/A.html</link>
<guid isPermaLink="true">/news/view/25857/A.html</guid>
<comments>/news/25857/A.html</comments>
<pubDate>Sat, 03 Oct 2015 00:42:42 GMT</pubDate>
<description><![CDATA[]]></description>
<category>headline,hacker,bank,cybercrime,data loss,fraud</category>
</item>
</channel>
</rss>
XSLT
<?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" indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="item/comments">
<taglink>
<xsl:text></xsl:text>
<xsl:value-of select="text()" />
</taglink>
</xsl:template>
<!--delimits values if separated by comma-->
<xsl:template match="item/category[contains(.,',')]">
<category>
<xsl:variable name="elementName" select="name(..)"/>
<xsl:call-template name="splitIntoElements">
<xsl:with-param name="baseName" select="name(..)" />
<xsl:with-param name="txt" select="." />
</xsl:call-template>
</category>
</xsl:template>
<xsl:template name="splitIntoElements">
<xsl:param name="baseName" />
<xsl:param name="txt" />
<xsl:param name="delimiter" select="','" />
<xsl:param name="index" select="1" />
<xsl:variable name="first" select="substring-before($txt, $delimiter)" />
<xsl:variable name="remaining" select="substring-after($txt, $delimiter)" />
<xsl:element name="value">
<xsl:choose>
<xsl:when test="$first">
<xsl:value-of select="$first" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$txt" />
</xsl:otherwise>
</xsl:choose>
</xsl:element>
<xsl:if test="$remaining">
<xsl:call-template name="splitIntoElements">
<xsl:with-param name="baseName" select="$baseName" />
<xsl:with-param name="txt" select="$remaining" />
<xsl:with-param name="index" select="$index" />
<xsl:with-param name="delimiter" select="$delimiter" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Output
<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
<channel>
<item>
<title>A</title>
<link>/news/view/25857/A.html</link>
<guid isPermaLink="true">/news/view/25857/A.html</guid>
<taglink>/news/25857/A.html</taglink>
<pubDate>Sat, 03 Oct 2015 00:42:42 GMT</pubDate>
<description/>
<category>
<value>headline</value>
<value>hacker</value>
<value>bank</value>
<value>cybercrime</value>
<value>data loss</value>
<value>fraud</value>
</category>
</item>
</channel>
</rss>
Expected Output - There are many 'item' within 'channel' so there is expected be have many 'records' within 'record'
<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
<Records>
<Record>
<title>A</title>
<link>/news/view/25857/A.html</link>
<guid isPermaLink="true">/news/view/25857/A.html</guid>
<taglink>/news/25857/A.html</taglink>
<pubDate>Sat, 03 Oct 2015 00:42:42 GMT</pubDate>
<description/>
<category>
<value>headline</value>
<value>hacker</value>
<value>bank</value>
<value>cybercrime</value>
<value>data loss</value>
<value>fraud</value>
</category>
</Record>
</Records>
</rss>
You can simply add a couple of templates that match element to be renamed, and spit out the new element name, for example :
<xsl:template match="channel">
<Records>
<xsl:apply-templates/>
</Records>
</xsl:template>
<xsl:template match="item">
<Record>
<xsl:apply-templates/>
</Record>
</xsl:template>
Related
I am new to XSLT. Please share XSLT for below input and Output XMLs.
Input XML :
<Cars>
<Car>
<Company>Maruthi</Company>
<Model>Alto</Model>
<FeatureName>CC|Mileage</FeatureName>
<FeatureValue>800|20</FeatureValue>
</Car>
<Car>
<Company>Hyundai</Company>
<Model>i10</Model>
<FeatureName>CC|Mileage|Airbag</FeatureName>
<FeatureValue>1000|18|Y</FeatureValue>
</Car>
</Cars>
Output XML :
<Cars>
<Car>
<Company>Maruthi </Company>
<Model>Alto </Model>
<FeatureName><CC>800</CC><Mileage>20</Mileage></FeatureName>
</Car>
<Car>
<Company>Hyundai </Company>
<Model>i10 </Model>
<FeatureName><CC>1000</CC><Mileage>18</Mileage><Airbag>Y</Airbag></FeatureName>
</Car>
</Cars>
Please provide the XSLT to transform input to Output XML.
Thanks in Advance.
This works:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="Cars/Car/FeatureValue"/>
<xsl:template match="Cars/Car/FeatureName">
<xsl:param name="featureNames" select="concat(./text(),'|')"/>
<xsl:param name="featureValues" select="concat(following-sibling::FeatureValue/text(),'|')"/>
<FeatureName>
<xsl:call-template name="features">
<xsl:with-param name="featureNames" select="$featureNames" />
<xsl:with-param name="featureValues" select="$featureValues" />
</xsl:call-template>
</FeatureName>
</xsl:template>
<xsl:template name="features">
<xsl:param name="featureNames"/>
<xsl:param name="featureValues"/>
<xsl:param name="featureName" select="substring-before($featureNames,'|')"/>
<xsl:param name="featureValue" select="substring-before($featureValues,'|')"/>
<xsl:choose>
<xsl:when test="$featureName">
<xsl:element name="{$featureName}"><xsl:value-of select="$featureValue" /></xsl:element>
<xsl:call-template name="features">
<xsl:with-param name="featureNames" select="substring-after($featureNames,'|')" />
<xsl:with-param name="featureValues" select="substring-after($featureValues,'|')" />
</xsl:call-template>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
My (simplified) input XML looks like this:
<?xml version="1.0" encoding="utf-8"?>
<root>
<recordList>
<record>
<id>16</id>
<MaterialGroup>
<material>
<term>metal, glass</term>
</material>
<material.notes />
<material.part>body</material.part>
</MaterialGroup>
<MaterialGroup>
<material>
<term>wood</term>
</material>
<material.notes>fragile</material.notes>
<material.part>lid</material.part>
</MaterialGroup>
</record>
<record>
...
</record>
</recordList>
</root>
Note that term may contain a comma-separated list of multiple materials (metal, glass).
Desired output:
I want to split the material/term and need to duplicate the grandparent Material with all attributes and nodes for that.
<?xml version="1.0" encoding="utf-8"?>
...
<MaterialGroup>
<material>
<term>metal</term>
</material>
<material.notes />
<material.part>body</material.part>
</MaterialGroup>
<MaterialGroup>
<material>
<term>glass</term>
</material>
<material.notes />
<material.part>body</material.part>
</MaterialGroup>
<MaterialGroup>
<material>
<term>wood</term>
</material>
<material.notes>fragile</material.notes>
<material.part>lid</material.part>
</MaterialGroup>
</record>
...
The first MaterialGroup is copied for every token in the delimited grandchild element material/term, and the term text is set to the token text. material.parts and material.notes can be copied unchanged.
My stylesheet:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="separator" select="','"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="material/term" mode="s">
<xsl:param name="split_term"/>
<xsl:value-of select="$split_term"/>
</xsl:template>
<xsl:template match="MaterialGroup" name="tokenize">
<xsl:param name="text" select="material/term"/>
<xsl:choose>
<xsl:when test="not(contains($text, $separator))">
<xsl:copy>
<xsl:apply-templates/>
<xsl:apply-templates select="material/term" mode="s">
<xsl:with-param name="split_term">
<xsl:value-of select="normalize-space($text)"/>
</xsl:with-param>
</xsl:apply-templates>
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates/>
<xsl:apply-templates select="material/term" mode="s">
<xsl:with-param name="split_term">
<xsl:value-of select="normalize-space(substring-before($text, $separator))"/>
</xsl:with-param>
</xsl:apply-templates>
</xsl:copy>
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="substring-after($text, $separator)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Actual output:
<?xml version="1.0" encoding="utf-8"?>
<root>
<recordList>
<record>
<id>16</id>
<MaterialGroup>
<material>
<term>metal, glass</term>
</material>
<material.notes />
<material.part>body</material.part>
metal
</MaterialGroup>
<MaterialGroup>
<material>
<term>metal, glass</term>
</material>
<material.notes />
<material.part>body</material.part>
glass
</MaterialGroup>
<MaterialGroup>
<material>
<term>wood</term>
</material>
<material.notes>fragile</material.notes>
<material.part>lid</material.part>
wood
</MaterialGroup>
</record>
<record>
...
</record>
</recordList>
</root>
The tokens (metal, glass) occur as text elements as MaterialGroup children, below material.parts. The text element where it should actually appear (material/term) is unchanged.
I looked at couple solutions to similar problems, but no success:
https://stackoverflow.com/a/5480198/2044940
https://stackoverflow.com/a/10430719/2044940
http://codesequoia.wordpress.com/2012/02/15/xslt-example-add-a-new-node-to-elements/
...
Any ideas?
Edit: Solution by Martin, without modes as suggested by michael:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="separator" select="', '"/>
<xsl:template match="#* | node()">
<xsl:param name="term"/>
<xsl:copy>
<xsl:apply-templates select="#* | node()">
<xsl:with-param name="term" select="$term"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="material/term">
<xsl:param name="term"/>
<xsl:copy>
<xsl:value-of select="$term"/>
</xsl:copy>
</xsl:template>
<xsl:template match="MaterialGroup" name="tokenize">
<xsl:param name="text" select="material/term"/>
<xsl:choose>
<xsl:when test="not(contains($text, $separator))">
<xsl:copy>
<xsl:apply-templates>
<xsl:with-param name="term" select="$text"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates>
<xsl:with-param name="term" select="substring-before($text, $separator)"/>
</xsl:apply-templates>
</xsl:copy>
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="substring-after($text, $separator)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
I think you need to pass your term around:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="separator" select="', '"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#* | node()" mode="s">
<xsl:param name="term"/>
<xsl:copy>
<xsl:apply-templates select="#* | node()" mode="s">
<xsl:with-param name="term" select="$term"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="material/term" mode="s">
<xsl:param name="term"/>
<xsl:copy>
<xsl:value-of select="$term"/>
</xsl:copy>
</xsl:template>
<xsl:template match="MaterialGroup" name="tokenize">
<xsl:param name="text" select="material/term"/>
<xsl:choose>
<xsl:when test="not(contains($text, $separator))">
<xsl:copy>
<xsl:apply-templates mode="s">
<xsl:with-param name="term" select="$text"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates mode="s">
<xsl:with-param name="term" select="substring-before($text, $separator)"/>
</xsl:apply-templates>
</xsl:copy>
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="substring-after($text, $separator)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
That way, with your input, I get
<root>
<recordList>
<record>
<id>16</id>
<MaterialGroup>
<material>
<term>metal</term>
</material>
<material.notes/>
<material.part>body</material.part>
</MaterialGroup>
<MaterialGroup>
<material>
<term>glass</term>
</material>
<material.notes/>
<material.part>body</material.part>
</MaterialGroup>
<MaterialGroup>
<material>
<term>wood</term>
</material>
<material.notes>fragile</material.notes>
<material.part>lid</material.part>
</MaterialGroup>
</record>
<record>
...
</record>
</recordList>
</root>
I'm trying to recursively flatten / normalize a structure below with no luck.
<models>
<model name="AAA" root="true">
<items>
<item name="a"/>
<item name="b"/>
</items>
<submodels>
<submodel ref="BBB"/>
<submodel ref="CCC" />
</submodels>
</model>
<model name="BBB">
<items>
<item name="c"/>
<item name="d"/>
</items>
<submodels>
<submodel ref="CCC" />
</submodels>
</model>
<model name="CCC">
<item name="e" />
</model>
</models>
The expected result is the following:
/AAA
/AAA/a
/AAA/b
/AAA/BBB
/AAA/BBB/c
/AAA/BBB/d
/AAA/BBB/CCC
/AAA/BBB/CCC/e
/AAA/CCC
/AAA/CCC/e
I have tried using in a recursive way. But the main issue is that several models can refer to a single model. Eg. AAA -> CCC and BBB -> CCC.
This short and simple transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="kmodelByName" match="model" use="#name"/>
<xsl:key name="ksubmodelByRef" match="submodel" use="#ref"/>
<xsl:template match="/*">
<xsl:apply-templates select="model[not(key('ksubmodelByRef', #name))]"/>
</xsl:template>
<xsl:template match="model|item">
<xsl:param name="pPath"/>
<xsl:value-of select="concat('
', $pPath, '/', #name)"/>
<xsl:apply-templates select="item|*/item|*/submodel">
<xsl:with-param name="pPath" select="concat($pPath, '/', #name)"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="submodel">
<xsl:param name="pPath"/>
<xsl:apply-templates select="key('kmodelByName', #ref)">
<xsl:with-param name="pPath" select="$pPath"/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<models>
<model name="AAA" root="true">
<items>
<item name="a"/>
<item name="b"/>
</items>
<submodels>
<submodel ref="BBB"/>
<submodel ref="CCC" />
</submodels>
</model>
<model name="BBB">
<items>
<item name="c"/>
<item name="d"/>
</items>
<submodels>
<submodel ref="CCC" />
</submodels>
</model>
<model name="CCC">
<item name="e"/>
</model>
</models>
produces the wanted, correct result:
/AAA
/AAA/a
/AAA/b
/AAA/BBB
/AAA/BBB/c
/AAA/BBB/d
/AAA/BBB/CCC
/AAA/BBB/CCC/e
/AAA/CCC
/AAA/CCC/e
Explanation:
Proper use of keys makes the transformation short, easy to express and efficient.
Proper use of templates.
Proper use of parameter - passing to templates.
Firstly, I suspect you want to math the model that is marked as a root
<xsl:apply-templates select="model[#root='true']" />
Then you would have a template to match model elements, but one that takes a parameter that contains the current 'path' to the parent model
<xsl:template match="model">
<xsl:param name="path" />
You could output the full path like so
<xsl:variable name="newpath" select="concat($path, '/', #name)" />
<xsl:value-of select="concat($newpath, '
')" />
You could then output the items but passing in the new path as a parameter
<xsl:apply-templates select="items/item|item">
<xsl:with-param name="path" select="$newpath" />
</xsl:apply-templates>
To match the sub-models, ideally a key could be used
<xsl:key name="models" match="model" use="#name" />
Then you could match the sub-models like so
<xsl:apply-templates select="key('models', submodels/submodel/#ref)">
<xsl:with-param name="path" select="$newpath" />
</xsl:apply-templates>
This would recursively match the same model template.
Here is the full XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="yes"/>
<xsl:key name="models" match="model" use="#name" />
<xsl:template match="/models">
<xsl:apply-templates select="model[#root='true']" />
</xsl:template>
<xsl:template match="model">
<xsl:param name="path" />
<xsl:variable name="newpath" select="concat($path, '/', #name)" />
<xsl:value-of select="concat($newpath, '
')" />
<xsl:apply-templates select="items/item|item">
<xsl:with-param name="path" select="$newpath" />
</xsl:apply-templates>
<xsl:apply-templates select="key('models', submodels/submodel/#ref)">
<xsl:with-param name="path" select="$newpath" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="item">
<xsl:param name="path" />
<xsl:value-of select="concat($path, '/', #name, '
')" />
</xsl:template>
</xsl:stylesheet>
When applied to your sample XML, the following is output
/AAA
/AAA/a
/AAA/b
/AAA/BBB
/AAA/BBB/c
/AAA/BBB/d
/AAA/BBB/CCC
/AAA/BBB/CCC/e
/AAA/CCC
/AAA/CCC/e
I'm not sure what your rules of transformation are. This is a bit of a guess, but the following XSLT 1.0 style-sheet does transform your supplied input to the expected output.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:apply-templates select="models/model[#root='true']">
<xsl:with-param name="base" select="''" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="model|item">
<xsl:param name="base" />
<xsl:variable name="new-base" select="concat($base,'/',#name)" />
<xsl:value-of select="concat($new-base,'
')" />
<xsl:apply-templates select="items/item | item | submodels/submodel/#ref">
<xsl:with-param name="base" select="$new-base" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="#ref">
<xsl:param name="base" />
<xsl:apply-templates select="../../../../model[#name=current()]">
<xsl:with-param name="base" select="$base" />
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
There is probably an easier way to do this, but I was able to get the output you wanted with the following xslt:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" exclude-result-prefixes="exsl" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common">
<xsl:variable name="root" select="/models/model[#root = 'true']/#name"/>
<xsl:template match="models">
<xsl:for-each select="model">
<xsl:variable name="savedNode" select="."/>
<xsl:variable name="precedingValue">
<xsl:call-template name="preceding">
<xsl:with-param name="modelName" select="#name"/>
</xsl:call-template>
</xsl:variable>
<xsl:if test="not(exsl:node-set($precedingValue)/preceding)">
<xsl:call-template name="model">
<xsl:with-param name="node" select="$savedNode"/>
</xsl:call-template>
</xsl:if>
<xsl:for-each select="exsl:node-set($precedingValue)/preceding">
<xsl:sort select="position()" data-type="number" order="descending"/>
<xsl:variable name="precedingTmp" select="normalize-space(.)"/>
<xsl:variable name="normalizedPreceding" select="translate($precedingTmp, ' ', '')"/>
<xsl:call-template name="model">
<xsl:with-param name="node" select="$savedNode"/>
<xsl:with-param name="preceding" select="$normalizedPreceding"/>
</xsl:call-template>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
<xsl:template name="model">
<xsl:param name="node"/>
<xsl:param name="preceding"/>
\<xsl:value-of select="$preceding"/><xsl:value-of select="$node/#name"/>
<xsl:for-each select="$node/items/item">
\<xsl:value-of select="$preceding"/><xsl:value-of select="$node/#name"/>\<xsl:value-of select="#name"/>
</xsl:for-each>
</xsl:template>
<xsl:template name="preceding">
<xsl:param name="modelName"/>
<xsl:for-each select="preceding::model">
<xsl:for-each select="submodels/submodel">
<xsl:choose>
<xsl:when test="contains(#ref, $modelName)">
<preceding>
<xsl:call-template name="preceding">
<xsl:with-param name="modelName" select="$modelName"></xsl:with-param>
</xsl:call-template>
<xsl:value-of select="../../#name"/>\
</preceding>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
<xsl:template match="items">
<xsl:for-each select="item">
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Input
<models>
<model name="AAA" root="true">
<items>
<item name="a"/>
<item name="b"/>
</items>
<submodels>
<submodel ref="BBB"/>
<submodel ref="CCC" />
</submodels>
</model>
<model name="BBB">
<items>
<item name="c"/>
<item name="d"/>
</items>
<submodels>
<submodel ref="CCC" />
</submodels>
</model>
<model name="CCC">
<items>
<item name="e"/>
</items>
</model>
</models>
Output
\AAA
\AAA\a
\AAA\b
\AAA\BBB
\AAA\BBB\c
\AAA\BBB\d
\AAA\BBB\CCC
\AAA\BBB\CCC\e
\AAA\CCC
\AAA\CCC\e
I hope it helps.
xslt have function(like substring vice versa) or how to solve it? I had xml:
<document>
<Line>
<Line-Item>
<LineNumber>10</LineNumber>
<EAN>111</EAN>
<BIC>123123</BIC>
<SIC>AVD091</SIC>
</Line-Item>
</Line>
<Line>
<Line-Item>
<LineNumber>20</LineNumber>
<EAN>22222</EAN>
<BIC>3232332</BIC>
<SIC>AVD25482</SIC>
</Line-Item>
</Line>
</document>
needed output:
10 111 123123 AVD091
20 22222 3232332 AVD25482
Field line number start from 1 column position, EAN start from 11 column position, BIC start from 19 and SIC from 31.
Try this XSLT 1.0 style-sheet. The pad template is an XSLT 1.0 version of Martin's mf:pad function.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:apply-templates select="document/Line/Line-Item"/>
</xsl:template>
<xsl:template name="pad">
<xsl:param name="value" />
<xsl:param name="width" />
<xsl:variable name="col-max" select="' '"/>
<xsl:value-of select="substring( concat($value,$col-max), 1, $width)" />
</xsl:template>
<xsl:template match="Line-Item" >
<xsl:call-template name="pad" >
<xsl:with-param name="value" select="LineNumber"/>
<xsl:with-param name="width" select="10" />
</xsl:call-template>
<xsl:call-template name="pad" >
<xsl:with-param name="value" select="EAN"/>
<xsl:with-param name="width" select="8" />
</xsl:call-template>
<xsl:call-template name="pad" >
<xsl:with-param name="value" select="BIC"/>
<xsl:with-param name="width" select="12" />
</xsl:call-template>
<xsl:value-of select="SIC" />
<xsl:value-of select="'
'" />
</xsl:template>
<xsl:template match="*" />
</xsl:stylesheet>
This short and generic transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<my:fields>
<fieldset name="LineNumber" width="10"/>
<fieldset name="EAN" width="8"/>
<fieldset name="BIC" width="12"/>
</my:fields>
<xsl:variable name="vSpaces" select="' '"/>
<xsl:variable name="vFields" select="document('')/*/my:fields/*"/>
<xsl:template match="Line-Item">
<xsl:text>
</xsl:text>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="Line-Item/*">
<xsl:value-of select=
"concat(.,
substring($vSpaces,
1,
$vFields[#name = name(current())]/#width
-
string-length()
)
)"/>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<document>
<Line>
<Line-Item>
<LineNumber>10</LineNumber>
<EAN>111</EAN>
<BIC>123123</BIC>
<SIC>AVD091</SIC>
</Line-Item>
</Line>
<Line>
<Line-Item>
<LineNumber>20</LineNumber>
<EAN>22222</EAN>
<BIC>3232332</BIC>
<SIC>AVD25482</SIC>
</Line-Item>
</Line>
</document>
produces the wanted, correct result:
10 111 123123 AVD091
20 22222 3232332 AVD25482
Do note:
The element my:fields can be put in its own XML document. Thus, no modifications would be required to the XSLT code if some fields widths need to be modified.
Here is a sample stylesheet (XSLT 2.0, sorry, started writing before your comment indicated a request for 1.0):
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="xs mf">
<xsl:param name="col-max" as="xs:string" select="' '"/>
<xsl:strip-space elements="*"/>
<xsl:output method="text"/>
<xsl:function name="mf:pad" as="xs:string">
<xsl:param name="input" as="xs:string"/>
<xsl:param name="col-length" as="xs:integer"/>
<xsl:sequence select="concat($input, substring($col-max, 1, $col-length - string-length($input)))"/>
</xsl:function>
<xsl:template match="Line">
<xsl:if test="position() > 1">
<xsl:text>
</xsl:text>
</xsl:if>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="LineNumber">
<xsl:sequence select="mf:pad(., 10)"/>
</xsl:template>
<xsl:template match="EAN">
<xsl:sequence select="mf:pad(., 9)"/>
</xsl:template>
<xsl:template match="BIC">
<xsl:sequence select="mf:pad(., 12)"/>
</xsl:template>
<xsl:template match="SIC">
<xsl:sequence select="mf:pad(., string-length())"/>
</xsl:template>
</xsl:stylesheet>
There is list
<items>
<item parentid='1'>
<amount>3</amount>
</item>
<item parentid='2'>
<amount>1</amount>
</item>
</items>
and document:
<udata id='1'>
<price>10</price>
</udata>
<udata id='1'>
<price>20</price>
</udata>
How to sum price's all document's?
To sum count I use'd:
<xsl:value-of select="sum(items/item/amount)"/>
I'd use:
<xsl:apply-templates select="udata/items/item" mode='price2' />
<xsl:template mode='price2' match='item'>
<xsl:apply-templates select="document(concat('upage://', page/#parentId))" mode='price'>
<xsl:with-param select='amount' name='count'/>
</xsl:apply-templates>
</xsl:template>
<xsl:template mode='price' match='/'>
<xsl:param name='count'/>
<xsl:value-of select="$count * /udata/page/properties/group[#name='price_prop']/property[#name='price']/value"/>
</xsl:template>
In result i had:
3020
I need 50. How to do this?
Here is a sample assuming XSLT 2.0 (e.g. as possible with Saxon 9 or AltovaXML):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:param name="data-url" select="'test2012050103.xml'"/>
<xsl:variable name="data-doc" select="document($data-url)"/>
<xsl:key name="k1" match="udata" use="#id"/>
<xsl:template match="items">
<xsl:value-of select="sum(for $item in item return $item/amount * key('k1', $item/#parentid, $data-doc)/price)"/>
</xsl:template>
</xsl:stylesheet>
Example documents are
<items>
<item parentid='1'>
<amount>3</amount>
</item>
<item parentid='2'>
<amount>1</amount>
</item>
</items>
and
<root>
<udata id='1'>
<price>10</price>
</udata>
<udata id='2'>
<price>20</price>
</udata>
</root>
Output is 50.
[edit]Here is an XSLT 1.0 stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:param name="data-url" select="'test2012050103.xml'"/>
<xsl:variable name="data-doc" select="document($data-url)"/>
<xsl:key name="k1" match="udata" use="#id"/>
<xsl:template match="items">
<xsl:call-template name="sum">
<xsl:with-param name="items" select="item"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="sum">
<xsl:param name="items" select="/.."/>
<xsl:param name="total" select="0"/>
<xsl:choose>
<xsl:when test="not($items)">
<xsl:value-of select="$total"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="price">
<xsl:for-each select="$data-doc">
<xsl:value-of select="$items[1]/amount * key('k1', $items[1]/#parentid)/price"/>
</xsl:for-each>
</xsl:variable>
<xsl:call-template name="sum">
<xsl:with-param name="items" select="$items[position() > 1]"/>
<xsl:with-param name="total" select="$total + $price"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Here is a shorter solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:variable name="vPrices" select=
"document('file:///c:/temp/delete/priceData.xml')/*/*"/>
<xsl:template match="/*">
<xsl:apply-templates select="item[1]"/>
</xsl:template>
<xsl:template match="item" name="sumProducts">
<xsl:param name="pAccum" select="0"/>
<xsl:variable name="vNewAccum" select=
"$pAccum +amount * $vPrices[#id = current()/#parentid]/price"/>
<xsl:if test="not(following-sibling::*)">
<xsl:value-of select="$vNewAccum"/>
</xsl:if>
<xsl:apply-templates select="following-sibling::*">
<xsl:with-param name="pAccum" select="$vNewAccum"/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
When applied on the following XML document:
<items>
<item parentid='1'>
<amount>3</amount>
</item>
<item parentid='2'>
<amount>1</amount>
</item>
</items>
and having the file c:\temp\delete\priceData.xml contain:
<root>
<udata id='1'>
<price>10</price>
</udata>
<udata id='2'>
<price>20</price>
</udata>
</root>
then the wanted, correct result is produced:
50