XElement default namespace on attributes provides unexpected behaviour - c#

I am having trouble creating an XML document that contains a default namespace and a named namespace, hard to explain easier to just show what I am trying to produce...
<Root xmlns="http://www.adventure-works.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:SchemaLocation="http://www.SomeLocatation.Com/MySchemaDoc.xsd">
<Book title="Enders Game" author="Orson Scott Card" />
<Book title="I Robot" author="Isaac Asimov" />
</Root>
but what I end up with is this...
<Root xmlns="http://www.adventure-works.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:SchemaLocation="http://www.SomeLocatation.Com/MySchemaDoc.xsd">
<Book p3:title="Enders Game" p3:author="Orson Scott Card" xmlns:p3="http://www.adventure-works.com" />
<Book p3:title="I Robot" p3:author="Isaac Asimov" xmlns:p3="http://www.adventure-works.com" />
</Root>
The code that I wrote to produce this XML snippet is this...
XNamespace aw = "http://www.adventure-works.com";
XNamespace xsi = "http://www.w3.org/2001/XMLSchema-instance";
XElement root = new XElement(aw + "Root",
new XAttribute("xmlns", "http://www.adventure-works.com"),
new XAttribute(XNamespace.Xmlns + "xsi", "http://www.w3.org/2001/XMLSchema-instance"),
new XAttribute(xsi + "SchemaLocation", "http://www.SomeLocatation.Com/MySchemaDoc.xsd"),
new XElement(aw + "Book",
new XAttribute(aw + "title", "Enders Game"),
new XAttribute(aw + "author", "Orson Scott Card")),
new XElement(aw + "Book",
new XAttribute(aw + "title", "I Robot"),
new XAttribute(aw + "author", "Isaac Asimov")));
based on an example on MSDN
****EDIT****
Ok, with some more experimentation I am now very confused on how XML namespaces work....
if I remove the aw + theattribute I get what I was after...but now it seems that what I was after is not actually what I expected. I thought that namespaces were inherited from their parents, is this not true of attributes as well? because, this code to read the attributes does not work as I expected...
XElement xe = XElement.Parse(textBox1.Text);
XNamespace aw = "http://www.adventure-works.com";
var qry = from x in xe.Descendants(aw + "Book")
select (string)x.Attribute(aw + "author");
However if I remove the aw + on the attribute its ok, leading me to assume that I cannot have attributes in the default namespace. Is this correct?

Good question. I dug around a bit, and found this bit of the XML spec:
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 later goes on to give this example:
For example, each of the bad empty-element tags is illegal in the following:
<!-- http://www.w3.org is bound to n1 and n2 -->
<x xmlns:n1="http://www.w3.org"
xmlns:n2="http://www.w3.org" >
<bad a="1" a="2" />
<bad n1:a="1" n2:a="2" />
</x>
However, each of the following is legal, the second because the default namespace does not > apply to attribute names:
<!-- http://www.w3.org is bound to n1 and is the default -->
<x xmlns:n1="http://www.w3.org"
xmlns="http://www.w3.org" >
<good a="1" b="2" />
<good a="1" n1:a="2" />
</x>
So basically, it looks like attribute names don't get namespaces by default, which explains everything you've seen :)

XElement doc = XElement.Parse(ToXml());
doc.DescendantsAndSelf().Attributes().Where(a => a.IsNamespaceDeclaration).Remove();
var ele = doc.DescendantsAndSelf();
foreach (var el in ele)
el.Name = ns != null ? ns + el.Name.LocalName : el.Name.LocalName;
For anyone else who spent 2 days trying to find an answer.

Related

C# linq XML DeepCompare and tag removal

