[英]Validate a specific element of an unvalidated XmlDocument
我有旧版 XML 文档,其中包含我想针对 XML 架构进行验证的嵌套(非根)元素。 模式本身并没有将 XML 文档作为一个整体来描述,而只是一个特定的嵌套元素。
XML 文档类似于从第三方系统收到的消息,没有xmlns
属性,甚至没有 XML 处理指令。 这是我无法影响的遗产。 例子:
<XM>
<MH> … nested header elements … </MH>
<MD>
<RECSET>
… payload elements go here …
</RECSET>
</MD>
</XM>
我的目标是针对 XSD 验证/XM/MD/RECSET
,该 Z0CD3581B75ED89C665CFZ 定义了RECSET
元素和嵌套在其中的任何有效负载元素。 我没有描述外部元素的模式,即XM
、 MH
、 MD
。 我可以修改所有现有模式并添加虚拟定义,例如允许xs:all
,但这不是首选。
验证是处理管道中的一个可选步骤,我想避免不必要地重复 XML 解析和其他增加执行时间的处理(吞吐量很重要)。
另一个限制是我想使用XmlDocument
,因为在处理管道中我需要一个XmlDocument
实例来使用XmlSerializer
将反序列化到 object model 中。 同样,这是我想要保留的现有解决方案。
我的尝试如下:
// build an XmlDocument instance as the intermediate format of the message
var xml = new XmlDocument();
xml.LoadXml(msg.TransportMessage);
// obtain a pre-cached XmlSchemaSet instance matching the message represented by XmlDocument
XmlSchemaSet schemaSet = … ;
// find the whole payload represented by the RECSET element
var nodeToValidate = xml.SelectSingleNode("/XM/MD/RECSET");
// attach schemas to the document and validate the payload node
xml.Schemas = xsd;
xml.Validate(ValidationCallback, nodeToValidate);
这会导致错误:
找不到传递给验证的节点的架构信息。 该节点可能在其当前 position 中无效。 导航到具有架构信息的祖先,然后再次调用 Validate。
我研究了XmlDocument
和DocumentSchemaValidator
class的实现,在特定节点验证的情况下,它会在 DOM 中搜索架构信息。 因此,我尝试将对正确模式的引用附加到节点 ad hoc:
XmlAttribute noNamespaceAttribute = xml.CreateAttribute("xsi:noNamespaceSchemaLocation", "http://www.w3.org/XMLSchema-instance");
foreach (XmlSchemaElement x in schemaSet.GlobalElements.Values)
{
if (x.Name == "RECSET")
{
noNamespaceAttribute.InnerText = x.SourceUri!;
break;
}
}
nodeToValidate.Attributes!.Append(noNamespaceAttribute);
但是,这会导致完全相同的错误消息。
实现这种验证的一种工作方法是获取nodeToValidate.OuterXml
并使用验证XmlReader
或新的XmlDocument
实例对其进行解析。 但是,这会导致 memory 和 CPU 方面的另一个开销。 我宁愿避开这条路线。
有没有办法告诉验证引擎根据明确指定的模式验证特定节点?
您的问题是XmlDocument.Schemas
旨在表示整个文档的架构:
当执行 XmlDocument 的 Validate 方法时,与 XmlDocument object 关联的 XmlSchemaSet object 中包含的架构用于验证。
在您的情况下,您没有整个文档的架构,因此当您尝试通过将XmlDocument.Schemas
设置为该子节点的架构来验证文档的特定节点时,验证失败,可能是因为验证代码无法导航通过根文档的架构(不存在)来查找要检查的子元素的特定子架构。
解决方法的选项取决于您在调用XmlDocument.Validate(ValidationEventHandler, XmlNode)
时要完成的工作。 如文档中所述,此方法实际上执行两个不同但相关的操作:
正如预期的那样,它根据 Schemas 属性中包含的模式验证 XmlNode object 中的 XML 数据。
它还执行信息集扩充:
Validate方法执行信息集扩充。 具体来说,在成功验证后,应用架构默认值,必要时将文本值转换为原子值,并将类型信息与经过验证的信息项相关联。 结果是XmlDocument中先前未键入的 XML 子树替换为已键入的子树。
行动 #1 似乎很清楚,但究竟什么是信息集扩充? 这没有明确记录,但一种效果是填充XmlNode.SchemaInfo
的内容。 For instance, using the XML and XSD from https://www.w3schools.com/xml/schema_example.asp as an example, if I validate the root element against the XSD and check the contents of DocumentElement.SchemaInfo
before and after as follows :
var nodeToValidate = xml.DocumentElement;
Console.WriteLine("DocumentElement.SchemaInfo before: {0}", new { nodeToValidate?.SchemaInfo.IsDefault, nodeToValidate?.SchemaInfo.IsNil, nodeToValidate?.SchemaInfo.MemberType, nodeToValidate?.SchemaInfo.SchemaAttribute, nodeToValidate?.SchemaInfo.SchemaElement, nodeToValidate?.SchemaInfo.SchemaType, nodeToValidate?.SchemaInfo.Validity });
// attach schemas to the document and validate the payload node
xml.Schemas = schemaSet;
xml.Validate(ValidationCallback, nodeToValidate);
Console.WriteLine("DocumentElement.SchemaInfo after: {0}", new { nodeToValidate?.SchemaInfo.IsDefault, nodeToValidate?.SchemaInfo.IsNil, nodeToValidate?.SchemaInfo.MemberType, nodeToValidate?.SchemaInfo.SchemaAttribute, nodeToValidate?.SchemaInfo.SchemaElement, nodeToValidate?.SchemaInfo.SchemaType, nodeToValidate?.SchemaInfo.Validity });
结果清楚地表明DocumentElement.SchemaInfo
已被填充。
DocumentElement.SchemaInfo before: { IsDefault = False, IsNil = False, MemberType = , SchemaAttribute = , SchemaElement = , SchemaType = , Validity = NotKnown }
DocumentElement.SchemaInfo after: { IsDefault = False, IsNil = False, MemberType = , SchemaAttribute = , SchemaElement = System.Xml.Schema.XmlSchemaElement, SchemaType = System.Xml.Schema.XmlSchemaComplexType, Validity = Valid }
演示小提琴#1在这里。
此外,似乎XmlDocument.Validate(ValidationEventHandler, XmlNode)
实际上可能会将其他节点插入到文档中,请参阅XmlDocument.NodeInserted 在 XmlDocument.Validate() 上触发的一个这样的示例。
但是您真的需要通过信息集扩充来修改您的XmlDocument
,还是只需要执行只读验证?
如果您不需要信息集扩充,您可以通过从中构造XmlNodeReader
来验证XmlNode
,然后使用阅读器进行只读验证。 首先介绍以下扩展方法:
public static class XmlNodeExtensions
{
public static void Validate(this XmlNode node, XmlReaderSettings settings)
{
if (node == null)
throw new ArgumentNullException(nameof(node));
using (var innerReader = new XmlNodeReader(node))
using (var reader = XmlReader.Create(innerReader, settings))
{
while (reader.Read())
;
}
}
public static void Validate(this XmlNode node, XmlSchemaSet schemaSet, XmlSchemaValidationFlags validationFlags, ValidationEventHandler validationEventHandler)
{
if (node == null)
throw new ArgumentNullException(nameof(node));
var settings = new XmlReaderSettings();
settings.ValidationType = ValidationType.Schema;
settings.ValidationFlags |= validationFlags;
if (validationEventHandler != null)
settings.ValidationEventHandler += validationEventHandler;
settings.Schemas = schemaSet;
node.Validate(settings);
}
}
现在您将能够:
XmlSchemaSet schemaSet = ...
var nodeToValidate = xml.SelectSingleNode("/XM/MD/RECSET");
nodeToValidate.Validate(schemaSet, default, ValidationCallback);
请注意,您永远不会使用这种方法设置XmlDocument.Schemas
。 演示小提琴#2在这里。
如果您确实需要扩充信息集,您将需要重新考虑您的方法,可能通过在运行时以编程方式为<XM><MD>...</MD></XM>
包装器元素生成一个合理的XmlSchema
并将其添加到XmlDocument.Schemas
验证前的XmlDocument.Schemas
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.