Compare Node Names in every level of XML - c#

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)

Related

Valid XSL Transform won't parse in c#

I have this XML input:
<?xml version="1.0"?>
<xc:XmlCache xmlns:xc="XmlCache" xmlns:mp="mx.MarketParameters" xmlns:rt="mx.MarketParameters.Rates" xmlns:rtcu="mx.MarketParameters.Rates.Curve">
<xc:XmlCacheArea xc:value="MarketParameters">
<mp:nickName xmlns:mp="mx.MarketParameters" xc:value="BO">
<mp:date xc:value="20211208">
<rt:rate xmlns:rt="mx.MarketParameters.Rates">
<rtcu:curve xmlns:rtcu="mx.MarketParameters.Rates.Curve">
<rtcu:currency xc:value="USD">
<rtcu:label xc:value="USD: SOV_ZC">
<rtcu:type xc:value="Swap">
<rtcu:generator xc:value="USD S TREAS">
<rtcu:market xc:value="">
<rtcu:maturity xc:value="10Y" xc:dates="20211210-20311210" xc:type="Fields">
<mp:askConvexitySpread xc:keyFormat="N">0</mp:askConvexitySpread>
<mp:askCurveSpread xc:keyFormat="N">0.000000000</mp:askCurveSpread>
<mp:askDiscount xc:keyFormat="N">0.864632927818374</mp:askDiscount>
<mp:askDiscountBasisSpread xc:keyFormat="N"/>
<mp:askMarketQuote xc:keyFormat="N">1.446068284</mp:askMarketQuote>
<mp:askZeroCoupon xc:keyFormat="N">1.45291000006736</mp:askZeroCoupon>
<mp:askZeroCouponBasisSpread xc:keyFormat="N"/>
<mp:askZeroCouponSpread xc:keyFormat="N">0.000000000</mp:askZeroCouponSpread>
<mp:bidConvexitySpread xc:keyFormat="N">0</mp:bidConvexitySpread>
<mp:bidCurveSpread xc:keyFormat="N">0.000000000</mp:bidCurveSpread>
<mp:bidDiscount xc:keyFormat="N">0.864632927818374</mp:bidDiscount>
<mp:bidDiscountBasisSpread xc:keyFormat="N"/>
<mp:bidMarketQuote xc:keyFormat="N">1.446068284</mp:bidMarketQuote>
<mp:bidZeroCoupon xc:keyFormat="N">1.45291000006736</mp:bidZeroCoupon>
<mp:bidZeroCouponBasisSpread xc:keyFormat="N"/>
<mp:bidZeroCouponSpread xc:keyFormat="N">0.000000000</mp:bidZeroCouponSpread>
<mp:frontierDate xc:keyFormat="N">0</mp:frontierDate>
<mp:intervalSplineEta xc:keyFormat="N">-1</mp:intervalSplineEta>
<mp:midConvexitySpread xc:keyFormat="N">0</mp:midConvexitySpread>
<mp:midCurveSpread xc:keyFormat="N">0.000000000</mp:midCurveSpread>
<mp:midDiscount xc:keyFormat="N">0.864632927818374</mp:midDiscount>
<mp:midDiscountBasisSpread xc:keyFormat="N"/>
<mp:midMarketQuote xc:keyFormat="N">1.446068284</mp:midMarketQuote>
<mp:midZeroCoupon xc:keyFormat="N">1.45291000006736</mp:midZeroCoupon>
<mp:midZeroCouponBasisSpread xc:keyFormat="N"/>
<mp:midZeroCouponSpread xc:keyFormat="N">0.000000000</mp:midZeroCouponSpread>
<mp:selected xc:keyFormat="N">1</mp:selected>
</rtcu:maturity>
<rtcu:maturity xc:value="15Y" xc:dates="20211210-20361210" xc:type="Fields">
<mp:askConvexitySpread xc:keyFormat="N">0</mp:askConvexitySpread>
<mp:askCurveSpread xc:keyFormat="N">0.000000000</mp:askCurveSpread>
<mp:askDiscount xc:keyFormat="N">0.769521955887512</mp:askDiscount>
<mp:askDiscountBasisSpread xc:keyFormat="N"/>
<mp:askMarketQuote xc:keyFormat="N">1.716406662</mp:askMarketQuote>
<mp:askZeroCoupon xc:keyFormat="N">1.74466000012162</mp:askZeroCoupon>
<mp:askZeroCouponBasisSpread xc:keyFormat="N"/>
<mp:askZeroCouponSpread xc:keyFormat="N">0.000000000</mp:askZeroCouponSpread>
<mp:bidConvexitySpread xc:keyFormat="N">0</mp:bidConvexitySpread>
<mp:bidCurveSpread xc:keyFormat="N">0.000000000</mp:bidCurveSpread>
<mp:bidDiscount xc:keyFormat="N">0.769521955887512</mp:bidDiscount>
<mp:bidDiscountBasisSpread xc:keyFormat="N"/>
<mp:bidMarketQuote xc:keyFormat="N">1.716406662</mp:bidMarketQuote>
<mp:bidZeroCoupon xc:keyFormat="N">1.74466000012162</mp:bidZeroCoupon>
<mp:bidZeroCouponBasisSpread xc:keyFormat="N"/>
<mp:bidZeroCouponSpread xc:keyFormat="N">0.000000000</mp:bidZeroCouponSpread>
<mp:frontierDate xc:keyFormat="N">0</mp:frontierDate>
<mp:intervalSplineEta xc:keyFormat="N">-1</mp:intervalSplineEta>
<mp:midConvexitySpread xc:keyFormat="N">0</mp:midConvexitySpread>
<mp:midCurveSpread xc:keyFormat="N">0.000000000</mp:midCurveSpread>
<mp:midDiscount xc:keyFormat="N">0.769521955887512</mp:midDiscount>
<mp:midDiscountBasisSpread xc:keyFormat="N"/>
<mp:midMarketQuote xc:keyFormat="N">1.716406662</mp:midMarketQuote>
<mp:midZeroCoupon xc:keyFormat="N">1.74466000012162</mp:midZeroCoupon>
<mp:midZeroCouponBasisSpread xc:keyFormat="N"/>
<mp:midZeroCouponSpread xc:keyFormat="N">0.000000000</mp:midZeroCouponSpread>
<mp:selected xc:keyFormat="N">1</mp:selected>
</rtcu:maturity>
</rtcu:market>
</rtcu:generator>
</rtcu:type>
</rtcu:label>
</rtcu:currency>
</rtcu:curve>
</rt:rate>
</mp:date>
</mp:nickName>
</xc:XmlCacheArea>
</xc:XmlCache>
I am using this transform :
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:mp="mx.MarketParameters" xmlns:xc="xmlCache" xmlns:rt="mx.MarketParameters.Rates" xmlns:rtcu="mx.MarketParameters.Rates.Curve">
<xsl:template match="/">
<data>
<xsl:apply-templates select="*"/>
</data>
</xsl:template>
<xsl:template match="rtcu:maturity">
<row>
<xsl:attribute name="Cle"><xsl:value-of select="../../../../../#*:value"/>;<xsl:value-of select="../../../../#*:value"/>;<xsl:value-of select="../../../#*:value"/>;<xsl:value-of select="../../#*:value"/>;<xsl:value-of select="../#*:value"/>;<xsl:value-of select="#*:value"/></xsl:attribute>
<xsl:attribute name="Source1"><xsl:value-of select="mp:askZeroCoupon"/></xsl:attribute>
<xsl:attribute name="Source2"><xsl:value-of select="mp:bidZeroCoupon"/></xsl:attribute>
<xsl:attribute name="Source3"><xsl:value-of select="mp:midZeroCoupon"/></xsl:attribute>
</row>
</xsl:template>
</xsl:stylesheet>
And the result is as expected when I use the validator here: https://www.freeformatter.com/xsl-transformer.html
<data xmlns:mp="mx.MarketParameters"
xmlns:xc="xmlCache"
xmlns:rt="mx.MarketParameters.Rates"
xmlns:rtcu="mx.MarketParameters.Rates.Curve">
<row Cle="USD;USD: SOV_ZC;Swap;USD S TREAS;;10Y"
Source1="1.45291000006736"
Source2="1.45291000006736"
Source3="1.45291000006736"/>
<row Cle="USD;USD: SOV_ZC;Swap;USD S TREAS;;15Y"
Source1="1.74466000012162"
Source2="1.74466000012162"
Source3="1.74466000012162"/>
</data>
However when I run this transform in a C# app and call the Load() function in XslCompiledTransform when I pass it this transform above, it fails with the error
Expected end of the expression, found `':'.\r\n../../../../../#* -->:<--`
It basically refuses to check the other namespaces for the attribute I'm looking for on this node.
I know that the attributes are in the xc namespace but when I use the xc: namespace to get the attribute values to build my key like so:
<xsl:value-of select="#xc:value"/>
The values are all null which results in a key that looks like this in the output:
<row Cle=";;;;;"
No bueno!
How can I output the values of all the xc:value attributes from these tags:
<rtcu:currency xc:value="USD">
<rtcu:label xc:value="USD: SOV_ZC">
<rtcu:type xc:value="Swap">
<rtcu:generator xc:value="USD S TREAS">
<rtcu:market xc:value="">
<rtcu:maturity xc:value="10Y"
into my key row:
<row Cle="USD;USD: SOV_ZC;Swap;USD S TREAS;;10Y"
while not triggering a parsing error in the c# Load() function?
There are two errors in your code:
Namespace wildcards like *:value are a feature of XPath-2.0 and above
Local name tests (namespace wildcards) were eventually added to XPath 2.0
So you can't use them in your XSLT-1.0 code
You have a typo in your xc: namespace declaration:
In your XML it's xmlns:xc="XmlCache"
In your XSLT it's xmlns:xc="xmlCache"
If you fix both, the code should work as desired.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:mp="mx.MarketParameters" xmlns:xc="XmlCache" xmlns:rt="mx.MarketParameters.Rates" xmlns:rtcu="mx.MarketParameters.Rates.Curve">
<xsl:template match="/">
<data>
<xsl:apply-templates select="*"/>
</data>
</xsl:template>
<xsl:template match="rtcu:maturity">
<row>
<xsl:attribute name="Cle"><xsl:value-of select="../../../../../#xc:value"/>;<xsl:value-of select="../../../../#xc:value"/>;<xsl:value-of select="../../../#xc:value"/>;<xsl:value-of select="../../#xc:value"/>;<xsl:value-of select="../#xc:value"/>;<xsl:value-of select="#xc:value"/></xsl:attribute>
<xsl:attribute name="Source1"><xsl:value-of select="mp:askZeroCoupon"/></xsl:attribute>
<xsl:attribute name="Source2"><xsl:value-of select="mp:bidZeroCoupon"/></xsl:attribute>
<xsl:attribute name="Source3"><xsl:value-of select="mp:midZeroCoupon"/></xsl:attribute>
</row>
</xsl:template>
</xsl:stylesheet>
BTW you can simplify one of your expressions to
<xsl:attribute name="Cle"><xsl:for-each select="ancestor-or-self::rtcu:*"><xsl:value-of select="#xc:value"/><xsl:if test="position()!=last() and position()!=1">;</xsl:if></xsl:for-each></xsl:attribute>
This concatenates the xc:value attributes of all rtcu:* ancestors. Maybe that's of some use to you.

