我如何根据生成的 .Net class 或原始 XSD 中的 element/property 名称从 xml 文件中将值提取为字符串?
How can I extract values as strings from an xml file based on the element/property name in a generated .Net class or the original XSD?
我有一个大的复杂 XSD 集合。
我使用 xsd.exe 从那些 XSD 生成了 C# classes。当然,虽然生成的 classes 中的大多数属性是 string
s,但许多是 decimal
s、DateTime
s、enum
s 或 bool
s,就像他们应该的那样。
现在,我有一些以正确的 XML 格式构建的未验证数据,但很可能无法通过 XSD 验证,更不用说放入相关的实例中了.Net 对象。例如,在这个阶段,据我们所知,应该是 DateTime 的元素的值可能是 "ABC" - 甚至不能解析为 DateTime - 更不用说其他符合 maxLength 或正则表达式模式限制的字符串元素了。此数据已准备好传递到规则引擎,我们已经必须使所有内容有效,包括根据其他数据项适当设置默认值等。
我知道如何使用 System.Xml
中的各种类型按名称读取给定元素的字符串值。很明显,我可以手工编写代码以按名称找出今天存在的所有元素——但如果 XSD 发生变化,则需要重新编写代码。我希望能够直接读取 XSD 或在生成的 classes 上使用反射(必要时包括 [System.Xml.Serialization.XmlTypeAttribute(TypeName=...]
之类的属性)以准确找到如何递归查询 XML 将任何给定元素的原始、未经验证的字符串版本传递给规则集,然后在规则使其有效后,将其放回强类型对象或放回副本XML 用于序列化到对象中。
(我想到另一种方法是以某种方式自动生成对象的“stringly typed”版本 - 其中没有 DateTimes 等;只有字符串 - 并序列化 xml 到那个。我什至疯狂地想把 xsd.exe 生成的 .cs 文件和 search/replacing 所有不是字符串的枚举和基本类型转换为字符串,但必须有更好的方式。)
换句话说,是否有现有的通用方法从某些 XML 中提取 XElement 或属性值, 将 对应于 .Net 中的给定项目class如果它被序列化,没有实际序列化它?
很抱歉自己回答,也很抱歉我的回答中缺少实际代码,但我还没有得到我雇主的许可来分享这方面的实际代码。正在制作中,有动静会在这里更新
我能够实现我称之为 Tolerant XML Reader 的东西。与大多数 XML 反序列化不同,它首先使用反射来查看所需 .Net 类型的结构,然后 然后 尝试找到相关的 XElements 并解释它们。任何额外的元素都将被忽略(因为它们从未被查找过),任何未找到的元素都是默认的,任何找到的元素都会被进一步解释。
C#中的main方法签名如下:
public static T TolerantDeserializeIntoType<T>(
XDocument doc,
out List<string> messagesList,
out bool isFromSuppliedData,
XmlSchemaSet schemas = null,
bool tolerant = true)
对它的典型调用可能如下所示:
List<string> messagesList;
bool defaultOnly;
SomeType result = TolerantDeserializeIntoType<SomeType>(someXDocument, out messagesList, out defaultOnly);
(您可以使用 var;在此示例中,为了清楚起见,我只是明确地将类型放在那里)。
这将采用 any XDocument(所以原始的唯一标准是它的格式正确),并创建指定类型的实例(SomeType,在这个例如)从它。
请注意,即使 XML 中的任何内容都没有被识别,它仍然不会失败。新实例将简单地使所有属性/public 字段为空或默认,MessageList 将列出所有已完成的默认设置,布尔输出参数将为 FALSE。
完成所有工作的递归方法具有相似的签名,只是它采用 XElement 而不是 XDocument,并且不采用 schemaSet。 (目前的实现还有一个明确的 bool 来指示默认为 false 的递归调用。这是一种稍微脏的方式,允许它在抛出错误之前收集所有失败消息,如果 tolerant 为 false;在未来的版本中我将重构它以仅公开 publicly 一个没有它的版本,如果我什至想要制作 XElement 版本 public):
public static T TolerantDeserializeXElementIntoType<T>(
ref XElement element,
ref List<string> messagesList,
out bool isFromSuppliedValue,
bool tolerant = true,
bool recursiveCall = false)
工作原理,详情
从主调用开始,带有 XDocument 和可选 SchemaSet 的调用:
如果提供了一个将编译的模式集(实际上,它也寻找 xsi:noNamespaceSchemaLocation)初始 XDocument 和 schemaSet 调用在提供的 XDocument 中运行标准 XDocument.Validate(),但这只收集任何发出的验证错误回调。它不会抛出异常,并且只有两个原因:
- 它将为 MessageList 提供一些有用的消息,并且
它将填充所有 XElements 的 SchemaInfo 到
可能稍后在 XElement 版本中使用。
(但是请注意,
模式完全是可选的。它实际上只是用来解决
C# 可能不清楚的一些模棱两可的情况
对象,如果给定的 XElement 是必需的或不是。)
从那里开始,在根节点和提供的 C# 类型上调用递归 XElement 版本。
我已经让代码寻找由 xsd.exe 生成的 C# 对象的样式,尽管即使没有 xsd.exe 提供的 CustomAttributes,使用属性和字段的大多数基本结构化对象也可能工作,如果 Xml 元素的名称与对象的属性和字段相同。
该方法查找:
- 数组
- 简单值类型,明确:
- 字符串
- 枚举
- 布尔值
- 然后随便
否则通过使用相关的 TryParse() 方法,通过反射找到。
- (注意 nulls/xsi:nill='true' 值也必须特别
处理)
- 对象,递归。
它还在对象中为每个字段查找布尔值 'xxxSpecified' 或它找到的 属性 'xxx',并适当地设置它。这就是 xsd.exe 在 null 不够的情况下指示从 XML 中省略的元素的方式。
这是大纲。正如我所说,我可能会在适当的时候将实际代码放在 GitHub 之类的地方。希望这对某人有帮助。
我有一个大的复杂 XSD 集合。
我使用 xsd.exe 从那些 XSD 生成了 C# classes。当然,虽然生成的 classes 中的大多数属性是 string
s,但许多是 decimal
s、DateTime
s、enum
s 或 bool
s,就像他们应该的那样。
现在,我有一些以正确的 XML 格式构建的未验证数据,但很可能无法通过 XSD 验证,更不用说放入相关的实例中了.Net 对象。例如,在这个阶段,据我们所知,应该是 DateTime 的元素的值可能是 "ABC" - 甚至不能解析为 DateTime - 更不用说其他符合 maxLength 或正则表达式模式限制的字符串元素了。此数据已准备好传递到规则引擎,我们已经必须使所有内容有效,包括根据其他数据项适当设置默认值等。
我知道如何使用 System.Xml
中的各种类型按名称读取给定元素的字符串值。很明显,我可以手工编写代码以按名称找出今天存在的所有元素——但如果 XSD 发生变化,则需要重新编写代码。我希望能够直接读取 XSD 或在生成的 classes 上使用反射(必要时包括 [System.Xml.Serialization.XmlTypeAttribute(TypeName=...]
之类的属性)以准确找到如何递归查询 XML 将任何给定元素的原始、未经验证的字符串版本传递给规则集,然后在规则使其有效后,将其放回强类型对象或放回副本XML 用于序列化到对象中。
(我想到另一种方法是以某种方式自动生成对象的“stringly typed”版本 - 其中没有 DateTimes 等;只有字符串 - 并序列化 xml 到那个。我什至疯狂地想把 xsd.exe 生成的 .cs 文件和 search/replacing 所有不是字符串的枚举和基本类型转换为字符串,但必须有更好的方式。)
换句话说,是否有现有的通用方法从某些 XML 中提取 XElement 或属性值, 将 对应于 .Net 中的给定项目class如果它被序列化,没有实际序列化它?
很抱歉自己回答,也很抱歉我的回答中缺少实际代码,但我还没有得到我雇主的许可来分享这方面的实际代码。正在制作中,有动静会在这里更新
我能够实现我称之为 Tolerant XML Reader 的东西。与大多数 XML 反序列化不同,它首先使用反射来查看所需 .Net 类型的结构,然后 然后 尝试找到相关的 XElements 并解释它们。任何额外的元素都将被忽略(因为它们从未被查找过),任何未找到的元素都是默认的,任何找到的元素都会被进一步解释。
C#中的main方法签名如下:
public static T TolerantDeserializeIntoType<T>(
XDocument doc,
out List<string> messagesList,
out bool isFromSuppliedData,
XmlSchemaSet schemas = null,
bool tolerant = true)
对它的典型调用可能如下所示:
List<string> messagesList;
bool defaultOnly;
SomeType result = TolerantDeserializeIntoType<SomeType>(someXDocument, out messagesList, out defaultOnly);
(您可以使用 var;在此示例中,为了清楚起见,我只是明确地将类型放在那里)。
这将采用 any XDocument(所以原始的唯一标准是它的格式正确),并创建指定类型的实例(SomeType,在这个例如)从它。
请注意,即使 XML 中的任何内容都没有被识别,它仍然不会失败。新实例将简单地使所有属性/public 字段为空或默认,MessageList 将列出所有已完成的默认设置,布尔输出参数将为 FALSE。
完成所有工作的递归方法具有相似的签名,只是它采用 XElement 而不是 XDocument,并且不采用 schemaSet。 (目前的实现还有一个明确的 bool 来指示默认为 false 的递归调用。这是一种稍微脏的方式,允许它在抛出错误之前收集所有失败消息,如果 tolerant 为 false;在未来的版本中我将重构它以仅公开 publicly 一个没有它的版本,如果我什至想要制作 XElement 版本 public):
public static T TolerantDeserializeXElementIntoType<T>(
ref XElement element,
ref List<string> messagesList,
out bool isFromSuppliedValue,
bool tolerant = true,
bool recursiveCall = false)
工作原理,详情
从主调用开始,带有 XDocument 和可选 SchemaSet 的调用:
如果提供了一个将编译的模式集(实际上,它也寻找 xsi:noNamespaceSchemaLocation)初始 XDocument 和 schemaSet 调用在提供的 XDocument 中运行标准 XDocument.Validate(),但这只收集任何发出的验证错误回调。它不会抛出异常,并且只有两个原因:
- 它将为 MessageList 提供一些有用的消息,并且
它将填充所有 XElements 的 SchemaInfo 到 可能稍后在 XElement 版本中使用。
(但是请注意, 模式完全是可选的。它实际上只是用来解决 C# 可能不清楚的一些模棱两可的情况 对象,如果给定的 XElement 是必需的或不是。)
从那里开始,在根节点和提供的 C# 类型上调用递归 XElement 版本。
我已经让代码寻找由 xsd.exe 生成的 C# 对象的样式,尽管即使没有 xsd.exe 提供的 CustomAttributes,使用属性和字段的大多数基本结构化对象也可能工作,如果 Xml 元素的名称与对象的属性和字段相同。
该方法查找:
- 数组
- 简单值类型,明确:
- 字符串
- 枚举
- 布尔值
- 然后随便 否则通过使用相关的 TryParse() 方法,通过反射找到。
- (注意 nulls/xsi:nill='true' 值也必须特别 处理)
- 对象,递归。
它还在对象中为每个字段查找布尔值 'xxxSpecified' 或它找到的 属性 'xxx',并适当地设置它。这就是 xsd.exe 在 null 不够的情况下指示从 XML 中省略的元素的方式。
这是大纲。正如我所说,我可能会在适当的时候将实际代码放在 GitHub 之类的地方。希望这对某人有帮助。