Failure for XSL to create child elements in XML transformation - c#

Trying and trying to get the following to work. I have an XML file (serialized C# class) similar to
<?xml version="1.0" encoding="utf-8"?>
<MyXml xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Order>
<Header>
<OrderNumber>1234</OrderNumber>
</Header>
<Line>
<Sku>abc</Sku>
<Qty>300</Qty>
</Line>
<Line>
<Sku>xyz</Sku>
<Qty>19</Qty>
</Line>
</Order>
I need to transform this to:
<?xml version="1.0" encoding="utf-8"?>
<Order Number="1234">
<Line>
<Product>abc</Product>
<Quantity>300</Quantity>
</Line>
<Line>
<Product>xyz</Product>
<Quantity>19</Quantity>
</Line>
</Order>
Making the lines children of the Order element.
Here's my most succesful attempt so far.
<?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:template match="Header" >
<Order>
<xsl:attribute name="Number">
<xsl:value-of select="OrderNumber"/>
</xsl:attribute>
<xsl:apply-templates select="Line"/>
</Order>
</xsl:template>
<xsl:template match="Line">
<Line>
<Product>
<xsl:value-of select="Sku" />
</Product>
<Quantity>
<xsl:value-of select="Qty" />
</Quantity>
</Line>
</xsl:template>
</xsl:stylesheet>
Which produces the incorrect xml below with multiple root nodes.
<?xml version="1.0" encoding="utf-8"?>
<Order OrderNumber="1234" />
<Line><Product>abc</Product><Quantity>300</Quantity></Line>
<Line><Product>xyz</Product><Quantity>19</Quantity></Line>
There's clearly something I'm missing, but after hours of trying I cannot get the output Line elements to be children of Order at all. Can someone point me in the right direction? Also why does the indent option only seem to affect the first level of the output xml?

You have a template matching Header, but within this you do <xsl:apply-templates select="Line"/> to get the Line elements, but Line is not a child of Header, so this selects nothing.
The Line elements in your output are actually being selected due to XSLT's built-in template rules. You don't have a template matching Order, so XSLT's built-in template is used, which will select both Header and Line elements under the Order.
One solution is is to change your the template matching Header to match Order instead.
Try this XSLT
<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:template match="Order" >
<Order Number="{Header/OrderNumber}">
<xsl:apply-templates select="Line"/>
</Order>
</xsl:template>
<xsl:template match="Line">
<Line>
<Product>
<xsl:value-of select="Sku" />
</Product>
<Quantity>
<xsl:value-of select="Qty" />
</Quantity>
</Line>
</xsl:template>
</xsl:stylesheet>
Note the use of Attribute Value Templates to simplify the creation of the Number attribute on Order.

Another solution is using xsl:apply-templates on ../Line instead of Line with a mode attribute on the template. So the changes to the template are minimal.
<?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:template match="Header" >
<Order>
<xsl:attribute name="Number">
<xsl:value-of select="OrderNumber"/>
</xsl:attribute>
<xsl:apply-templates select="../Line" mode="sub"/> <!-- set 'mode' to 'sub' and add '../' to XPath-->
</Order>
</xsl:template>
<xsl:template match="Line" mode="sub"> <!-- use template only when 'mode' is set to 'sub' -->
<Line>
<Product>
<xsl:value-of select="Sku" />
</Product>
<Quantity>
<xsl:value-of select="Qty" />
</Quantity>
</Line>
</xsl:template>
<xsl:template match="text()" /> <!-- ignore all unmatched text() nodes -->
</xsl:stylesheet>

Related

xml to html conversion using xslt is not working

Here my Xml
<document>
<metadata>
<title>Sign in to my account</title>
</metadata>
<topic>
<conceptuldocument>
<legacybold> Hi hello world </legacybold>
</conceptuldocument>
</topic>
</document>
I only need the title which is under the metadata data tag
all I would need is <h1> sign in to my account</h1>
but i am getting out put html as
<h1>sign in to my account</h1>Hi hello world
here my xslt :
<?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="html" indent="yes"/>
<xsl:template match="/">
<xsl:choose>
<xsl:when test="metadata">
<xsl:apply-templates select="metadata"/>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template match="metadata">
<h1>
<xsl:value-of select="title"/>
</h1>
</xsl:template>
</xsl:stylesheet>
C# :
using (StringWriter sw = new StringWriter())
{
transform.Transform(xmldocument.DocumentElement,null, sw);
string html = sw.ToString();
}
output image
xml image
Xslt file image
wondering if any one can help me point what am i missing here , very new to xslt its just been a day.
Simplest fix is to change your entry template to
<xsl:template match="/">
<xsl:apply-templates select="document/metadata"/>
</xsl:template>
Note that your xsl:choose around the xsl:apply-templates is a waste of space. If the element doesn't exist, xsl:apply-templates will do nothing.

How to apply XSLT transformations to XML file and produce another XML?

Input.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<output>
<orders>
<order>
<id>1</id>
<number>10002</number>
<type>Loading</type>
<date>2013-01-01T02:30:00</date>
</order>
<order>
<id>2</id>
<number>10003</number>
<type>Loading</type>
<date>2013-01-01T010:30:00</date>
</order>
</orders>
<quantities>
<quantity>
<id_order>1</id_order>
<unit>KG</unit>
<value>1000</value>
</quantity>
<quantity>
<id_order>2</id_order>
<unit>PAL</unit>
<value>3</value>
</quantity>
</quantities>
</output>
Output.xml should look like this
<?xml version="1.0" encoding="ISO-8859-1"?>
<output>
<orders>
<order>
<id>1</id>
<number>10002</number>
<type>Loading</type>
<KG>1000</KG>
<PAL>3</PAL>
<M3>1.5</M3>
</order>
<order>
<id>2</id>
<number>10003</number>
<type>Loading</type>
<KG>2000</KG>
<PAL>4</PAL>
</order>
</orders>
</output>
What is the format of XSLT file I need to apply ?
ps: ignore this line, sTu!"#id SO asks me to write more text in this post thinking its all code. ignore this line, sTu!"#id SO asks me to write more text in this post thinking its all code.
The following XSLT does what you need (except formatting):
<?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" encoding="iso-8859-1"/>
<!-- Copy all elements recursively -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<!-- Copy this element with subelements -->
<xsl:template match="order">
<!-- Save ID for queries -->
<xsl:variable name="id" select="id" />
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
<!-- Find all quantity elements by ID -->
<xsl:for-each select="//quantity[id_order=$id]">
<!-- Create element with name set to value of unit element and value of value element -->
<xsl:element name="{unit}">
<xsl:value-of select="value"/>
</xsl:element>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<!-- Skip these elements -->
<xsl:template match="quantities" />
<xsl:template match="date" />
</xsl:stylesheet>
RESULT
<?xml version="1.0" encoding="UTF-8"?>
<output>
<orders>
<order>
<id>1</id>
<number>10002</number>
<type>Loading</type>
<KG>1000</KG>
</order>
<order>
<id>2</id>
<number>10003</number>
<type>Loading</type>
<PAL>3</PAL>
</order>
</orders>
</output>

copy-of select name

I have this data in source XML:
<Firma ID_FIRMY="15" ROLE_FIRMY="O " KOD_FIRMY="Tomášov">
This is my XSL:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="utf-8" indent="no"/>
<xsl:template match="root/Firma">
<MoneyData>
<xsl:copy-of select="#ID_FIRMY"/>
<xsl:copy-of select="#KOD_FIRMY"/>
</MoneyData>
</xsl:template>
</xsl:stylesheet>
This is my XML output:
<?xml version="1.0" encoding="utf-8"?>
<MoneyData ID_FIRMY="15" KOD_FIRMY="Tomášov"/>
But I need this output: (change names..)
<?xml version="1.0" encoding="utf-8"?>
<MoneyData KodAgendy="15" HospRokOd="Tomášov"/>
How do I do that?
Write templates to transform the attributes:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="utf-8" indent="no"/>
<xsl:template match="root/Firma">
<MoneyData>
<xsl:apply-templates select="#* | node()"/>
</MoneyData>
</xsl:template>
<xsl:template match="Firma/#ID_FIRMY">
<xsl:attribute name="KodAgendy"><xsl:value-of select="."/></xsl:attribute>
</xsl:template>
<xsl:template match="Firma/#KOD_FIRMY">
<xsl:attribute name="HospRokOd"><xsl:value-of select="."/></xsl:attribute>
</xsl:template>
<xsl:template match="#ROLE_FIRMY"/>
</xsl:stylesheet>

Update an XSLT to incorporate template calling

I am editing an xslt in C#.
It has a template "Get" defined in it.
I want to call this template and pass it into a variable.
Template:
<xsl:template name="Get">
<xsl:param name="varMonth" />
<xsl:choose>
<xsl:when test="$varMonth='JAN'">
<xsl:value-of select="'A'" />
</xsl:when>
</xsl:choose>
</xsl:template>
XSLT:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<DocumentElement>
<PositionMaster>
<xsl:variable name="varName">
<xsl:value-of select="''" />
</xsl:variable>
</PositionMaster>
</DocumentElement>
</xsl:template>
</xsl:stylesheet>
Code:
I am getting a string as an input to the param of the template
string input = "A";
XmlDocument xslDoc = new XmlDocument();
xslDoc.Load("a.xslt");
XmlNamespaceManager nsMgr = new XmlNamespaceManager(xslDoc.NameTable);
nsMgr.AddNamespace("xsl", "http://www.w3.org/1999/XSL/Transform");
XmlElement valueOf = (XmlElement)xslDoc.SelectSingleNode("/xsl:stylesheet/xsl:template[#match = '/']/DocumentElement/PositionMaster/xsl:variable[#name = "varName"]/xsl:value-of", nsMgr);
if (valueOf != null)
{
// What should i write here to get the below modified XSLT
}
Required XSLT:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<DocumentElement>
<PositionMaster>
<xsl:variable name="varName">
<xsl:call-template name="Get">
<xsl:with-param name="Month" select="input"/>
</xsl:call-template>
</xsl:variable>
</PositionMaster>
</DocumentElement>
</xsl:template>
</xsl:stylesheet>
Am I right in understanding that you want to transform the stylesheet using C# code? That seems crazy; you are using XSLT, so you have an XML transformation language at your disposal: use it!
However, I'm confused because neither your original stylesheet nor your modified stylesheet is valid XSLT. You can't have a <DocumentElement> element as a child of xsl:stylesheet; it surely needs to be inside a template.
Transforming XSLT stylesheets using XSLT is common practice and it can be a smart approach to some problems. However, it is often done when there are better techniques available, for example adding stylesheet parameters (global xsl:param elements).

how to do looping in XSLT?

I've got the following XML structure :
<Article>
<id>1</id>
<line>L11</line>
<line>L12</line>
<line>L13</line>
</Article>
<Article>
<id>2</id>
<line>L21</line>
<line>L22</line>
<line>L23</line>
</Article>
I want to use XSLT to iterate over all the lines of only one article at a time so that I can achieve the following structure : (every article is converted into an order, and its lines are converted to line structures in the new structure)
<orders>
<order>
<id>1</id>
<order_line>L11</order_line>
<order_line>L12</order_line>
<order_line>L13</order_line>
</order>
<order>
<id>2</id>
<order_line>L21</order_line>
<order_line>L22</order_line>
<order_line>L23</order_line>
</order>
</orders>
With XSLT, try to think of the task at hand as a set of patterns or rules you have to match, and the actions to be taken each time such a pattern is encountered. You can usually let the runtime worry about looping, etc, to discover patterns.
In your specific case, you've described two patterns you want special logic. Every time there is the element Article, you wish to apply the rule to change its name to order.
Every time the element line is encountered as a child of Article, replace it with order_line. For any other pattern, you just want to copy the contents as they were in the original document:
<!-- Match element Article, and whenever it's encountered, insert an 'order' element, and copy the contents of Article -->
<xsl:template match="Article">
<order> <xsl:apply-templates/> </order>
</xsl:template>
<!-- Match element 'line', and whenever it's encountered, insert an 'order_line' element, and copy the contents -->
<xsl:template match="Article/line">
<order_line> <xsl:apply-templates/> </order_line>
</xsl:template>
<!-- Match any other element we haven't specified explicity, and copy it verbatim -->
<xsl:template match="node()">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
This is a straightforward transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/*">
<orders>
<xsl:apply-templates/>
</orders>
</xsl:template>
<xsl:template match="Article">
<order>
<xsl:apply-templates/>
</order>
</xsl:template>
<xsl:template match="line">
<order_line>
<xsl:apply-templates/>
</order_line>
</xsl:template>
<xsl:template match="id">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
EDIT: A general root element rule.
This complete XSLT 1.0 transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<orders>
<xsl:apply-templates/>
</orders>
</xsl:template>
<xsl:template match="Article">
<order>
<xsl:apply-templates/>
</order>
</xsl:template>
<xsl:template match="line">
<order_line>
<xsl:apply-templates/>
</order_line>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document (wrapped into a single top element to make it well-formed):
<t>
<Article>
<id>1</id>
<line>L11</line>
<line>L12</line>
<line>L13</line>
</Article>
<Article>
<id>2</id>
<line>L21</line>
<line>L22</line>
<line>L23</line>
</Article>
</t>
produces the wanted, correct result:
<orders>
<order>
<id>1</id>
<order_line>L11</order_line>
<order_line>L12</order_line>
<order_line>L13</order_line>
</order>
<order>
<id>2</id>
<order_line>L21</order_line>
<order_line>L22</order_line>
<order_line>L23</order_line>
</order>
</orders>
Explanation:
The identity rule copies every node "as-is".
The identity rule is overriden by three templates, each of which matches a particular kind of element (the top element, Article and line) and simply renames the element, respectively to: orders, order and order_line.
This is just of the top of my head but I think you can do it this way:
<orders>
<xsl:for-each select="//Article">
<order>
<id><xsl:value-of select="./id"/></id>
<xsl:for-each select="./line">
<order_line><xsl:value-of select="child::text()" /></order_line>
</xsl:for-each>
</order>
</xsl:for-each>
</orders>
Anyway, it should be enough to get you started. There is only a for-each loop in XSLT though, no for loop with a counter.
Your source XML file is not well-formed. For clarity, my response assumes you've wrapped it in a element. This is a pretty trivial XML transformation so I've included some comments explaining what each line of code is doing.
<!-- change the template match xpath to whatever the root xpath is in your document -->
<xsl:template match="root">
<orders>
<!-- loop over each Article -->
<xsl:for-each select="Article">
<order>
<!-- pass id through -->
<xsl:copy-of select="id"/>
<!-- loop over each line -->
<xsl:for-each select="line">
<!-- create an <order_line> node -->
<order_line>
<!-- get the value of the current <line> -->
<xsl:value-of select="."/>
</order_line>
</xsl:for-each>
</order>
</xsl:for-each>
</orders>
</xsl:template>
Try this, but I am not a xslt expert :)
<?xml version="1.0" encoding="utf-8"?>
<xsl:template match="/" name="A">
<orders>
<xsl:for-each select="Articles/Article">
<order>
<id>
<xsl:value-of select="id" />
</id>
<xsl:for-each select="line">
<order_line>
<xsl:value-of select="node()"></xsl:value-of>
</order_line>
</xsl:for-each>
</order>
</xsl:for-each>
</orders>
</xsl:template>
The correct way to do "looping" in XSLT is to let the XSLT processor walk the document tree for you. Anything else is fighting against the nature of XSLT.
Here's a (near-) complete solution:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<orders><xsl:apply-templates/></orders>
</xsl:template>
<xsl:template match="Article">
<order>
<id><xsl:value-of select="id"/></id>
<xsl:apply-templates select="line"/>
</order>
</xsl:template>
<xsl:template match="Article/line">
<order_line><xsl:value-of select="."/></order_line>
</xsl:template>
</xsl:stylesheet>

Categories

Resources