web-dev-qa-db-ja.com

XmlSerializerを使用するときにXMLファイルにコメントを書き込む方法は?

XMLストリームにシリアル化するオブジェクトFooがあります。

_public class Foo {
  // The application version, NOT the file version!
  public string Version {get;set;}
  public string Name {get;set;}
}

Foo foo = new Foo { Version = "1.0", Name = "Bar" };
XmlSerializer xmlSerializer = new XmlSerializer(foo.GetType());
_

これは高速で簡単に機能し、現在必要なすべてを実行します。

私が抱えている問題は、いくつかのマイナーなコメントを含む別のドキュメントファイルを維持する必要があるということです。上記の例のように、Nameは明らかですが、Versionはアプリケーションのバージョンであり、この場合に予想されるデータファイルのバージョンではありません。そして、コメントで明確にしたい、似たような小さなことがたくさんあります。

WriteComment()関数を使用してXMLファイルを手動で作成すればこれを実行できることはわかっていますが、シリアライザー機能を引き続き使用できるように実装できる属性または代替構文はありますか?

22
Jensen

デフォルトのインフラストラクチャを使用することはできません。目的に応じてIXmlSerializableを実装する必要があります。

非常に単純な実装:

public class Foo : IXmlSerializable
{
    [XmlComment(Value = "The application version, NOT the file version!")]
    public string Version { get; set; }
    public string Name { get; set; }


    public void WriteXml(XmlWriter writer)
    {
        var properties = GetType().GetProperties();

        foreach (var propertyInfo in properties)
        {
            if (propertyInfo.IsDefined(typeof(XmlCommentAttribute), false))
            {
                writer.WriteComment(
                    propertyInfo.GetCustomAttributes(typeof(XmlCommentAttribute), false)
                        .Cast<XmlCommentAttribute>().Single().Value);
            }

            writer.WriteElementString(propertyInfo.Name, propertyInfo.GetValue(this, null).ToString());
        }
    }
    public XmlSchema GetSchema()
    {
        throw new NotImplementedException();
    }

    public void ReadXml(XmlReader reader)
    {
        throw new NotImplementedException();
    }
}

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class XmlCommentAttribute : Attribute
{
    public string Value { get; set; }
}

出力:

<?xml version="1.0" encoding="utf-16"?>
<Foo>
  <!--The application version, NOT the file version!-->
  <Version>1.2</Version>
  <Name>A</Name>
</Foo>

別の方法、おそらく望ましい:デフォルトのシリアライザーでシリアル化してから、後処理を実行します。つまり、XMLを更新します。 XDocumentまたはXmlDocumentを使用します。

14

これは、デフォルトのインフラストラクチャを使用して、タイプ XmlComment のオブジェクトを返すプロパティを利用し、それらのプロパティを [XmlAnyElement("SomeUniquePropertyName")] でマークすることで可能になります。 。

つまり次のようにFooにプロパティを追加した場合:

_public class Foo
{
    [XmlAnyElement("VersionComment")]
    public XmlComment VersionComment { get { return new XmlDocument().CreateComment("The application version, NOT the file version!"); } set { } }

    public string Version { get; set; }
    public string Name { get; set; }
}
_

次のXMLが生成されます。

_<Foo>
  <!--The application version, NOT the file version!-->
  <Version>1.0</Version>
  <Name>Bar</Name>
</Foo>
_

ただし、問題はこれ以上のもの、つまりドキュメントシステムでコメントを検索する方法を求めていることです。以下は、拡張メソッドを使用して、反映されたコメントプロパティ名に基づいてドキュメントを検索することにより、これを実現します。

_public class Foo
{
    [XmlAnyElement("VersionXmlComment")]
    public XmlComment VersionXmlComment { get { return GetType().GetXmlComment(); } set { } }

    [XmlComment("The application version, NOT the file version!")]
    public string Version { get; set; }

    [XmlAnyElement("NameXmlComment")]
    public XmlComment NameXmlComment { get { return GetType().GetXmlComment(); } set { } }

    [XmlComment("The application name, NOT the file name!")]
    public string Name { get; set; }
}

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class XmlCommentAttribute : Attribute
{
    public XmlCommentAttribute(string value)
    {
        this.Value = value;
    }

    public string Value { get; set; }
}

public static class XmlCommentExtensions
{
    const string XmlCommentPropertyPostfix = "XmlComment";

    static XmlCommentAttribute GetXmlCommentAttribute(this Type type, string memberName)
    {
        var member = type.GetProperty(memberName);
        if (member == null)
            return null;
        var attr = member.GetCustomAttribute<XmlCommentAttribute>();
        return attr;
    }

    public static XmlComment GetXmlComment(this Type type, [CallerMemberName] string memberName = "")
    {
        var attr = GetXmlCommentAttribute(type, memberName);
        if (attr == null)
        {
            if (memberName.EndsWith(XmlCommentPropertyPostfix))
                attr = GetXmlCommentAttribute(type, memberName.Substring(0, memberName.Length - XmlCommentPropertyPostfix.Length));
        }
        if (attr == null || string.IsNullOrEmpty(attr.Value))
            return null;
        return new XmlDocument().CreateComment(attr.Value);
    }
}
_

