【问题标题】:Serializing complex XML string content序列化复杂的 XML 字符串内容
【发布时间】:2019-12-11 11:30:51
【问题描述】:

我正在尝试将 XML 内容序列化为对象。我正在使用.Net 4.6.1。我要序列化的 XML 是:

<THEROOT>
  <ITEM>
    <TITLE>
      This is my title
    </TITLE>
    <DESCRIPTION>
      <P>Line 1 of the description</P>
      <P>Line 2 of the description</P>
      <P>Final line of description</P>
    </DESCRIPTION>
    <MOREINFO>
      <P>Some additional stuff here</P>
      <P>And another line</P>
      <P>And final line</P>
    </MOREINFO>
  </ITEM>
  <ITEM>
    <TITLE>
      Another object is here
    </TITLE>
    <DESCRIPTION>
      <P>Some description</P>
      <P>That I need to parse</P>
      <P>Into a string</P>
    </DESCRIPTION>
    <MOREINFO>
      <P>More info lines</P>
      <P>Would go here</P>
    </MOREINFO>
  </ITEM>
</THEROOT>

这是我要序列化的对象:

public class TestModel
{
    [XmlRoot(ElementName = "THEROOT")]
    public class TheRoot
    {
        [XmlElement(ElementName = "ITEM")]
        public List<Item> Item { get; set; }
    }

    [XmlRoot(ElementName = "ITEM")]
    public class Item
    {
        [XmlElement(ElementName = "TITLE")]
        public string Title { get; set; }

        [XmlElement(ElementName = "DESCRIPTION")]
        public string Description { get; set; }

        [XmlElement(ElementName = "MOREINFO")]
        public string MoreInfo { get; set; }
    }
}

为了完整起见,这是我的序列化代码(假设上面的 XML 内容是一个字符串)..

TestModel.TheRoot rootObject;
using (TextReader tr = new StringReader(myXML))
{
    using (XmlTextReader xr = new XmlTextReader(tr))
    {
        xr.Namespaces = false;
        XmlSerializer serializer = new XmlSerializer(typeof(TestModel.TheRoot));
        rootObject = (TestModel.TheRoot)serializer.Deserialize(xr);
    }
}

在当前状态下,我收到错误消息“只能对具有简单或空内容的元素调用 ReadElementString 方法”。我理解这是因为我的描述内容中有 html 标签,这些标签被反序列化为复杂对象。

我发现我“可以”将模型修改成这样......

public class TestModel
{
    [XmlRoot(ElementName = "THEROOT")]
    public class TheRoot
    {
        [XmlElement(ElementName = "ITEM")]
        public List<Item> Item { get; set; }
    }

    [XmlRoot(ElementName = "ITEM")]
    public class Item
    {
        [XmlElement(ElementName = "TITLE")]
        public string Title { get; set; }

        [XmlElement(ElementName = "DESCRIPTION")]
        public Description Description { get; set; }

        [XmlElement(ElementName = "MOREINFO")]
        public MoreInfo MoreInfo { get; set; }
    }

    [XmlRoot(ElementName = "DESCRIPTION")]
    public class Description
    {
        [XmlElement(ElementName = "P")]
        public P[] P { get; set; }
    }

    [XmlRoot(ElementName = "MOREINFO")]
    public class MoreInfo
    {
        [XmlElement(ElementName = "P")]
        public P[] P { get; set; }
    }

    [XmlRoot(ElementName = "P")]
    public class P
    {
        [XmlText]
        public string Text { get; set; }
    }
}

这在某种程度上确实有效 - 但是当我知道我只想将整个内容视为字符串时,这很麻烦。 假设我无法更改 XML 的格式,有没有办法可以将 Description 和 MoreInfo 节点都反序列化为单个字符串对象?

我在这里和其他网站上看到过各种帖子,但似乎都没有完美的工作。

【问题讨论】:

  • 您总是可以只拥有一个只读属性,它将P 项目的内容作为一个字符串返回:public string Content =&gt; string.Join(" ", Description.P)
  • 我确实有,但问题是我首先需要将它序列化为某种结构。我宁愿不使用显示的第二个模型,因为它很乱(实际模型比这个大得多)。不过同意 - 如果有办法强制序列化程序将内容放入字符串数组或其他任何内容中 - 我可以与另一个解决方案一起执行此操作。

标签: c# .net xml deserialization


【解决方案1】:

我最近遇到了这个问题,我想建议使用 XSLT 转换来处理复杂的场景。

例如我的一个 xslt 文件

XSL 允许挑选您想要使用的数据,并且非常灵活。

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/">
    <xsl:element name="ClaimDisciplines">
        <xsl:apply-templates select="//Providers/ProviderDetail"/>
    </xsl:element>
</xsl:template>

<xsl:template match="//Providers/ProviderDetail">
    <xsl:element name="ClaimDisciplineDTO">
        <xsl:element name="OriginalId">
            <xsl:value-of select="DisciplineID" />
        </xsl:element>
        <xsl:element name="DisciplineDescription">
            <xsl:value-of select="DisciplineDescription" />
        </xsl:element>
        <xsl:element name="SubDisciplineId">
            <xsl:value-of select="SubDisciplineID"/>
        </xsl:element>
        <xsl:element name="DicsiplineGuid">
            <xsl:value-of select="DisciplineGuid" />
        </xsl:element>
        <xsl:element name="IsMain">
            <xsl:value-of select="IsMain" />
        </xsl:element>
    </xsl:element>
</xsl:template>

我用来序列化为必要对象的代码

此代码是通用的,并且还提供了 xml 中的空字段。

