Сериализация списка <string>и использование каждой строки в качестве узла xml - PullRequest
1 голос
/ 16 июля 2010

Я столкнулся с проблемой и подумал, есть ли простой способ ее решения.

Здесь у меня есть шаблон XML, определяющий некоторые свойства и их значения.

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

Все, что нужно для извлечения свойств / вложенных свойств, определенных в шаблоне, для создания нового XML-файла со всеми присоединенными значениями, что-то вроде

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

Поскольку я не знаю содержимого шаблона во время разработки, я попытался загрузить его и создал класс List с использованием LINQ, но не могу получить приведенный выше результат при его непосредственной сериализации. Поэтому вместо создания класса 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 ]

2 голосов
/ 16 июля 2010

Если все, что вы хотите сделать, изменить (преобразовать) один 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 голос
/ 16 июля 2010

Исходя из того, что у вас есть выше, должно работать что-то простое.

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 узлов в шаблоне, вам нужно подумать об этом еще немного.

...