I am trying to remove a particular element named <source file="..." /> from my XML so I can compare them.
<?xml version="1.0" encoding="utf-8"?>
<!--XML document generated using OCR technology from Nuance Communications, Inc.-->
<document xmlns="http://www.nuance.com/omnipage/xml/ssdoc-schema3.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<page ocr-vers="OmniPageCSDK16" app-vers="PaperVision Capture">
<description backColor="d5d3d4">
<source file="D:\Users\user\AppData\Roaming\OCR\\PVCPROCESSING_8\a9cfb6f2-b170-46f6-a00a-2f1557baee26.tmp" dpix="150" dpiy="150" sizex="1279" sizey="1652" />
<theoreticalPage size="Letter" marginLeft="1700" marginTop="154" marginRight="739" marginBottom="3805" offsetX="-500" offsetY="-20" width="12240" height="15840" />
</description>
I have tried these methods to no avail. It compiles fine but isnt working
doc1.Root.Element("document").Descendants().Where(e=>e.Name == "source").Remove();
doc1.Root.Element("document").Elements().Where(e=>e.Name == "source").Remove();
doc2.Root.Elements().Where(e=>e.Name == "source").Remove();
doc1.Descendants("document").Where(e=>e.Name == "source").Remove();
Anyone have any clues to what I am doing wrong.
You don't take xmlnamespace into consideration.
See this simple example
string xml1 = "<document> </document>";
var elem1 = XDocument.Parse(xml1).Element("document"); //elem1 contains document
Now insert a namespace http://aaa (as in your case)
string xml2 = "<document xmlns=\"http://aaa\"> </document>";
var elem2 = XDocument.Parse(xml2).Element("document"); //elem2 is null
elem2 is null now.
How to solve? use XNamespace
XNamespace ns = "http://aaa";
var elem3 = XDocument.Parse(xml2).Element(ns + "document"); //elem3 contains document
And finally, a more complex example(see the usage of XmlNamespace):
string xml4 = "<document xmlns=\"http://aaa\"> <subitem> <subsubitem> </subsubitem> </subitem> </document>";
XNamespace ns = "http://aaa";
var elems4 = XDocument.Parse(xml4).Element(ns + "document").Descendants(ns + "subsubitem")
.ToList();

System.InvalidOperationException when modifiying value in an xml file in C#

So i... Have this snippet of code what writes to an existing xml file... the code to me is VERY simple...
XElement element;
XDocument xdoc = XDocument.Load(FileLoc);
element = xdoc.Elements(XName.Get("gold", "http://schemas.datacontract.org/2004/07/DumaLegend")).Single();
element.Value = Gold.Text;
Good Right? good! but why does it give out that error which means that it can't find the thing? it's a very valid thing....
here is the xml file:
<?xml version="1.0" encoding="utf-8"?>
<Save xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/DumaLegend">
<saveInfo>
<energyPieces>0</energyPieces>
<fullEnergyCells>4</fullEnergyCells>
<fullHearts>4</fullHearts>
<globalSwitches xmlns:d3p1="a">
<d3p1:switchList xmlns:d4p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays" />
</globalSwitches>
<gold>0</gold>
<hasBigFireball>false</hasBigFireball>
<hasCombo>false</hasCombo>
<hasCrossbow>false</hasCrossbow>
<hasDash>false</hasDash>
<hasDashUpgrade>false</hasDashUpgrade>
<hasDoubleJump>false</hasDoubleJump>
<hasFireball>false</hasFireball>
<hasHookshot>false</hasHookshot>
<hasInvisPot>false</hasInvisPot>
<hasSecondCombo>false</hasSecondCombo>
<hasShieldUpgrade>false</hasShieldUpgrade>
<hasSmallFireball>false</hasSmallFireball>
<heartPieces>0</heartPieces>
<heroPosOnMap>0</heroPosOnMap>
<heroTokens>0</heroTokens>
<itemSlot1 xmlns:d3p1="http://schemas.datacontract.org/2004/07/DumaLegend.Objects.Consumables" i:nil="true" />
<itemSlot2 xmlns:d3p1="http://schemas.datacontract.org/2004/07/DumaLegend.Objects.Consumables" i:nil="true" />
<lives>3</lives>
<worldsUnlocked>0</worldsUnlocked>
<worldsUnlockedOnMap>0</worldsUnlockedOnMap>
</saveInfo>
<saveSlot>0</saveSlot>
</Save>
Use xdoc.Descendants(XName.Get("gold", "http://schemas.datacontract.org/2004/07/DumaLegend")).
From the docs for Elements
Returns a filtered collection of the child elements of this element or document, in document order. Only elements that have a matching XName are included in the collection.
There is only one child elements of your document, and that is the Save element.
What you are looking for is at the path Save/saveInfo/gold. So you can either use Elements like this:
XNamespace ns = "http://schemas.datacontract.org/2004/07/DumaLegend";
var gold = doc.Elements(ns + "Save")
.Elements(ns + "saveInfo")
.Elements(ns + "gold")
.Single();
Or you can use Descendants, which will search all child elements recursively.
XNamespace ns = "http://schemas.datacontract.org/2004/07/DumaLegend";
var gold = doc.Descendants(ns + "gold").Single();

