Translate XSLT transformation to C# - c#

I am struggling with understanding an XSLT transformation. Currently I receive a serialized object in XML format, then apply XSLT and send new XML back the program. But now I need to eliminate XSLT step and to do the transformation internally in the program. The problem is I've seen XSLT sheet for the second time. Transformation sheet look really simmple but I still cannot undersnad what is going on there.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:t="http://tempuri.org/">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template name="CopyEverything" match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/t:Data/Flagged">
<xsl:element name="Flagged">
<xsl:apply-templates select="/t:Data/Covers/node()|#*"/>
</xsl:element>
</xsl:template>
<xsl:template match="/t:Data/FlaggedDetails">
<xsl:element name="FlaggedDetails">
<xsl:apply-templates select="/t:Data/TotalFlaggedDetails/node()|#*"/>
</xsl:element>
</xsl:template>
<xsl:template match="/t:Data/System/RArray">
<xsl:element name="{local-name()}">
<xsl:for-each select="/t:Data/System/RArray/Elem">
<xsl:call-template name="CopyEverything"/>
</xsl:for-each>
<xsl:for-each select="/t:Data/Elem/Elem">
<xsl:variable name="currentCode" select="Code" />
<xsl:variable name="showAlways" select="ShowAlways" />
<xsl:if test="count(/t:Data/System/RArray/Elem[Code=$currentCode])=0">
<xsl:call-template name="CopyEverything"/>
</xsl:if>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
As far as I understand something from Data/Flagged and Data/FlaggedDetails is copied to Data/System/RArray, I cannot translate this logic to C#. I have to eliminate serialization stemps, so I move objects between collections (if this is what is going on) without XSLT. Could someone help me on this?

I wouldn't advise "translating" an XSLT transformation into C# due to many reasons:
This isn't always easy due to the incompatibilities between the two languages:
Think how to simulate templates and match patterns in C# ?
How to simulate the XSLT processing model?
How to simulate importing stylesheet modules and determining which are the objects with highest import precedence?
How to simulate keys?
You will have to implement in C# a number of useful XPath (and XSLT) standard functions -- all the string functions, such as translate(), normalize-space(), substring-before(), substring-after(), ..., etc.
Instructions such as <xsl:number> and functions such as format-number().
So, if you have the time needed to invest in all this and the result is successful (which is not too-likely), the translation would be several times longer than the original and in most cases -- completely not-understandable, not extensible, not maintainable.
I also doubt that a translation would run significantly faster than the original -- it can run slower in some cases (for example, not implementing keys efficiently).
Conclusion: I strongly advise against engaging in such destructive activity.

You can do the XSTL transformation inside your C# program using the XslCompiledTransform class (reference : http://msdn.microsoft.com/en-us/library/system.xml.xsl.xslcompiledtransform.aspx).

Try to use Xsl To .NET Code Generator; it is an add-in to Microsoft Visual Studio 2010, it helps you generate C# code from an XSL template.

I tried chat.openai and got this conversion (I need to do more of this too):
using System;
using System.Linq;
using System.Xml.Linq;
namespace XsltToCSharp
{
class Program
{
static void Main(string[] args)
{
XNamespace t = "http://tempuri.org/";
XDocument input = XDocument.Load("input.xml");
XDocument output = new XDocument(
new XElement(t + "Data",
CopyFlagged(input, t),
CopyFlaggedDetails(input, t),
CopySystemRArray(input, t)
)
);
output.Save("output.xml");
}
static XElement CopyFlagged(XDocument input, XNamespace t)
{
return new XElement("Flagged",
input.Descendants(t + "Covers").Elements()
);
}
static XElement CopyFlaggedDetails(XDocument input, XNamespace t)
{
return new XElement("FlaggedDetails",
input.Descendants(t + "TotalFlaggedDetails").Elements()
);
}
static XElement CopySystemRArray(XDocument input, XNamespace t)
{
XElement rArray = new XElement("RArray");
var elems = input.Descendants(t + "Elem").ToList();
foreach (var elem in elems)
{
var code = (string)elem.Element(t + "Code");
var showAlways = (string)elem.Element(t + "ShowAlways");
var existingElem = input.Descendants(t + "Elem")
.Where(x => (string)x.Element(t + "Code") == code)
.FirstOrDefault();
if (existingElem == null)
{
rArray.Add(elem);
}
}
return rArray;
}
}
}

