Using the following example xml containing one duplicate:
<Persons>
<Person>
<PersonID>7506</PersonID>
<Forename>K</Forename>
<Surname>Seddon</Surname>
<ChosenName />
<MiddleName />
<LegalSurname />
<Gender>Male</Gender>
</Person>
<Person>
<PersonID>6914</PersonID>
<Forename>Clark</Forename>
<Surname>Kent</Surname>
<ChosenName>Clark</ChosenName>
<MiddleName />
<LegalSurname>Kent</LegalSurname>
<Gender>Male</Gender>
</Person>
<Person>
<PersonID>6914</PersonID>
<Forename>Clark</Forename>
<Surname>Kent</Surname>
<ChosenName>Clark</ChosenName>
<MiddleName />
<LegalSurname>Kent</LegalSurname>
<Gender>Male</Gender>
</Person>
</Persons>
I have the following code where I am trying to build an XDocument with the output of an XPath query that filters the duplicate elements:
string outputXml = null;
var original = XDocument.Parse(xmlString);
string xpathFilterDups = "//Person[not(PersonID = following::Person/PersonID)]";
XDocument people = new XDocument("Persons", original.XPathSelectElements(xpathFilterDups));
outputXml = people.ToString();
I get the error:
An exception of type 'System.ArgumentException' occurred in System.Xml.Linq.dll but was not handled in user code
Non white space characters cannot be added to content.
On this line:
XDocument people = new XDocument("Persons", original.XPathSelectElements(xpath));
What am I doing wrong? :-\
You can ignore pretty much all your code, the issue is just this:
XDocument people = new XDocument("Persons");
You can't create an XDocument containing a string, you need to add an element:
XDocument people = new XDocument(
new XElement("Persons",
original.XPathSelectElements(xpathFilterDups)));
Related
If I have the following xml:
<Employees>
<Person>
<ID>1000</ID>
<Name>Nima</Name>
<LName>Agha</LName>
</Person>
<Person>
<ID>1002</ID>
<Name>Ligha</Name>
<LName>Ligha</LName>
</Person>
<Person>
<ID>1003</ID>
<Name>Jigha</Name>
<LName>Jigha</LName>
</Person>
</Employees>
I want to add a new node, after an indexed node, and add the remaining person nodes as the children of this new node.
So the new xml after adding would look like this:
<Employees>
<Person>
<ID>1000</ID>
<Name>Nima</Name>
<LName>Agha</LName>
</Person>
<RefNode>
<Person>
<ID>1002</ID>
<Name>Ligha</Name>
<LName>Ligha</LName>
</Person>
<Person>
<ID>1003</ID>
<Name>Jigha</Name>
<LName>Jigha</LName>
</Person>
</RefNode>
</Employees>
So far I have tried using
ElementAt(index).AddAfterSelf()
but that just adds it next in line, and doesn't add the next two Persons as children.
You could do as following. Have added comments in code for better understanding
var xdoc = XDocument.Parse(xml);
// Unclear how you are identifying the node after which the change has to happen. For sake of example, using ID
var selectedNode = xdoc.Descendants("Person")
.First(x=>Convert.ToInt32(x.Element("ID").Value)==1000);
// Collection of nodes that would be added as child of newly inserted node
var nodeAfterSelectedNode = xdoc.Descendants("Person")
.SkipWhile(x=>x==selectedNode)
.ToList();
// Create the new node with previously identified 'nodeAfterSelectedNode' as Children
var newElement = new XElement("RefNode",nodeAfterSelectedNode);
// Remove the existing Nodes (ones that are being moved to child)
foreach(var node in nodeAfterSelectedNode)
{
node.Remove();
}
// add the new node
selectedNode.AddAfterSelf(newElement);
var newXml = xdoc.ToString();
Output
<Employees>
<Person>
<ID>1000</ID>
<Name>Nima</Name>
<LName>Agha</LName>
</Person>
<RefNode>
<Person>
<ID>1002</ID>
<Name>Ligha</Name>
<LName>Ligha</LName>
</Person>
<Person>
<ID>1003</ID>
<Name>Jigha</Name>
<LName>Jigha</LName>
</Person>
</RefNode>
</Employees>
Output Sample
I'm trying to build up Xml that looks like the following (taken from another question) but using the XElement/XNamespace classes:
<person xmlns:json='http://james.newtonking.com/projects/json' id='1'>
<name>Alan</name>
<url>http://www.google.com</url>
<role json:Array='true'>Admin</role>
</person>
This is so I can serialize using Newtonsoft.Json.JsonConvert.SerializeXmlNode() and maintain correct arrays.
The problem I'm having is creating json:Array='true'.
Other examples show XmlDocument classes or raw creation of Xml string, but is there a way to achieve it using XElement? I've tried several things with XNamespace to attempt to create the "json" prefix without success.
Yes, you can achieve it with XElement. For example:
XNamespace json = "http://james.newtonking.com/projects/json";
XDocument xml = new XDocument(new XElement("person",
new XAttribute(XNamespace.Xmlns + "json", json),
new XAttribute("id", 1),
new XElement("name", "Alan"),
new XElement("url", "http://www.google.com"),
new XElement("role", new XAttribute(json + "Array", true), "Admin")));
Will produce the following:
<person xmlns:json="http://james.newtonking.com/projects/json" id="1">
<name>Alan</name>
<url>http://www.google.com</url>
<role json:Array="true">Admin</role>
</person>
Very Simple. I have 2 documents
Doc1
<Person>
<Name>Bob</Name>
</Person>
Doc2
<Animal>
<Name>Zippy</Name>
</Animal>
And I want to create
Doc3
<Person>
<Name>Bob</Name>
</Person>
<Animal>
<Name>Zippy</Name>
</Animal>
The code I have below is close but insert the XML INSIDE the other one and I don't want that
string xmlUserData = GetUserData(fileId);
string xmlPurchaseDate = GetPurchaseData();
XDocument xdocUserData = XDocument.Parse(xmlUserData);
XDocument xdocPurchaseDate = XDocument.Parse(xmlPurchaseDate);
XElement xe1 = xdocUserData.Descendants("USERDATA").FirstOrDefault();
XElement xe2 = xdocPurchaseDate.Descendants("PurchaseAdvice").FirstOrDefault();
xe1.Add(xe2.Nodes());
Yes, you can wrap elements in a root:
XDocument doc = new XDocument();
XElement rootElement = new XElement("Root");
rootElement.Add(new XElement("person"));
rootElement.Add(new XElement("animal"));
doc.Add(rootElement);
gives:
<Root>
<person />
<animal />
</Root>
Please consider this XML:
<Employees>
<Person>
<ID>1000</ID>
<Name>Nima</Name>
<LName>Agha</LName>
</Person>
<Person>
<ID>1002</ID>
<Name>Ligha</Name>
<LName>Ligha</LName>
</Person>
<Person>
<ID>1003</ID>
<Name>Jigha</Name>
<LName>Jigha</LName>
</Person>
</Employees>
That is content of a XElement variable.Now I have another XElement variable with this content:
<Person>
<ID>1001</ID>
<Name>Aba</Name>
<LName>Aba</LName>
</Person>
I want to add this XEelemnt variable to first XElement in a specific position (for example as second Person tag). How I can do this?
thanks
First you need to load the xml string, second you get the position where you want to insert the xml, then insert the new xml. Here is an example how to do it.
var reader = new StringReader(#"<Employees>
<Person>
<ID>1000</ID>
<Name>Nima</Name>
<LName>Agha</LName>
</Person>
<Person>
<ID>1002</ID>
<Name>Ligha</Name>
<LName>Ligha</LName>
</Person>
<Person>
<ID>1003</ID>
<Name>Jigha</Name>
<LName>Jigha</LName>
</Person>
</Employees>");
var xdoc = XDocument.Load(reader);
xdoc.Element("Employees").
Elements("Person").
First().
AddAfterSelf(new XElement("Person",
new XElement("ID", 1001),
new XElement("Name", "Aba"),
new XElement("LName", "Aba")));
var sb = new StringBuilder();
var writer = new StringWriter(sb);
xdoc.Save(writer);
Console.WriteLine(sb);
UPDATE
If you want to insert by index, just get the element first. For example you want to insert as second position, then you need to get the first index (index = 0).
var xdoc = XDocument.Load(reader);
xdoc.Element("Employees").
Elements("Person").
ElementAt(0).
AddAfterSelf(new XElement("Person",
new XElement("ID", 1001),
new XElement("Name", "Aba"),
new XElement("LName", "Aba")));
PS: For simplicity purpose I didn't add nullity check.
Namespaces and XML are still confusing the hell out of me.
Here is my XML (that comes from a SOAP request)
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<MyResponse xmlns="http://tempuri.org/">
<OutputXML xmlns="http://tempuri.org/XMLSchema.xsd">
<Result>
<OutputXML>
<Result>
<Foo>
<Bar />
</Foo>
</Result>
</OutputXML>
</Result>
</OutputXML>
</MyResponse>
</soap:Body>
</soap:Envelope>
I am trying to extract the actual XML part from the SOAP response (starting with the Foo element):
var nsmgr = new XmlNamespaceManager(document.NameTable);
nsmgr.AddNamespace("soap", "http://schemas.xmlsoap.org/soap/envelope/");
nsmgr.AddNamespace("", "http://tempuri.org/");
nsmgr.AddNamespace("", "http://tempuri.org/XMLSchema.xsd");
var xml = document.DocumentElement
.SelectSingleNode("Foo", nsmgr)
.InnerXml;
But SelectSingleNode returns null. I've tried some different variations on this but can't get anything working. What am I not understanding?
Try this one:
var nsmgr = new XmlNamespaceManager(document.NameTable);
nsmgr.AddNamespace("aaa", "http://tempuri.org/XMLSchema.xsd");
var xml = document.DocumentElement
.SelectSingleNode("aaa:Foo", nsmgr)
.InnerXml;
this is because of Default namespaces has no perfix.
You can use GetElementsByTagName to use namespace uri directly:
var xml = document.GetElementsByTagName("Foo",
"http://tempuri.org/XMLSchema.xsd")[0].InnerXml;
You can use LINQ to XML to get your result, also specify the namespace
XDocument document = XDocument.Load("test.xml");
XNamespace ns = "http://tempuri.org/XMLSchema.xsd";
var test = document.Descendants(ns + "Foo").FirstOrDefault();
Or if you don't want to specify NameSpace then:
var test2 = document.Descendants()
.Where(a => a.Name.LocalName == "Foo")
.FirstOrDefault();