Modifying namespace in XML document programmatically
2015-08-11 09:33
543 查看
Modifying namespace in XML document programmatically
static XElement stripNS(XElement root) { return new XElement( root.Name.LocalName, root.HasElements ? root.Elements().Select(el => stripNS(el)) : (object)root.Value ); } static void Main() { var xml = XElement.Parse(@"<?xml version=""1.0"" encoding=""utf-16""?> <ArrayOfInserts xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema""> <insert> <offer xmlns=""http://schema.peters.com/doc_353/1/Types"">0174587</offer> <type2 xmlns=""http://schema.peters.com/doc_353/1/Types"">014717</type2> <supplier xmlns=""http://schema.peters.com/doc_353/1/Types"">019172</supplier> <id_frame xmlns=""http://schema.peters.com/doc_353/1/Types"" /> <type3 xmlns=""http://schema.peters.com/doc_353/1/Types""> <type2 /> <main>false</main> </type3> <status xmlns=""http://schema.peters.com/doc_353/1/Types"">Some state</status> </insert> </ArrayOfInserts>"); Console.WriteLine(stripNS(xml)); }I needed to validate an XML document with a given XSD document. Seems easy enough… so let’s have a look at the schema first:
[code]<?xml version="1.0" encoding="utf-8"?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://my.namespace" elementFormDefault="qualified" targetNamespace="http://my.namespace"> <xs:element name="customer"> <xs:complexType> <xs:sequence> <xs:element name="firstname" type="xs:string" /> <xs:element name="lastname" type="xs:string" /> <xs:element name="age" type="xs:integer" /> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>[/code]
The XML instance is:
[code]<?xml version="1.0" encoding="utf-8" ?> <customer> <firstname>Homer</firstname> <lastname></lastname> <age>36</age> </customer>[/code]
The code is straightforward:
[code]static void Main(string[] args) { // Load the xml document XDocument source = XDocument.Load(@"instance.xml"); // Load the schema XmlSchemaSet xmlSchemaSet = new XmlSchemaSet(); xmlSchemaSet.Add(null, XmlReader.Create(@"customer.xsd")); // Validate try { source.Validate(xmlSchemaSet, ValidationCallback, true); } catch (Exception ex) { Console.WriteLine(ex.Message); } } static void ValidationCallback(object sender, System.Xml.Schema.ValidationEventArgs e) { Console.WriteLine(string.Format("[{0}] {1}", e.Severity, e.Message)); }[/code]
If you run this, no errors are thrown so it seems to validate. To be sure, let’s change the age in an invalid value:
[code]<Age>invalid!</Age>[/code]
and test again. Well… actually, no validation error is thrown in this case either… what’s going on here?Actually, the XML is not validated at all, because it’s not in the same namespace (http://my.namespace) as the schema definition. This is very dangerous, as we might easily get mislead by thinking that it validates because no errors are thrown. So how do we solve it?We could ask the sender to provide the correct namespace in the XML file – this would be the best solution because then it would just work – if you try to validate the following XML:
[code]<?xml version="1.0" encoding="utf-8" ?> <customer xmlns="http://my.namespace"> <firstname>Homer</firstname> <lastname></lastname> <age>invalid</age> </customer>[/code]
…then the validation error is thrown, because the namespaces now match:Unfortunately, it is not always possible to change the XML file, so how can we bypass this namespace conflict? If appears that if we would change the namespace in the loaded XML document to the one we are using in our schema, the conflict is resolved. A first attempt may be:
[code]// Load the xml document XDocument source = XDocument.Load(@"instance.xml"); // Change namespace to reflect schema namespace source.Root.SetAttributeValue("xmlns", "http://my.namespace"); // Load the schema XmlSchemaSet xmlSchemaSet = new XmlSchemaSet(); xmlSchemaSet.Add(null, XmlReader.Create(@"customer.xsd")); // Validate try { source.Validate(xmlSchemaSet, ValidationCallback, true); } catch (Exception ex) { Console.WriteLine(ex.Message); }[/code]
If we run this, the validation error is still not thrown, so setting the namespace attribute is not enough. The reason is that once the XDocument is loaded, every element in the tree gets prefixed with the namespace name. So we need to change them all, and so I wrote the following method that does this:
[code]static void Main(string[] args) { // Load the xml document XDocument source = XDocument.Load(@"instance.xml"); // Change namespace to reflect schema namespace source = SetNamespace(source,"http://my.namespace"); // Load the schema XmlSchemaSet xmlSchemaSet = new XmlSchemaSet(); xmlSchemaSet.Add(null, XmlReader.Create(@"customer.xsd")); // Validate try { source.Validate(xmlSchemaSet, ValidationCallback, true); } catch (Exception ex) { Console.WriteLine(ex.Message); } } public static XDocument SetNamespace(XDocument source, XNamespace xNamespace) { foreach (XElement xElement in source.Descendants()) { // First make sure that the xmlns-attribute is changed xElement.SetAttributeValue("xmlns", xNamespace.NamespaceName); // Then also prefix the name of the element with the namespace xElement.Name = xNamespace + xElement.Name.LocalName; } return source; } static void ValidationCallback(object sender, System.Xml.Schema.ValidationEventArgs e) { Console.WriteLine(string.Format("[{0}] {1}", e.Severity, e.Message)); }[/code]
The SetNameSpace method will set the corrrect namespace for each element in the XDocument. And if we run it now, the validation error is thrown again because the namespace in the XDocument has been modified and matches the schema namespace.
Related
Parsing large XML filesIn "C#"Strategy patternIn "C#"A reference architecture (part 7)In "Architecture"3 thoughts on “Modifying namespace in XML document programmatically”
Janez says:November 18, 2010 at 4:30 pm
Thanks, a working solution to a problem that took the better part of my day. :-)ReplyJim says:
July 3, 2013 at 4:58 pm
This solution was very hard to fine…thanks so much for posting it.ReplyMike says:
June 19, 2015 at 3:51 pm
This was very helpful and got me past some serious frustration! I was changing a child element tree to match a parent namespace, but I did not want to have the extra size of including the SetAttributeValue on all elements. My change was a change from one default namespace to another existing and prefixed one. This did the trick for me. Below are some minor adjustments that might be useful to others in some cases.public static XDocument SetNamespace(XDocument source, XNamespace original, XNamespace target)
{
//First change the element name (and namespace)
foreach (XElement xElement in source.Descendants().Where(x => x.Name.Namespace == original))
xElement.Name = target + xElement.Name.LocalName;//Second, remove the default namespace attribute.
foreach (XElement xElement in source.Descendants().Where(x => x.Attributes().Where(y => y.Name == “xmlns”).Count() > 0))
xElement.Attribute(“xmlns”).Remove();return source;
}Reply
Leave a Reply
相关文章推荐
- C++ Constructor
- Eclipse使用指定JDK,无需配置Path变量
- android断点下载并显示进度,关于handler,和主线程不能联网采取子线程联网下载,和多线程下载学习
- Sencha Touch 搭建命令
- O-c中类的继承与派生的概念
- 华为训练题目:初级——名字漂亮度
- BZOJ2388: 旅行规划
- 九度oj 1168
- RHEV平台中如何在 RED HAT ENTERPRISE LINUX 虚拟机上安装 GUEST 代理和驱动
- Oracle修改默认字符编码
- jQuery radio的取值与赋值
- Centos7/RHEL7 安装goldendict
- 存在超自然力量吗?
- C/C++知识要点6——定义只能在堆/栈上生成对象的类
- php防止恶意刷新页面的方法
- Fragment-8.11
- Web项目发布
- 17-集合框架-13-常用对象API(集合框架-LinkedList集合)
- 安装配置Apache2 Perl cgi-bin
- Universal-Image-Loader(android图片缓存)