Here is another possibility using C# string literal.
using System;
using System.Linq;
using System.Xml.Linq;
namespace XsltToCSharp
{
class Program
{
static void Main(string[] args)
{
XNamespace t = "http://tempuri.org/";
XDocument input = XDocument.Load("input.xml");
XDocument output = XDocument.Parse(
$#"<Data xmlns=""http://tempuri.org/"">
{CopyFlagged(input, t)}
{CopyFlaggedDetails(input, t)}
{CopySystemRArray(input, t)}
</Data>"
);
output.Save("output.xml");
}
static string CopyFlagged(XDocument input, XNamespace t)
{
return $#"<Flagged>
{string.Join("", input.Descendants(t + "Covers").Elements().ToList().Select(x => x.ToString()))}
</Flagged>";
}
static string CopyFlaggedDetails(XDocument input, XNamespace t)
{
return $#"<FlaggedDetails>
{string.Join("", input.Descendants(t + "TotalFlaggedDetails").Elements().ToList().Select(x => x.ToString()))}
</FlaggedDetails>";
}
static string CopySystemRArray(XDocument input, XNamespace t)
{
string rArray = "";
var elems = input.Descendants(t + "Elem").ToList();
foreach (var elem in elems)
{
var code = (string)elem.Element(t + "Code");
var showAlways = (string)elem.Element(t + "ShowAlways");
var existingElem = input.Descendants(t + "Elem")
.Where(x => (string)x.Element(t + "Code") == code)
.FirstOrDefault();
if (existingElem == null)
{
rArray += elem.ToString();
}
}
return $#"<RArray>
{rArray}
</RArray>";
}
}
}

Related

Find and Replace through XML files

I have a lot of XML files that need to be edited.
I need to find all instances with:
Example
<Btn>
<sText>Hold</sText>
and add a field before it
<Btn>//star of new fields
<sText>Tools</sText>
*rest of fields*
</Btn> //end of added fields
<Btn> //start of original search
<sText>Hold</sText>
I have read the using regex on XML is not advisable. What would be the best way to achieve a large one time operation for multiple files for something like this?
For regex I tried but with no luck to just start out with searching for the needed fields.
/<Btn>(.*?(\n))+.*?<sText>Hold</sText>/im
Using editors like notepad++,Brackets currently to edit files. Any suggestions on doing a large one time one time operation would be greatly appreciated. Doing the changes by hand in the GUI to hundreds of configs is not desirable.Just looking for an alternative route to save sanity.
You can create an object for your XML document. From there you can traverse through all of its nodes, find what you are looking for and add them to a list. When you already have the list, you can then write your logic for inserting the nodes that you want. I'm using LINQ.
public class Program
{
static void Main(string[] args)
{
XDocument doc = XDocument.Load("YourXmlFile.xml");
RootElement root = new RootElement(doc.Elements().FirstOrDefault());
foreach (XElement item in root.GetInstances())
{
//--Your logic for adding the fields you want
}
Console.ReadLine();
}
}
public class RootElement
{
public List<XElement> childElements { get; set; }
public RootElement(XElement xElement)
{
childElements = new List<XElement>();
foreach (XElement e in xElement.Elements())
{
childElements.Add(e);
}
}
public List<XElement> GetInstances()
{
List<XElement> instances = new List<XElement>();
foreach (XElement item in childElements)
{
if (item.Name == "Btn")
{
IEnumerable<XElement> elements = item.Elements();
XElement child = elements.FirstOrDefault(x => x.Name == "sText");
if (child != null)
{
if (child.Value == "Hold")
{
instances.Add(item);
}
}
}
}
return instances;
}
}
I have an XSL approach you might like to try. XSL is great for transforming XML documents of one kind into another (amongst other things).
As I understand it, you need to find each instance of Btn and copy it to a new instance before its current location.
With this in mind, here's how I got it to work.
Test.xml file:
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="Test.xslt"?>
<Something>
<Btn>
<sText>Hold</sText>
<Another>Foo</Another>
</Btn>
<Btn>
<sText>Hold</sText>
</Btn>
<Btn>
<sText>Hold</sText>
</Btn>
</Something>
Note the use of the stylesheet reference, you would need to add this to the documents you wish to edit.
Test.xslt file:
<?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:template match="/">
<xsl:element name="Output">
<xsl:apply-templates select="//Btn" />
</xsl:element>
</xsl:template>
<xsl:template match="Btn">
<xsl:element name="NewBtn">
<xsl:copy-of select="current()/*" />
</xsl:element>
<xsl:element name="Btn">
<xsl:copy-of select="current()/*" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
The output should look like this:
<?xml version="1.0" encoding="utf-8"?>
<Output>
<NewBtn>
<sText>Hold</sText>
<Another>Foo</Another>
</NewBtn>
<Btn>
<sText>Hold</sText>
<Another>Foo</Another>
</Btn>
<NewBtn>
<sText>Hold</sText>
</NewBtn>
<Btn>
<sText>Hold</sText>
</Btn>
<NewBtn>
<sText>Hold</sText>
</NewBtn>
<Btn>
<sText>Hold</sText>
</Btn>
</Output>
The newly duplicated instances of your Btn nodes are named NewBtn in this example.
Note that I've changed/added some elements here (Output, Something) in order to get valid XML.
I hope that helps!
You can try to solve it without regular expressions. For example you can use XmlReader and XmlWriter
Read one row with XmlReader
Check for your condition
Skip/modify row
Write row with XmlWriter
It's the most memory and CPU efficient solution since you don't need to load whole file to memory and XML Writer/Reader in C# are pretty fast compared to XDocument or other fancy xml parsers. Moreover it's simple so you need to mess with regexes and can contain any complicated logic you will need.

