I have a string containing some XML:
<some>
<xml></xml>
<tags/>
<TheData/>
<more>asdf</more>
<of/>
<them></them>
</some>
Instead of <TheData/> we could have <TheData></TheData> or other equivalent forms.
Questions:
1: How can I make sure that the XML does indeed contain an empty TheData element?
2: How can I insert another XML string, HTML encoded into TheData? Example of the other XML:
<mydata>
<name>John</name>
<address>New York City</address>
</mydata>
End result:
<some>
<xml></xml>
<tags/>
<TheData>
<mydata>
<name>John</name>
<address>New York City</address>
</mydata>
</TheData>
<more>asdf</more>
<of/>
<them></them>
</some>
Are there quick ways to do this with e.g. LINQ to XML?
First of all, you have to load your XML into memory as an XDocument instance:
var doc = XDocument.Load(yourFilePath);
Then, you can answer your questions:
1: How can I make sure that the XML does indeed contain an empty TheData element?
var theData = doc.Root.Element("TheData");
if(theData != null && string.IsNullOrEmpty((string)theData))
{
// document contains TheData element which is empty
}
2: How can I insert another XML string, HTML encoded into TheData?
theData.Value = #"<mydata>
<name>John</name>
<address>New York City</address>
</mydata>";
You don't have to bother about XML encoding here. XElement class will take care of that.
Related
From the following XML, I want to find a value based on the Employer.
<?xml version="1.0" encoding="UTF-8"?>
<Document>
<Details>
<Employer>Taxes</Employer>
<Adr>
<Strt>Street</Strt>
<Twn>Town</Twn>
</Adr>
</Details>
<DetailsAcct>
<Recd>
<Payroll>
<Id>9</Id>
</Payroll>
</Recd>
<br>
<xy>A</xy>
</br>
</DetailsAcct>
</Document>
the C# code I applied is
detail = root.SelectSingleNode($"//w:Document//w:Employer[contains(text(), 'Taxes']/ancestor::Employer",nsmgr);
But it gives me an invalid token error.
What am I missing?
The error was due to [contains(...], notice closing parentheses is missing. And since you want to return Employer element, no need for ancestor::Employer here :
//w:Document//w:Employer[contains(., 'Taxes')]
If the XML posted resembles structure of the actual XML (except the namespaces), better to use more specific XPath i.e avoid using costly // :
/w:Document/w:Details/w:Employer[contains(., 'Taxes')]
An alternative is to use LINQ to XML.
If the XML is in a string:
string xml = "<xml goes here>";
XDocument document = XDocument.Parse(xml);
XElement element = document.Descendants("Employer").First();
string value = element.Value;
If the XML is in a .xml file:
XDocument document = XDocument.Load("xmlfile.xml");
XElement element = document.Descendants("Employer").First();
string value = element.Value;
You can also find an employer element with a specific value, if that's what you need:
XElement element = document.Descendants("Employer").First(e => e.Value == "Taxes");
Note: this will throw an exception if no element is found with the specified value. If that is not acceptable, then you can replace .First(...) with .FirstOrDefault(...) which will simply return null if no element is found.
Lets say i have this XML
<root>
<myEntity> // list 1
<name>Test 1</name>
<entityNew> // list 2
<newName/>
<newName/>
</entityNew>
</myEntity>
<myEntity>
<name>Test 2</name>
<entityNew>
<newName/>
<newName/>
</entityNew>
</myEntity>
</root>
I want to get the entityNew list.
What I tried was this
//myEntity[1]/entityNew but it's not working, same as //myEntity[position()=1]/entityNew or something like this //myEntity[1]//entityNew
but when i tried it in an online XPath tester, it worked, this is the site i used http://www.freeformatter.com/xpath-tester.html
After some fiddling I noticed that HtmlAgilityPack is treating the node names as all lowercase and XPath is case sensitive so the queries you have tried are returning null.
I tried the following sample application (notice all node names are lowercase):
string xml = File.ReadAllText("XMLFile1.xml");
var doc = new HtmlDocument();
doc.LoadHtml(xml);
var navigator = doc.CreateNavigator();
var iterator = navigator.Select("root//myentity[1]/entitynew");
iterator.MoveNext();
Console.WriteLine(iterator.Current.OuterXml);
and the output is this:
<entitynew> // list 2
<newname />
<newname />
</entitynew>
I have a project where i read XML that was exported from another system. The XML looks like this:
<?xml version="1.0" encoding="ISO-8859-1"?><xmlimexport>
<companydata/>
<articles/>
<customers/>
<suppliers/>
<orders>
<order>
<atOrder>
<OOI_HEAD_DOCUMENT_NUMBER>12345</OOI_HEAD_DOCUMENT_NUMBER>
**... more rows ...**
</atOrder>
<rows>
<row><OOI_ROW_ARTICLE_NUMBER>12345</OOI_ROW_ARTICLE_NUMBER><OOI_ROW_ARTICLE_TEXT>SuperDuperArticleName</OOI_ROW_TEXT>**... more data...**</row>
</rows>
</order>
</orders>
<bests/>
<invoices/>
<supplierinvoices/>
<pricelists/>
<parcels/>
</xmlimexport>
So what i do is load the path to the XML file then:
XmlDocument doc = new XmlDocument();
// Load xml file
doc.Load(xmlFile);
// Read order data
XmlNodeList orderList = doc.GetElementsByTagName("order");
foreach (XmlElement order in orderList)
{
try
{
// Read atOrder data (single node)
XmlNode atOrder = order.SelectSingleNode("atOrder");
// Read article data (one or many nodes)
XmlNodeList articles = order.GetElementsByTagName("row");
// Create a order
Order customerOrder = new Order();
Then read data with:
customerOrder.CUS_ID = Convert.ToInt32(atOrder.SelectSingleNode("OOI_HEAD_DOCUMENT_NUMBER").InnerText);
But since it can be both Strings, Booleans, Datetime, Date and INT in those fields i find myself having to use Convert. very much, is this the proper way to do this or should i use a different approach?
you can also generate an XSD and based on an XSD a class to deserialize the XML (short tutorial here)
you can read the XML directly into a DataSet (.ReadXML)
Have a look at LinqToXml I think that might help:
http://msdn.microsoft.com/en-us/library/bb387098.aspx
If I receive a string that is only a list of numbers (e.g. 1,2,3,5), is it possible to convert it to XML format, like this?
<foo>
<id>1</id>
<id>2</id>
<id>3</id>
</foo>
So far I had planned to use something along the lines of this
string s = "example";
XmlDocument xm = new XmlDocument();
xm.LoadXml(string.Format("<foo>{0}</foo>", s));
But I'm unsure as to how I should split the string so that I only get the numbers without using the obvious Split(), which is something my manager doesn't want me to do (otherwise I'd just skip the whole XML format).
Basically, is there a way for me to 'easily' serialize that string into XML format?
Use LINQtoXML
string items="1,4,6,3";
XElement elm = new XElement("foo");
foreach(var item in items.Split(','))
{
elm.Add(new XElement("id",item));
}
Now ele will have the XML you are looking for
You can use the XDocument.Parse(string) method
You can build up the xml string and then pass it to the method
http://msdn.microsoft.com/en-us/library/system.xml.linq.xdocument.parse.aspx
I've got an XML file which I use to create objects, change the objects, then save the objects back into the XML file.
What do I have to change in the following code so that it extracts a node from the XML based on the id, replaces that node with the new one, and saves it back into the XML?
The following gives me 'System.Xml.Linq.XElement' does not contain a constructor that takes '0' arguments':
//GET ALL SMARTFORMS AS XML
XDocument xmlDoc = null;
try
{
xmlDoc = XDocument.Load(FullXmlDataStorePathAndFileName);
}
catch (Exception ex)
{
HandleXmlFileNotFound(ex);
}
//EXTRACT THE NODE THAT NEEDS TO BE REPLACED
XElement oldElementToOverwrite = xmlDoc.Descendants("smartForm")
.Where(sf => (int)sf.Element("id") == 2)
.Select(sf => new XElement());
//CREATE THE NODE THAT WILL REPLACE IT
XElement newElementToSave = new XElement("smartForm",
new XElement("id", this.Id),
new XElement("idCode", this.IdCode),
new XElement("title", this.Title)
);
//OVERWRITE OLD WITH NEW
oldElementToOverwrite.ReplaceWith(newElementToSave);
//SAVE XML BACK TO FILE
xmlDoc.Save(FullXmlDataStorePathAndFileName);
XML file:
<?xml version="1.0" encoding="utf-8" ?>
<root>
<smartForm>
<id>1</id>
<whenCreated>2008-12-31</whenCreated>
<itemOwner>system</itemOwner>
<publishStatus>published</publishStatus>
<correctionOfId>0</correctionOfId>
<idCode>customerSpecial</idCode>
<title>Edit Customer Special</title>
<description>This form has a special setup.</description>
<labelWidth>200</labelWidth>
</smartForm>
<smartForm>
<id>2</id>
<whenCreated>2008-12-31</whenCreated>
<itemOwner>system</itemOwner>
<publishStatus>published</publishStatus>
<correctionOfId>0</correctionOfId>
<idCode>customersMain</idCode>
<title>Edit Customer</title>
<description>This form allows you to edit a customer.</description>
<labelWidth>100</labelWidth>
</smartForm>
<smartForm>
<id>3</id>
<whenCreated>2008-12-31</whenCreated>
<itemOwner>system</itemOwner>
<publishStatus>published</publishStatus>
<correctionOfId>0</correctionOfId>
<idCode>customersNameOnly</idCode>
<title>Edit Customer Name</title>
<description>This form allows you to edit a customer's name only.</description>
<labelWidth>100</labelWidth>
</smartForm>
</root>
Well, the error has nothing to do with saving, or even with replacement - it has to do with you trying to create an XElement without specifying the name. Why are you trying to use Select at all? My guess is you just want to use Single:
XElement oldElementToOverwrite = xmlDoc.Descendants("smartForm")
.Where(sf => (int)sf.Element("id") == 2)
.Single();
(As Noldorin notes, you can give Single a predicate to avoid using Where at all. Personally I quite like to split the two operations up, but they'll be semantically equivalent.)
That will return the single element in the sequence, or throw an exception if there are 0 elements or more than one. Alternatives are to use SingleOrDefault, First, or FirstOrDefault:
SingleOrDefault if it's legal to have 0 or 1
First if it's legal to have 1 or more
FirstOrDefault if it's legal to have 0 or more
If you're using an "OrDefault" one, the result will be null if there are no matches.
I think the problem is simply your use of the Select call in the statement assigning oldElementToOverwrite. You actually seem to want the Single extension method.
XElement oldElementToOverwrite = xmlDoc.Descendants("smartForm")
.Single(sf => (int)sf.Element("id") == 2)