c# Generate XML document based on Xpath
XPath isn't really intended to generate documents but to access em.
Another (probably better) approach would be to serialize it.
But, since the question requires the fields of xml to be variable, serialization isn't a viable way. i STRONGLY advise you to change this requirement and you'll see why in the following example:
using System;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
using (var writer = new System.IO.StreamWriter(@"C:\Users\luigi.trabacchin\Desktop\asd.xml"))
{
var doc = new System.Xml.XmlDocument();
var root = doc.CreateElement("Root");
doc.AppendChild(root);
for (var i = 0; i <= 1; i++)
{
var dataNode = doc.CreateElement("Data");
root.AppendChild(dataNode);
{
var node = doc.CreateElement("Name");
dataNode.AppendChild(node);
var text = doc.CreateTextNode($"Name {i}");
node.AppendChild(text);
}
{
var node = doc.CreateElement("Surname");
dataNode.AppendChild(node);
var text = doc.CreateTextNode($"Surname {i}");
node.AppendChild(text);
}
{
var node = doc.CreateElement("Gender");
dataNode.AppendChild(node);
var text = doc.CreateTextNode(i %2 == 0 ? "M" : "F");
node.AppendChild(text);
}
}
doc.Save(writer);
}
Console.WriteLine("Hello World!");
}
}
}
It quickly becomes very tedious and hard to work with.
Still i hope this answer your question.
Next time, maybe, state all the requirements directly in the question!
Create XML document from XPaths
XPath purpose is to query xml documents, not to create them, so I don't think what you are trying to achieve is possible this way.
Maybe you can get inspiration there :
Create XML Nodes based on XPath?
Creating XML nodes from XPATH and merging that to existing XML
What the code does, is, it first creates some XML fragment from that VPop
variable and the result from that, for your given sample data, is simply <data<txCurr>MYD</txCurr</data>
, i.e. a single data
element with a single txtCurr
element. The next step then merges the XML fragment with the input fragment, based on the XPaths the XPath 3.1 path
function gives. So the information that you might have wanted the second data
element is already gone after that first step, somehow it expects you to ensure your "input" paths specify and therefore create two data
elements (e.g. <xsl:variable name="vPop" as="element()*"><item path="/data[1]"/><item path="/data[2]/txCurr">MYD</item></xsl:variable>
), otherwise the whole approach can't work.
Or the first step would need to be rewritten not only to break up and create elements based on names but also to try to infer which indices are left out/missing and also create them, something that is (even more) complex than the current approach; here is a basic, and admittedly, currently rather convoluted approach:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:array="http://www.w3.org/2005/xpath-functions/array"
exclude-result-prefixes="#all"
xmlns:mf="http://example.com/mf"
expand-text="yes">
<xsl:variable name="vPop" as="element()*">
<item path="/data[2]/txCurr">MYD</item>
</xsl:variable>
<xsl:variable name="new-nodes">
<xsl:sequence select="mf:generate-nodes($vPop ! map:entry(@path!string(), string(.)))"/>
</xsl:variable>
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/" name="xsl:initial-template">
<xsl:sequence select="mf:merge($main-input/*, $new-nodes/*)"/>
<xsl:comment>Run with {system-property('xsl:product-name')} {system-property('xsl:product-version')} {system-property('Q{http://saxon.sf.net/}platform')}</xsl:comment>
</xsl:template>
<xsl:function name="mf:merge" as="node()*">
<xsl:param name="node1" as="node()*"/>
<xsl:param name="node2" as="node()*"/>
<xsl:for-each-group select="$node1, $node2" group-by="path()">
<xsl:copy>
<xsl:sequence select="mf:merge(@*, current-group()[2]/@*)"/>
<xsl:sequence select="mf:merge(node(), current-group()[2]/node())"/>
</xsl:copy>
</xsl:for-each-group>
</xsl:function>
<xsl:output method="xml" indent="yes" />
<xsl:mode on-no-match="shallow-copy"/>
<xsl:param name="main-input" as="document-node()" select="parse-xml-fragment($main-fragment)"/>
<xsl:param name="main-fragment" as="xs:string"><![CDATA[<header>
<identifier>12345</identifier>
</header>
<data>
<txCtry>SG</txCtry>
</data>
<data>
<txCtry>TH</txCtry>
</data>
<data>
<txCtry>MY</txCtry>
</data>]]></xsl:param>
<xsl:function name="mf:generate-nodes" as="node()*" visibility="public">
<xsl:param name="xpath-values" as="map(xs:string, item()*)*"/>
<xsl:for-each-group select="$xpath-values" group-adjacent="
let $first-step := replace(map:keys(.), '^/?([^/]+)(.*$)', '$1'),
$exp := replace($first-step, '\[[0-9]+\]$', '')
return
$exp">
<xsl:choose>
<xsl:when test="current-grouping-key() = ''">
<xsl:choose>
<xsl:when test="?* instance of node()+">
<xsl:sequence select="?*"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="?*"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:iterate select="
1 to max(current-group() !
(let $key := map:keys(.),
$first-step := replace($key, '^/?([^/]+)(.*$)', '$1'),
$pos := if (not(ends-with($first-step, ']'))) then
1
else
replace($first-step, '^[^\[]+(\[([0-9]+)\])$', '$2') ! xs:integer(.)
return
$pos))">
<xsl:variable name="exp" select="current-grouping-key()"/>
<xsl:variable name="step" as="xs:string*" select="
if (. eq 1) then
current-grouping-key()
else
(), current-grouping-key() || '[' || . || ']'"/>
<xsl:variable name="current-grouping-steps"
select="current-group()[map:keys(.) ! tokenize(., '/')[normalize-space()][1][. = $step]]"/>
<xsl:choose>
<xsl:when test="not($exp = '') and not(exists($current-grouping-steps))">
<xsl:choose>
<xsl:when test="starts-with($exp, 'comment()')">
<xsl:comment/>
</xsl:when>
<xsl:when test="starts-with($exp, 'processing-instruction(')">
<xsl:processing-instruction name="{replace($exp, '^processing-instruction\(([''"]?)([^''"]+)["'']?\)$', '$2')}"/>
</xsl:when>
<xsl:when test="starts-with($exp, '@')">
<xsl:attribute name="{substring($exp, 2)}"/>
</xsl:when>
<xsl:when test="$exp">
<xsl:element name="{$exp}"/>
</xsl:when>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:for-each-group select="$current-grouping-steps"
group-by="replace(map:keys(.), '^/?([^/]+)(.*$)', '$1')">
<xsl:variable name="name" as="xs:string"
select="replace(current-grouping-key(), '\[[0-9]+\]$', '')"/>
<xsl:choose>
<xsl:when test="starts-with($name, 'comment()')">
<xsl:comment select="?*"/>
</xsl:when>
<xsl:when
test="starts-with($name, 'processing-instruction(')">
<xsl:processing-instruction name="{replace($name, '^processing-instruction\(([''"]?)([^''"]+)["'']?\)$', '$2')}" select="?*"/>
</xsl:when>
<xsl:when test="starts-with($name, '@')">
<xsl:attribute name="{substring($name, 2)}" select="?*"
/>
</xsl:when>
<xsl:when test="$name">
<xsl:element name="{$name}">
<xsl:sequence
select="mf:generate-nodes(current-group() ! map:entry(map:keys(.) ! replace(., '^/?[^/]+(.*)', '$1'), ?*))"
/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="?* instance of node()+">
<xsl:sequence select="?*"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="?*"/>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:otherwise>
</xsl:choose>
</xsl:iterate>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:function>
</xsl:stylesheet>
Add nodes in XML based on Xpath and also add multiple similar tags in same parent
As I understand your code, when you want to create the second Official
node, the makeXPath function will find an existing Official
node in the first for
loop.node = doc.SelectSingleNode(subXpath)
will return this existing node, and you will then set the innerText
without creating a new one.
I believe you should check for the existence of the full path before the first for
loop and create the sibling then.
Something like
public XmlNode makeXPath(XmlDocument doc, string xpath, string innertext)
{
string[] partsOfXPath = xpath.Split('/');
XmlNode node = null;
if (doc.SelectSingleNode(xpath) != null) {
//get the parent
node = doc.SelectSingleNode(string.Join("/", partsOfXPath, 0, partsOfXPath.Length-1));
node = node.AppendChild(doc.CreateElement(xpath));
node.InnerText = innertext.TrimStart(' ');
}
else {
for (int xpathPos = partsOfXPath.Length; xpathPos > 0; xpathPos--)
{
string subXpath = string.Join("/", partsOfXPath, 0, xpathPos);
node = doc.SelectSingleNode(subXpath);
if (node != null)
{
// append new descendants
for (int newXpathPos = xpathPos; newXpathPos < partsOfXPath.Length; newXpathPos++)
{
node = node.AppendChild(doc.CreateElement(partsOfXPath[newXpathPos]));
}
break;
}
}
node.InnerText = innertext.TrimStart(' ');
}
return node;
}
Create XML nodes using XPath
Is it possible to create XML nodes using XPath?
No, it isn't. XPath can only select existing nodes.
If you want to create new nodes, you need XSLT or XQuery.
But even then, creating a node that satisfies a given path like /a/b/c/foo[5]
is non-trivial, and not something that the languages can do automatically.
How to Generate an XML File from a set of XPath Expressions?
This problem has an easy solution if one builds upon the solution of the previous problem:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="my:my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kNSFor" match="namespace" use="@of"/>
<xsl:variable name="vStylesheet" select="document('')"/>
<xsl:variable name="vPop" as="element()*">
<item path="/create/article/@type">richtext</item>
<item path="/create/article/@lang">en-us</item>
<item path="/create/article[1]/id">1</item>
<item path="/create/article[1]/description">bar</item>
<item path="/create/article[1]/name[1]">foo</item>
<item path="/create/article[1]/price[1]/amount">00.00</item>
<item path="/create/article[1]/price[1]/currency">USD</item>
<item path="/create/article[1]/price[2]/amount">11.11</item>
<item path="/create/article[1]/price[2]/currency">AUD</item>
<item path="/create/article[2]/id">2</item>
<item path="/create/article[2]/description">some name</item>
<item path="/create/article[2]/name[1]">some description</item>
<item path="/create/article[2]/price[1]/amount">00.01</item>
<item path="/create/article[2]/price[1]/currency">USD</item>
<namespace of="create" prefix="ns1:"
url="http://predic8.com/wsdl/material/ArticleService/1/"/>
<namespace of="article" prefix="ns1:"
url="xmlns:ns1='http://predic8.com/material/1/"/>
<namespace of="@lang" prefix="xml:"
url="http://www.w3.org/XML/1998/namespace"/>
<namespace of="price" prefix="ns1:"
url="xmlns:ns1='http://predic8.com/material/1/"/>
<namespace of="id" prefix="ns1:"
url="xmlns:ns1='http://predic8.com/material/1/"/>
</xsl:variable>
<xsl:template match="/">
<xsl:sequence select="my:subTree($vPop/@path/concat(.,'/',string(..)))"/>
</xsl:template>
<xsl:function name="my:subTree" as="node()*">
<xsl:param name="pPaths" as="xs:string*"/>
<xsl:for-each-group select="$pPaths" group-adjacent=
"substring-before(substring-after(concat(., '/'), '/'), '/')">
<xsl:if test="current-grouping-key()">
<xsl:choose>
<xsl:when test=
"substring-after(current-group()[1], current-grouping-key())">
<xsl:variable name="vLocal-name" select=
"substring-before(concat(current-grouping-key(), '['), '[')"/>
<xsl:variable name="vNamespace"
select="key('kNSFor', $vLocal-name, $vStylesheet)"/>
<xsl:choose>
<xsl:when test="starts-with($vLocal-name, '@')">
<xsl:attribute name=
"{$vNamespace/@prefix}{substring($vLocal-name,2)}"
namespace="{$vNamespace/@url}">
<xsl:value-of select=
"substring(
substring-after(current-group(), current-grouping-key()),
2
)"/>
</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:element name="{$vNamespace/@prefix}{$vLocal-name}"
namespace="{$vNamespace/@url}">
<xsl:sequence select=
"my:subTree(for $s in current-group()
return
concat('/',substring-after(substring($s, 2),'/'))
)
"/>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="current-grouping-key()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:for-each-group>
</xsl:function>
</xsl:stylesheet>
When this transformation is applied on any XML document (not used), the wanted, correct result is produced:
<ns1:create xmlns:ns1="http://predic8.com/wsdl/material/ArticleService/1/">
<ns1:article xmlns:ns1="xmlns:ns1='http://predic8.com/material/1/" type="richtext"
xml:lang="en-us"/>
<ns1:article xmlns:ns1="xmlns:ns1='http://predic8.com/material/1/">
<ns1:id>1</ns1:id>
<description>bar</description>
<name>foo</name>
<ns1:price>
<amount>00.00</amount>
<currency>USD</currency>
</ns1:price>
<ns1:price>
<amount>11.11</amount>
<currency>AUD</currency>
</ns1:price>
</ns1:article>
<ns1:article xmlns:ns1="xmlns:ns1='http://predic8.com/material/1/">
<ns1:id>2</ns1:id>
<description>some name</description>
<name>some description</name>
<ns1:price>
<amount>00.01</amount>
<currency>USD</currency>
</ns1:price>
</ns1:article>
</ns1:create>
Explanation:
A reasonable assumption is made that throughout the generated document any two elements with the same
local-name()
belong to the same namespace -- this covers the predominant majority of real-world XML documents.The namespace specifications follow the path specifications. A nsmespace specification has the form:
<namespace of="target element's local-name" prefix="wanted prefix" url="namespace-uri"/>
Before generating an element with
xsl:element
, the appropriate namespace specification is selected using an index created by anxsl:key
. From this namespace specification the values of itsprefix
andurl
attributes are used in specifying in thexsl:element
instruction the values of the full element name and the element's namespace-uri.
Related Topics
Singleton by Jon Skeet Clarification
Passing Data to Master Page in ASP.NET MVC
What's the Difference Between Iequatable and Just Overriding Object.Equals()
Why Use Try {} Finally {} with an Empty Try Block
What Is the Point of Lookup<Tkey, Telement>
C# Adding Button with Value at Runtime
Wix - How to Run/Install Application Without Ui
Nunit VS. Visual Studio 2008's Test Projects for Unit Testing
How to Create a Product Key for My C# Application
How to Set Up HTML/Email Templates with ASP.NET
How to Wait for Async Method to Complete