how to pass dynamically created xml in XsltArgumentList msxsl

I am in a fix here where I have to pass the dynamically created xml in C# code to be passed to xslt as param and then get the values from it.
following is the sample xslt
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt"
version="1.0">
<xsl:param name="Keys"></xsl:param>
<xsl:template match="/">
<MyKey>MYNODE</MyKey>
<xsl:value-of select="msxsl:node-set($Keys)/Keys/Item/Header"/>
</xsl:template>
</xsl:stylesheet>
Then from the code in C# I call Transform method
XslCompiledTransform proc = new XslCompiledTransform();
proc.Load("sheet.xslt");
XsltArgumentList xsltArgs = new XsltArgumentList();
XmlDocument doc1 = new XmlDocument();
// populate as needed e.g.
doc1.LoadXml("<Keys><Item><Header>fooHeader</Header></Item></Keys>");
xsltArgs.AddParam("Keys", "", doc1.InnerXml.ToString());
// pass xsltArgs as second argument to Transform method
proc.Transform(someInput, xsltArgs, someOutput);
Here I am not able to get the value for MYNODE in results
Thanks
You have two problems here. Firstly how you pass the parameters
xsltArgs.AddParam("doc1", "", doc1);
But in your XSLT you have it named "Keys"
<xsl:param name="Keys"></xsl:param>
Therefore you need to change your C# code
xsltArgs.AddParam("Keys", "", doc1);
There is also a problem with your XSLT.
<xsl:value-of select="msxsl:node-set($Keys)/Keys/item/header"/>
XML is case-sensitive. Your XML contains "Item", but XSLT is looking for "item". It should be these
<xsl:value-of select="msxsl:node-set($Keys)/Keys/Item/Header"/>
Infact, I don't think you need node-set here. Try this too
<xsl:value-of select="$Keys/Keys/Item/Header"/>
Once you have built a document with
XmlDocument doc1 = new XmlDocument();
// populate as needed e.g.
doc1.LoadXml("<Keys><Item><Header>fooHeader</Header></Item></Keys>");
you should pass that document in as the parameter value doing
xsltArgs.AddParam("Keys", "", doc1);
Then you should be able to use that parameter as in
<xsl:param name="Keys"></xsl:param>
<xsl:template match="/">
<MyKey>MYNODE</MyKey>
<xsl:value-of select="$Keys/Keys/Item/Header"/>
</xsl:template>
Complete example, C# is
XslCompiledTransform proc = new XslCompiledTransform();
proc.Load("../../XSLTFile1.xslt");
XsltArgumentList xsltArgs = new XsltArgumentList();
XmlDocument doc1 = new XmlDocument();
// populate as needed e.g.
doc1.LoadXml("<Keys><Item><Header>fooHeader</Header></Item></Keys>");
xsltArgs.AddParam("Keys", "", doc1);
proc.Transform(new XmlDocument(), xsltArgs, Console.Out);
XSLT is
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt"
version="1.0">
<xsl:param name="Keys"></xsl:param>
<xsl:template match="/">
<MyKey>MYNODE</MyKey>
<xsl:value-of select="$Keys/Keys/Item/Header"/>
</xsl:template>
</xsl:stylesheet>
the output to the console is
<?xml version="1.0" encoding="ibm850"?><MyKey xmlns:msxsl="urn:schemas-microsoft
-com:xslt">MYNODE</MyKey>fooHeader

