XML Element and Namespace - c#

I have the following method to parse XMLElements:
DisplayMessages(XElement root)
{
var items = root.Descendants("Item");
foreach (var item in items)
{
var name = item.Element("Name");
....
}
}
In debug mode, I can see the root as XML like this:
<ItemInfoList>
<ItemInfo>
<Item>
<a:Name>item 1</a:Name>
...
<Item>
...
and var name is null (I expect to get "item 1"). I tried to use "a:Name" but it caused exception("character : cannot be used in name"). I am not sure if I have to set namespace in root XElelement or not. All the xml node under root should be in the same namespace.
I am new to XElement. In my codes, item.Element("Name") will get its children node "Name"'s value value, is that right?

You need to use element names that include namespace. Try this:
static void DisplayMessages(XElement root)
{
var items = root.Descendants(root.GetDefaultNamespace() + "Item");
foreach (var item in items)
{
var name = item.Element(item.GetNamespaceOfPrefix("a") + "Name");
Console.WriteLine(name.Value);
}
}
Note that operator + is overloaded for XNamespace class in order to make code shorter: XNamespace.Addition Operator.

You do need to define the "a" namespace in the root element:
<Root a:xmlns="http:///someuri.com">
...
</Root>
Then you can select an element in a non-default namespace using this syntax in LINQ to XML:
XNamespace a = "http:///someuri.com"; // must match declaration in document
...
var name = item.Element(a + "Name");
EDIT:
To retrieve the default namespace:
XNamespace defaultNamespace = document.Root.GetDefaultNamespace();
// XNamespace.None is returned when default namespace is not explicitly declared
To find other namespace declarations:
var declarations = root.Attributes().Where(a => a.IsNamespaceDeclaration);
Note that namespaces can be declared on any element though so you would need to recursively search all elements in a document to find all namespace declarations. In practice though this is generally done in the root element, if you can control how the XML is generated then that won't be an issue.

You need to create XNames that have a non-null Namespace. To do so, you have to create an XNamespace, and add the element name, see Creating an XName in a Namespace.

If you work with XML data that contains namespaces, you need to declare these namespaces. (That's a general observation I made, even though it seems to make it difficult to "just have a look" on data you don't know).
You need to declare an XNamespace for your XElements, as in these MSDN samples: Element(), XName

Related

Creating XmlNode with specified spread sheet type

I'm little bit new in xml manipulation. I want to create a XmlNode.
I've already tried to OwnerDocument.CreateElement method and also tried OwnerDocument.CreateNode method, but I can't create the following XmlNode:
<Data ss:Type="String"/>
Can you help me with this problem? I already tried everything which I found, but nothing.
XmlDocument has been superseded in the .NET framework by the newer XDocument API, which plays nicer with Linq and in general is a modern library to use for XML manipulation.
Here is an example how you can use that API to insert an element into an existing XML document, with an attribute that has a namespace prefix which is previously declared.
XDocument ownerDocument = XDocument.Parse("<OwnerDocument></OwnerDocument>");
XNamespace ssNameSpace = "http://whatever/somenamespace";
// add namespace declaration to the root, set ss as the namespace prefix
// you only need to do this if the document doesn't already have the namespace declaration
ownerDocument.Root.Add(new XAttribute(XNamespace.Xmlns + "ss", ssNameSpace.NamespaceName));
// add our new data element to the root, and add the type attribute prefixed with the ss namespace
ownerDocument.Root.Add(new XElement("Data", new XAttribute(ssNameSpace + "Type", "String")));
That will produce the following XML:
<OwnerDocument xmlns:ss="http://whatever/somenamespace">
<Data ss:Type="String" />
</OwnerDocument>
If you are really tied to using XmlDocument, you can acheive the same there as follows:
XmlDocument ownerDocument = new XmlDocument();
ownerDocument.LoadXml("<OwnerDocument></OwnerDocument>");
// add namespace declaration to the root, set ss as the namespace prefix
var nsDeclarationAttribute = ownerDocument.CreateAttribute("xmlns:ss");
nsDeclarationAttribute.Value = "http://whatever/somenamespace";
ownerDocument.DocumentElement.Attributes.Append(nsDeclarationAttribute);
// add data element, and add a type attribute to that
var dataElement = ownerDocument.CreateElement("Data");
var typeAttribute = ownerDocument.CreateAttribute("Type", "http://whatever/somenamespace");
typeAttribute.Value = "String";
dataElement.Attributes.Append(typeAttribute);
// append to main document
ownerDocument.DocumentElement.AppendChild(dataElement);

