You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于C# XmlReader从XML提取并推断复杂类的实现问题

XML产品解析:两个核心问题的解决方案

一、XmlReader.Read()的层级行为问题

是的,XmlReader是流式向前读取的,每次调用Read()都会移动到文档中的下一个节点,不管节点层级深浅。当找到<product>起始标签后,再次调用Read()会直接进入<product>内部的第一个子节点(比如示例XML里的<ean>),直到遍历完<product>下的所有节点,最终遇到</product>结束标签。

二、下复杂ProductOption类的解析方案

要处理<variations>下的多层级结构,需要嵌套循环逐层读取子节点。首先定义匹配XML结构的实体类:

public class Product
{
    public string ID { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public string DescriptionLong { get; set; }
    public List<ProductOption> Options { get; set; }
    public List<string> VariantIds { get; set; }
    public List<Product> VariantProducts { get; set; }
}

public class ProductOption
{
    public string AttributeId { get; set; }
    public string DisplayName { get; set; }
    public List<VariationAttributeValue> Values { get; set; }
}

public class VariationAttributeValue
{
    public string Value { get; set; }
    public string DisplayValue { get; set; }
    public string Description { get; set; }
}

修改原解析代码,补全<variations>部分逻辑:

public List<Product> ParseXml(string xmlFilePath)
{
    List<Product> products = new List<Product>();
    XmlReaderSettings settings = new XmlReaderSettings { IgnoreWhitespace = true };

    using(XmlReader reader = XmlReader.Create(xmlFilePath, settings))
    {
        while(reader.Read())
        {
            if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "product")
            {
                Product product = new Product
                {
                    ID = reader.GetAttribute("product-id"),
                    Options = new List<ProductOption>(),
                    VariantIds = new List<string>(),
                    VariantProducts = new List<Product>()
                };

                while(reader.Read())
                {
                    if(reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "product")
                    {
                        products.Add(product);
                        break;
                    }
                    if(reader.NodeType != XmlNodeType.Element) continue;

                    switch(reader.LocalName)
                    {
                        case "short-description":
                            product.Description = reader.ReadElementContentAsString();
                            break;
                        case "long-description":
                            product.DescriptionLong = reader.ReadElementContentAsString();
                            break;
                        case "display-name":
                            product.Name = reader.ReadElementContentAsString();
                            break;
                        case "variations":
                            while(reader.Read())
                            {
                                if(reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "variations") break;
                                if(reader.NodeType != XmlNodeType.Element) continue;

                                if(reader.LocalName == "attributes")
                                {
                                    while(reader.Read())
                                    {
                                        if(reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "attributes") break;
                                        if(reader.NodeType != XmlNodeType.Element) continue;

                                        if(reader.LocalName == "variation-attribute")
                                        {
                                            ProductOption option = new ProductOption
                                            {
                                                AttributeId = reader.GetAttribute("attribute-id"),
                                                Values = new List<VariationAttributeValue>()
                                            };

                                            while(reader.Read())
                                            {
                                                if(reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "variation-attribute")
                                                {
                                                    product.Options.Add(option);
                                                    break;
                                                }
                                                if(reader.NodeType != XmlNodeType.Element) continue;

                                                switch(reader.LocalName)
                                                {
                                                    case "display-name":
                                                        option.DisplayName = reader.ReadElementContentAsString();
                                                        break;
                                                    case "variation-attribute-values":
                                                        while(reader.Read())
                                                        {
                                                            if(reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "variation-attribute-values") break;
                                                            if(reader.NodeType != XmlNodeType.Element) continue;

                                                            if(reader.LocalName == "variation-attribute-value")
                                                            {
                                                                VariationAttributeValue value = new VariationAttributeValue
                                                                {
                                                                    Value = reader.GetAttribute("value")
                                                                };

                                                                while(reader.Read())
                                                                {
                                                                    if(reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "variation-attribute-value")
                                                                    {
                                                                        option.Values.Add(value);
                                                                        break;
                                                                    }
                                                                    if(reader.NodeType != XmlNodeType.Element) continue;

                                                                    switch(reader.LocalName)
                                                                    {
                                                                        case "display-value":
                                                                            value.DisplayValue = reader.ReadElementContentAsString();
                                                                            break;
                                                                        case "description":
                                                                            value.Description = reader.ReadElementContentAsString();
                                                                            break;
                                                                    }
                                                                }
                                                            }
                                                        }
                                                        break;
                                                }
                                            }
                                        }
                                    }
                                }
                                else if(reader.LocalName == "variants")
                                {
                                    while(reader.Read())
                                    {
                                        if(reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "variants") break;
                                        if(reader.NodeType != XmlNodeType.Element) continue;

                                        if(reader.LocalName == "variant")
                                        {
                                            string variantId = reader.GetAttribute("product-id");
                                            if(!string.IsNullOrEmpty(variantId)) product.VariantIds.Add(variantId);
                                            reader.Skip();
                                        }
                                    }
                                }
                            }
                            break;
                    }
                }
            }
        }
    }

