基于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(); }
补充说明
- 命名空间处理:示例XML带命名空间,读取时用
reader.LocalName代替reader.Name,避免命名空间前缀干扰。 - 流式读取优化:用
reader.Skip()快速跳过空节点或无需处理的节点;ReadElementContentAsString()会自动读取到元素结束标签,无需额外调用Read()。 - 变体关联逻辑:先收集所有产品,再通过主产品的
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
相关产品推荐
相关产品推荐