次のXMLが生成されます。

_<Foo>
  <!--The application version, NOT the file version!-->
  <Version>1.0</Version>
  <!--The application name, NOT the file name!-->
  <Name>Bar</Name>
</Foo>
_

ノート:

  • 拡張メソッドXmlCommentExtensions.GetXmlCommentAttribute(this Type type, string memberName)は、コメントプロパティの名前がxxxXmlCommentであると想定しています。ここで、xxxは「実際の」プロパティです。その場合、着信memberName属性に CallerMemberNameAttribute のマークを付けることで、不動産名を自動的に判別できます。これは、本名を渡すことで手動で上書きできます。

  • タイプとメンバー名がわかると、拡張メソッドは、プロパティに適用されている_[XmlComment]_属性を検索して、関連するコメントを検索します。これは、別のドキュメントファイルへのキャッシュルックアップに置き換えることができます。

  • コメントされる可能性のあるプロパティごとにxxxXmlCommentプロパティを追加する必要がありますが、これは IXmlSerializableを直接実装する よりも負担が少ない可能性があります。これは非常に注意が必要です。 、逆シリアル化のバグにつながる可能性があり、複雑な子プロパティのネストされたシリアル化が必要になる可能性があります。

  • 各コメントが関連する要素の前にあることを確認するには、 C#でのシリアル化の順序の制御 を参照してください。

  • XmlSerializerがプロパティをシリアル化するには、ゲッターとセッターの両方が必要です。したがって、私は何もしないコメントプロパティセッターを与えました。

動作中 。Netフィドル

19
dbc

シリアル化後のxmlの最後にコメントを追加します(魔法はxmlWriterをフラッシュすることです)。

byte[] buffer;

XmlSerializer serializer = new XmlSerializer(result.GetType());

var settings = new XmlWriterSettings() { Encoding = Encoding.UTF8 };

using (MemoryStream memoryStream = new MemoryStream())
{
    using (XmlWriter xmlWriter = XmlWriter.Create(memoryStream, settings))
    {
        serializer.Serialize(xmlWriter, result);

        xmlWriter.WriteComment("test");

        xmlWriter.Flush();

        buffer = memoryStream.ToArray();
    }
}
0
user3212442

おそらくパーティーに遅れましたが、KirillPolishchukソリューションを使用して逆シリアル化しようとしたときに問題が発生しました。最後に、XMLをシリアル化した後で編集することにしました。ソリューションは次のようになります。

public static void WriteXml(object objectToSerialize, string path)
{
    try
    {
        using (var w = new XmlTextWriter(path, null))
        {
            w.Formatting = Formatting.Indented;
            var serializer = new XmlSerializer(objectToSerialize.GetType());
            serializer.Serialize(w, objectToSerialize);
        }

        WriteComments(objectToSerialize, path);
    }
    catch (Exception e)
    {
        throw new Exception($"Could not save xml to path {path}. Details: {e}");
    }
}

public static T ReadXml<T>(string path) where T:class, new()
{
    if (!File.Exists(path))
        return null;
    try
    {
        using (TextReader r = new StreamReader(path))
        {
            var deserializer = new XmlSerializer(typeof(T));
            var structure = (T)deserializer.Deserialize(r);
            return structure;
        }
    }
    catch (Exception e)
    {
        throw new Exception($"Could not open and read file from path {path}. Details: {e}");
    }
}

private static void WriteComments(object objectToSerialize, string path)
{
    try
    {
        var propertyComments = GetPropertiesAndComments(objectToSerialize);
        if (!propertyComments.Any()) return;

        var doc = new XmlDocument();
        doc.Load(path);

        var parent = doc.SelectSingleNode(objectToSerialize.GetType().Name);
        if (parent == null) return;

        var childNodes = parent.ChildNodes.Cast<XmlNode>().Where(n => propertyComments.ContainsKey(n.Name));
        foreach (var child in childNodes)
        {
            parent.InsertBefore(doc.CreateComment(propertyComments[child.Name]), child);
        }

        doc.Save(path);
    }
    catch (Exception)
    {
        // ignored
    }
}

private static Dictionary<string, string> GetPropertiesAndComments(object objectToSerialize)
{
    var propertyComments = objectToSerialize.GetType().GetProperties()
        .Where(p => p.GetCustomAttributes(typeof(XmlCommentAttribute), false).Any())
        .Select(v => new
        {
            v.Name,
            ((XmlCommentAttribute) v.GetCustomAttributes(typeof(XmlCommentAttribute), false)[0]).Value
        })
        .ToDictionary(t => t.Name, t => t.Value);
    return propertyComments;
}

[AttributeUsage(AttributeTargets.Property)]
public class XmlCommentAttribute : Attribute
{
    public string Value { get; set; }
}
0
Gabriel_ES