序列化列表<string>并将每个字符串用作xml节点</string>

时间:2010-07-16 01:23:09

标签: c# linq dynamic linq-to-xml reflection.emit

我遇到了一个问题,想知道是否有简单的解决方法。

这里我有一个XML模板,定义了一些属性及其值。

<Properties>
  <Property name="ID">10000</Property>
  <Property name="Name">
    <SubProperty name="FirstName">Foo</SubProperty>
    <SubProperty name="LastName">Bar</SubProperty >
  </Property>
</Properties>

我只需提取模板中定义的Properties / subProperties来生成一个新的XML文件,并附上所有值,如

<Items>
  <ID>10000</ID>
  <Name>
    <FirstName>Foo</FirstName>
    <LastName>Bar</LastName>
  </Name>
</Items>

由于我在设计时不知道模板的内容,我尝试加载它并使用LINQ创建了List类,但在直接序列化时无法得到上面的结果。因此,我不是创建一个List类,而是使用Reflection.Emit创建一个动态对象,然后将该对象序列化为XML。


private static readonly XDocument doc = XDocument.Load("Template.xml");

static void Main(string[] args) {
    var newType = CreateDynamicType();
    var newObject = Activator.CreateInstance(newType);
    var properties = newType.GetProperties();
    foreach (var property in properties) {
        // assign values
    }
    SerializeToXml(newObject);
}

private static Type CreateDynamicType() {

    AssemblyName assemblyName = new AssemblyName() { Name = "DynamicTypeAdapter" };
    AssemblyBuilder assembly = 
        Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
    ModuleBuilder module = 
        assembly.DefineDynamicModule(assembly.GetName().Name, false);
    TypeBuilder type = module.DefineType("Items", TypeAttributes.Public | TypeAttributes.Class);

    foreach (var p in doc.Descendants("Property")) {
        string pName = p.Attribute("name").Value;
        TypeBuilder subType = module.DefineType(pName, TypeAttributes.Public | TypeAttributes.Class);
        foreach (var sp in p.Descendants("SubProperty")) {
            CreateDynamicProperty(subType, sp.Attribute("name").Value, typeof(string));
        }
        var propertyType = subType.CreateType();
        CreateDynamicProperty(type, pName, propertyType);
    }

    return type.CreateType();
}

private static void CreateDynamicProperty(TypeBuilder typeBuilder, string propertyName, Type propertyType) {
    PropertyBuilder property = typeBuilder.DefineProperty(propertyName,
    PropertyAttributes.None, propertyType, new Type[] { typeof(string) });

    FieldBuilder field = typeBuilder.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);

    MethodAttributes GetSetAttributes = MethodAttributes.Public | MethodAttributes.HideBySig;

    MethodBuilder getMethod =
        typeBuilder.DefineMethod("get_value", GetSetAttributes, propertyType, Type.EmptyTypes);

    ILGenerator getIL = getMethod.GetILGenerator();
    getIL.Emit(OpCodes.Ldarg_0);
    getIL.Emit(OpCodes.Ldfld, field);
    getIL.Emit(OpCodes.Ret);

    MethodBuilder setMethod =
            typeBuilder.DefineMethod("set_value", GetSetAttributes, null, new Type[] { typeof(string) });

    ILGenerator setIL = setMethod.GetILGenerator();
    setIL.Emit(OpCodes.Ldarg_0);
    setIL.Emit(OpCodes.Ldarg_1);
    setIL.Emit(OpCodes.Stfld, field);
    setIL.Emit(OpCodes.Ret);

    property.SetGetMethod(getMethod);
    property.SetSetMethod(setMethod);
}

它工作正常,但有没有简单的方法这样做? 任何评论表示赞赏。感谢

2 个答案:

答案 0 :(得分:2)

如果您只想将一种XML格式更改(转换)为另一种XML格式,那么我认为您采用的方法并不是最合适的。框架中还有其他API支持这种功能。在您的情况下,要求似乎相当简单,所以我会选择Linq To Xml选项。以下是一个快速示例,它可以生成所需的输出。

XDocument doc = XDocument.Parse(@"<Properties>
                <Property name='ID'>10000</Property>
                <Property name='Name'>
                    <SubProperty name='FirstName'>Foo</SubProperty>
                    <SubProperty name='LastName'>Bar</SubProperty>
                </Property>
            </Properties>");

XElement items = new XElement("Items", 
                               from property in doc.Descendants("Property")
                               select new XElement((string)property.Attribute("name"),
                                                    // If there are no child elements (SubPropety)
                                                    // get the property value
                                                    property.HasElements ? null : (string)property,
                                                    // Another way for checking if there are any child elements
                                                    // You could also use property.HasElements like the previous statement
                                                    property.Elements("SubProperty").Any() ?
                                                    from subproperty in property.Elements("SubProperty")
                                                    select new XElement((string)subproperty.Attribute("name"),
                                                                        (string)subproperty) : null)

                          );

一些可能有帮助的资源包括:

http://msdn.microsoft.com/en-us/library/bb387098.aspx

http://msdn.microsoft.com/en-us/library/bb308960.aspx

http://iqueryable.com/2007/08/03/TransformingXMLWithLINQToXML.aspx

http://www.codeproject.com/KB/linq/LINQtoXML.aspx

答案 1 :(得分:1)

根据你的上述内容,这样简单的事情应该有效。

var root = XElement.Parse(xml);
var result = new XElement("Items");
foreach (var p in root.Descendants("Property")) 
{
 var subs =  p.Descendants("SubProperty").Select( sp => Transpose(sp) );

    // The trick is here - XElement constructor uses params object[], 
    // so we can pass an arbitrary number of arguments to build the XElement
 var item = new XElement( p.Attribute("name").Value, subs, subs.Any() : null ? p.Value );

    result.Add( item );
}

// Transpose method
XElement Transpose(XElement xe)
{
 return new XElement( xe.Attribute("name").Value, xe.Value );
}


// result
<Items>
  <ID>10000</ID>
  <Name>
    <FirstName>Foo</FirstName>
    <LastName>Bar</LastName>
  </Name>
</Items>

注意:如果您有多个嵌套级别,或者需要能够区分模板中的多个Item节点,则需要对其进行更多考虑。