0

首先,我受限于 .NET 2.0,因此 LINQ 对我来说不是一个选择(尽管我很想看到一个 LINQ 解决方案作为推动项目迁移到 .NET 3.5 的素材,如果它很容易的话)。

我有一个 XSD,它在构建时通过 xsd.exe 转换为一组 C# 类。在运行时,一个 XML 文件被加载并反序列化到 C# 类中(此时发生验证)。然后,我需要将该内存配置对象(包括在导入 XML 文件期间填充的默认值)转换为键值对字典。

我希望字典键是值的点分隔路径。属性值和元素文本将被视为值,其他所有内容都是其中的关键。

例如,想象以下 XML 文件:

<rootNode>
    <foo enabled="true"/>
    <bar enabled="false" myAttribute="5.6">
        <baz>Some Text</baz>
        <baz>Some other text.</baz>
    </bar>
</rootNode>

将变成带有以下键的字典:

"rootNode.foo.enabled" = (Boolean)true
"rootNode.bar.enabled" = (Boolean)false
"rootNode.bar.myAttribute" = (Float)5.6
"rootNode.bar.baz" = List<String> { "Some Text", "Some other text." }

值得注意的是,rootNode 被忽略不是因为它很特殊,而是因为它没有文本或属性。此外,字典是正确键入的对象字典(这已经在反序列化中完成,这是我想使用 C# 对象而不是直接使用 XML 的原因之一)。

有趣的是,xsd.exe 创建的对象已经非常接近我想要的形式。类名类似于 rootNodeFoo,上面有一个名为 myAttribute 的浮点字段。

我考虑过但不确定如何进行的一件事是使用反射来迭代对象树并使用每个对象的类的名称来确定节点的名称(我可能需要调整大小写一点点)。问题在于,它感觉像是一个错误的解决方案,因为我已经可以访问一个反序列化器,它应该能够为我完成所有这些工作,而且速度要快得多。

另一种选择是使用 XSLT 将数据直接序列化为我想要的格式。这里的问题是我的 XSLT 知识是有限的,我相信(如果我错了,请纠正我)我会在路上输掉打字(一切都将是一个字符串)所以我将不得不再次手动反序列化以获得类型退出(这次没有使用 .NET 反序列化程序时得到的 XSD 验证)。

万一这很重要,我用来从 XML 文件中获取配置对象的调用是这样的:

var rootNode = new XmlRootAttribute();
rootNode.ElementName = "rootNode";
rootNode.Namespace = "urn:myNamespace";
var serializer = new XmlSerializer(typeof(rootNode), rootNode);
using (var reader = new StringReader(xmlString))
{
    var deserializedObject = (rootNode)serializer.Deserialize(reader);
}
4

2 回答 2

2

第一个观察:使用对象图并不是开始生成点表示的最佳位置。您正在谈论具有名称且处于明确定义的层次结构中的节点,并且您希望从中生成某种点符号;xml DOM 似乎是执行此操作的最佳位置。

您描述问题的方式存在一些问题。

第一个是在处理多个同名元素时的策略。通过使该字典值实际上是一个列表,您已经避开了示例中的问题,但假设您的 xml 看起来像这样:

<rootNode>
    <foo enabled="true">
        <bar enabled="false" myAttribute="5.6" />
        <bar enabled="true" myAttribute="3.4" />
    </foo>
</rootNode>

除了foo.enabled = (Boolean)true应该相当明显之外,你为两myAttribute片叶子建议什么字典键?或者你会有一个条目,foo.bar.myAttribute = List<float> {5.6, 3.4}?所以,问题 #1,没有明确的方法来处理多个类似命名的非叶节点。

第二个问题是选择数据类型以在叶节点(即属性或元素值)进行最终转换。如果您正在写入 a Dictionary<string, object>,您可能希望根据正在读取的元素/属性的 Schema 简单类型来选择一种类型。我不知道该怎么做,但建议查找System.Convert该类的各种用途。

假设暂时不会出现问题 #1,并且您对Dictionary<string, string>实现没问题,这里有一些代码可以帮助您入门:

static void Main(string[] args)
{
    var xml = @"
<rootNode>
    <foo enabled=""true"">
         <bar enabled=""false"" myAttribute=""5.6"" />
         <baz>Text!</baz>
    </foo>
</rootNode>
";

    var document = new XmlDocument();
    document.LoadXml(xml);
    var retVal = new Dictionary<string, string>();
    Go(retVal, document.DocumentElement, new List<string>());
}

private static void Go(Dictionary<string, string> theDict, XmlElement start, List<string> keyTokens)
{
    // Process simple content
    var textNode = start.ChildNodes.OfType<XmlText>().SingleOrDefault();
    if (textNode != null)
    {
        theDict[string.Join(".", keyTokens.ToArray())] = textNode.Value;
    }

    // Process attributes
    foreach (XmlAttribute att in start.Attributes)
    {
        theDict[string.Join(".", keyTokens.ToArray()) + "." + att.Name] = att.Value;
    }

    // Process child nodes
    foreach (var childNode in start.ChildNodes.OfType<XmlElement>())
    {
        Go(theDict, childNode, new List<string>(keyTokens) { childNode.Name });   // shorthand for .Add
    }
}

结果如下:

运行示例代码的结果

于 2013-10-01T23:05:53.523 回答
0

一种方法是实现客户格式化程序并将其插入标准序列化模式,创建一个实现 IFormatter 的类,即 MyDotFormatter

http://msdn.microsoft.com/en-us/library/system.runtime.serialization.iformatter.aspx

然后执行如下

Stream stream = File.Open(filename, FileMode.Create);
MyDotFormatter dotFormatter = new MyDotFormatter();
Console.WriteLine("Writing Object Information");
try
{
dotFormatter.Serialize(stream, objectToSerialize);
}
catch (SerializationException ex)
{
Console.WriteLine("Exception for Serialization data : " + ex.Message);
throw;
}
finally
{
stream.Close();
Console.WriteLine("successfully wrote object information");
}
于 2013-10-01T13:30:51.557 回答