Using XSLT to query and pull data from XML

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; }
}
}

How to add XML node before and after set of nodes

I'm looking for a way to add this node "LoanSecondaryStatusDates" at the beginning and its corresponding closing tag "LoanSecondaryStatusDates" to the end. I've produced the below using "FOR XML" in SQL server but can't figure out how to add the beginning and end tags. If this is possible using "FOR XML" an example would be great, othwerwise C# would be fine. Thanks!
Currently:
<Loans>
<Loan>
<GUID></GUID>
<AgentCompanyName></AgentCompanyName>
<LoanSecondaryStatus>
<StatusName>Name</StatusName>
<StatusDate>Date</StatusDate>
</LoanSecondaryStatus>
<LoanSecondaryStatus>
<StatusName>Name</StatusName>
<StatusDate>Date</StatusDate>
</LoanSecondaryStatus>
</Loan>
</Loans>
End result should be:
<Loans>
<Loan>
<GUID></GUID>
<AgentCompanyName></AgentCompanyName>
<LoanSecondaryStatusDates>
<LoanSecondaryStatus>
<StatusName>Name</StatusName>
<StatusDate>Date</StatusDate>
</LoanSecondaryStatus>
<LoanSecondaryStatus>
<StatusName>Name</StatusName>
<StatusDate>Date</StatusDate>
</LoanSecondaryStatus>
</LoanSecondaryStatusDates>
</Loan>
</Loans>
FOR XML Query
SELECT
[GUID]
,[AgentCompanyName],
(
SELECT
'Borrower Docs Sent/Req' as 'StatusName',
CASE WHEN t.BorrowerDocsSent IS NOT NULL THEN t.BorrowerDocsSent ELSE '' END as 'StatusDate'
FROM Encompass_loanData as t
WHERE t.[GUID] = E.[GUID]
FOR XML PATH('LoanSecondaryStatus'), TYPE
),
(
SELECT
t.BorrowerCity as 'StatusName',
t.[GUID] as 'StatusDate'
FROM Encompass_loanData as t
WHERE t.[GUID] = E.[GUID]
FOR XML PATH('LoanSecondaryStatus'), TYPE
)
From Encompass_loanData E
FOR XML PATH ('Loan'), type, root('Loans')
SQL Fiddle: http://sqlfiddle.com/#!6/d672a/2/0
The xml you have published does not have a valid root. So I added <xml> as the root.
This can be done quite easily in C# too. But this is a way to do it using xpath.
<xml>
<LoanSecondaryStatus>
<StatusName>Name</StatusName>
<StatusDate>Date</StatusDate>
</LoanSecondaryStatus>
<LoanSecondaryStatus>
<StatusName>Name</StatusName>
<StatusDate>Date</StatusDate>
</LoanSecondaryStatus>
</xml>
Here is the XSL.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<LoanSecondaryStatusDates>
<xsl:for-each select="xml/LoanSecondaryStatus">
<LoanSecondaryStatus>
<StatusName>
<xsl:value-of select="StatusName"/>
</StatusName>
<StatusDate>
<xsl:value-of select="StatusDate"/>
</StatusDate>
</LoanSecondaryStatus>
</xsl:for-each>
</LoanSecondaryStatusDates>
</xsl:template>
</xsl:stylesheet>
Output:
<?xml version="1.0" encoding="utf-8"?>
<LoanSecondaryStatusDates>
<LoanSecondaryStatus>
<StatusName>Name</StatusName>
<StatusDate>Date</StatusDate>
</LoanSecondaryStatus>
<LoanSecondaryStatus>
<StatusName>Name</StatusName>
<StatusDate>Date</StatusDate>
</LoanSecondaryStatus>
</LoanSecondaryStatusDates>
Updated with SQL query
SELECT
[GUID]
,[AgentCompanyName],
(
SELECT NULL,
(
SELECT
'Borrower Docs Sent/Req' as 'StatusName',
CASE WHEN t.BorrowerDocsSent IS NOT NULL THEN t.BorrowerDocsSent ELSE '' END as 'StatusDate'
FROM Encompass_loanData as t
WHERE t.[GUID] = E.[GUID]
FOR XML PATH('LoanSecondaryStatus'), TYPE
),
(
SELECT NULL AS X
FOR XML PATH('LoanSecondaryStatusDates'), TYPE
),
(
SELECT
t.BorrowerCity as 'StatusName',
t.[GUID] as 'StatusDate'
FROM Encompass_loanData as t
WHERE t.[GUID] = E.[GUID]
FOR XML PATH('LoanSecondaryStatus'), TYPE
),
NULL
FOR XML PATH('LoanSecondaryStatusDates'),TYPE
)
FROM Encompass_loanData E
WHERE [LASTMODIFIED] >= '20160121'
FOR XML PATH ('Loan'), type, root('Loans')
Take a look at the XElement class, you'll be able to modify the xml tree easily with that.
https://msdn.microsoft.com/en-us/library/system.xml.linq.xelement(v=vs.110).aspx
http://www.dotnetperls.com/xelement
I was able to wrap the child elements with this XSL
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Loan">
<xsl:copy>
<xsl:apply-templates select="#*|node()[not(self::LoanSecondaryStatus)]"/>
<LoanSecondaryStatusDates>
<xsl:apply-templates select="LoanSecondaryStatus"/>
</LoanSecondaryStatusDates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

