I am trying to use XSLT to solve the following problem but am a bit stuck.
I have an ID of a tool, let's say a hammer. I need to take that ID variable, pass it into an XSLT stylesheet, use the XSLT to match all the Task elements in an XML file containing a tool with that ID .
So in one document (tasks.xml) I have the following (note the <tool><id>)
<data>
<DMs>
<task>
<DMC>TEST-4BX-AG3-00-00-0000-125B-A</DMC>
<techName>Fixing fence</techName>
<infoName>Inserting panels</infoName>
<tools>
<tool>
<id>1</id><qty>1</qty>
</tool>
<tool>
<id>4</id><qty>1</qty>
</tool>
</tools>
</task>
<task>
<DMC>TEST-4BX-AG3-00-00-0000-125B-A</DMC>
<techName>Fixing floor</techName>
<infoName>Install floorboard</infoName>
<notes>-</notes>
<tools>
<tool>
<id>89</id><qty>1</qty>
</tool>
<tool>
<id>25</id><qty>2</qty>
</tool>
</tools>
</task>
</DMs>
</data>
So assuming the hammer's ID is "1", i now need to search the Tasks.xml file and match all Tasks where the hammer is used. Only tasks that have the hammer should be included in the output.
That's the first part of the problem.
I have another file, Tools.xml, which contains all the tool information, as follows:
<data>
<tools>
<tool id="1">
<toolName>Hammer</toolName>
<toolPN>345123</toolPN>
<toolCage>-</toolCage>
</tool>
<tool id="2">
<toolName>Digital Multimeter Set</toolName>
<toolPN>Fluke Model No. 89IV</toolPN>
<toolCage>-</toolCage>
</tool>
<tool id="3">
<toolName>Digital Storage Oscilloscope</toolName>
<toolPN>Tektronix 3052B</toolPN>
<toolCage>-</toolCage>
</tool>
<tool id="4">
<toolName>Socket set</toolName>
<toolPN>737828</toolPN>
<toolCage>-</toolCage>
</tool>
</tools>
</data>
I also need to pull the tool's actual name and details from Tools.XML so the output xml file lists the tools details such as name and part number etc. instead of just an ID.
So what I want is something like this output:
<data>
<DMs>
<task>
<DMC>TEST-4BX-AG3-00-00-0000-125B-A</DMC>
<techName>Fixing fence</techName>
<infoName>Inserting panels</infoName>
<tools>
<tool id="1">
<toolName>Hammer</toolName>
<toolPN>345123</toolPN>
<toolCage>-</toolCage>
</tool>
<tool id="4">
<toolName>Socket set</toolName>
<toolPN>737828</toolPN>
<toolCage>-</toolCage>
</tool>
</tools>
</task>
<task>
I've been messing around with the XSLT but can't get it right. Everything i try seems to completely remove the tools from the output.
Here's some XSLT i've been playing with using Document() function and XSLT Key, but i'm really just trying to learn XSLT as i go and am getting frustrated.
<?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="dataModuleLookupDoc" select="document('C:\TOOLS.xml')"/>
<xsl:key name="toolIDKey" match="tool" use="#id"/>
<xsl:template match="task">
<xsl:apply-templates select="$dataModuleLookupDoc"/>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="tools"/>
<xsl:template match="tool">
<xsl:variable name="toolID" select="#ID"/>
<xsl:for-each select="$dataModuleLookupDoc">
<xsl:value-of select="key('toolIDKey',toolID)"/>
</xsl:for-each>
<xsl:text> </xsl:text>
<xsl:apply-templates/>
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Instead of key('toolIDKey',toolID) you want key('toolIDKey, $toolID). That should find the element in the other document, of course the <xsl:value-of select="key('toolIDKey, $toolID)"/> seems too simple an attempt to "pull the tool's actual name and details", either use xsl:copy-of if you simply want the element from the other document copied or push it through some templates with xsl:apply-templates to transform it as needed.
Try xml linq
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME1 = #"c:\temp\test1.xml";
const string FILENAME2 = #"c:\temp\test2.xml";
static void Main(string[] args)
{
XDocument xTools = XDocument.Load(FILENAME2);
Dictionary<int, Tool> toolsDict = xTools.Descendants("tool")
.Select(x => new Tool() {
id = (int)x.Attribute("id"),
name = (string)x.Element("toolName"),
pn = (string)x.Element("toolPN"),
cage = (string)x.Element("toolCage")
}).GroupBy(x => x.id, y => y)
.ToDictionary(x => x.Key, y => y.FirstOrDefault());
XDocument xTasks = XDocument.Load(FILENAME1);
List<XElement> toolTask = xTasks.Descendants("tool").ToList();
foreach (XElement tool in toolTask)
{
int id = (int)tool.Element("id");
int qty = (int)tool.Element("qty");
if(toolsDict.ContainsKey(id))
{
Tool idTool = toolsDict[id];
tool.ReplaceWith(new XElement("tool", new object[] {
new XAttribute("id", id),
new XAttribute("qty", qty),
new XElement("toolName", idTool.name),
new XElement("toolPN", idTool.pn),
new XElement("toolCage", idTool.cage)
}));
}
// <toolName>Hammer</toolName>
// <toolPN>345123</toolPN>
// <toolCage>-</toolCage>
//</tool>
}
}
}
public class Tool
{
public int id { get;set;}
public string name { get; set; }
public string pn { get; set; }
public string cage { get; set; }
}
}
Related
i need help for below xml. I wonder how can I bring the XML below, I've been trying for a very long time, but it didn't work. I would be grateful if you could help with this.
I can do the same thing in php, but since my data is a little big, php takes a lot of time, I read the file from the web service with c# and save it as xml, but it didn't work out as I wanted.
<ResultOfProductList xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ServerCode>kenan</ServerCode>
<ClientCode>B124565</ClientCode>
<Username>karanfil</Username>
<ClientIPAddress>78.135.235.3</ClientIPAddress>
<Successful>true</Successful>
<RequestDate>2022-07-22T12:28:54.6238843+03:00</RequestDate>
<ElapsedTimeMS>17</ElapsedTimeMS>
<MethodType>GetAllProductsByParts</MethodType>
<Result EndOfProducts="false">
<CustomerCode>B124565</CustomerCode>
<CustomerName>kenan</CustomerName>
<Date>2022-07-22T12:28:54.6238843+03:00</Date>
<Brands>
<Brand ID="174" Lang="tr" BrandName="PARTSMALL-KORE" StandardName=""/>
</Brands>
<Products>
<Product ID="134898" BrandID="174" ProductCode="KR-PML-PTA-086" ProducerCode="" MinOrderAmount="1" PiecesInBox="1" Unit="PCE" New="false">
<ProductNames>
<ProductName Lang="tr">VITES HALATI ( HYUNDAI : ACCENT 95-00 )</ProductName>
</ProductNames>
<BaseOeNr>43794-22000</BaseOeNr>
<Pricing>
<ListPriceCurrency>USD</ListPriceCurrency>
<LocalCurrency>TLY</LocalCurrency>
<CurrencyRate>17.6599</CurrencyRate>
<ListPriceWoVAT>31.24</ListPriceWoVAT>
<LocalListPriceWVat>651.0004</LocalListPriceWVat>
<LocalListPriceWoVat>551.695251</LocalListPriceWoVat>
<LocalNetPriceWVat>377.580261</LocalNetPriceWVat>
<LocalNetPriceWoVat>319.983246</LocalNetPriceWoVat>
<Discount1>42</Discount1>
<Discount2>0</Discount2>
<Discount3>0</Discount3>
<Discount4>0</Discount4>
<Discount5>0</Discount5>
<Discount6>0</Discount6>
<InDiscount>false</InDiscount>
</Pricing>
<Stocks>
<Stock WarehouseID="1" Equality="Eq">0</Stock>
<Stock WarehouseID="7" Equality="Eq">0</Stock>
<Stock WarehouseID="4" Equality="Eq">0</Stock>
<Stock WarehouseID="2" Equality="Eq">0</Stock>
<Stock WarehouseID="5" Equality="Eq">0</Stock>
</Stocks>
</Product>
</Result>
</ResultOfProductList>
Desired output
<Products>
<Product>
<ID>134898</ID>
<BrandID>174</BrandID>
<BaseOeNr>43794-22000</BaseOeNr>
<ProductCode>KR-PML-PTA-086</ProductCode>
<ProductName>VITES HALATI ( HYUNDAI : ACCENT 95-00 )</ProductName>
<LocalCurrency>TLY</LocalCurrency>
<LocalNetPriceWVat>377.580261</LocalNetPriceWVat>
<Stocks>WarehouseID=1 + WarehouseID=7 + WarehouseID=4 + WarehouseID=2 + WarehouseID=5 </Stocks>
</Product>
</Products>
Here is XSLT based solution.
It is just not clear if the <Stocks> element value is what you need.
Input XML
<?xml version="1.0"?>
<ResultOfProductList xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ServerCode>kenan</ServerCode>
<ClientCode>B124565</ClientCode>
<Username>karanfil</Username>
<ClientIPAddress>78.135.235.3</ClientIPAddress>
<Successful>true</Successful>
<RequestDate>2022-07-22T12:28:54.6238843+03:00</RequestDate>
<ElapsedTimeMS>17</ElapsedTimeMS>
<MethodType>GetAllProductsByParts</MethodType>
<Result EndOfProducts="false">
<CustomerCode>B124565</CustomerCode>
<CustomerName>kenan</CustomerName>
<Date>2022-07-22T12:28:54.6238843+03:00</Date>
<Brands>
<Brand ID="174" Lang="tr" BrandName="PARTSMALL-KORE" StandardName=""/>
</Brands>
<Products>
<Product ID="134898" BrandID="174" ProductCode="KR-PML-PTA-086" ProducerCode="" MinOrderAmount="1" PiecesInBox="1" Unit="PCE" New="false">
<ProductNames>
<ProductName Lang="tr">VITES HALATI ( HYUNDAI : ACCENT 95-00 )</ProductName>
</ProductNames>
<BaseOeNr>43794-22000</BaseOeNr>
<Pricing>
<ListPriceCurrency>USD</ListPriceCurrency>
<LocalCurrency>TLY</LocalCurrency>
<CurrencyRate>17.6599</CurrencyRate>
<ListPriceWoVAT>31.24</ListPriceWoVAT>
<LocalListPriceWVat>651.0004</LocalListPriceWVat>
<LocalListPriceWoVat>551.695251</LocalListPriceWoVat>
<LocalNetPriceWVat>377.580261</LocalNetPriceWVat>
<LocalNetPriceWoVat>319.983246</LocalNetPriceWoVat>
<Discount1>42</Discount1>
<Discount2>0</Discount2>
<Discount3>0</Discount3>
<Discount4>0</Discount4>
<Discount5>0</Discount5>
<Discount6>0</Discount6>
<InDiscount>false</InDiscount>
</Pricing>
<Stocks>
<Stock WarehouseID="1" Equality="Eq">0</Stock>
<Stock WarehouseID="7" Equality="Eq">0</Stock>
<Stock WarehouseID="4" Equality="Eq">0</Stock>
<Stock WarehouseID="2" Equality="Eq">0</Stock>
<Stock WarehouseID="5" Equality="Eq">0</Stock>
</Stocks>
</Product>
</Products>
</Result>
</ResultOfProductList>
XSLT
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="utf-8" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/ResultOfProductList">
<Products>
<xsl:for-each select="Result/Products/Product">
<xsl:variable name="BrandID" select="#BrandID"/>
<Product>
<ID>
<xsl:value-of select="#ID"/>
</ID>
<!--<BrandID>
<xsl:value-of select="#BrandID"/>
</BrandID>-->
<BrandName><xsl:value-of select="../../Brands/Brand[#ID=$BrandID]/#BrandName"/></BrandName>
<BaseOeNr>
<xsl:value-of select="BaseOeNr"/>
</BaseOeNr>
<ProductCode>
<xsl:value-of select="#ProductCode"/>
</ProductCode>
<ProductName>
<xsl:value-of select="ProductNames/ProductName"/>
</ProductName>
<LocalCurrency>
<xsl:value-of select="Pricing/LocalCurrency"/>
</LocalCurrency>
<LocalNetPriceWVat>
<xsl:value-of select="Pricing/LocalNetPriceWVat"/>
</LocalNetPriceWVat>
<Stocks><xsl:value-of select="sum(Stocks/Stock)"/></Stocks>
</Product>
</xsl:for-each>
</Products>
</xsl:template>
</xsl:stylesheet>
Output XML
<Products>
<Product>
<ID>134898</ID>
<BrandName>PARTSMALL-KORE</BrandName>
<BaseOeNr>43794-22000</BaseOeNr>
<ProductCode>KR-PML-PTA-086</ProductCode>
<ProductName>VITES HALATI ( HYUNDAI : ACCENT 95-00 )</ProductName>
<LocalCurrency>TLY</LocalCurrency>
<LocalNetPriceWVat>377.580261</LocalNetPriceWVat>
<Stocks>0</Stocks>
</Product>
</Products>
c#
void Main()
{
const string SOURCEXMLFILE = #"e:\Temp\input.xml";
const string XSLTFILE = #"e:\Temp\process.xslt";
const string OUTPUTXMLFILE = #"e:\temp\output.xml";
try
{
XsltArgumentList xslArg = new XsltArgumentList();
using (XmlReader src = XmlReader.Create(SOURCEXMLFILE))
{
XslCompiledTransform xslt = new XslCompiledTransform();
xslt.Load(XSLTFILE, new XsltSettings(true, true), new XmlUrlResolver());
XmlWriterSettings settings = xslt.OutputSettings.Clone();
settings.IndentChars = "\t";
// to remove BOM
settings.Encoding = new UTF8Encoding(false);
using (XmlWriter result = XmlWriter.Create(OUTPUTXMLFILE, settings))
{
xslt.Transform(src, xslArg, result, new XmlUrlResolver());
result.Close();
}
}
Console.WriteLine("File '{0}' has been generated.", OUTPUTXMLFILE);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
I have a task that is very cumbersome, as I have to do it by hand (my company bought a tool for a ludicrous amount of money that doesn't work anymore).
I have two XML lists, each of them has half of it missing, so I need to piece it together into a third one before sending it to the system. As well as changing the header to the date I'm sending the file.
I've managed to set up the input for the date, it's easy enough to ask the user for the input. But the rest of the task is driving me mad, I tried many things I've found around here and on the Microsoft forums, to no avail... this task is killing me because I'm the only one that is "tech savvy" to do it (it's literally monkey job of copying and pasting text from XMLs).
It's not trucks and cars, obviously, and there are more files than two, but they all are like this: one kind has only cars, with an empty truck node, and the other has trucks but with empty cars node.
Example of File 1
<?xml version="1.0" encoding="utf-8"?>
<document>
<header>
<startDate>01/01/2020</startDate>
<endDate>01/02/2020</endDate>
</header>
<body>
<cars>
<car>
<ID>1</ID>
<Name>Blue Car</Name>
</car>
<car>
<ID>2</ID>
<Name>Red Car</Name>
</car>
</cars>
<trucks>
</trucks>
</body>
</document>
Example of File 2
<?xml version="1.0" encoding="utf-8"?>
<document>
<header>
<startDate>01/01/2020</startDate>
<endDate>01/02/2020</endDate>
</header>
<body>
<cars>
</cars>
<trucks>
<truck>
<ID>1</ID>
<Name>Blue Truck</Name>
</truck>
<truck>
<ID>2</ID>
<Name>Red Truck</Name>
</truck>
</trucks>
</body>
</document>
Example of File 3
<?xml version="1.0" encoding="utf-8"?>
<document>
<header>
<Date>03/02/2020</Date>
</header>
<body>
<cars>
<car>
<ID>1</ID>
<Name>Blue Car</Name>
</car>
<car>
<ID>2</ID>
<Name>Red Car</Name>
</car>
</cars>
<trucks>
<truck>
<ID>1</ID>
<Name>Blue Truck</Name>
</truck>
<truck>
<ID>2</ID>
<Name>Red Truck</Name>
</truck>
</trucks>
</body>
</document>
Use XSLT:
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="*">
<xsl:copy><xsl:apply-templates/></xsl:copy>
</xsl:template>
<xsl:template match="cars">
<cars>
<xsl:copy-of select="car"/>
<xsl:copy-of select="document('other-doc.xml')//car"/>
</cars>
</xsl:template>
<xsl:template match="trucks">
<trucks>
<xsl:copy-of select="truck"/>
<xsl:copy-of select="document('other-doc.xml')//truck"/>
</trucks>
</xsl:template>
</xsl:transform>
This can of course be run very easily using from C# using the System.Xml.Xsl processor.
I haven't tried to do anything with the dates in the header because I'm not sure what your logic is, but that's easily added.
If you want to use a later version of XSLT it becomes a little shorter but then you need to install a third-party library:
<xsl:transform version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="cars">
<cars>
<xsl:copy-of select="car, doc('other-doc.xml')//car"/>
</cars>
</xsl:template>
<xsl:template match="trucks">
<trucks>
<xsl:copy-of select="truck, doc('other-doc.xml')//truck"/>
</trucks>
</xsl:template>
</xsl:transform>
Please use below code
string xml1 = #"<document><header><startDate>01/01/2020</startDate><endDate> 01/02/ 2020</endDate></header><body><cars><car><ID>1</ID><Name>Blue Car</Name></car><car><ID>2</ID><Name>Red Car</Name></car></cars><trucks></trucks></body></document>";
//load xml in StringReader
StringReader sr = new StringReader(xml1);
DataSet ds1 = new DataSet();
// Read XML in ds1
ds1.ReadXml(sr);
string xml2 = #"<document><header><startDate> 01 / 01 / 2020 </startDate><endDate> 01 / 02 / 2020</endDate></header><body><cars></cars><trucks><truck><ID> 1 </ID><Name> Blue Truck </Name></truck><truck><ID>2</ID><Name>Red Truck</Name></truck></trucks></body></document>";
sr = new StringReader(xml2);
DataSet ds2 = new DataSet();
ds2.ReadXml(sr);
DataSet dsHeaders = new DataSet();
dsHeaders.Tables.Add(ds1.Tables[0].Copy());
string headersXML = dsHeaders.GetXml();
headersXML = headersXML.Replace("<NewDataSet>", "").Replace("</NewDataSet>", "");
// Console.WriteLine(headersXML.Trim());
DataSet dsCars = new DataSet("Cars");
dsCars.Tables.Add(ds1.Tables[3].Copy());
string carsXML = dsCars.GetXml();
// Console.WriteLine(carsXML);
DataSet dsTrucks = new DataSet("Trucks");
dsTrucks.Tables.Add(ds2.Tables[3].Copy());
string trucksXML = dsTrucks.GetXml();
// Console.WriteLine(trucksXML);
string resultXML = #"<document>" + headersXML + "<body>" + Environment.NewLine + carsXML + trucksXML + Environment.NewLine + "</body>" + Environment.NewLine + "</document>";
Console.WriteLine(resultXML);
Your required XML
<document>
<header>
<startDate>01/01/2020</startDate>
<endDate> 01/02/ 2020</endDate>
</header>
<body>
<Cars>
<car>
<ID>1</ID>
<Name>Blue Car</Name>
</car>
<car>
<ID>2</ID>
<Name>Red Car</Name>
</car>
</Cars><Trucks>
<truck>
<ID> 1 </ID>
<Name> Blue Truck </Name>
</truck>
<truck>
<ID>2</ID>
<Name>Red Truck</Name>
</truck>
</Trucks>
</body>
</document>
Using xml linq :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication2
{
class Program
{
const string FILE1 = #"c:\TEMP\TEST.XML";
const string FILE2 = #"c:\TEMP\TEST1.XML";
static void Main(string[] args)
{
string xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?><document></document>";
XDocument doc3 = XDocument.Parse(xml);
XElement doc = doc3.Root;
doc.Add(new XElement("header", new XElement("Date", DateTime.Now.ToString("mm/dd/yyyy"))));
XDocument doc1 = XDocument.Load(FILE1);
XDocument doc2 = XDocument.Load(FILE2);
XElement body = new XElement("body");
doc.Add(body);
XElement cars = new XElement("cars");
body.Add(cars);
List<XElement> cars1 = doc1.Descendants("car").ToList();
cars.Add(cars1);
List<XElement> cars2 = doc2.Descendants("car").ToList();
cars.Add(cars2);
XElement trucks = new XElement("trucks");
body.Add(trucks);
List<XElement> trucks1 = doc1.Descendants("truck").ToList();
trucks.Add(trucks1);
List<XElement> trucks2 = doc2.Descendants("truck").ToList();
trucks.Add(trucks2);
}
}
}
I am building some text snippets from XML documents using XSL transformation. The text can include '<' and '>' (and other special characters) in the output.
Given the following xml as data.xml:
<SCH>
<Ship Id="1" Name="Dicke Bertha" OperatingCostsDay="10.0000000" currency="USD" />
</SCH>
and the following xsl as transformation.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:template match="/">
Schiff: <xsl:for-each select="SCH/Ship"> Id: <xsl:value-of select="#Id"/> <xsl:value-of select="#Name"/> (OC:<xsl:value-of select="#OperatingCostsDay"/> <xsl:value-of select="#currency"/>)
</xsl:for-each>Betriebskosten < 500 USD oder > 1 Mio. USD
</xsl:template>
</xsl:stylesheet>
I have the problem that the escape sequences < and > when loading the XSLT:
var xslDocument = XDocument.Load("transformation.xslt");
The output after the transformation is not - as expected - with the '<' and '>' characters but it has the HTML escape sequences.
Schiff: Id: 1 Dicke Bertha (OC:10.0000000 USD)
Betriebskosten < 500 USD oder > 1 Mio. USD
What can I do here?
For completeness, here is the full code example:
class Program
{
static void Main(string[] args)
{
var xslDocument = XDocument.Load("transformation.xslt");
var compiled = new XslCompiledTransform();
using (var reader = xslDocument.CreateReader())
{
compiled.Load(reader);
}
var xml = File.ReadAllText("data.xml");
Console.WriteLine(Transform(compiled, xml));
}
public static string Transform(XslCompiledTransform xsl, string xml)
{
// allow fragments
var writerSettings = new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Auto };
var readerSettings = new XmlReaderSettings { ConformanceLevel = ConformanceLevel.Auto };
var stringReader = new StringReader(xml);
var details = new StringBuilder();
using (var reader = XmlReader.Create(stringReader, readerSettings))
{
using (var writer = XmlWriter.Create(details, writerSettings))
{
xsl.Transform(reader, writer);
}
}
return details.ToString();
}
}
I am building some text snippets
If you are outputting text, then set your output method to text. Then your processor will know not to escape characters that are reserved in XML.
XSLT
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="utf-8" />
<xsl:template match="/">
Schiff: <xsl:for-each select="SCH/Ship"> Id: <xsl:value-of select="#Id"/> <xsl:value-of select="#Name"/> (OC:<xsl:value-of select="#OperatingCostsDay"/> <xsl:value-of select="#currency"/>)
</xsl:for-each>Betriebskosten < 500 USD oder > 1 Mio. USD
</xsl:template>
</xsl:stylesheet>
Result:
Schiff: Id: 1 Dicke Bertha (OC:10.0000000 USD)
Betriebskosten < 500 USD oder > 1 Mio. USD
Note also that literal text is best put inside xsl:text instructions - otherwise you're passing all the unwanted surrounding whitespace to the output.
in XSLT, you can try like
Here I have added <xsl:text disable-output-escaping="yes"><</xsl:text> and <xsl:text disable-output-escaping="yes">></xsl:text>
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:template match="/">
Schiff: <xsl:for-each select="SCH/Ship"> Id: <xsl:value-of select="#Id"/> <xsl:value-of select="#Name"/> (OC:<xsl:value-of select="#OperatingCostsDay"/> <xsl:value-of select="#currency"/>)
</xsl:for-each> Betriebskosten <xsl:text disable-output-escaping="yes"><</xsl:text> 500 USD oder <xsl:text disable-output-escaping="yes">></xsl:text> 1 Mio. USD
</xsl:template>
</xsl:stylesheet>
I am developing a app with C# and trying to complete a XML that I have got from a JSON. And for the XML to be valid for my app, I need to group the elements with the same name under a father element.
For example, I got this XML
<root>
<row>
<id>0001</id>
<type>credit</type>
<investment>1000</investment>
<ppr>0.83</ppr>
<candidate>
<id>5001</id>
<name>Hugo</name>
</candidate>
<candidate>
<id>5002</id>
<name>Jack</name>
</candidate>
<candidate>
<id>5005</id>
<name>Kate</name>
</candidate>
</row>
And I need to group all the elements with the name candidate, under a father node candidates, like this
<root>
<row>
<id>0001</id>
<type>credit</type>
<investment>1000</investment>
<ppr>0.83</ppr>
<candidates>
<candidate>
<id>5001</id>
<name>Hugo</name>
</candidate>
<candidate>
<id>5002</id>
<name>Jack</name>
</candidate>
<candidate>
<id>5005</id>
<name>Kate</name>
</candidate>
</candidates>
</row>
But here is my problem: I don't know the names that I can receive from the JSON. So I need to do this comparison and complete the XML without knowing the "candidate" node name. I need this for any name that I can receive.
Also in this example the XML only has 2 levels, but it can have any number of levels. I can iterate over the XML without problem with this function:
public void findAllNodes(XmlNode node)
{
Console.WriteLine(node.Name);
foreach (XmlNode n in node.ChildNodes)
findAllNodes(n);
}
How can I make the comparison and group the nodes?
A fairly naive implementation could use LINQ to group elements by name and add a parent element for those that have more than 1 item in a group. This would be recursive, so child elements of an element were grouped until the tree was exhausted.
The naive-ness is that such a solution would break if there were mixed content elements, and it would group elements that weren't siblings (basically, both issues will result in things ending up in the wrong order). It should give you a good start, and could be enough for your purposes.
private static IEnumerable<XElement> GroupElements(IEnumerable<XElement> elements)
{
var elementsByName = elements.GroupBy(x => x.Name);
foreach (var grouping in elementsByName)
{
var transformed = grouping.Select(e =>
new XElement(e.Name,
GroupElements(e.Elements()),
e.Attributes(),
e.Nodes().OfType<XText>()));
if (grouping.Count() == 1)
{
yield return transformed.Single();
}
else
{
var groupName = grouping.Key + "s";
yield return new XElement(groupName, transformed);
}
}
}
You can use this by parsing/loading your existing XML and then transforming the root elements and creating a new document from those:
var original = XDocument.Parse(xml);
var grouped = new XDocument(GroupElements(original.Elements()));
See this fiddle for a working demo.
Here's an XSLT 2.0 solution:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="*[*]">
<xsl:copy>
<xsl:for-each-group select="*" group-adjacent="node-name(.)">
<xsl:choose>
<xsl:when test="count(current-group()) > 1">
<xsl:element name="{name()}s" namespace="{namespace-uri()}">
<xsl:apply-templates select="current-group()"/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
Output:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<row>
<id>0001</id>
<type>credit</type>
<investment>1000</investment>
<ppr>0.83</ppr>
<candidates>
<candidate>
<id>5001</id>
<name>Hugo</name>
</candidate>
<candidate>
<id>5002</id>
<name>Jack</name>
</candidate>
<candidate>
<id>5005</id>
<name>Kate</name>
</candidate>
</candidates>
</row>
</root>
Limitations
It doesn't handle mixed content (elements with children plus text content)
It drops attributes (easily fixed)
For sending atomic data types will use like
transformer.SetParameter(new QName("", "", customXml), new XdmAtomicValue("true"));
how to pass a XML/Node as a param to XSLT from C# ?
Can you please help me
followed your code it's working fine but i am getting only text inside the xml(what i am passing in parameter) but not Nodes
XSLT :
<xsl:param name="look-up" as="document-node()"/>
<xsl:template match="xpp:document">
<w:document xml:space="preserve">
<xsl:value-of select="$look-up"/>
</w:document>
</xsl:template>
XML
<?xml version="1.0" encoding="UTF-8"?>
<document version="1.0" xmlns="http://www.sdl.com/xpp">
//some tags
</document>
passing parameter (xml)
<Job>
</id>
</Job>
I think you should use the Processor object to construct an XdmNode, see the documentation which says:
The Processor provides a method NewDocumentBuilder which, as the name
implies, returns a DocumentBuilder. This may be used to construct a
document (specifically, an XdmNode) from a variety of sources. The
input can come from raw lexical XML by specifying a Stream or a Uri,
or it may come from a DOM document built using the Microsoft XML
parser by specifying an XmlNode, or it may be supplied
programmatically by nominating an XmlReader.
Then you can pass in the XdmNode to the SetParameter method http://saxonica.com/documentation/html/dotnetdoc/Saxon/Api/XsltTransformer.html#SetParameter%28Saxon.Api.QName,Saxon.Api.XdmValue%29 as XdmValue is a base class of XdmNode (XmlValue -> XdmItem -> XdmNode).
Here is an example that works for me with Saxon 9.5 HE on .NET:
Processor proc = new Processor();
DocumentBuilder db = proc.NewDocumentBuilder();
XsltTransformer trans;
using (XmlReader xr = XmlReader.Create("../../XSLTFile1.xslt"))
{
trans = proc.NewXsltCompiler().Compile(xr).Load();
}
XdmNode input, lookup;
using (XmlReader xr = XmlReader.Create("../../XMLFile1.xml"))
{
input = db.Build(xr);
}
using (XmlReader xr = XmlReader.Create("../../XMLFile2.xml"))
{
lookup = db.Build(xr);
}
trans.InitialContextNode = input;
trans.SetParameter(new QName("lookup-doc"), lookup);
using (XmlWriter xw = XmlWriter.Create(Console.Out))
{
trans.Run(new TextWriterDestination(xw));
}
The XSLT is
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:param name="lookup-doc"/>
<xsl:key name="map" match="map" use="key"/>
<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">
<xsl:copy>
<xsl:value-of select="key('map', ., $lookup-doc)/value"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
the XML documents are
<root>
<item>foo</item>
</root>
and
<root>
<map>
<key>foo</key>
<value>bar</value>
</map>
</root>
the resulting output is
<root>
<item>bar</item>
</root>