Ignoring supplied namespaces when validating XML with XSD
背景:
我们正在构建一个应用程序,它允许我们的客户以预定义(即我们不控制)的XML格式提供数据。XSD由第三方提供给我们,我们希望在处理它之前收到一个通过模式验证的XML文件。
问题:
我们所提供的XSD包含默认和目标命名空间,这意味着如果客户提供的XML文件不包含命名空间,那么验证将通过。显然,我们不希望他们提供说他们通过了但不应该通过的东西,但更大的问题是,如果我找不到执行XML验证的解决方案,我们需要对每个元素进行大量的额外检查。
问题:
是否可以强制.NET执行验证并忽略所提供的XML和XSD上的命名空间。也就是说,在某种程度上"假定"名称空间是附加的。
到目前为止,我拥有的解决方案是:
实例XML:
1 2 3 4 | <?xml version="1.0"?> <xsd:schema version='3.09' elementFormDefault='qualified' attributeFormDefault='unqualified' id='blah' targetNamespace='urn:schemas-blah.com:blahExample' xmlns='urn:blah:blahExample' xmlns:xsd='http://www.w3.org/2001/XMLSchema'> ... </xsd:schema> |
命名空间不同
1 2 3 4 | <?xml version="1.0" encoding="UTF-8" ?> <root xmlns="urn:myCompany.com:blahExample1" attr1="2001-03-03" attr2="google"> ... </root> |
完全没有命名空间。
1 2 3 4 | <?xml version="1.0" encoding="UTF-8" ?> <root attr1="2001-03-03" attr2="google"> ... </root> |
试图解决同样的问题。我想出了一个我认为是相当干净的解决方案。为了清晰起见,我对输入参数进行了一些验证。
首先,场景:有一个WebService接收一个文件,该文件应该是"格式良好的"XML,并且对XSD有效。当然,我们不相信"好消息",也不相信"我们知道"是正确的,这对XSD是有效的。
下面给出了这种WebService方法的代码,我认为这是不言而喻的。
主要关注点是验证发生的顺序,加载前不检查名称空间,加载后检查,但要干净。
我决定我可以接受一些异常处理,因为大多数文件都是"好的",因为这是处理的框架方式(所以我不会反对)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | private DataTable xmlErrors; [WebMethod] public string Upload(byte[] f, string fileName) { string ret ="This will have the response"; // this is the namespace that we want to use string xmlNs ="http://mydomain.com/ns/upload.xsd"; // you could put a public url of xsd instead of a local file string xsdFileName = Server.MapPath("~") +"//" +"shiporder.xsd"; // a simple table to store the eventual errors // (more advanced ways possibly exist) xmlErrors = new DataTable("XmlErrors"); xmlErrors.Columns.Add("Type"); xmlErrors.Columns.Add("Message"); try { XmlDocument doc = new XmlDocument(); // create a document // bind the document, namespace and xsd doc.Schemas.Add(xmlNs, xsdFileName); // if we wanted to validate if the XSD has itself XML errors // doc.Schemas.ValidationEventHandler += // new ValidationEventHandler(Schemas_ValidationEventHandler); // Declare the handler that will run on each error found ValidationEventHandler xmlValidator = new ValidationEventHandler(Xml_ValidationEventHandler); // load the document // will trhow XML.Exception if document is not"well formed" doc.Load(new MemoryStream(f)); // Check if the required namespace is present if (doc.DocumentElement.NamespaceURI == xmlNs) { // Validate against xsd // will call Xml_ValidationEventHandler on each error found doc.Validate(xmlValidator); if (xmlErrors.Rows.Count == 0) { ret ="OK"; } else { // return the complete error list, this is just to proove it works ret ="File has" + xmlErrors.Rows.Count +" xml errors"; ret +="when validated against our XSD."; } } else { ret ="The xml document has incorrect or no namespace."; } } catch (XmlException ex) { ret ="XML Exception: probably xml not well formed..."; ret +="Message =" + ex.Message.ToString(); } catch (Exception ex) { ret ="Exception: probably not XML related..." ret +="Message =" + ex.Message.ToString(); } return ret; } private void Xml_ValidationEventHandler(object sender, ValidationEventArgs e) { xmlErrors.Rows.Add(new object[] { e.Severity, e.Message }); } |
现在,XSD将具有如下特性:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <?xml version="1.0" encoding="utf-8"?> <xs:schema id="shiporder" targetNamespace="http://mydomain.com/ns/upload.xsd" elementFormDefault="qualified" xmlns="http://mydomain.com/ns/upload.xsd" xmlns:mstns="http://mydomain.com/ns/upload.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema" > <xs:simpleType name="stringtype"> <xs:restriction base="xs:string"/> </xs:simpleType> ... </xs:schema> |
"好的"XML应该是这样的:
1 2 3 4 5 6 | <?xml version="1.0" encoding="utf-8" ?> <shiporder orderid="889923" xmlns="http://mydomain.com/ns/upload.xsd"> <orderperson>John Smith</orderperson> <shipto> <names>Ola Nordmann</names> Langgt 23</address> |
我测试了"错误的XML格式"、"根据XSD无效的输入"、"不正确的命名空间"。
参考文献:
从MemoryStream读取
尝试避免异常处理检查是否正常
根据XSD验证,捕获错误
关于内联模式验证的有趣文章
嗨,马丁,评论对我的答案来说太短了,所以我会在这里给出答案,这可能是一个完整的答案,让我们一起改进一下:)
我做了以下测试:
- 测试:xmlns="blaa"
- 结果:由于名称空间错误,文件被拒绝。
- 测试:xmlns="http://mydomain.com/ns/upload.xsd"和xmlns:a="blaa",元素有"a:someElement"
- 结果:文件重新展开时出错,表示不需要"a:someElement"
- test:xmlns="http://mydomain.com/ns/upload.xsd"和xmlns:a="blaa",元素有"someelement",缺少某些必需的属性
- 结果:文件返回错误,说明属性丢失
所遵循的策略(我更喜欢)是,如果文档不符合,那么就不接受,但提供一些关于原因的信息(例如"错误的名称空间")。
这一策略似乎与你之前所说的相反:
however, if a customer misses out the namespace declaration in their submitted XML then I would like to say that we can still validate it. I don't want to just say"You messed up, now fix it!"
在这种情况下,您似乎可以忽略XML中定义的名称空间。为此,您将跳过正确命名空间的验证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | ... // Don't Check if the required namespace is present //if (doc.DocumentElement.NamespaceURI == xmlNs) { // Validate against xsd // will call Xml_ValidationEventHandler on each error found doc.Validate(xmlValidator); if (xmlErrors.Rows.Count == 0) { ret ="OK - is valid against our XSD"; } else { // return the complete error list, this is just to proove it works ret ="File has" + xmlErrors.Rows.Count +" xml errors"; ret +="when validated against our XSD."; } //} else { // ret ="The xml document has incorrect or no namespace."; //} ... |
其他想法…
在并行思路中,为了用自己的名称空间替换所提供的名称空间,也许可以设置
参考文献:
向根元素添加多个命名空间
我使用xmlschemavalidationflags.reportvalidationwarnings标志。否则,具有未知命名空间(或不具有命名空间)的XML将自动通过验证。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | public static void Validate(string xml, string schemaPath) { //oops: no ValidationFlag property, cant use linq //var d = XDocument.Parse(xml); //var sc = new XmlSchemaSet(); //sc.Add(null, schemaPath); //sc.CompilationSettings.EnableUpaCheck = false; //d.Validate(sc, null); XmlReaderSettings Xsettings = new XmlReaderSettings(); Xsettings.Schemas.Add(null, schemaPath); Xsettings.ValidationType = ValidationType.Schema; Xsettings.ValidationFlags |= XmlSchemaValidationFlags.ReportValidationWarnings; Xsettings.Schemas.CompilationSettings.EnableUpaCheck = false; Xsettings.ValidationEventHandler += new ValidationEventHandler(ValidationCallBack); XmlReader reader = XmlReader.Create(new StringReader(xml), Xsettings); while (reader.Read()) { } } private static void ValidationCallBack(object sender, ValidationEventArgs e) { if (e.Severity == XmlSeverityType.Warning) throw new Exception(string.Format("No validation occurred. {0}", e.Message)); else throw new Exception(string.Format("Validation error: {0}", e.Message)); } |
XSD模式背后的关键是它使非类型化的XML成为强类型化的XML。
XML类型可以定义为节点名和命名空间的组合。
如果有人向您发送没有名称空间的XML,那么尽管有意图,XML并没有引用由XSD模式定义的类型。
从XML验证的角度来看,只要