Converting an XElement with value into an Empty element (where XElement.IsEmpty = true)

I'm working with C# .Net 3.5 and trying to convert a given xml (XDocument) into an empty one (where XElement.IsEmpty would be true) containing no text values . I tried setting the XElement.Value to String.Empty but that results in <element><element> which isn't what i need. I needed it to be <element />. Can someone suggest how this can be done in .NET.
below is input example:
<Envelope>
<Body>
<Person>
<first>John</first>
<last>Smith</last>
<address>123</address>
</Person>
</Body>
<Envelope>
expected output:
<Envelope>
<Body>
<Person>
<first />
<last />
<address />
</Person>
</Body>
<Envelope>
You can use ReplaceWith() function to replace desired elements with empty elements :
var xml = #"<Envelope>
<Body>
<Person>
<first>John</first>
<last>Smith</last>
<address>123</address>
</Person>
</Body>
</Envelope>";
var doc = XDocument.Parse(xml);
foreach (XElement propertyOfPerson in doc.XPathSelectElements("/Envelope/Body/Person/*").ToList())
{
propertyOfPerson.ReplaceWith(new XElement(propertyOfPerson.Name.LocalName));
}
Console.WriteLine(doc.ToString());
Result :
In Interest of sharing and whilst i have accepted the answer above, i actually went with the below approach and using A XSLT to transform the XML into what i wanted, so using the below code:
//an XSLT which removes the values and stripes the white spaces
const string xslMarkup = "<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"> <xsl:output method=\"xml\" omit-xml-declaration=\"yes\" indent=\"no\"/> <xsl:strip-space elements=\"*\"/> <xsl:template match=\"#* | node()\"> <xsl:copy> <xsl:apply-templates select=\"#* | node()\"/> </xsl:copy> </xsl:template> <xsl:template match=\"node()|#*\"> <xsl:copy> <xsl:apply-templates select=\"node()|#*\"/> </xsl:copy> </xsl:template><xsl:template match=\"*/text()\"/> </xsl:stylesheet>";
var transformedXml = new XDocument();
XNode xml = YOUR_XML_OBJECT_HERE;
using (var writer = transformedXml.CreateWriter())
{
// Load the XSLT
var xslt = new XslCompiledTransform();
xslt.Load(XmlReader.Create(new StringReader(xslMarkup)));
// Execute the transform and output the results to a writer.
xslt.Transform(xml.CreateReader(), writer);
}
return transformedXml.ToString(SaveOptions.DisableFormatting);
Try creating a new XElement without a value:
var xElement = new XElement("Envelope", new XElement("Body", new XElement("Person", "")))
In that manner.

How to find an Xml Element when it's prefixed with a namespace?