public static class XmlHelpers
{
    public static T DeserializeXmlObject<T>(string xml) where T : class
    {
        if (string.IsNullOrEmpty(xml))
        {
            return default;
        }

        using (var stringReader = new StringReader(xml))
        {
            var serializer = new XmlSerializer(typeof(T));

            return (T) serializer.Deserialize(stringReader);
        }
    }

    public static List<T> PopulateDtoFromXml<T>(string pathToXsl, string inputXml) where T : class
    {
        var stylesheet = new XslCompiledTransform();
        stylesheet.Load(pathToXsl);


        List<T> returnList = default;

        using (var sr = new StringReader(inputXml))
        {
            using (var xr = XmlReader.Create(sr))
            {
                using (var sw = new StringWriter())
                {
                    stylesheet.Transform(xr, null, sw);
                    var resultXml = sw.ToString();

                    var cleanXml = XDocument.Parse(resultXml, LoadOptions.None);
                    cleanXml.Descendants()
                        .Where(e => e.IsEmpty || string.IsNullOrWhiteSpace(e.Value))
                        .Remove();

                    var listOfItems = cleanXml.Descendants().Where(x => x.HasElements && x.Ancestors().Any()).ToList();

                    foreach (var item in listOfItems)
                    {
                        try
                        {
                            var result = DeserializeXmlObject<T>(item.ToString());

                            if (result == null) continue;
                            if (returnList == null) returnList = new List<T>();
                            returnList.Add(result);
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine(e);
                            throw;
                        }
                    }


                }
            }
        }


        return returnList;

    }
}

【讨论】:

  • 感谢您的发帖 - 我们已经在应用程序的不同部分广泛使用 XSLT,但考虑到我只想获取少数项目的字符串内容,这有点过头了。转换虽然非常强大,但支持起来有点难看。
【解决方案2】:

使用 XML LINQ:

using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;


namespace ConsoleApplication9
{
    class Program
    {
        const string FILENAME = @"c:\temp\test.xml";
        static void Main(string[] args)
        {
            TestModel model = new TestModel(FILENAME);
        }
    }
    public class TestModel
    {
        public List<Item> items { get; set; }
        public TestModel(string filename)
        {
            XDocument doc = XDocument.Load(filename);

            items = doc.Descendants("ITEM").Select(x => new Item()
            {
                Title = ((string)x.Element("TITLE")).Trim(),
                Description = string.Join(",", x.Element("DESCRIPTION")
                   .Elements("P").Select(y => (string)y)),
                MoreInfo = string.Join(",", x.Element("MOREINFO")
                   .Elements("P").Select(y => (string)y))
            }).ToList();
        }


    }
    public class Item
    {
        public string Title { get; set; }

        public string Description { get; set; }

        public string MoreInfo { get; set; }
    }
}

将以下内容与您的解决方案一起使用以扁平化结果

            private string _Description { get; set; }
            [XmlElement(ElementName = "DESCRIPTION")]
            public Description Description
            {
                get { return new Description() { P = _Description.Split(new char[] { ',' }).Select(x => new P() { Text = x}).ToArray() }; }
                set
                {
                    _Description = string.Join(",",value.P.Select(x => x.Text));
                }
            }

            private string _MoreInfo { get; set; }
            [XmlElement(ElementName = "MOREINFO")]
            public MoreInfo MoreInfo
            {
                get { return new MoreInfo() { P = _MoreInfo.Split(new char[] { ',' }).Select(x => new P() { Text = x }).ToArray() }; }
                set
                {
                    _MoreInfo = string.Join(",", value.P.Select(x => x.Text));
                }
            }

【讨论】:

  • 应该说,理想情况下正在寻找使用 XmlSerializer 的解决方案。感谢我可以通过 dom 解析来做到这一点,但我们目前的解决方案已经有了类似的东西。不过感谢您的建议。
  • 我使用您的序列化添加了代码以展平结果。
【解决方案3】:

这是我的看法:

使用XmlIgnore 为可能包含子元素(HTML 实体)的属性赋予属性,如下所示:

[XmlIgnore, XmlElement(ElementName = "DESCRIPTION")]

[XmlIgnore, XmlElement(ElementName = "MOREINFO")]

然后将委托附加到序列化程序上的UnknownElement 事件(UnknownNodeUnknownAttribute 也可用。)。

XmlSerializer serializer = new XmlSerializer(typeof(TestModel.TheRoot));
serializer.UnknownElement += Serialize_PossibleHTMLElementToString;

稍加思考,您可以将父元素的所有内部节点视为字符串(例如您希望保留的 HTML)。可以修改此方法以使其更具体。

    private static void Serialize_PossibleHTMLElementToString(object sender, XmlElementEventArgs e)
    {
        if (e.ObjectBeingDeserialized != null)
        {
            var node = e.Element.InnerXml;
            string elementName = e.Element.Name;

            Item item = (Item)e.ObjectBeingDeserialized;

            var element = item.GetType().GetProperties().FirstOrDefault(x => x.GetCustomAttributes(typeof(XmlElementAttribute), true).Where(attr => (attr as XmlElementAttribute).ElementName == elementName).Count() > 0);

            if(element != null)
                element.SetValue(item, node.Trim());
        }
    }

这将产生以下输出:

【讨论】:

  • 尽管我早先在测试中已经使用过这种方法,但我还是回到了原点。它并不完美(将元素及其所有子元素标记为文本内容的属性会很好)但结合自定义属性它可以完成这项工作并且似乎是目前最好的解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-07
  • 1970-01-01
  • 1970-01-01
  • 2014-10-04
  • 1970-01-01
相关资源
最近更新 更多