Get rid of xmlns attribute when adding node to existing xml

I have an XML-file:
<ns2:root xmlns:ns2="namespace">
<ns2:a>
<ns2:b>some content</b>
<ns2:c>some content</c>
<ns2:d>some content</d>
</a>
</root>
I need to add a new node in the specific place, my code is:
var doc = XDocument.Load(file);
XNamespace ns2 = "namespace";
doc.Element(ns2 + "root").Element(ns2 + "a").Element(ns2 + "c").AddAfterSelf(
new XElement(ns2+"new",
new XElement("new1",
new XElement("new2","some content"),
new XElement("new3", "some content"))));
The output is:
<ns2:root xmlns:ns2="namespace">
<ns2:a>
<ns2:b>some content</b>
<ns2:c>some content</c>
<ns2:new>
<new1 xmlns="">
<new2>some content</new2>
<new3>some content</new3>
</new1>
</new>
<ns2:d>some content</d>
</a>
</root>
Desired output is:
<ns2:root xmlns:ns2="namespace">
<ns2:a>
<ns2:b>some content</b>
<ns2:c>some content</c>
<ns2:new>
<new1>
<new2>some content</new2>
<new3>some content</new3>
</new1>
</new>
<ns2:d>some content</d>
</a>
</root>
How can I avoid adding xmlns atrribute to the node new1?
Edited mistake in desired output.
Add "ns2 +" before every element name that needs to be in the sitemap namespace
var doc = XDocument.Load(file);
XNamespace ns2 = "namespace";
doc.Element(ns2 + "root").Element(ns2 + "a").Element(ns2 + "c").AddAfterSelf(
new XElement(ns2+"new",
new XElement(ns2+"new1",
new XElement(ns2+"new2","some content"),
new XElement(ns2+"new3", "some content"))));
Adding a default namespace before element resolved my issue
XNamespace defaultNs = "http://www.iata.org/IATA/2007/00";
target.AddAfterSelf(new XElement(defaultNs + "Suffix", suffix));

Selecting namespaced XML node attributes with namespace alias instead of URI on an XElement