I have an Xml File as below:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ea:Stories ea:WWVersion="2.0" xmlns:aic="http://ns.adobe.com/AdobeInCopy/2.0" xmlns:ea="urn:SmartConnection_v3">
<ea:Story ea:GUID="D8BEFD6C-AB31-4B0E-98BF-7348968795E1" pi0="style="50" type="snippet" readerVersion="6.0" featureSet="257" product="8.0(370)" " pi1="SnippetType="InCopyInterchange"">
<ea:StoryInfo>
<ea:SI_EL>headline</ea:SI_EL>
<ea:SI_Words>4</ea:SI_Words>
<ea:SI_Chars>20</ea:SI_Chars>
<ea:SI_Paras>1</ea:SI_Paras>
<ea:SI_Lines>1</ea:SI_Lines>
<ea:SI_Snippet>THIS IS THE HEADLINE</ea:SI_Snippet>
<ea:SI_Version>AB86A3CA-CEBC-49AA-A334-29641B95748D</ea:SI_Version>
</ea:StoryInfo>
</ea:Story>
</ea:Stories>
As you can see all elements have "ea:" which is a namespace prefix.
I'm writing an XSLT file to show the SI_Snippet text which is "THIS IS THE HEADLINE".
How to write the xpath in the XSLT file? Should it contain the namespace or should it be excluded?
//ea:Story[ea:SI_EL='headline']/ea:SI_Snippet or
//Story[SI_EL='headline']/SI_Snippet
Actually both fail in the online tool I used: http://xslt.online-toolz.com/tools/xslt-transformation.php
So there should be another way?
If later, how does it know which namespace to look at? Should I be passing the namespace to the XslTransformer at runtime?
You should declare the namespace in your XSLT and then use the prefix that you give it:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ea="urn:SmartConnection_v3">
<xsl:template match="/">
<xsl:value-of select="//ea:Story[ea:SI_EL='headline']/ea:SI_Snippet" />
</xsl:template>
<!-- ... -->
</xsl:stylesheet>
Note the xmlns:ea="urn:SmartConnection_v3" in the root element. This is important.
Try to use XDocument?
var xml = #"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>
<ea:Stories ea:WWVersion=""2.0"" xmlns:aic=""http://ns.adobe.com/AdobeInCopy/2.0"" xmlns:ea=""urn:SmartConnection_v3"">
<ea:Story ea:GUID=""D8BEFD6C-AB31-4B0E-98BF-7348968795E1"" pi0=""style="50" type="snippet" readerVersion="6.0" featureSet="257" product="8.0(370)" "" pi1=""SnippetType="InCopyInterchange""">
<ea:StoryInfo>
<ea:SI_EL>headline</ea:SI_EL>
<ea:SI_Words>4</ea:SI_Words>
<ea:SI_Chars>20</ea:SI_Chars>
<ea:SI_Paras>1</ea:SI_Paras>
<ea:SI_Lines>1</ea:SI_Lines>
<ea:SI_Snippet>THIS IS THE HEADLINE</ea:SI_Snippet>
<ea:SI_Version>AB86A3CA-CEBC-49AA-A334-29641B95748D</ea:SI_Version>
</ea:StoryInfo>
</ea:Story>
</ea:Stories>";
XDocument xdoc = XDocument.Parse(xml.ToString());
XElement v = xdoc.Descendants().FirstOrDefault(x => x.Name.LocalName == "SI_Snippet");
EDIT
XPathNavigator navigator = xmldDoc.CreateNavigator();
XmlNamespaceManager ns = new XmlNamespaceManager(navigator.NameTable);
ns.AddNamespace("ea", "urn:SmartConnection_v3");
var v = xmlDoc.SelectSingleNode("//ea:SI_Snippet", ns);

C#: How to remove namespace information from XML elements

How can I remove the "xmlns:..." namespace information from each XML element in C#?
Zombiesheep's cautionary answer notwithstanding, my solution is to wash the xml with an xslt transform to do this.
wash.xsl:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="no" encoding="UTF-8"/>
<xsl:template match="/|comment()|processing-instruction()">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="#*|node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="#*">
<xsl:attribute name="{local-name()}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
From here http://simoncropp.com/working-around-xml-namespaces
var xDocument = XDocument.Parse(
#"<root>
<f:table xmlns:f=""http://www.w3schools.com/furniture"">
<f:name>African Coffee Table</f:name>
<f:width>80</f:width>
<f:length>120</f:length>
</f:table>
</root>");
xDocument.StripNamespace();
var tables = xDocument.Descendants("table");
public static class XmlExtensions
{
public static void StripNamespace(this XDocument document)
{
if (document.Root == null)
{
return;
}
foreach (var element in document.Root.DescendantsAndSelf())
{
element.Name = element.Name.LocalName;
element.ReplaceAttributes(GetAttributes(element));
}
}
static IEnumerable GetAttributes(XElement xElement)
{
return xElement.Attributes()
.Where(x => !x.IsNamespaceDeclaration)
.Select(x => new XAttribute(x.Name.LocalName, x.Value));
}
}
I had a similar problem (needing to remove a namespace attribute from a particular element, then return the XML as an XmlDocument to BizTalk) but a bizarre solution.
Before loading the XML string into the XmlDocument object, I did a text replacement to remove the offending namespace attribute. It seemed wrong at first as I ended up with XML that could not be parsed by the "XML Visualizer" in Visual Studio. This is what initially put me off this approach.
However, the text could still be loaded into the XmlDocument and I could output it to BizTalk fine.
Note too that earlier, I hit one blind alley when trying to use childNode.Attributes.RemoveAll() to remove the namespace attribute - it just came back again!

Categories