XmlSerializer: сериализация свойства класса как атрибута пользовательского подэлемента - PullRequest
5 голосов
/ 06 февраля 2012

Я использую XmlSerializer.Мой класс:

[Serializable]
[XmlRoot(ElementName="MyClass")]
public class MyClass
{
    public string Value;
}

Я хотел бы сериализовать его так, чтобы Value заканчивался как атрибут подэлемента с именем (например) "Текст".

Желаемыйрезультат:

<MyClass>
    <Text Value="3"/>
</MyClass>

Но НЕ (что будет результатом маркировки Value как XmlAttribute)

<MyClass Value="3">
</MyClass>

И НЕ (что будет означать пометку Value как XmlElement):

<MyClass>
    <Value>3</Value>
</MyClass>

Как мне этого добиться?

Я знаю, что могуизмените тип Value со строки на другой сериализуемый пользовательский класс.

К сожалению, у меня много таких свойств, поэтому мне нужно создать десятки крошечных классов.

Есть ли более быстрое решение?


РЕДАКТИРОВАТЬ:

В ответ на ваши комментарии:

  • Нет, не все свойства должны быть сериализованысубэлементу с именем «Текст».Имя Subelement уникально и однозначно.

  • Пример выходного XML:

    <visibility>
        <site visible="yes"/>
        <comparator visible="no"/>
        <expiration days="7"/>
        <comment>blahblahblah</comment>
    <visibility>
    
  • Пример класса:

!

[XmlRoot(ElementName="Visibility")]
public class Visibility
{
    [XPath("/site@visible")] // if only this was possible!
    public string OnSite
    {
        get { return SiteVisible ? "yes" : "no"; }
    }

    [XPath("/comparator@visible")] // as above...
    public string InComparator
    {
        get { return ComparatorVisible ? "yes" : "no"; }
    }

    [XmlIgnore]
    public bool SiteVisible;
    [XmlIgnore]
    public bool ComparatorVisible;

    [XPath("/expiration@days")] // as above...
    public int ExpiresAfterDays; 

    [XmlElement("comment")] // this is easy
    public string Comment;
}

Ответы [ 3 ]

4 голосов
/ 06 февраля 2012

Для такого рода гибкости вы должны подумать о реализации IXmlSerializable, так как это дает вам гораздо больший контроль:

[XmlRoot("visibility")]
public class Visibility : IXmlSerializable
{
    public string Site;
    public string Comparator;
    public int Expiration;
    public string Comment;

    public XmlSchema GetSchema()
    {
        throw new NotImplementedException();
    }

    public void ReadXml(XmlReader reader)
    {
        // implement me if you want to deserialize too.
        throw new NotImplementedException();
    }

    public void WriteXml(XmlWriter writer)
    {
        WriteProperty(writer, "site", "visible", Site);
        WriteProperty(writer, "comparator ", "visible", Comparator);
        WriteProperty(writer, "expiration ", "days", Expiration);

        if (!string.IsNullOrEmpty(Comment))
        {
            writer.WriteElementString("comment", Comment);
        }
    }

    private void WriteProperty<T>(XmlWriter writer, string elementName, string attibuteName, T value)
    {
        if (value != null)
        {
            writer.WriteStartElement(elementName);
            writer.WriteAttributeString(attibuteName, value.ToString());
            writer.WriteEndElement();
        }
    }
}

Очевидно, что здесь есть немного ручной работы, но она позволяет вам хранить весь код сериализации в одном месте, вместо того, чтобы распространять меньшие классы.

В приведенном выше примере реализована только сериализация - вам нужно написать эквивалентную реализацию десериализации, если вам нужно десериализовать из xml в ваш тип.

4 голосов
/ 06 февраля 2012

Без изменения типа Value Я думаю, что это невозможно. Вы можете добавить атрибут XmlElement(ElementName="Text") на Value, но вы получите результат, подобный следующему:

<MyClass> 
    <Text>3</Text> 
</MyClass> 

Отредактировано: Другое решение может включать преобразование XSLT: вы можете сгенерировать XML с использованием сериализации .Net и после применения преобразования XML.

XslTransform myXslTransform = new XslTransform();
myXslTransform.Load(xsltDoc);
myXslTransform.Transform(sourceDoc, resultDoc);

Переделка моего примера должна выглядеть примерно так:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/">
    <root>
        <xsl:apply-templates/>
    </root>
    </xsl:template>
    <xsl:template match="MyClass">
        <MyClass>
            <Text>
               <xsl:attribute name="Value">
                    <xsl:value-of select="Text"/>
               </xsl:attribute>
            </Text>
        </MyClass>
    </xsl:template>
</xsl:stylesheet>
0 голосов
/ 06 февраля 2012

Спасибо за все ответы.Жаль, что библиотека .NET XmlSerialization этого не допускает (думаю, что так и должно быть!).Я ищу как можно более универсальное решение.

Лучшее, которое я мог бы придумать (лучшее, учитывающее критерии максимальной универсальности и в то же время достаточно быстрое для реализации), - позволить сериализовать XmlSerializerмой класс так, как ему нравится, затем просто преобразуйте выходные данные, переместив определенные элементы во вложенные местоположения.

Что-то в этом роде:

    /// <remarks>
    /// (angle brackets replaced with round ones to avoid confusing the XML-based documentation comments format)
    /// 
    /// Let input XML be:
    /// 
    ///     (root)
    ///         (days)3(/days)
    ///     (/root)
    ///     
    /// Calling Reposition on this input with mappings argument being:
    ///     (key) "days"
    ///     (value) { "time", "days" }
    ///     
    /// Returns:
    /// (root)
    ///     (time days="3" /)
    /// (/root)
    /// </remarks>        
    static XElement Reposition(XElement input, KeyValuePair<string, string[]>[] mappings)
    {
        var result = new XElement(input);
        foreach (var mapping in mappings)
        {
            var element = result.Element(mapping.Key);
            if (element == null)
            {
                continue;
            }
            var value = element.Value;
            element.Remove();

            var insertAt = result;
            foreach (var breadcrumb in mapping.Value)
            {
                if (breadcrumb == mapping.Value.Last())
                {
                    insertAt.Add(new XAttribute(breadcrumb, value));
                }
                else
                {
                    insertAt.Add(new XElement(breadcrumb));
                    insertAt = insertAt.Element(breadcrumb);
                }
            }
        }
        return result;
    }

Я думаю, я бы совместил его с пользовательским атрибутомчто-то вроде атрибута XPath, который, как я хотел, существовал бы: посмотрите пример кода в моем вопросе) и оберните эту функцию в мой собственный класс сериализатора.

Есть какие-нибудь комментарии / идеи по этому подходу?

Я могу представить себе потенциальный недостаток производительности (переписывание / повторный анализ XML после каждой сериализации), но не ожидается, что полученные фрагменты XML будут иметь большой размер, поэтому это, вероятно, незначительно.

Вопрос десериализации не беспокоит меня на данный момент (десериализация уже была реализованаи делается довольно "вручную", XPath и некоторыми служебными методами).

...