i'm re posting my question in a simpler way.
i need to search for a specific node in a XML file, and once i see it, i need to create a new node and insert it after that. the problem is that there are 2 nodes with the same value. and i need to insert the new node twice after each instance. with the code below: it's inserting the new nodes twice but in the same place after the first instance only.
original XML:
<eventlist>
<event type="AUDIOPLAYER">
<properties>
<schedule startType="-ParentEnd1" />
<media mediaType="Audio" />
</properties>
</event>
<event type="AUDIOPLAYER">
<properties>
<schedule startType="-ParentEnd2" />
<media mediaType="Audio" />
</properties>
</event>
</eventlist>
intended XML:
<eventlist>
<event type="AUDIOPLAYER">
<properties>
<schedule startType="-ParentEnd1" />
<media mediaType="Audio" />
</properties>
</event>
<event type="VIZ" />
<event type="AUDIOPLAYER">
<properties>
<schedule startType="-ParentEnd2" />
<media mediaType="Audio" />
</properties>
</event>
<event type="VIZ" />
</eventlist>
but the current output is:
<eventlist>
<event type="AUDIOPLAYER">
<properties>
<schedule startType="-ParentEnd1" />
<media mediaType="Audio" />
</properties>
</event>
<event type="VIZ" />
<event type="VIZ" />
<event type="AUDIOPLAYER">
<properties>
<schedule startType="-ParentEnd2" />
<media mediaType="Audio" />
</properties>
</event>
</eventlist>
the code is below here:
XmlDocument xdoc = new XmlDocument();
xdoc.Load(#"C:\Users\namokhtar\Desktop\newxml\testxml.xml");
foreach (XmlNode node in xdoc.SelectNodes("/eventlist/event[#type='AUDIOPLAYER']"))
{
XmlNode srcNode = node.SelectSingleNode("/eventlist/event[#type='AUDIOPLAYER']");
XmlNode newElem = xdoc.CreateElement("event");
XmlAttribute newAttr = xdoc.CreateAttribute("type");
newAttr.Value = "VIZ";
newElem.Attributes.Append(newAttr);
srcNode.ParentNode.InsertAfter(newElem, srcNode);
}
xdoc.Save(#"C:\Users\namokhtar\Desktop\newxml\newxml1.xml");
please advise me...
I haven´t fully tested this, but I´m almost sure this should do the trick:
foreach (XmlNode node in xdoc.SelectNodes("/eventlist/event[#type='AUDIOPLAYER']"))
{
XmlNodeList srcNodes = node.SelectNodes("/eventlist/event[#type='AUDIOPLAYER']");
foreach (XmlNode srcNode in srcNodes)
{
XmlNode newElem = xdoc.CreateElement("event");
XmlAttribute newAttr = xdoc.CreateAttribute("type");
newAttr.Value = "VIZ";
newElem.Attributes.Append(newAttr);
srcNode.ParentNode.InsertAfter(newElem, srcNode);
}
}
The problem is you were selecting single node from matching expression, and you need to select all the nodes that match that and insert the new node after each of them.
Hope this helps!
Here is a solution using LINQ:
var xml = XDocument.Parse(File.ReadAllText(#"C:\Users\namokhtar\Desktop\newxml\testxml.xml"));
var elems = xml.Root.Elements()
.Where(e => e.Name == "event" && e.Attribute("type")?.Value == "AUDIOPLAYER");
foreach (var elem in elems)
{
elem.AddAfterSelf(new XElement("event", new XAttribute("type", "VIZ")));
}
xml.Save(#"C:\Users\namokhtar\Desktop\newxml\newxml1.xml");
Related
I have following XML:
XML
<PatientDetailsXML>
<PList>
<PName type="Patient"> // node [i] = 0
<properties>
<Room bedType="Auto" />
<PName title="Joe Beom" PId="1234">
<Details>
<classification classification="paymenttype" category="Wallet" />
<classification classification="Humor" category="1" />
<classification classification="Food" category="Fruit" />
</Details>
</PName>
</properties>
<childEvents>
</childEvents>
</PName>
<PName type="Patient"> // node[i] =1
<properties>
<Room bedType="Auto" />
<PName title="John Bair" PId="4567">
<Details>
<classification classification="paymenttype" category="Wallet" />
<classification classification="Humor" category="2" />
<classification classification="Food" category="Fruit" />
</Details>
</PName>
</properties>
<childEvents>
</childEvents>
</PName>
<PName type="Patient"> // node[i] = 2
<properties>
<Room bedType="Auto" />
<PName title="Bairstow" PId="1234">
<Details>
<classification classification="paymenttype" category="CreditCard" />
<classification classification="Humor" category="1" />
<classification classification="Food" category="Vegetables" />
</Details>
</PName>
</properties>
<childEvents>
</childEvents>
</PName>
When I'm at node[0], i'm doing a search for classification='paymenttype' and category ='CreditCard', I find the 3rd item node[i] = 2
var next = currNode.SelectSingleNode(#"following::classifications/classification[#classification='paymenttype'and #category ='CreditCard'] /ancestor::PName/#title"); // This gives me the title `Bairstow`
Now I need to jump to my previous node where classification='paymenttype'and #category ='CreditCard'.
var previous = next.SelectSingleNode(#"preceding::classifications/classification[#classification='paymenttype'and #category ='CreditCard']/ancestor::event");
// this doesn't seem to work
CODE
var query = #"//PList/PName[.//PName/classifications/classification[#classification='paymenttype' and #category='Wallet']]";
var nodes = docs.SelectNodes(query).OfType<XmlNode>().ToArray();
XmlNodeList nodeList = docs.SelectNodes(query);
for (int i = 0; i < nodes.Count(); i++)
{
//do something
//jump to method to do something else
string testPayment = Externals.Next(nodes[i]);
}
public static string Next(XMLNode currNode)
{
var next = currNode.SelectSingleNode(#"following::classifications/classification[#classification='Humor'and #category ='1'] /ancestor::PName/#title");
// This gives me the title `Bairstow`
// Now I need to select the title of the previous node e.g.John Bair
var previous = next.SelectSingleNode(#"preceding::classifications/classification[#classification='paymenttype'and #category ='CreditCard']/ancestor::event");
// this is not pointing to the node with title JohnBair
}
I have the following xml. How do I test for last node?
<PatientDetailsXML>
<PList>
<PName type="Patient">
<properties>
<Room bedType="Auto" />
<PName title="Joe Beom" PId="1234">
<Details>
<classification classification="paymenttype" category="Wallet" />
<classification classification="Humor" category="None" />
<classification classification="Food" category="Fruit" />
</Details>
</PName>
</properties>
<childEvents>
</childEvents>
</PName>
<PName type="Patient">
<properties>
<Room bedType="Auto" />
<PName title="John Bair" PId="1234">
<Details>
<classification classification="paymenttype" category="Found" />
<classification classification="Humor" category="None" />
<classification classification="Food" category="Fruit" />
</Details>
</PName>
</properties>
<childEvents>
</childEvents>
</PName>
<!-- End of List -->
</PList>
</PatientDetailsXML>
During the for loop I end up <!-- End of List -->, I do a test for the comment but then I need to also test if there is no node after that.
var nodes = docs.SelectNodes(query).OfType<XmlNode>().ToArray();
XmlNodeList nodeList = docs.SelectNodes(query);
for (int i = 0; i < nodes.Count(); i++)
{
XmlNode multinextnode = nodes[i];
if (multinextnodetest == "#comment")
{
multinextnode.NextSibling;
{
if (multinextnode.Equals(multinextnode.LastChild))
{
Console.WriteLine("final");
}
// some code
}
}
// somecode
}
This multinextnode.Equals(multinextnode.LastChild) doesn't seem to work for me. In the for loop I'm jumping to next node as you can see above multinextnode.NextSibling. This is absolutely necessary to achieve my target with the code. But during the jump I may hit a null in the multinextnode. Which means there is no child/sibling/node after that, right before </PList>. So I need to do a test for lastchild
Right now I'm simply doing a null test and getting the job done. But I need to know if there is a better way.
I have edited this little MSDN example https://support.microsoft.com/en-us/kb/311530
And I have the following 2 XML's
Book1.xml
<?xml version="1.0"?>
<catalog>
<book1 id="1" />
<bookSum number="1" />
</catalog>
Book2.xml
<?xml version="1.0"?>
<catalog>
<book2 id="2" />
<bookSum number="1" specialAttribute="123" />
</catalog>
C# Code
XmlReader xmlreader1 = XmlReader.Create(#"C:\Book1.xml");
XmlReader xmlreader2 = XmlReader.Create(#"C:\book2.xml");
DataSet ds = new DataSet();
ds.ReadXml(xmlreader1);
DataSet ds2 = new DataSet();
ds2.ReadXml(xmlreader2);
ds.Merge(ds2);
ds.WriteXml(#"C:\Merge.xml");
Console.WriteLine("Completed merging XML documents");
Merge.xml (Book1.xml + Book2.xml)
<?xml version="1.0" standalone="yes"?>
<catalog>
<book1 id="1" />
<bookSum number="1" />
<bookSum number="1" specialAttribute="x" />
<book2 id="2" />
</catalog>
And the question is
how to join
<bookSum number="1" />
<bookSum number="1" specialAttribute="123" />
into one line ?
<bookSum number="2" specialAttribute="123" />
I did it this way and is working:
var doc = XDocument.Load(#"C:\Book1.xml");
doc.Root.Add(XDocument.Load(#"C:\Book2.xml").Root.Elements()); // merged
var sum = doc.Descendants("bookSum").Attributes("number").Sum(t => int.Parse(t.Value));
var xElement = new XElement("bookSum", new XAttribute("number", sum));
foreach(var attrib in doc.Descendants("bookSum").Attributes())
{
if (xElement.Attribute(attrib.Name) == null)
xElement.Add(attrib);
}
doc.Descendants("bookSum").ToList().ForEach(t => t.ReplaceWith(xElement));
doc.Root.Descendants("bookSum").First().Remove();
Console.WriteLine(doc);
I have an xml doc layout like so
<?xml version="1.0" encoding="utf-8" ?>
<Document name="document name">
<DataSet name="dataset 1">
<Dimension name="dimension 1">
<Metric name="metric 1">
<Data value="1">
<Data value="2">
<Data value="3">
</Metric>
<Metric name="metric 2">
<Data value="4">
<Data value="5">
<Data value="6">
</Metric>
</Dimension>
<Dimension name="dimension 2">
<Metric name="metric 3">
<Data value="6">
<Data value="7">
<Data value="8">
</Metric>
<Metric name="metric 4">
<Data value="9">
<Data value="10">
<Data value="11">
</Metric>
</Dimension>
</DataSet>
</Document>
I am attempting to split the Metrics out with their ancestors but not with their siblings. For example I want a file to look like...
<?xml version="1.0" encoding="utf-8" ?>
<Document name="document name">
<DataSet name="dataset 1">
<Dimension name="dimension 1">
<Metric name="metric 1">
<Data value="1">
<Data value="2">
<Data value="3">
</Metric>
</Dimension>
</DataSet>
</Document>
and file 2 to look like ...
<?xml version="1.0" encoding="utf-8" ?>
<Document name="document name">
<DataSet name="dataset 1">
<Dimension name="dimension 1">
<Metric name="metric 2">
<Data value="4">
<Data value="5">
<Data value="6">
</Metric>
</Dimension>
</DataSet>
</Document>
My attempt to solve this was to create a method that would accept an xmlfile, outputDirectory, elementName, attributeName, and attributeValue.
This would allow me to search the document for a specific metric and pull it out into its own file with its entire tree, but not with it's siblings.
at the top of my method i have...
XDocument doc = XDocument.Load(xmlFile);
IEnumerable<XElement> elementsInPath = doc.Descendants()
.Elements(elementName)
.Where(p => p.Attribute(attributeName).Value.Contains(attributeValue))
.AncestorsAndSelf()
.InDocumentOrder()
.ToList();
However when I iterate through the elementsInPath The output gives all of the parents of the matched "Metric" with each parent return all of its children, and the only child i want present is the one that was matched by the input params.
Any help would be appreciated. Just for reference I save the files using the following snippet
int i = 1;
foreach (XElement element in elementsInPath)
{
XDocument tmpDoc = new XDocument();
tmpDoc.Add(element);
tmpDoc.Save(outputDirectory + elementName + "_" + i + ".xml");
i++;
}
Also to note, If I use the following code I get the exact metrics I am looking for, but i need to encapsulate them within their parents.
IEnumerable<XElement> elementsInPath = doc.Descendants(elementName)
.Where(p => p.Attribute(attributeName).Value.Contains(attributeValue))
.InDocumentOrder()
.ToList();
So you're really building a new document for each Metric element. The most straightforward way to do this would be:
foreach (XElement el in doc.Root.Descendants("Metric"))
{
XElement newDoc =
new XElement("Document",
new XAttribute(doc.Root.Attribute("name")),
new XElement("DataSet",
new XAttribute(el.Parent.Parent.Attribute("name")),
new XElement("Dimension",
new XAttribute(el.Parent.Attribute("name")),
el)
)
)
);
newDoc.Save(el.Attribute("name").Value.Replace(" ", "_") + ".xml");
}
This hopefully illustrates how the dynamic version of this would work - iterate through the ancestors and create new elements based on them:
foreach (XElement el in doc.Root.Descendants("Metric"))
{
XElement newDoc = el;
foreach(XElement nextParent in el.Ancestors()) // iterate through ancestors
{
//rewrap in ancestor node name/attributes
newDoc = new XElement(nextParent.Name, nextParent.Attributes(), newDoc);
}
newDoc.Save(el.Attribute("name").Value.Replace(" ", "_") + ".xml");
}
Just using the Ancestors() functionality won't work, because when you tried to add them it would also add their children (the siblings of the element you're trying to split on).
I have a XML file with the following structure:
<deftable>
<table>
<job jobname="">
<incond name="" />
<outcond name="" />
</job>
<job jobname="">
<incond name="" />
<outcond name="" />
<incond name="" />
<outcond name="" />
</job>
</table>
<table>
<job jobname="">
<incond name="" />
<outcond name="" />
</job>
<job jobname="">
<incond name="" />
<outcond name="" />
<incond name="" />
<outcond name="" />
</job>
</table>
</deftable>
inside the tag deftable i can have multiple tables.
in the table tag i can have multiple JOBs, and inside those I have multiple incond and outcond.
I'm trying to obtain the values for jobname, and also the value for the name attribute of incond and outcond.
I have tried several paths to accomplish this. The latest thing I tried was this, but I can't seem to get it to work.
XmlDocument doc = new XmlDocument();
doc.Load(file);
XmlNodeList nodes = doc.DocumentElement.SelectNodes("/deftable/table");
int i = 1;
foreach (XmlNode node in nodes)
{
Console.WriteLine(node["job"].GetAttribute("jobname") + " -- " + i);
i++;
XmlNodeList nodes2 = doc.DocumentElement.SelectNodes("/deftable/table/job");
foreach (XmlNode item in nodes2)
{
Console.WriteLine(item["incond"].GetAttribute("name"));
}
}
Any help is greatly appreciated.
I am not sure how much the following snippet is useful for you, but you can try with Linq to Xml for your case as shown below.
XDocument jobsRoot = XDocument.Load(#"C:\Jobs.xml");
var jobData= jobsRoot.Descendants()
.Where(it => it.Name.LocalName == "job")
.Select(job => new
{
JobName = job.Attribute("jobname").Value,
IncondName = job.Descendants().Where(it => it.Name.LocalName == "incond").Select(inc => inc.Attribute("name").Value),
OutcondName = job.Descendants().Where(it => it.Name.LocalName == "outcond").Select(inc => inc.Attribute("name").Value)
});
I think the linq to XML implementation isn't as great as XML to Dynamic
It's described on Deserialize XML To Object using Dynamic ( since you mentioned you tried several methods). Otherwhise, the answer above would probably do.