iterating XmlDocument with xpath - c#

How do we iterate an XmlDocument using an xpath?
I'm attempting to return a list of nodes by xpath:
public static List<string> Filter(string xpath, string input, string ns, string nsUrl)
{
var bytes = Encoding.UTF8.GetBytes(input); //i believe this unescapes the string
var stream = new MemoryStream(bytes);
var doc = new XmlDocument();
XmlNamespaceManager namespaceManager = new XmlNamespaceManager(doc.NameTable);
namespaceManager.AddNamespace(ns, nsUrl);
var links = new List<string>();
var nodes = doc.SelectNodes(xpath, namespaceManager);
using (var reader = new XmlTextReader(stream))
{
reader.Namespaces = false;
doc.Load(reader);
}
foreach (XmlNode node in nodes)
{
if (IsNullOrWhiteSpace(node.InnerText))
{
continue;
}
links.Add(node.InnerText);
}
return links;
}
however, the count is always 0 !
I'm using this xpath. notice how i am using only 1 namespace:
/ns0:Visit/ns0:DocumentInterface/ns0:Documents/ns0:Document/ns0:BinaryData
The header of the file looks like this:
<ns0:Visit xmlns:ns0="http://NameSpace.ExternalSchemas.Patient"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
I'm certain that I am using the right xpath because I tested it against my payload:
I'm calling the function this way:
var links = Filter(xpath, xml, "ns0", "http://NameSpace.ExternalSchemas.Patient");
How do we iterate an XmlDocument using an xpath? Perhaps the XmlDocument should be an XDocument instead?

Related

Rename xml descendants

I'm trying to
Load external xml
Select only "oldName" nodes
Change "oldName" nodes to "newName" nodes
Create and save new xml file
The code below does not work, it only creates "items" node.
// Load xml
XElement doc = XElement.Load("http://.../file.xml");
var nodes = doc.Descendants().Where(element => element.Name.LocalName.Equals("oldName"));
foreach (var element in nodes)
{
if (element.Name.LocalName.Equals("oldName"))
{
element.Name = "newName";
}
}
XElement newDoc = new XElement("item", nodes);
// Save xml
newDoc.Save("C:/newFile.xml");
Any help would be appreciated.
EDIT: Selecting "oldName" nodes works, but I need to rename them also.
// Load xml
XElement doc = XElement.Load("http://.../file.xml");
var nodes = doc.Descendants().Where(element => element.Name.LocalName.Equals("oldName"));
XElement newDoc = new XElement("item", nodes);
// Save xml
newDoc.Save("C:/newFile.xml");
EDIT: Added file example
file.xml
<ignoreThisElement>
<oldName>
...
</oldName>
</ignoreThisElement>
newFile.xml
<newName>
...
</newName>
The reason why your code only creates an empty "items" node is because the 'nodes' var no longer contains elements with the old name. You changed all the elements to the new variable, but the linq query still references the old name.
// Load xml
XElement doc = XElement.Load("http://.../file.xml");
//using linq to get all nodes with name 'oldName'
var nodes = doc.Descendants().Where(element => element.Name.LocalName.Equals("oldName"));
//you rename the nodes here, so the linq query no longer finds nodes with 'oldName'
foreach (var element in nodes)
{
if (element.Name.LocalName.Equals("oldName"))
{
element.Name = "newName";
}
}
//result is 'items' node with no children
XElement newDoc = new XElement("item", nodes);
// Save xml
newDoc.Save("C:/newFile.xml");
The solution would be for your to extract the required nodes first, then move them to your newDoc element. Thereafter you can rename the nodes:
// Load xml
XElement doc = XElement.Load("http://.../file.xml");
var nodes = doc.Descendants().Where(element => element.Name.LocalName.Equals("oldName"));
//move the extracted old nodes to your newDoc first
XElement newDoc = new XElement("item", nodes);
//now rename
foreach (var element in newDoc.Descendants())
{
if (element.Name.LocalName.StartsWith("oldName"))
{
element.Name = "newName";
}
}
// Save xml
newDoc.Save("C:/newFile.xml");
Alternatively, you could requery the doc variable for the new node names after renaming them:
// Load xml
XElement doc = XElement.Load("http://.../file.xml");
//using linq to get all nodes with name 'oldName'
var nodes = doc.Descendants().Where(element => element.Name.LocalName.Equals("oldName"));
foreach (var element in nodes)
{
if (element.Name.LocalName.Equals("oldName"))
{
element.Name = "newName";
}
}
//get all newName nodes
var newNodes = doc.Descendants().Where(element => element.Name.LocalName.Equals("newName"));
XElement newDoc = new XElement("item", newNodes);
// Save xml
newDoc.Save("C:/newFile.xml");
You can try something like this
// Load the xml
var doc = XDocument.Load("http://.../file.xml");
// loop thru the element an d search for a specific node and change the name
foreach (var element in doc.Descendants())
{
if (element.Name.LocalName.StartsWith("oldName"))
{
element.Name = "newName";
}
}
// Save xml
doc.Save("C:/newFile.xml");
Try following :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication3
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XElement doc = XElement.Load(FILENAME);
List<XElement> nodes = doc.Descendants().Where(element => element.Name.LocalName.Equals("oldName")).ToList();
for(int index = nodes.Count() - 1; index >= 0; index--)
{
XElement node = nodes[index];
node.ReplaceWith(new XElement("newName", node.Descendants()));
}
}
}
}

