I'm new to XSLT and am having some problems trying to format an XML document which has recursive nodes.
There have 2 styles of tree node which are group and data.
The problem is my current XSLT template unable to generate the content when the Nodes have mixed of group and data styles.
XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:template match="Nodes[TreeNode]">
<xsl:apply-templates select="TreeNode" />
</xsl:template>
<xsl:template match="Nodes[not(TreeNode)]" />
<xsl:template match="TreeNode[Style='Data']">
<!--<table>
<thead>
<tr>
<th scope="col">Value</th>
</tr>
</thead>
<tbody>-->
<tr>
<td>
<xsl:value-of select="Value"/>
</td>
</tr>
<!--</tbody>
</table>-->
</xsl:template>
<xsl:template match="TreeNode[Style='Group']">
<group>
<p>
<xsl:value-of select="Label"/>
</p>
<xsl:apply-templates select="Nodes" />
</group>
</xsl:template>
</xsl:stylesheet>
XML
<?xml version="1.0" encoding="utf-8"?>
<TreeNode xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Label>Root</Label>
<Style>Group</Style>
<Nodes>
<TreeNode>
<Label>A</Label>
<Style>Group</Style>
<Nodes>
<TreeNode>
<Label>B</Label>
<Style>Group</Style>
<Nodes>
<TreeNode>
<Label />
<Value>AAA</Value>
<Style>Data</Style>
<Nodes />
</TreeNode>
<TreeNode>
<Label />
<Value>BBB</Value>
<Style>Data</Style>
<Nodes />
</TreeNode>
</Nodes>
</TreeNode>
<TreeNode>
<Label>C</Label>
<Style>Group</Style>
<Nodes>
<TreeNode>
<Label />
<Value>CCC</Value>
<Style>Data</Style>
<Nodes />
</TreeNode>
<TreeNode>
<Label />
<Value>DDD</Value>
<Style>Data</Style>
<Nodes />
</TreeNode>
</Nodes>
</TreeNode>
<TreeNode>
<Label>D</Label>
<Style>Group</Style>
<Nodes>
<TreeNode>
<Label />
<Value>EEE</Value>
<Style>Data</Style>
<Nodes />
</TreeNode>
</Nodes>
</TreeNode>
</Nodes>
</TreeNode>
</Nodes>
</TreeNode>
Expected Result:
You have two templates at the start of your XSLT
<xsl:template match="Nodes[TreeNode]">
<xsl:apply-templates select="TreeNode" />
</xsl:template>
<xsl:template match="Nodes[not(TreeNode)]" />
These could actually be merged into one; like so:
<xsl:template match="Nodes">
<xsl:apply-templates select="TreeNode" />
</xsl:template>
The reason being is that if Nodes does not have a TreeNode under it, then <xsl:apply-templates select="TreeNode" /> will not select anything anyway, so the effect is the same. (In fact, you could drop this template entirely if Nodes could only ever have TreeNode under it, as XSLT's built-in templates will do the same thing).
However, in answer to your problem, is that what I think you need is another template that you need another template that matches Nodes in the case where there is a child TreeNode for "Data"
<xsl:template match="Nodes[TreeNode/Style='Data']">
<table>
<xsl:apply-templates select="TreeNode" />
</table>
</xsl:template>
Try this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:strip-space elements="*" />
<xsl:template match="Nodes[TreeNode/Style='Data']">
<table>
<xsl:apply-templates select="TreeNode" />
</table>
</xsl:template>
<xsl:template match="Nodes">
<xsl:apply-templates select="TreeNode" />
</xsl:template>
<xsl:template match="TreeNode[Style='Data']">
<tr>
<td>
<xsl:value-of select="Value"/>
</td>
</tr>
</xsl:template>
<xsl:template match="TreeNode[Style='Group']">
<group>
<p>
<xsl:value-of select="Label"/>
</p>
<xsl:apply-templates select="Nodes" />
</group>
</xsl:template>
</xsl:stylesheet>
Related
In the XML below I am trying to remove the /Bundle/entry/resource/Patient/contained/Patient elements when any of the /Bundle/entry/resource/Patient/contained/Patient/identifier/system value attributes contain "remove-this-Patient" , I could use the full value == "https://example.com/remove-this-Patient" but the "contain" is better for me since the url section can be from multiple places and be slightly different.
I have tried the two code samples below and other variations but none work. The code runs without error but the target Patient element is not removed.
Just as a test I tried using the /Bundle/entry/resource/Patient/contained/Patient/id element in the "where" clause and I was able to get this to work, so I think it has something to do with the /Bundle/entry/resource/Patient/contained/Patient/identifier element being repeating inside the Patient element.
Starting XML
<Bundle>
<id value="xxxx" />
<entry>
<fullUrl value="xxxxxxx" />
<resource>
<Patient>
<id value="xxxx" />
<contained>
<Practitioner>
<id value="xx"/>
</Practitioner>
</contained>
<contained>
<Patient>
<id value="xxxx" />
<identifier>
<type>
<coding>
</coding>
</type>
<system value="http://example.com/remove-this-Patient" />
<value value="xxx" />
</identifier>
<identifier>
<type>
<coding>
</coding>
</type>
<system value="https://example.com/some-other-value" />
<value value="xxx" />
</identifier>
</Patient>
</contained>
<contained>
<Patient>
<id value="xxxx" />
<identifier>
<type>
<coding>
</coding>
</type>
<system value="https://example.com/some-other-thing" />
<value value="xxx" />
</identifier>
<identifier>
<type>
<coding>
</coding>
</type>
<system value="https://example.com/some-other-value" />
<value value="xxx" />
</identifier>
</Patient>
</contained>
</Patient>
</resource>
</entry>
</Bundle>
Desired output would have the /contained/Patient element removed when the child element identifier/system value = "http://example.com/remove-this-Patient"
<Bundle>
<id value="xxxx" />
<entry>
<fullUrl value="xxxxxxx" />
<resource>
<Patient>
<id value="xxxx" />
<contained>
<Practitioner>
<id value="xx"/>
</Practitioner>
</contained>
<contained>
</contained>
<contained>
<Patient>
<id value="xxxx" />
<identifier>
<type>
<coding>
</coding>
</type>
<system value="https://example.com/some-other-thing" />
<value value="xxx" />
</identifier>
<identifier>
<type>
<coding>
</coding>
</type>
<system value="https://example.com/some-other-value" />
<value value="xxx" />
</identifier>
</Patient>
</contained>
</Patient>
</resource>
</entry>
</Bundle>
The two queries below are my attempt to make it work with XDocument, but neither work. They run without error but do not remove the Patient.
xdoc.Root.Descendants("entry").Descendants("resource").Descendants("Patient").Descendants("contained").Descendants("Patient").Where(x => x.Element("identifier").Element("system").Attribute("value").Value.Contains("remove-this-Patient")).Remove();
xdoc.Root.Descendants("entry").Descendants("resource").Descendants("Patient").Descendants("contained").Descendants("Patient").Where(x => (string)x.Descendants("identifier").Where(y=> ("system").Attribute("value")=="https://example.com/remove-this-Patient").Remove();
Please try the following solution based on XSLT transformation.
The XSLT below is following a so called Identity Transform pattern.
The 2nd template removes not needed "/Bundle/entry/resource/Patient/contained/Patient" XML elements based on the presence of the 'remove-this-Patient' value in the #value attribute.
Input XML
<Bundle>
<id value="xxxx"/>
<entry>
<fullUrl value="xxxxxxx"/>
<resource>
<Patient>
<id value="xxxx"/>
<contained>
<Practitioner>
<id value="xx"/>
</Practitioner>
</contained>
<contained>
<Patient>
<id value="xxxx"/>
<identifier>
<type>
<coding>
</coding>
</type>
<system value="http://example.com/remove-this-Patient"/>
<value value="xxx"/>
</identifier>
<identifier>
<type>
<coding>
</coding>
</type>
<system value="https://example.com/some-other-value"/>
<value value="xxx"/>
</identifier>
</Patient>
</contained>
<contained>
<Patient>
<id value="xxxx"/>
<identifier>
<type>
<coding>
</coding>
</type>
<system value="https://example.com/some-other-thing"/>
<value value="xxx"/>
</identifier>
<identifier>
<type>
<coding>
</coding>
</type>
<system value="https://example.com/some-other-value"/>
<value value="xxx"/>
</identifier>
</Patient>
</contained>
</Patient>
</resource>
</entry>
</Bundle>
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:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/Bundle/entry/resource/Patient/contained/Patient[identifier/system[contains(#value, 'remove-this-Patient')]]">
</xsl:template>
</xsl:stylesheet>
Output XML
<Bundle>
<id value="xxxx" />
<entry>
<fullUrl value="xxxxxxx" />
<resource>
<Patient>
<id value="xxxx" />
<contained>
<Practitioner>
<id value="xx" />
</Practitioner>
</contained>
<contained />
<contained>
<Patient>
<id value="xxxx" />
<identifier>
<type>
<coding />
</type>
<system value="https://example.com/some-other-thing" />
<value value="xxx" />
</identifier>
<identifier>
<type>
<coding />
</type>
<system value="https://example.com/some-other-value" />
<value value="xxx" />
</identifier>
</Patient>
</contained>
</Patient>
</resource>
</entry>
</Bundle>
Try following :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication5
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
string id = "xxxx";
List<XElement> possibleDeletes = doc.Descendants().Where(x => (x.Element("identifier") != null) && (string)x.Element("id").Attribute("value") == id).ToList();
foreach (XElement possibleDelete in possibleDeletes)
{
List<XElement> identifiers = possibleDelete.Elements("identifier").ToList();
foreach (XElement identifier in identifiers)
{
if (((string)identifier.Element("system").Attribute("value")).Contains("remove")) identifier.Remove();
}
}
}
}
}
I have a XML file containing data as shown below-
<?xml version="1.0" encoding="utf-8"?>
<Root>
<RootInner>
<NodesMain>
<Main>
<Modules>
<Module>
<Description>
<Child>
<Descriptionettings>
<setting name="ChildId" value="22" />
<setting name="ChildName" value="Child_1" />
<setting name="Capacity" value="100" />
<setting name="ChildType" value="DefaultChild" />
</Descriptionettings>
</Child>
<Child>
<Descriptionettings>
<setting name="ChildId" value="33" />
<setting name="ChildName" value="Reject" />
<setting name="Capacity" value="200" />
<setting name="ChildType" value="Reject" />
</Descriptionettings>
</Child>
</Description>
<Header>
<setting name="ModuleName" value="CC" />
<setting name="ModuleType" value="CC" />
<setting name="ModulePosition" value="3" />
</Header>
</Module>
<Module>
<Description>
<Child>
<Descriptionettings>
<setting name="ChildId" value="19" />
<setting name="ChildName" value="Child_1" />
<setting name="Capacity" value="100" />
<setting name="ChildType" value="DefaultChild" />
</Descriptionettings>
</Child>
<Child>
<Descriptionettings>
<setting name="ChildId" value="18" />
<setting name="ChildName" value="Reject" />
<setting name="Capacity" value="200" />
<setting name="ChildType" value="Reject" />
</Descriptionettings>
</Child>
</Description>
<Header>
<setting name="ModuleName" value="AA" />
<setting name="ModuleType" value="AA" />
<setting name="ModulePosition" value="1" />
</Header>
</Module>
<Module>
<Description>
<Child>
<Descriptionettings>
<setting name="ChildId" value="OC11" />
<setting name="ChildName" value="OC11" />
<setting name="Capacity" value="100" />
<setting name="ChildType" value="SDMChild" />
</Descriptionettings>
</Child>
<Child>
<Descriptionettings>
<setting name="ChildId" value="OC14" />
<setting name="ChildName" value="OC14" />
<setting name="Capacity" value="100" />
<setting name="ChildType" value="SDMChild" />
</Descriptionettings>
</Child>
</Description>
<Header>
<setting name="ModuleName" value="BB" />
<setting name="ModuleType" value="BB" />
<setting name="ModulePosition" value="2" />
</Header>
</Module>
</Modules>
</Main>
</NodesMain>
</RootInner>
</Root>
I want to sort the element by "ModulePosition" in C#.Net. Final output should be as follows. Sample code tried by me posted below
<Root>
<RootInner>
<NodesMain>
<Main>
<Modules>
<Module>
<Description>
<Child>
<Descriptionettings>
<setting name="ChildId" value="19" />
<setting name="ChildName" value="Child_1" />
<setting name="Capacity" value="100" />
<setting name="ChildType" value="DefaultChild" />
</Descriptionettings>
</Child>
<Child>
<Descriptionettings>
<setting name="ChildId" value="18" />
<setting name="ChildName" value="Reject" />
<setting name="Capacity" value="200" />
<setting name="ChildType" value="Reject" />
</Descriptionettings>
</Child>
</Description>
<Header>
<setting name="ModuleName" value="AA" />
<setting name="ModuleType" value="AA" />
<setting name="ModulePosition" value="1" />
</Header>
</Module>
<Module>
<Description>
<Child>
<Descriptionettings>
<setting name="ChildId" value="OC11" />
<setting name="ChildName" value="OC11" />
<setting name="Capacity" value="100" />
<setting name="ChildType" value="SDMChild" />
</Descriptionettings>
</Child>
<Child>
<Descriptionettings>
<setting name="ChildId" value="OC14" />
<setting name="ChildName" value="OC14" />
<setting name="Capacity" value="100" />
<setting name="ChildType" value="SDMChild" />
</Descriptionettings>
</Child>
</Description>
<Header>
<setting name="ModuleName" value="BB" />
<setting name="ModuleType" value="BB" />
<setting name="ModulePosition" value="2" />
</Header>
</Module>
<Module>
<Description>
<Child>
<Descriptionettings>
<setting name="ChildId" value="22" />
<setting name="ChildName" value="Child_1" />
<setting name="Capacity" value="100" />
<setting name="ChildType" value="DefaultChild" />
</Descriptionettings>
</Child>
<Child>
<Descriptionettings>
<setting name="ChildId" value="33" />
<setting name="ChildName" value="Reject" />
<setting name="Capacity" value="200" />
<setting name="ChildType" value="Reject" />
</Descriptionettings>
</Child>
</Description>
<Header>
<setting name="ModuleName" value="CC" />
<setting name="ModuleType" value="CC" />
<setting name="ModulePosition" value="3" />
</Header>
</Module>
</Modules>
</Main>
</NodesMain>
</RootInner>
</Root>
I am using XDocument to load contents and orderby linq expression to sort. But the results are not accurate, sample code below
var documents2 = xDoc.Descendants("Module").OrderBy(x => (int)(x.Elements("attribute").First().Attribute("value"))).ToList();
With XSLT you can sort the Module children of the Modules element as follows:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<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="Modules">
<xsl:copy>
<xsl:apply-templates select="Module">
<xsl:sort select="Header/setting[#name = 'ModulePosition']/#value" data-type="number"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
http://xsltfiddle.liberty-development.net/948Fn5h
With LINQ and C# you could use
XDocument doc = XDocument.Load("input.xml");
foreach (XElement modules in doc.Descendants("Modules"))
{
modules.ReplaceNodes(
modules
.Elements("Module")
.OrderBy(m => (int)m.Element("Header").Elements("setting").First(s => (string)s.Attribute("name") == "ModulePosition").Attribute("value")).ToList()
);
}
doc.Save("output.xml");
I have the following xsl stylesheet and xml but I can't get the elements in the xml parent node in xsl template match is not working, I can't get table and costumer nodes from xml
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/">
<HTML>
<HEAD>
<TITLE>Title</TITLE>
</HEAD>
<BODY>
<xsl:apply-templates/>
</BODY>
</HTML>
</xsl:template>
<xsl:template match="/parent">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="costumer">
<p>Costumer</p>
</xsl:template>
<xsl:template match="table">
<xsl:variable name="name" select="#name"/>
<xsl:variable name="type" select="#type"/>
<xsl:variable name="height" select="#height"/>
<xsl:variable name="width" select="#wdth"/>
<xsl:variable name="margin-top" select="#margin-top"/>
<xsl:variable name="margin-left" select="#margin-left"/>
<table id="{$name}" width="{$width}" height="{$height}" style="margin-top:{$margin-top}; margin-left:{$margin-left}">
<thead>
<tr>
<xsl:apply-templates select="*[1]/*" mode="th"/>
</tr>
</thead>
<tbody>
<xsl:apply-templates select="*"/>
</tbody>
</table>
</xsl:template>
<xsl:template match="/*/*/*" mode="th">
<th>
<xsl:value-of select="*"/>
</th>
</xsl:template>
<xsl:template match="/*/*">
<tr>
<xsl:apply-templates select="*"/>
</tr>
</xsl:template>
<xsl:template match="/*/*/*">
<xsl:variable name="texttd" select="#text"/>
<td>
<xsl:value-of select="$texttd"/>
</td>
</xsl:template>
</xsl:stylesheet>
And below is xml file
<?xml version="1.0" encoding="utf-8"?>
<parent>
<table name="region1" type="td" wdth="0" height="0" margin-top="1" margin-left="122">
<td margin-top="0" margin-left="152" width="36" height="13" font-family="Arial-BoldMT" font-size="16" font-weight="0" text="??"?" line-height="300" is-visible="True" color="#0A4462" />
<Text name="region1" type="Title" wdth="0" height="0" margin-top="7" margin-left="138">
<Title margin-top="0" margin-left="14" width="36" height="13" font-family="Arial-BoldMT" font-size="16" font-weight="0" text="??"?" line-height="300" is-visible="True" color="#0A4462" />
</Text>
</table>
<table name="region1" type="td" wdth="0" height="0" margin-top="1" margin-left="122">
<td margin-top="0" margin-left="152" width="36" height="13" font-family="Arial-BoldMT" font-size="16" font-weight="0" text="??"?" line-height="300" is-visible="True" color="#0A4462" />
<Text name="region1" type="Title" wdth="0" height="0" margin-top="7" margin-left="138">
<Title margin-top="0" margin-left="14" width="36" height="13" font-family="Arial-BoldMT" font-size="16" font-weight="0" text="??"?" line-height="300" is-visible="True" color="#0A4462" />
</Text>
</table>
<table name="region1" type="td" wdth="0" height="0" margin-top="1" margin-left="122">
<td margin-top="0" margin-left="152" width="36" height="13" font-family="Arial-BoldMT" font-size="16" font-weight="0" text="??"?" line-height="300" is-visible="True" color="#0A4462" />
<Text name="region1" type="Title" wdth="0" height="0" margin-top="7" margin-left="138">
<Title margin-top="0" margin-left="14" width="36" height="13" font-family="Arial-BoldMT" font-size="16" font-weight="0" text="??"?" line-height="300" is-visible="True" color="#0A4462" />
</Text>
</table>
<costumer></costumer>
<costumer></costumer>
</parent>
The generic templates like <xsl:template match="/*/*/*" > get a higher priority. It seems the number of slashes / in an expression count towards the priority.
I think you should increase the priority of the templates you want by making the XPath more specific
<xsl:template match="/parent/table">
<xsl:template match="/parent/costumer">
This will still not work for the costumer, so you need to move it to the end of the stylesheet, because items at the end are preferred in case of equal priorities.
You could also reduce the priority on the generic items explicitly like this:
<xsl:template match="/*/*/*" priority="-1">
As an alternative you can increase the priority of the items you want explicitly:
<xsl:template match="table" priority="5">
Other that that I would need the full expected output to see if that all helps building the solution you're looking for.
BTW: I guess you want customer instead of costumer.
I have an xml content and i am applying xslt 1.0 for transformation on it. I am also passing parameters for filteration. but i am not able to to grouping on filtered data in xslt 1.0.
I will pass "Country Value" (as like 'United States') as parameter for filteration. after filteration, Grouping will be applied on "Group" field for filtered data. and if only one group exist then dont group data. grouping applied only in case if more then one group becomes possible.
please help me on this.
thanks in advance.
Here is my sample XML content.
<?xml version="1.0" encoding="utf-8" ?>
<DataRows>
-<DataRow>
- <Country>
<Conty>United States</Conty>
<Conty>United Kingdom</Conty>
</Country>
<Group>Group 1</Group>
<Order>1</Order>
<Name>Name 1_1</Name>
<Title>Title 1</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl />
<EmailId />
</DataRow>
-<DataRow>
- <Country>
<Conty>United States</Conty>
<Conty>United Kingdom</Conty>
</Country>
<Group>Group 1</Group>
<Order>2</Order>
<Name>Name 2_2</Name>
<Title>Title 2</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl />
<EmailId />
</DataRow>
-<DataRow>
- <Country>
<Conty>United States</Conty>
</Country>
<Group>Group 1</Group>
<Order>1</Order>
<Name>Name 3_1</Name>
<Title>Title 3</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl />
<EmailId />
</DataRow>
-<DataRow>
- <Country>
<Conty>United States</Conty>
<Conty>Germany</Conty>
</Country>
<Group>Group 1</Group>
<Order>2</Order>
<Name>Name 4_2</Name>
<Title>Title 4</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl />
<EmailId />
</DataRow>
-<DataRow>
- <Country>
<Conty>United States</Conty>
</Country>
<Group>Group 2</Group>
<Order>4</Order>
<Name>Name 8_4</Name>
<Title>Title 8</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl />
<EmailId />
</DataRow>
-<DataRow>
- <Country>
<Conty>United Kingdom</Conty>
</Country>
<Group>Group 2</Group>
<Order>1</Order>
<Name>Name 9_1</Name>
<Title>Title 9</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl />
<EmailId />
</DataRow>
-<DataRow>
- <Country>
<Conty>United States</Conty>
<Conty>Germany</Conty>
</Country>
<Group>Group 2</Group>
<Order>3</Order>
<Name>Name 5_3</Name>
<Title>Title 5</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl />
<EmailId />
</DataRow>
-<DataRow>
- <Country>
<Conty>United States</Conty>
<Conty>Germany</Conty>
</Country>
<Group>Group 2</Group>
<Order>4</Order>
<Name>Name 6_4</Name>
<Title>Title 6</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl />
<EmailId />
</DataRow>
-<DataRow>
- <Country>
<Conty>United States</Conty>
</Country>
<Group>Group 2</Group>
<Order>3</Order>
<Name>Name 7_3</Name>
<Title>Title 7</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl />
<EmailId />
</DataRow>
-<DataRow>
- <Country>
<Conty>Germany</Conty>
</Country>
<Group>Group 1</Group>
<Order>1</Order>
<Name>Name 10_1</Name>
<Title>Title 10</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl />
<EmailId />
</DataRow>
</DataRows>
If you really wanted to do filtering first, then grouping, then you are looking at some sort of 'two pass' transform. This can be achieved by use of the node-set extension function, to create a result-tree fragment which contains the filtered data.
In the following example, I am using Microsoft's extension function, but depending on your platform, you may have to specify another. ( EXSLT is another common one. Use name space http://exslt.org/common for that. )
<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:param name="Conty">United Kingdom</xsl:param>
<xsl:variable name="FilteredData">
<xsl:apply-templates select="/DataRows/DataRow" mode="filter"/>
</xsl:variable>
<xsl:template match="DataRow" mode="filter">
<!-- Check this DataRow matches the filter -->
<xsl:if test="Country[Conty=$Conty]">
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="filter"/>
</xsl:copy>
</xsl:if>
</xsl:template>
<!-- Ignore Country node in the filter -->
<xsl:template match="Country" mode="filter"/>
<!-- Identity template for filter -->
<xsl:template match="#*|node()" mode="filter">
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="filter"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/DataRows">
<xsl:copy>
<!-- Read the filtered data -->
<xsl:choose>
<!-- Check there is a Group which differs from the first group -->
<xsl:when test="msxsl:node-set($FilteredData)/DataRow[position() > 1][Group != msxsl:node-set($FilteredData)/DataRow[1]/Group]">
<xsl:apply-templates select="msxsl:node-set($FilteredData)"/>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="msxsl:node-set($FilteredData)" mode="nogroup"/>
</xsl:otherwise>
</xsl:choose>
</xsl:copy>
</xsl:template>
<!-- Filtered data row -->
<xsl:template match="DataRow">
<!-- Is this DataRow the first in the group -->
<xsl:if test="not(preceding-sibling::DataRow[Group=current()/Group])"><!-- If so, create the group node -->
<Group>
<xsl:attribute name="name">
<xsl:value-of select="Group"/>
</xsl:attribute><!-- Get all the DataRow elements from the filter for the current group -->
<xsl:apply-templates select="../DataRow[Group=current()/Group]" mode="ingroup"/>
</Group>
</xsl:if>
</xsl:template>
<!-- Identity template for the group -->
<xsl:template match="#*|node()" mode="ingroup">
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="ingroup"/>
</xsl:copy>
</xsl:template>
<!-- Ignore Group and Country node in the grouping -->
<xsl:template match="Group|Country" mode="ingroup"/>
<!-- Identity template for no grouping -->
<xsl:template match="#*|node()" mode="nogroup">
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="nogroup"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When this is used, the output should be as follows
<DataRows>
<Group name="Group 1">
<DataRow>
<Order>1</Order>
<Name>Name 1_1</Name>
<Title>Title 1</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl/>
<EmailId/>
</DataRow>
<DataRow>
<Order>2</Order>
<Name>Name 2_2</Name>
<Title>Title 2</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl/>
<EmailId/>
</DataRow>
</Group>
<Group name="Group 2">
<DataRow>
<Order>1</Order>
<Name>Name 9_1</Name>
<Title>Title 9</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl/>
<EmailId/>
</DataRow>
</Group>
</DataRows>
See Understanding the node-set function for more information.
Unless you did a two-phase transform, I think you should probably do the grouping first, and then the filtering.
Grouping would be achieved by the common Meunchain Grouping method. You first define a key to look up DataRow elements based on their Group
<xsl:key name="RowLookup" match="DataRow" use="Group"/>
And then, to get the unique group names, you match the DataRow elements which happen to be the first occuring element in your key for their particular group
<xsl:apply-templates select="DataRow[generate-id() = generate-id(key('RowLookup', Group)[1])]"/>
So, now you have grouped by the Group elements, so you need to check there it at least one DataRow element for the current group that matches the filter
<xsl:if test="../DataRow[Group=current()/Group]/Country[Conty=$Conty]">
And then to get all DataRow elements for the current group, you could use the key
<xsl:apply-templates select="key('RowLookup', Group)" mode="ingroup"/>
Here is the full XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:param name="Conty">United Kingdom</xsl:param>
<xsl:key name="RowLookup" match="DataRow" use="Group"/>
<xsl:template match="/DataRows">
<xsl:copy>
<!-- Select unique groups -->
<xsl:apply-templates
select="DataRow[generate-id() = generate-id(key('RowLookup', Group)[1])]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="DataRow">
<!-- Check any DataRow elements for the current group match the filter -->
<xsl:if test="../DataRow[Group=current()/Group]/Country[Conty=$Conty]">
<Group>
<xsl:attribute name="name">
<xsl:value-of select="Group"/>
</xsl:attribute>
<!-- Get all the DataRow elements for the current group -->
<xsl:apply-templates select="key('RowLookup', Group)" mode="ingroup"/>
</Group>
</xsl:if>
</xsl:template>
<xsl:template match="DataRow" mode="ingroup">
<!-- Check this DataRow matches the filter -->
<xsl:if test="Country[Conty=$Conty]">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:if>
</xsl:template>
<!-- Ignore Group and Country elements -->
<xsl:template match="Group|Country"/>
<!-- Standard Identity Transform for all other nodes -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When you apply this XSLT to your sample XML, you get the following results
<DataRows>
<Group name="Group 1">
<DataRow>
<Order>1</Order>
<Name>Name 1_1</Name>
<Title>Title 1</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl/>
<EmailId/>
</DataRow>
<DataRow>
<Order>2</Order>
<Name>Name 2_2</Name>
<Title>Title 2</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl/>
<EmailId/>
</DataRow>
</Group>
<Group name="Group 2">
<DataRow>
<Order>1</Order>
<Name>Name 9_1</Name>
<Title>Title 9</Title>
<PhoneNo>732-989-9898</PhoneNo>
<ImageUrl/>
<EmailId/>
</DataRow>
</Group>
</DataRows>
I am not sure if this is the exact structure you want, but I hope it gives you the general idea.
I have the following structure to XML file:
<INSTANCE>
<Sections>
<Section>
<Forms>
<Form>
<Control id="GroupHeading1">
<Property/>
<Property/>
</Control>
<Control id="GroupHeading2">
<Property/>
<Control id="TextBox">
<Property/>
<Property/>
</Control>
</Control>
</Form>
</Forms>
</Section>
</Sections>
</INSTANCE>
I am trying to deserialize this into C# object, but I don't need to preserve the hierarchy (which is making it difficult for me to deserialize).
Is there XSL that can transform this to un-nest the Controls, and if possible add an attribute to any child Control with ParentId=""?
Thank you for any guidance!
Given XML, the XmlSerializer can produce a graph of objects that hold the same instance data.
This is known as XML de-serialization
You need to look here :
Using the XmlSerializer Attributes
Serialization and Deserialization in ASP.NET with C#
This template should get you started. I ran it against .NET 2.0.
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" method="xml"/>
<xsl:template match="*">
<xsl:element name="{name()}">
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="*"/>
</xsl:element>
</xsl:template>
<xsl:template match="Form">
<Form>
<xsl:copy-of select="#*"/>
<xsl:apply-templates select="//Control"/>
</Form>
</xsl:template>
<xsl:template match="Control">
<Control>
<xsl:if test="ancestor::Control/#id">
<xsl:attribute name="ParentID"><xsl:value-of select="ancestor::Control/#id"/></xsl:attribute>
</xsl:if>
<xsl:copy-of select="*|#*"/>
</Control>
</xsl:template>
</xsl:stylesheet>
This is the output (indented for readability).
<INSTANCE>
<Sections>
<Section>
<Forms>
<Form>
<Control id="GroupHeading1">
<Property />
<Property />
</Control>
<Control id="GroupHeading2">
<Property />
<Control id="TextBox">
<Property />
<Property />
</Control>
</Control>
<Control ParentID="GroupHeading2" id="TextBox">
<Property />
<Property />
</Control>
</Form>
</Forms>
</Section>
</Sections>
</INSTANCE>
The following stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<!-- first-level control elements -->
<xsl:template match="Control">
<Control>
<xsl:copy-of select="#*|*[not(self::Control)]" />
</Control>
<xsl:apply-templates select="Control" />
</xsl:template>
<!-- nested control elements -->
<xsl:template match="Control/Control">
<Control ParentId="{../#id}">
<xsl:copy-of select="#*|*[not(self::Control)]" />
</Control>
<xsl:apply-templates select="Control" />
</xsl:template>
</xsl:stylesheet>
Applied to the following document (same as original with one additional level of nesting for demonstration purposes):
<INSTANCE>
<Sections>
<Section>
<Forms>
<Form>
<Control id="GroupHeading1">
<Property />
<Property />
</Control>
<Control id="GroupHeading2">
<Property />
<Control id="TextBox">
<Property />
<Property />
<Control id="Grandchild">
<Property />
</Control>
</Control>
</Control>
</Form>
</Forms>
</Section>
</Sections>
</INSTANCE>
Produces an output with no nested <Control> elements:
<INSTANCE>
<Sections>
<Section>
<Forms>
<Form>
<Control id="GroupHeading1">
<Property />
<Property />
</Control>
<Control id="GroupHeading2">
<Property />
</Control>
<Control ParentId="GroupHeading2" id="TextBox">
<Property />
<Property />
</Control>
<Control ParentId="TextBox" id="Grandchild">
<Property />
</Control>
</Form>
</Forms>
</Section>
</Sections>
</INSTANCE>