I need to transform an XML file to another XML filtered. I want to use XSLT/C# to do this operation.
My source code in C# will execute the XSLT file with a list of parameter (I'm using XslCompiledTransform class).
My question is : how in XSLT language I can parse all parameters transmited from the C# to filter the output XML file.
Example : a list of car
<cars>
<car brand="Audi" model="A4/>
<car brand="Audi" model="A6/>
<car brand="Audi" model="A7/>
<car brand="Volvo" model="V40" />
<car brand="Volvo" model="V60" />
<car brand="Honda" model="Civic" />
<car brand="Mercedes" model="Class E" />
</cars>
a simple XSLT with brandsSelect parameter
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:param name="brandsSelect"></xsl:param>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
In my C# source code, I populate my variable :
brandsSelect = Volvo, Honda
Expected result :
<cars>
<car brand="Volvo" model="V40" />
<car brand="Volvo" model="V60" />
<car brand="Honda" model="Civic" />
</cars>
Thanks for your help !
What you could do (in XSLT 1.0 which is what XSLTCompiledTransform implements) is to do a string test to see if the parameter "contains" the brand attribute:
<xsl:template match="cars">
<xsl:copy>
<xsl:apply-templates select="car[contains($brandsSelect, #brand)]" />
</xsl:copy>
</xsl:template>
However, this will fail if one brand happens to be a substring of another (For example, if "Laudi" was brand as well as "Audi"
So, to make it robust, try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:param name="brandsSelect">Volvo,Honda</xsl:param>
<xsl:variable name="brandMatcher" select="concat(',', $brandsSelect, ',')" />
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="cars">
<xsl:copy>
<xsl:apply-templates select="car[contains($brandMatcher, concat(',', #brand, ','))]" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
It is important to note, the value of the brandsSelect should not contain any spaces between the brands, only commas.
Related
Is it possible to use XSLT or C# to go from
<XML>
<Name>Name1;Name2</Name>
<Adress>Adress1;Adress2</Adress>
</XML>
To this
<XML>
<LINE>
<Name>Name1</Name>
<Adress>Adress1</Adress>
</LINE>
<LINE>
<Name>Name2</Name>
<Adress>Adress2</Adress>
</LINE>
</XML>
Any help would be most appreciated.
This XSLT 2.0 transformation:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*">
<xsl:copy>
<xsl:variable name="vTop" select="."/>
<xsl:variable name="vAddresses" select="tokenize(Adress, ';')"/>
<xsl:for-each select="tokenize(Name, ';')">
<xsl:variable name="vPos" select="position()" as="xs:integer"/>
<xsl:apply-templates select="$vTop/Name">
<xsl:with-param name="pNameData" select="."/>
<xsl:with-param name="pAdrData" select="$vAddresses[$vPos]"/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template match="Name">
<xsl:param name="pNameData"/>
<xsl:param name="pAdrData"/>
<Line>
<Name><xsl:value-of select="$pNameData"/></Name>
<Adress><xsl:value-of select="$pAdrData"/></Adress>
</Line>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<XML>
<Name>Name1;Name2</Name>
<Adress>Adress1;Adress2</Adress>
</XML>
produces the wanted, correct result:
<XML>
<Line>
<Name>Name1</Name>
<Adress>Adress1</Adress>
</Line>
<Line>
<Name>Name2</Name>
<Adress>Adress2</Adress>
</Line>
</XML>
Here's a pretty simple XSLT 1.0 solution:
Input file:
<XML>
<Name>Name1;Name2</Name>
<Address>Address1;Address2</Address>
<Name>Name3;Name4</Name>
<Address>Address3;Address4</Address>
</XML>
Stylesheet:
<?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:strip-space elements="*"/>
<xsl:template match="XML">
<XML>
<xsl:apply-templates select="Name"/>
</XML>
</xsl:template>
<xsl:template match="Name" mode="name-address">
<xsl:param name="name" select="substring-before(., ';')"/>
<xsl:param name="address" select="substring-before(following-sibling::*[1][self::Address], ';')"/>
<LINE>
<Name>
<xsl:value-of select="$name"/>
</Name>
<Address>
<xsl:value-of select="$address"/>
</Address>
</LINE>
</xsl:template>
<xsl:template match="Name">
<xsl:apply-templates select="." mode="name-address"/>
<xsl:apply-templates select="." mode="name-address">
<xsl:with-param name="name" select="substring-after(., ';')"/>
<xsl:with-param name="address" select="substring-after(following-sibling::*[1][self::Address], ';')"/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
Output:
<?xml version="1.0" encoding="UTF-8"?>
<XML>
<LINE>
<Name>Name1</Name>
<Address>Address1</Address>
</LINE>
<LINE>
<Name>Name2</Name>
<Address>Address2</Address>
</LINE>
<LINE>
<Name>Name3</Name>
<Address>Address3</Address>
</LINE>
<LINE>
<Name>Name4</Name>
<Address>Address4</Address>
</LINE>
</XML>
I don't know about XSLT, but if it's possible, there's probably a really complex solution.
In C# it's fairly straightforward though. Here's one way to do it (using LINQ to XML):
var inputXml =
#"<XML>
<Name>Name1;Name2</Name>
<Adress>Adress1;Adress2</Adress>
</XML>";
var inputXmlDoc = XDocument.Parse(inputXml);
var names = inputXmlDoc.Root.Descendants("Name").Single().Value.Split(';');
var addresses = inputXmlDoc.Root.Descendants("Adress").Single().Value.Split(';');
var outputXmlDoc = new XDocument(
new XElement("XML",
names.Zip(addresses, (n, a) => new {n, a})
.Select(pair => new XElement("LINE",
new XElement("Name", pair.n),
new XElement("Address", pair.a)
))
)
);
Console.WriteLine(outputXmlDoc);
I have an application written in C# that needs to apply a template name to an xml file that is defined in an XSLT.
Example XML:
<Data>
<Person>
<Name>bob</Name>
<Age>43</Age>
</Person>
<Thing>
<Color>Red</Color>
</Thing>
</Data>
Example XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:param name="TargetName" />
<xsl:param name="RootPath" />
<xsl:Template Name="AgeGrabber">
<xsl:value-of select="/Person/Age" />
</xsl:Template>
<xsl:Template Name="ColorGrabber">
<xsl:value-of select="/Color" />
</xsl:Template>
</xsl:stylesheet>
Say I wanted to run the template "ColorGrabber" with path "/Data/Thing" and then run another transform with the template "AgeGrabber" with path "/Data". Is this possible? I was thinking I could pass in the path and the template name (hense the 2 params at the top) and then do some type of switch but it looks like xsl:call-template can not take a parameter as the name attribute.
How can I achieve this behaviour?
There are a number of issues with this question:
<xsl:stylesheet version="2.0" ... is specified, however, at present >NET doesn't natively support XSLT 2.0.
Thecode example is not too meaningful, because a single XML document cannot contain both /Person/Age and /Color elements -- a wellformed XML document has only a single top element and it can be either Person or Color, but not both.
In case there was a more meaningful example:
<Person>
<Age>27</Age>
<HairColor>blond</HairColor>
</Person>
one simple solution is:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pProperty" select="'Age'"/>
<xsl:template match="/">
<xsl:value-of select="/*/*[name()=$pProperty]"/>
</xsl:template>
</xsl:stylesheet>
and when this transformation is applied on the above XML document, it produces the wanted result:
27
In case the nestedness of the elements of interest can be arbitrary and/or we need to do different processing on the different elements, then an appropriate solution is to use matching templates (not named ones):
<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:param name="pProperty" select="'HairColor'"/>
<xsl:template match="Age">
<xsl:if test="$pProperty = 'Age'">
This person is <xsl:value-of select="."/> old.
</xsl:if>
</xsl:template>
<xsl:template match="HairColor">
<xsl:if test="$pProperty = 'HairColor'">
This person has <xsl:value-of select="."/> hair.
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the same XML document (above), again the correct result is produced:
This person has blond hair.
Finally, if you really want to simulate higher-order functions (HOF) in XSLT 1.0 or XSLT 2.0, see this answer: https://stackoverflow.com/a/8363249/36305 , or learn about FXSL.
Much simpler: prepare two apply-templates rules (for Age and Color elements) and conditionally send proper node to transform - //Person/Age or //Thing/Color
You got it backwards. You ought to create templates, matching the nodes you want to use.
<xsl:stylesheet>
<xsl:template match="Person|Thing">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="Person">
<xsl:value-of select="Age" />
</xsl:template>
<xsl:template match="Thing">
<xsl:value-of select="Color" />
</xsl:template>
</xsl:stylesheet>
i use the following XSLT by using XMLSpy:
<?xml version="1.0" encoding="UTF-16"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output method="xml" version="1.0" encoding="UTF-16" indent="yes"/>
<xsl:template match="*">
<xsl:element name="{lower-case(local-name())}">
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="* | text()"/>
</xsl:element>
</xsl:template>
<xsl:template match="#*">
<xsl:attribute name="{lower-case(local-name())}"><xsl:value-of select="."/></xsl:attribute>
</xsl:template>
</xsl:stylesheet>
If i try to use it in my source (XslCompiledTransform) i get an exception telling me that the function 'lower-case()' is not part of the XSLT synthax.
So i changed the transformation a little bit:
fn:lower-case
Now my exception is that the script or external object prefixed by 'http://www.w3.org/2005/xpath-functions' can not be found.
Whats the matter here? How can i fix it?
Regards
.NET does not implement XSLT 2.0/XPath 2.0.
In XPath 1.0 one can use the following expression, instead of lower-case():
translate(yourString,
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'abcdefghijklmnopqrstuvwxyz')
I have this XML for a table structure in Oracle (export option in PL/SQL developer). How can I generate code in C# for get entity class?
<?xml version="1.0" encoding="utf-8"?>
<ROWDATA>
<ROW>
<Name>ID_TRANSACCION</Name>
<Type>NUMBER(12)</Type>
<Nullable></Nullable>
<Default></Default>
<Comments>Identificador unico de la transacción.</Comments>
</ROW>
<ROW>
<Name>ID_RECIBO</Name>
<Type>NUMBER(12)</Type>
<Nullable></Nullable>
<Default></Default>
<Comments>Identificador unico del recibo.</Comments>
</ROW>
<ROW>
<Name>IMPORTE_COBRAR</Name>
<Type>NUMBER(10,2)</Type>
<Nullable>Y</Nullable>
<Default></Default>
<Comments>Importe a cobrar</Comments>
</ROW>
</ROWDATA>
Thanks!
Look into xsd.exe - this tool allows you to create an xsd schema from a sample xml document (your export form Oracle) and create a .net class from a given xsd schema.
Now that your sample code is visible it's clear that you would need to transform the xml before you could use it in connection with xsd.exe.
If you are looking for oracle support of the .NET EntityFramework you need to use a specific Oracle provider. One example would be DataDirect.
sample xslt for schema generation
<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
<xsl:template match="/">
<xsl:element name="xsd:schema">
<xsl:attribute name="attributeFormDefault">
qualified
</xsl:attribute>
<xsl:attribute name="elementFormDefault">
qualified
</xsl:attribute>
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<xsl:template match="ROWDATA">
<xsl:element name="xsd:element">
<xsl:attribute name="name">
<xsl:value-of select="local-name()"/>
</xsl:attribute>
<xsl:element name="xsd:complexType">
<xsl:element name="xsd:sequence">
<xsl:apply-templates />
</xsl:element>
</xsl:element>
</xsl:element>
</xsl:template>
<xsl:template match="ROW">
<xsl:element name="xsd:element">
<xsl:attribute name="name">
<xsl:value-of select="./Name"/>
</xsl:attribute>
<xsl:attribute name="nillable">
<xsl:value-of select="contains(./Nullable, 'Y')"/>
</xsl:attribute>
<xsl:if test="./Default != ''">
<xsl:attribute name="default">
<xsl:value-of select="./Default"/>
</xsl:attribute>
</xsl:if>
<xsl:element name="xsd:annotation">
<xsl:element name="xsd:documentation">
<xsl:value-of select="./Comments"/>
</xsl:element>
</xsl:element>
<xsl:element name="xsd:simpleType">
<xsl:element name="xsd:restriction">
<xsl:attribute name="base">xsd:decimal</xsl:attribute>
<!-- elaborate data type here -->
</xsl:element>
</xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
The resulting schema document can be used as an input for xsd.exe.
Here's my problem.I have 2 xmlfiles with identical structure, with the second xml containing only few node compared to first.
File1
<root>
<alpha>111</alpha>
<beta>22</beta>
<gamma></gamma>
<delta></delta>
</root>
File2
<root>
<beta>XX</beta>
<delta>XX</delta>
</root>
This's what the result should look like
<root>
<alpha>111</alpha>
<beta>22</beta>
<gamma></gamma>
<delta>XX</delta>
</root>
Basically if the node contents of any node in File1 is blank then it should read the values from File2(if it exists, that is).
I did try my luck with Microsoft XmlDiff API but it didn't work out for me(the patch process didn't apply changes to the source doc). Also I'm a bit worried about the DOM approach that it uses, because of the size of the xml that I'll be dealing with.
Can you please suggest a good way of doing this.
I'm using C# 2
Here is a little bit simpler and more efficient solution that that proposed by Alastair (see my comment to his solution).
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vFile2"
select="document('File2.xml')"/>
<xsl:template match="*">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[not(text())]">
<xsl:copy>
<xsl:copy-of
select="$vFile2/*/*[name() = name(current())]/text()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document:
<root>
<alpha>111</alpha>
<beta>22</beta>
<gamma></gamma>
<delta></delta>
</root>
produces the wanted result:
<root>
<alpha>111</alpha>
<beta>22</beta>
<gamma></gamma>
<delta>XX</delta>
</root>
In XSLT you can use the document() function to retrieve nodes from File2 if you encounter an empty node in File1. Something like:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="root/*[.='']">
<xsl:variable name="file2node">
<xsl:copy-of select="document('File2.xml')/root/*[name()=name(current())]"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="$file2node != ''">
<xsl:copy-of select="$file2node"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="*">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This merge seems very specific.
If that is the case, just write some code to load both xml files and apply the changes as you described.