Can't get attribute value with C# XPath

I've spent so much time with this already, still can't get the value of NTE attribute. Can someone please help?
C#
StreamReader sr = new StreamReader(resp.GetResponseStream());
XPathDocument xmlDoc = new XPathDocument(sr); // holds xml document
XPathNavigator xmlNav = xmlDoc.CreateNavigator(); //evaluates XPath expressions
XPathNodeIterator node = xmlNav.Select("/DATA2SC/CALL");
string dne = xmlNav.GetAttribute("NTE", "");
Console.WriteLine(dne);
sr.Close();
XML
<?xml version="1.0"?>
<DATA2SC PIN="00000">
<CALL
TR_NUM="00000001"
STATUS="WAITING_FOR_APPROVAL"
NTE="$15.00">
<PROBLEM>
Text
</PROBLEM>
</CALL>
</DATA2SC>
Can you try this code please ?
I already check and it's work
StreamReader sr = new StreamReader("c:\\x.xml");
XPathDocument xmlDoc = new XPathDocument(sr); // holds xml document
XPathNavigator xmlNav = xmlDoc.CreateNavigator(); //evaluates XPath expressions
var node = xmlNav.SelectSingleNode("/DATA2SC/CALL");
string dne = node.GetAttribute("NTE", "");
Console.WriteLine(dne);
OR
XDocument docXmlWorld = XDocument.Load("c:\\x.xml");
foreach (var node1 in docXmlWorld.Descendants("DATA2SC"))
{
foreach (var node2 in node1.Descendants("CALL"))
{
string dne = node2.Attribute("NTE").Value;
Console.Out.WriteLine(dne);
}
}
Or you can do like this too:
XDocument docXmlWorld = XDocument.Load("c:\\x.xml");
//Get the first child => [DATA2SC]
XElement elementNodeDATA2SC = docXmlWorld.Element("DATA2SC");
//Get the first child => [CALL]
XElement elementNodeCALL = elementNodeDATA2SC.Element("CALL");
//Get the attribute NTE from [CALL] node
string dne = elementNodeCALL.Attribute("NTE").Value;
Console.Out.WriteLine(dne);
The Select method, returns a collection of all nodes with the specified XPath.
You can use SelectSingleNode, to select the first node.
var node = xmlNav.SelectSingleNode("/DATA2SC/CALL");
string dne = node.GetAttribute("NTE", "");

Incorrect parsing of Textbox in docx by OpenXML

I am reading a .docx file using OpenXML in C#. It reads everything correctly but strangely, the content of textbox is being read thrice. What could be wrong? Here is the code to read .docx:
public static string TextFromWord(String file)
{
const string wordmlNamespace = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
StringBuilder textBuilder = new StringBuilder();
using (WordprocessingDocument wdDoc = WordprocessingDocument.Open(file, false))
{
// Manage namespaces to perform XPath queries.
NameTable nt = new NameTable();
XmlNamespaceManager nsManager = new XmlNamespaceManager(nt);
nsManager.AddNamespace("w", wordmlNamespace);
// Get the document part from the package.
// Load the XML in the document part into an XmlDocument instance.
XmlDocument xdoc = new XmlDocument(nt);
xdoc.Load(wdDoc.MainDocumentPart.GetStream());
XmlNodeList paragraphNodes = xdoc.SelectNodes("//w:p", nsManager);
foreach (XmlNode paragraphNode in paragraphNodes)
{
XmlNodeList textNodes = paragraphNode.SelectNodes(".//w:t", nsManager);
foreach (System.Xml.XmlNode textNode in textNodes)
{
textBuilder.Append(textNode.InnerText);
}
textBuilder.Append(Environment.NewLine);
}
}
return textBuilder.ToString();
}
The part of file I am talking about is:
The result is: I read it in a test application like this:
What's wrong here?