Linq query of XML attributes

So I'm trying to write a simple query that grabs all of a certain attribute from an XML file, but nothing seems to work. I've been able to do this with several other XML's but for some reason the one I'm working with here just won't cooperate. Any suggestions or advice would be hugely appreciated.
Here's what the XML looks like.
<Doc xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="Name" xsi:schemaLocation="[there's a link here]" Name="Name">
<Wrapper>
<Box_Collection>
<Box name="Test A" test="Test B"/>
<Box name="Test C" test="Test D"/>
<Box name="Test E" test="Test F"/>
</Box_Collection>
</Wrapper>
</Doc>
Here's my C# code:
XDocument customers = XDocument.Load(#"C:\Users\folder\file.xml");
IEnumerable<string> names =
from c in customers.Descendants("Box").Attributes("name")
select c.Value;
string nameList = "Names:";
foreach (string c in names)
{
namer += " " + c;
}
textBox.AppendText(nameList);
The reason is that your XML has default namespace declared at the root element :
xmlns="Name"
XML elements inherit ancestor default namespace by default, unless otherwise specified (f.e by using explicit prefix that point to different namespace URI). You can use XNamespace + element's local name to point to element in namespace :
XNamespace ns = "Name";
IEnumerable<string> names =
from c in customers.Descendants(ns+"Box").Attributes("name")
select c.Value;
Your document has a default namespace of "Name". You need to reference the namespace when selecting a node like so:
IEnumerable<string> names =
from c in customers.Descendants(XName.Get("Box", "Name")).Attributes("name")
select c.Value;

Extracting XML Child Elements Where the Parents are in a Defaulted Namespace

I have the below XML and I've been trying to extract the FirstName, LastName and OtherName for a while now I'm running into all sort of problems.
<OmdCds xmlns="cds"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cdsd="cds_dt"
xsi:schemaLocation="cds ontariomd_cds.xsd">
<PatientRecord>
<Demographics>
<Names>
<cdsd:LegalName namePurpose="L">
<cdsd:FirstName>
<cdsd:Part>SARAH</cdsd:Part>
<cdsd:PartType>GIV</cdsd:PartType>
<cdsd:PartQualifier>BR</cdsd:PartQualifier>
</cdsd:FirstName>
<cdsd:LastName>
<cdsd:Part>GOMEZ</cdsd:Part>
<cdsd:PartType>FAMC</cdsd:PartType>
<cdsd:PartQualifier>BR</cdsd:PartQualifier>
</cdsd:LastName>
<cdsd:OtherName>
<cdsd:Part>GABRIELA</cdsd:Part>
<cdsd:PartType>GIV</cdsd:PartType>
<cdsd:PartQualifier>BR</PartQualifier>
I currently trying to extract with the below c# code but still can't extract the above data. I'm getting a nullreferenceexception.
XmlDocument doc = new XmlDocument();
doc.Load(folder + "\\" + o.ToString());
XmlNamespaceManager namespaceManager = new XmlNamespaceManager(doc.NameTable);
namespaceManager.AddNamespace("cdsd", "http://www.w3.org/2001/XMLSchema-instance");
XmlNode firstName = doc.DocumentElement.SelectSingleNode("/PatientRecord/Demographics/Names/cdsd:LegalName/cdsd:FirstName/cdsd:Part", namespaceManager);
string fName = firstName.InnerText;
MessageBox.Show(fName);
I can see in the local watch item under doc.DocumentElement, all the InnerXML and InnerText. The InnerXML look something like this...
<PatientRecord xmlns=\"cds\"><Demographics><Names><cdsd:LegalName namePurpose=\"L\" xmlns:cdsd=\"cds_dt\"><cdsd:FirstName><cdsd:Part>SARAH</cdsd:Part><cdsd:PartType>GIV</cdsd:PartType><cdsd:PartQualifier>BR</cdsd:PartQualifier></cdsd:FirstName>
You have 3 namespace definitions in the document:
cds - as a default namespace
http://www.w3.org/2001/XMLSchema-instance- with the xsi prefix
cds_dt - with the cdsd prefix
I am wondering that you don't get an error message, because cds and cds_dt are no URIs and namspaces need to be URIs.
If you try to understand an element name you need to replaces the prefix with the actual namespace.
<PatientRecord> reads as {cds}:PatientRecord
<cdsd:LegalName> reads as {cds_dt}:LegalName
Now in XPath 1.0 the same happens with registered namespaces. But XPath does not have a default namespace. So elements without one are not expanded with a default namespace.
You need to register namespace prefixes on the namespace manager. The prefix does not need to be the same as in the document.
namespaceManager.AddNamespace("cdsd", "cds_dt");
namespaceManager.AddNamespace("cds", "cds");
Now you can use the registered namespaces in XPath:
doc.DocumentElement.SelectSingleNode(
"cds:PatientRecord/cds:Demographics/cds:Names/cdsd:LegalName/cdsd:FirstName/cdsd:Part",
namespaceManager
);
If the first character of an XPath expression is a slash the expression is relative to the document, otherwise to the current context node. You call SelectSingleNode() on the doc.DocumentElement - the OmdCds element node. PatientRecord is a child node, so you can start with it or use . for the current context node.
PatientRecord, Demographics and Names are in the cds namespace. This is because of the default namespace declaration on the OmdCds element (xmlns="cds"). The others are in the cdsd namespace, not xsi. You'll have to add them and use them in the XPATH:
namespaceManager.AddNamespace("cdsd", "cdsd");
namespaceManager.AddNamespace("cds", "cds");
XmlNode firstName = doc.DocumentElement.SelectSingleNode(
"/cds:PatientRecord/cds:Demographics/cds:Names/cdsd:LegalName/cdsd:FirstName/cdsd:Part",
namespaceManager);
BTW, you're getting a NullReferenceException because you're making the false assumption that your query will always return a node. You are now seeing what happens when it does not return a node. Always check for null whenever it's possible that a query returns no value.
Instead XmlDocument class you can use Linq to XML, is easy. You need using the System.Xml.Linq namspace, for example:
XDocument xdoc = XDocument.Load("path");
IEnumerable<XElement> nodes = (from p in xdoc.Descendants()
where p.Name.LocalName == "FirstName"
select p).Elements();
foreach (XElement nodeFirstName in nodes)
{
foreach (XElement parts in nodeFirstName.Elements())
{
string strExtracted = parts.Name.LocalName + " " + parts.Value;
}
}
The LocalName property is used beacuse elements have a prefix "cdsd"

LINQ to XML - How to fix default namespace in the root element

Consider generating the following XML structure, which has 2 prefixed namespaces:
XNamespace ns1 = "http://www.namespace.org/ns1";
const string prefix1 = "w1";
XNamespace ns2 = "http://www.namespace.org/ns2";
const string prefix2 = "w2";
var root =
new XElement(ns1 + "root",
new XElement(ns1 + "E1"
, new XAttribute(ns1 + "attr1", "value1")
, new XAttribute(ns2 + "attr2", "value2"))
, new XAttribute(XNamespace.Xmlns + prefix2, ns2)
, new XAttribute(XNamespace.Xmlns + prefix1, ns1)
);
It generates the following XML result (which is fine):
<w1:root xmlns:w2="http://www.namespace.org/ns2" xmlns:w1="http://www.namespace.org/ns1">
<w1:E1 w1:attr1="value1" w2:attr2="value2" />
</w1:root>
The problem arises when I try to change ns1 from a prefixed namespace to a default namespace by commenting out its XML declaration, as in:
var root =
new XElement(ns1 + "root",
new XElement(ns1 + "E1"
, new XAttribute(ns1 + "attr1", "value1")
, new XAttribute(ns2 + "attr2", "value2"))
, new XAttribute(XNamespace.Xmlns + prefix2, ns2)
//, new XAttribute(XNamespace.Xmlns + prefix1, ns1)
);
which produces:
<root xmlns:w2="http://www.namespace.org/ns2" xmlns="http://www.namespace.org/ns1">
<E1 p3:attr1="value1" w2:attr2="value2" xmlns:p3="http://www.namespace.org/ns1" />
</root>
Note the duplicate namespace definitions in root and E1 and attributes prefixed as p3 under E1. How can I avoid this from happening? How can I force declaration of default namespace in the root element?
Related Questions
I studied this question: How to set the default XML namespace for an XDocument
But the proposed answer replaces namespace for elements without any namespace defined. In my samples the elements and attributes already have their namespaces correctly set.
What I have tried
Based on too many trial and errors, it seems to me that attributes which are not directly under the root node, where the attribute and its direct parent element both have the same namespace as the default namespace; the namespace for the attribute needs to be removed!!!
Based on this I defined the following extension method which traverses all the elements of the resulting XML and performs the above. In all my samples thus far this extension method fixed the problem successfully, but it doesn't necessarily mean that somebody can't produce a failing example for it:
public static void FixDefaultXmlNamespace(this XElement xelem, XNamespace ns)
{
if(xelem.Parent != null && xelem.Name.Namespace == ns)
{
if(xelem.Attributes().Any(x => x.Name.Namespace == ns))
{
var attrs = xelem.Attributes().ToArray();
for (int i = 0; i < attrs.Length; i++)
{
var attr = attrs[i];
if (attr.Name.Namespace == ns)
{
attrs[i] = new XAttribute(attr.Name.LocalName, attr.Value);
}
}
xelem.ReplaceAttributes(attrs);
}
}
foreach (var elem in xelem.Elements())
elem.FixDefaultXmlNamespace(ns);
}
This extension method produces the following XML for our question, which is what I desire:
<root xmlns:w2="http://www.namespace.org/ns2" xmlns="http://www.namespace.org/ns1">
<E1 attr1="value1" w2:attr2="value2" />
</root>
However I don't like this solution, mainly because it is expensive. I feel I'm missing a small setting somewhere. Any ideas?
Quoting from here:
An attribute is not considered a child of its parent element. An attribute never inherits the namespace of its parent element. For that reason an attribute is only in a namespace if it has a proper namespace prefix. An attribute can never be in a default namespace.
and here:
A default namespace declaration applies to all unprefixed element names within its scope. Default namespace declarations do not apply directly to attribute names; the interpretation of unprefixed attributes is determined by the element on which they appear.
It seems that this odd behavior of LINQ-to-XML is rooted in standards. Therefore whenever adding a new attribute its namespace must be compared against the parents' default namespace which is active in its scope. I use this extension method for adding attributes:
public static XAttribute AddAttributeNamespaceSafe(this XElement parent,
XName attrName, string attrValue, XNamespace documentDefaultNamespace)
{
if (newAttrName.Namespace == documentDefaultNamespace)
attrName = attrName.LocalName;
var newAttr = new XAttribute(attrName, attrValue);
parent.Add(newAttr);
return newAttr;
}
And use this extension method for retrieving attributes:
public static XAttribute GetAttributeNamespaceSafe(this XElement parent,
XName attrName, XNamespace documentDefaultNamespace)
{
if (attrName.Namespace == documentDefaultNamespace)
attrName = attrName.LocalName;
return parent.Attribute(attrName);
}
Alternatively, if you have the XML structure at hand and want to fix the namespaces already added to attributes, use the following extension method to fix this (which is slightly different from that outlined in the question):
public static void FixDefaultXmlNamespace(this XElement xelem,
XNamespace documentDefaultNamespace)
{
if (xelem.Attributes().Any(x => x.Name.Namespace == documentDefaultNamespace))
{
var attrs = xelem.Attributes().ToArray();
for (int i = 0; i < attrs.Length; i++)
{
var attr = attrs[i];
if (attr.Name.Namespace == documentDefaultNamespace)
{
attrs[i] = new XAttribute(attr.Name.LocalName, attr.Value);
}
}
xelem.ReplaceAttributes(attrs);
}
foreach (var elem in xelem.Elements())
elem.FixDefaultXmlNamespace(documentDefaultNamespace);
}
Note that you won't need to apply the above method, if you have used the first two methods upon adding and retrieving attributes.
I have found something for you, from the C# in a Nutshell book:
You can assign namespaces to attributes too. The main difference is that it always
requires a prefix. For instance:
<customer xmlns:nut="OReilly.Nutshell.CSharp" nut:id="123" />
Another difference is that an unqualified attribute always has an empty namespace:
it never inherits a default namespace from a parent element.
So given your desired output i have made a simple check.
var xml = #"<root xmlns:w2=""http://www.namespace.org/ns2"" xmlns=""http://www.namespace.org/ns1"">
<E1 attr1=""value1"" w2:attr2=""value2"" />
</root>";
var dom = XElement.Parse(xml);
var e1 = dom.Element(ns1 + "E1");
var attr2 = e1.Attribute(ns2 + "attr2");
var attr1 = e1.Attribute(ns1 + "attr1");
// attr1 is null !
var attrNoNS = e1.Attribute("attr1");
// attrNoNS is not null
So in short attr1 does not have default namespace, but has an empty namespace.
Is this you want? If yes, just create you attr1 without namespace...
new XAttribute("attr1", "value1")

XPathSelectElements returns null

Load function is already defined in xmlData class
public class XmlData
{
public void Load(XElement xDoc)
{
var id = xDoc.XPathSelectElements("//ID");
var listIds = xDoc.XPathSelectElements("/Lists//List/ListIDS/ListIDS");
}
}
I'm just calling the Load function from my end.
XmlData aXmlData = new XmlData();
string input, stringXML = "";
TextReader aTextReader = new StreamReader("D:\\test.xml");
while ((input = aTextReader.ReadLine()) != null)
{
stringXML += input;
}
XElement Content = XElement.Parse(stringXML);
aXmlData.Load(Content);
in load function,im getting both id and and listIds as null.
My test.xml contains
<SEARCH>
<ID>11242</ID>
<Lists>
<List CURRENT="true" AGGREGATEDCHANGED="false">
<ListIDS>
<ListID>100567</ListID>
<ListID>100564</ListID>
<ListID>100025</ListID>
<ListID>2</ListID>
<ListID>1</ListID>
</ListIDS>
</List>
</Lists>
</SEARCH>
EDIT: Your sample XML doesn't have an id element in the namespace with the nss alias. It would be <nss:id> in that case, or there'd be a default namespace set up. I've assumed for this answer that in reality the element you're looking for is in the namespace.
Your query is trying to find an element called id at the root level. To find all id elements, you need:
var tempId = xDoc.XPathSelectElements("//nss:id", ns);
... although personally I'd use:
XDocument doc = XDocument.Parse(...);
XNamespace nss = "http://schemas.microsoft.com/SQLServer/reporting/reportdesigner";
// Or use FirstOrDefault(), or whatever...
XElement idElement = doc.Descendants(nss + "id").Single();
(I prefer using the query methods on LINQ to XML types instead of XPath... I find it easier to avoid silly syntax errors etc.)
Your sample code is also unclear as you're using xDoc which hasn't been declared... it helps to write complete examples, ideally including everything required to compile and run as a console app.
I am looking at the question 3 hours after it was submitted and 41 minutes after it was (last) edited.
There are no namespaces defined in the provided XML document.
var listIds = xDoc.XPathSelectElements("/Lists//List/ListIDS/ListIDS");
This XPath expression obviously doesn't select any node from the provided XML document, because the XML document doesn't have a top element named Lists (the name of the actual top element is SEARCH)
var id = xDoc.XPathSelectElements("//ID");
in load function,im getting both id and and listIds as null.
This statement is false, because //ID selects the only element named ID in the provided XML document, thus the value of the C# variable id is non-null. Probably you didn't test thoroughly after editing the XML document.
Most probably the original ID element belonged to some namespace. But now it is in "no namespace" and the XPath expression above does select it.
string xmldocument = "<response xmlns:nss=\"http://schemas.microsoft.com/SQLServer/reporting/reportdesigner\"><action>test</action><id>1</id></response>";
XElement Content = XElement.Parse(xmldocument);
XPathNavigator navigator = Content.CreateNavigator();
XmlNamespaceManager ns = new XmlNamespaceManager(navigator.NameTable);
ns.AddNamespace("nss", "http://schemas.microsoft.com/SQLServer/reporting/reportdesigner");
var tempId = navigator.SelectSingleNode("/id");
The reason for the null value or system returned value is due to the following
var id = xDoc.XPathSelectElements("//ID");
XpathSElectElements is System.xml.linq.XElment which is linq queried date. It cannot be directly outputed as such.
To Get individual first match element
use XPathSelectElement("//ID");
You can check the number of occurrences using XPathSelectElements as
var count=xDoc.XPathSelectElements("//ID").count();
you can also query the linq statement as order by using specific conditions
Inorder to get node value from a list u can use this
foreach (XmlNode xNode in xDoc.SelectNodes("//ListIDS/ListID"))
{
Console.WriteLine(xNode.InnerText);
}
For Second list you havnt got the value since, the XPath for list items is not correct

Categories

Resources