How to pass document type parameter to xslt using saxon?

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>

Xpath select query with also relative top level node in c#

I am using xpath to query my xml file in C#.
Here is my xml file
<?xml version="1.0" encoding="ISO-8859-1"?>
<bookstore>
<book>
<title lang="eng">Harry Potter</title>
<price>29.99</price>
</book>
<book>
<title lang="eng">Learning XML</title>
<price>39.95</price>
</book>
</bookstore>
And my C# code is
XPathNavigator nav;
XPathDocument docNav;
XPathNodeIterator NodeIter;
String strExpression;
docNav = new XPathDocument(#"C:\\DCU\\XQUE.xml");
nav = docNav.CreateNavigator();
// This expression uses standard XPath syntax.
strExpression = "/bookstore[./book/price>35.00]";
NodeIter = nav.Select(strExpression);
while (NodeIter.MoveNext())
{
Console.WriteLine("{0}", NodeIter.Current.OuterXml);
}
But I want to get output like this,
<bookstore>
<book>
<title lang="eng">Learning XML</title>
<price>39.95</price>
</book>
</bookstore>
I think anything missing with my xpath query line, please lead me a way out..
It seems that you're misunderstanding the purpose of XPath selection and what it can do. XPath won't create a whole new XML document for you. The typical choice for taking an XML input and producing a different XML output is to use XSLT. This would do it in your case:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="book[price <= 35]" />
</xsl:stylesheet>
If you really want to use just C# to do this, you could always do the following:
XPathNavigator nav;
XPathDocument docNav;
XPathNodeIterator NodeIter;
String strExpression;
docNav = new XPathDocument(#"C:\\DCU\\XQUE.xml");
nav = docNav.CreateNavigator();
// This expression uses standard XPath syntax.
strExpression = "/bookstore/book[price > 35.00]";
NodeIter = nav.Select(strExpression);
Console.WriteLine("<bookstore>");
while (NodeIter.MoveNext())
{
Console.WriteLine("{0}", NodeIter.Current.OuterXml);
}
Console.WriteLine("</bookstore>");
That expression selects <bookstore> elements, so the output will be the whole <bookstore> (with all its child book elements). If you want specific books you need to use a different XPath
strExpression = "/bookstore/book[price>35.00]";
which will print just the <book> elements that match, but without the surrounding <bookstore> tags.

Categories

Resources