Loop through multiple subnodes in XML

<Sections>
<Classes>
<Class>VI</Class>
<Class>VII</Class>
</Classes>
<Students>
<Student>abc</Student>
<Student>def</Student>
</Students>
</Sections>
I have to loop through Classes to get 'Class' into an array of strings. I have to also loop through 'Students' to get 'Student' put into an array of strings.
XDocument doc.Load("File.xml");
string str1;
foreach(XElement mainLoop in doc.Descendants("Sections"))
{
foreach(XElement classLoop in mainLoop.Descendants("Classes"))
str1 = classLoop.Element("Class").Value +",";
//Also get Student value
}
is not working to get all the classes. Also, I need to rewrite this without using LINQ to XML, i.e using XmlNodeList and XmlNodes.
XmlDocument doc1 = new XmlDocument();
doc1.Load("File.xml");
foreach(XmlNode mainLoop in doc.SelectNodes("Sections")) ??
Not sure how to go about it.
The XPath is straightforward. To get the results into an array you can either use LINQ or a regular loop.
var classNodes = doc.SelectNodes("/Sections/Classes/Class");
// LINQ approach
string[] classes = classNodes.Cast<XmlNode>()
.Select(n => n.InnerText)
.ToArray();
var studentNodes = doc.SelectNodes("/Sections/Students/Student");
// traditional approach
string[] students = new string[studentNodes.Count];
for (int i = 0; i < studentNodes.Count; i++)
{
students[i] = studentNodes[i].InnerText;
}
Not sure about rewriting it for XmlNodes but for your Classes and Students you can simply:
XDocument doc.Load("File.xml");
foreach(XElement c in doc.Descendants("Class"))
{
// do something with c.Value;
}
foreach(XElement s in doc.Descendants("Student"))
{
// do something with s.Value;
}
With LINQ to XML:
XDocument doc = XDocument.Load("file.xml");
var classNodes = doc.Elements("Sections").Elements("Classes").Elements("Class");
StringBuilder result = new StringBuilder();
foreach( var c in classNodes )
result.Append(c.Value).Append(",");
With XPath:
XmlDocument doc = new XmlDocument();
doc.Load("file.xml");
var classNodes = doc.SelectNodes("/Sections/Classes/Class/text()");
StringBuilder result = new StringBuilder();
foreach( XmlNode c in classNodes )
result.Append(c.Value).Append(",");

Enumerating Linq.Xelement

How to adjust this code to work when RESPONSE is no more string but Linq.Xelement?
String response = "anyxml data";
XmlDocument xmlDocument = LoadXMLDocument(response);
XmlNodeList nodeList = xmlDocument.GetElementsByTagName("fql_query_response");
if (nodeList != null && nodeList.Count > 0)
{
if (nodeList[0].HasChildNodes)
{
XmlNodeList results = xmlDocument.GetElementsByTagName("event_member");
Dictionary<string, EventUser> eventUserDict = new Dictionary<string, EventUser>();
foreach (XmlNode node in results)
{
myuids.Add(Int64.Parse(node.FirstChild.InnerText));
}
}
Do you mean you want to create an XmlDocument from an XElement?
The simplest way to do that may well be this:
XmlDocument doc = new XmlDocument();
using (XmlReader reader = element.CreateReader())
{
doc.Load(reader);
}
However, I have to say the code would probably be simpler if you just converted it all to LINQ to XML, which is generally a nicer API to start with. Is there any reason why you want to stay with XmlDocument?
You can use XElement.ToString() to create an XML string from the XElement which you can load into your XmlDocument:
XmlDocument xmlDocument = LoadXMLDocument(yourXElement.ToString());

Categories