    // 关联变体产品到主产品,并移除主列表中的独立变体项
    var masterProducts = products.Where(p => p.VariantIds.Any()).ToList();
    var variantProducts = products.Where(p => !p.VariantIds.Any() && masterProducts.Any(m => m.VariantIds.Contains(p.ID))).ToList();

    foreach(var master in masterProducts)
    {
        master.VariantProducts = variantProducts.Where(v => master.VariantIds.Contains(v.ID)).ToList();
    }

    return masterProducts.Concat(products.Where(p => !masterProducts.Any(m => m.VariantIds.Contains(p.ID)) && !p.VariantIds.Any())).ToList();
}

补充说明

  1. 命名空间处理:示例XML带命名空间,读取时用reader.LocalName代替reader.Name,避免命名空间前缀干扰。
  2. 流式读取优化:用reader.Skip()快速跳过空节点或无需处理的节点;ReadElementContentAsString()会自动读取到元素结束标签,无需额外调用Read()。
  3. 变体关联逻辑:先收集所有产品,再通过主产品的VariantIds匹配对应的变体产品,最后移除主列表中的独立变体项。

附:XML样本

<?xml version="1.0" encoding="utf-8"?>
<catalog catalog-id="master-catalog" xmlns="http://www.demandware.com/xml/impex/catalog/2006-10-31">
  <header>
    <image-settings>
      <internal-location base-path="/" />
      <view-types>
        <view-type>large</view-type>
        <view-type>medium</view-type>
        <view-type>small</view-type>
        <view-type>swatch</view-type>
      </view-types>
      <variation-attribute-id>f54ProductColor</variation-attribute-id>
      <alt-pattern>${productname}</alt-pattern>
      <title-pattern>${productname}</title-pattern>
    </image-settings>
  </header>
 <product product-id="999111130-m">
    <ean />
    <upc />
    <unit />
    <min-order-quantity>1</min-order-quantity>
    <step-quantity>1</step-quantity>
    <display-name xml:lang="x-default">Hoodie Frank Sinatra</display-name>
    <short-description>Best hoodie ever.</short-description>
    <long-description>Very very best hoodie everrrrrrrrrrrrrrrrrrrrrrrrrrr.</long-description>
    <brand>lemonMarshmello</brand>
    <variations>
      <attributes>
        <variation-attribute attribute-id="f54ProductColor" variation-attribute-id="f54ProductColor">
          <display-name>Color</display-name>
          <variation-attribute-values>
            <variation-attribute-value value="BLACK">
              <display-value>black</display-value>
              <description></description>
            </variation-attribute-value>
            <variation-attribute-value value="BEIGE">
              <display-value>beige</display-value>
              <description></description>
            </variation-attribute-value>
          </variation-attribute-values>
        </variation-attribute>
        <variation-attribute attribute-id="f54ProductSize" variation-attribute-id="f54ProductSize">
          <variation-attribute-values>
            <variation-attribute-value value="SM">
              <display-value>S</display-value>
              <description>;S-M</description>
            </variation-attribute-value>
            <variation-attribute-value value="ML">
              <display-value>M</display-value>
              <description>;M-L</description>
            </variation-attribute-value>
          </variation-attribute-values>
        </variation-attribute>
      </attributes>
      <variants>
        <variant product-id="999111130SM" default="true" />
        <variant product-id="999111130ML" />
        <variant product-id="999111130SM" />
        <variant product-id="999111130ML" />
      </variants>
      <variation-groups>
        <variation-group product-id="999111130" />
        <variation-group product-id="999111131" />
      </variation-groups>
    </variations>
  </product>
  
  <product product-id="999111130SM">
    <ean />
    <upc />
    <unit />
    <min-order-quantity>1</min-order-quantity>
    <step-quantity>1</step-quantity>
    <display-name xml:lang="x-default">Hoodie Frank Sinatra</display-name>
    <short-description>Best hoodie ever.</short-description>
    <long-description>Very very best hoodie everrrrrrrrrrrrrrrrrrrrrrrrrrr.</long-description>
    <custom-attributes>
      <custom-attribute attribute-id="f54ProductColor">BLACK</custom-attribute>
      <custom-attribute attribute-id="f54ProductSize">SM</custom-attribute>
      <custom-attribute attribute-id="f54Department">
        <value>woman</value>
      </custom-attribute>
      <custom-attribute attribute-id="f54FilterProductSize">S</custom-attribute>
      <custom-attribute attribute-id="f54searchExcluded" xml:lang="x-default">false</custom-attribute>
      <custom-attribute attribute-id="f54ComingSoonText"></custom-attribute>
      <custom-attribute attribute-id="f54ComingSoonDate"></custom-attribute>
    </custom-attributes>
  </product>
</catalog>  

内容的提问来源于stack exchange,提问作者NDGold

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 13:02:21