I'm trying to query out some information from a heavily namespaced XML document and am having some trouble finding attributes that are also namespaced.
The XML looks like:
<?xml version="1.0" encoding="utf-8" ?>
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
xmlns:owl="http://www.w3.org/2002/07/owl#"
xmlns:skos="http://www.w3.org/2004/02/skos/core#"
xmlns:geo="http://www.geonames.org/ontology#"
xmlns:dc="http://purl.org/dc/terms/"
xmlns:foaf="http://xmlns.com/foaf/0.1/"
xmlns:void="http://rdfs.org/ns/void#">
<geo:Country rdf:about="http://ontologi.es/place/AD" skos:notation="AD" rdfs:label="Andorra" />
<geo:Country rdf:about="http://ontologi.es/place/AE" skos:notation="AE" rdfs:label="United Arab Emirates" />
<geo:Country rdf:about="http://ontologi.es/place/AF" skos:notation="AF" rdfs:label="Afghanistan" />
<geo:Country rdf:about="http://ontologi.es/place/AG" skos:notation="AG" rdfs:label="Antigua & Barbuda" />
<geo:Country rdf:about="http://ontologi.es/place/AI" skos:notation="AI" rdfs:label="Anguilla" />
<geo:Country rdf:about="http://ontologi.es/place/AL" skos:notation="AL" rdfs:label="Albania" />
...
</rdf:RDF>
My goal is to create a list of objects that have a country code and a country name. Here's what works for me now:
XmlReader reader = XmlReader.Create(#"path/to/xml.xml");
XDocument root = XDocument.Load(reader);
XmlNameTable nameTable = reader.NameTable;
XmlNamespaceManager nsManager = new XmlNamespaceManager(nameTable);
nsManager.AddNamespace("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
nsManager.AddNamespace("rdfs", "http://www.w3.org/2000/01/rdf-schema#");
nsManager.AddNamespace("skos", "http://www.w3.org/2004/02/skos/core#");
nsManager.AddNamespace("geo", "http://www.geonames.org/ontology#");
var geoCountries =
from country in root.XPathSelectElements("./rdf:RDF/geo:Country", nsManager)
select new {
CountryCode = country.Attributes("{http://www.w3.org/2004/02/skos/core#}notation").First().Value,
CountryName = country.Attributes("{http://www.w3.org/2000/01/rdf-schema#}label").First().Value
};
This works fine, but I'd like to find the attributes using the namespace aliases, not the namespace URI (just because), or at least be able to lookup the URI using the alias. To try the latter idea, I eventually figured out I could do this:
country.Attributes(nsManager.LookupNamespace("skos") + "notation").First().Value
But I get an XmlException: The ':' character, hexadecimal value 0x3A, cannot be included in a name.
So then I tried:
country.Attributes("{" + nsManager.LookupNamespace("skos") + "}notation").First().Value
And then it works, but just seems like there could or should be an easier way, or rather, the {namespace}attribute syntax seems silly to me, like something that might be abstracted away in the framework.
So with all that, are there any shortcuts or easier ways to look up namespaced attributes?
I'd appreciate any feedback. Thanks!
using Linq to xml
XNamespace skos = XNamespace.Get("http://www.w3.org/2004/02/skos/core#");
XNamespace geo = XNamespace.Get("http://www.geonames.org/ontology#");
XNamespace rdfs = XNamespace.Get("http://www.w3.org/2000/01/rdf-schema#");
XDocument rdf = XDocument.Load(new StringReader(xmlstr));
foreach(var country in rdf.Descendants(geo + "Country"))
{
Console.WriteLine(
country.Attribute(skos + "notation").Value + " " +
country.Attribute(rdfs + "label").Value );
}
You can use System.Linq:
country.Attributes().Where(a => a.Name.LocalName == "notation")?.First()?.Value;

In C#, is there a way to generate an XDocument using the short prefix instead of the full namespace for each node?

I'm simply trying to make my XML a bit tidier and less bulky. I know in C# one can do something like this:
XNamespace ds = "http://schemas.microsoft.com/ado/2007/08/dataservices";
new XElement(ds + "MyDumbElementName", "SomethingStupid");
And get an XML simliar to this:
<root>
<MyDumbElementName xmlns="http://schemas.microsoft.com/ado/2007/08/dataservices">
SomethingStupid
</MyDumbElementName>
</root>
Instead of something like this:
<root xmlns:ds="http://schemas.microsoft.com/ado/2007/08/dataservices">
<ds:MyDumbElementName>
SomethingStupid
</ds:MyDumbElementName>
</root>
Obviously the second version is much prettier, easier to read, and compact. Is there any way to generate an XDocument equivalent of the compact version, without calling Parse("...")?
You may decide to take a risk and answer "No", in which case I believe the fair thing to do is wait for other people to answer, and if no-one gives a decent answer I'll accept your "No", otherwise if someone does provide an answer, I'll mark the "No" down. I hope that seems fair to you too.
EDIT: Perhaps I should be a bit more specific and say that I want to be able to use multiple name spaces, not just one.
You can explicitly override this behaviour by specifying the xmlns attribute:
XNamespace ns = "urn:test";
new XDocument (
new XElement ("root",
new XAttribute (XNamespace.Xmlns + "ds", ns),
new XElement (ns + "foo",
new XAttribute ("xmlns", ns),
new XElement (ns + "bar", "content")
))
).Dump ();
<root xmlns:ds="urn:test">
<foo xmlns="urn:test">
<bar>content</bar>
</foo>
</root>
By default the behaviour is to specify the xmlns in-line.
XNamespace ns = "urn:test";
new XDocument (
new XElement ("root",
new XElement (ns + "foo",
new XElement (ns + "bar", "content")
))
).Dump ();
Gives the output:
<root>
<foo xmlns="urn:test">
<bar>content</bar>
</foo>
</root>
So the default behaviour is your desired behaviour, except when the namespace is already defined:
XNamespace ns = "urn:test";
new XDocument (
new XElement ("root",
new XAttribute (XNamespace.Xmlns + "ds", ns),
new XElement (ns + "foo",
new XElement (ns + "bar", "content")
))
).Dump ();
<root xmlns:ds="urn:test">
<ds:foo>
<ds:bar>content</ds:bar>
</ds:foo>
</root>

Categories