开源完美模块组件化可扩展的Xml解析器Hand.ParseXml
一、首先对比Xml反序列化性能对比1.1 性能测试结果如下Deserialize是反序列化结果GetResult是基于反射的对象构建方法,比反序列化快33%GetResult2是定制对象构建组件,避免了反射,比GetResult快一点Custom继承重写EntityParser,避免字典查找,比GetResult2更快一点Method Mean Error StdDev Ratio Gen0 Gen1 Allocated Alloc RatioDeserialize 1,355.5 ns 4.44 ns 4.75 ns 1.00 0.7240 0.0300 12.2 KB 1.00GetResult 912.7 ns 5.07 ns 5.84 ns 0.67 0.6750 0.0250 11.38 KB 0.93GetResult2 798.3 ns 0.40 ns 0.42 ns 0.59 0.6500 - 10.95 KB 0.90Custom 779.4 ns 6.46 ns 7.43 ns 0.57 0.6500 - 10.95 KB 0.901.2 相关代码如下private static readonly XmlSerializer _serializer new(typeof(User));private static readonly EntityParser _parser HandXml.Default.Entity().WithItem(nameof(User.Id)).WithItem(nameof(User.Name)).WithItem(nameof(User.Age));private static readonly EntityParser _parser2 HandXml.Default.Entity(UserBuilder.Creater).WithItem(nameof(User.Id)).WithItem(nameof(User.Name)).WithItem(nameof(User.Age));private static readonly UserParser _customParser new(HandXml.Default);[Benchmark(Baseline true)]public User? Deserialize(){using var stringReader new StringReader(text);return (User?)_serializer.Deserialize(stringReader);}[Benchmark]public User GetResult(){return _parser.Get(text);}[Benchmark]public User GetResult2(){return _parser2.Get(text);}[Benchmark]public User Custom(){return _customParser.Get(text);}[Serializable]public class User{public int Id { get; set; }public string Name { get; set; }public int Age { get; set; }}////// 建造者模式///public class UserBuilder2: IMemberBuilder{private readonly User _original new();////// 原始对象///public User Original _original;///public User Build() _original;///public void Save(string name, TMember value){switch (name){case nameof(User.Id):if (value is int idValue)_original.Id idValue;break;case nameof(User.Name):if (value is string nameValue)_original.Name nameValue;break;case nameof(User.Age):if (value is int ageValue)_original.Age ageValue;break;default:break;}}////// 工厂模式///public static readonly ICreator Creater new DefaultCreater();}public class UserParser(HandXml xml): EntityParser(xml, UserBuilder2.Creater, null, true){#region 配置private readonly IXmlParser _id xml.Content();private readonly ContentReader _name xml.Content();private readonly IXmlParser _age xml.Content();#endregion///public override void ReadAttributes(IMemberStore entity, XmlReader reader) { }///public override void ReadItem(IMemberBuilder entity, XmlReader reader, string name){if (entity is UserBuilder2 builder)ReadItem(builder.Original, reader, name);elsebase.ReadItem(entity, reader, name);}////// 使用自定义构造器////////////public void ReadItem(User entity, XmlReader reader, string name){switch (name){case nameof(User.Id):if (_id.TryParse(reader, out var idResult))entity.Id idResult;break;case nameof(User.Name):if (_name.TryParse(reader, out var nameResult))entity.Name nameResult;break;case nameof(User.Age):if (_age.TryParse(reader, out var ageResult))entity.Age ageResult;break;}}}1.3 性能分析ParseXml开箱即用性能就比XmlSerializer好不少通过定制对象构建组件或扩展重写能进一步提高性能定制对象构建组件和扩展重写解析器可以通过SG来生成,之后会开发相应的源码生成器通用性对比2.1 XmlSerializer通过Attribute标记映射通过XmlRoot、XmlAttribute、XmlElement和XmlText等来映射属性一个类只能支持一种格式的xml文件[XmlRoot(“Root”)]public class Person{[XmlAttribute(“PersonId”)]public int Id { get; set; }[XmlElement(“PersonName”)]public string Name { get; set; }private static readonly XmlSerializer _serializer new(typeof(Person));[Fact]public void Deserialize(){var text “?xml version1.0 encodingutf-8?Root PersonId”“1”张三“;using var stringReader new StringReader(text);Person? person _serializer.Deserialize(stringReader) as Person;Assert.NotNull(person);Assert.Equal(1, person.Id);Assert.Equal(“张三”, person.Name);}[Fact]public void Throw(){var text ”?xml version1.0 encodingutf-8?1张三“;using var stringReader new StringReader(text);Assert.ThrowsAny(() _serializer.Deserialize(stringReader));}}2.2 ParseXml通过Fluent API配置WithItem配置节点WithAttribute配置属性Entity支持可选参数配置当前标签映射映射的成员可以是属性也可以是字段和构造函数的参数配置的解析Xml对象可以定义为静态或注入IOC容器,该对象解析Xml不产生状态支持多线程同时调用(线程安全)同一个类可以配置多个解析对象用来把不同格式的Xml转化为相同类型的对象public record User(int Id, string Name);.[Fact]public void WithNode(){var id 123;var name “Jxj”;var text $”?xml version1.0 encodingutf-8?{id}{name};var userParser HandXml.Default.EntityUser() .WithItemint(nameof(User.Id)) .WithItem(nameof(User.Name)); User result userParser.Get(text); Assert.NotNull(result); Assert.Equal(id, result.Id); Assert.Equal(name, result.Name);}[Fact]public void WithAttribute(){var id 123;var name “Jxj”;var text $“?xml version1.0 encodingutf-8?User Id”“{id}” Name“”{name}“” /;var userParser HandXml.Default.EntityUser() .WithAttributeint(nameof(User.Id)) .WithAttribute(nameof(User.Name)) .First(User); User result userParser.Parse(text); Assert.NotNull(result); Assert.Equal(id, result.Id); Assert.Equal(name, result.Name);}[Fact]public void Content(){var id 123;var name “Jxj”;var text $“?xml version1.0 encodingutf-8?User Id”“{id}”“{name}”;var userParser HandXml.Default.EntityUser(nameof(User.Name)) .WithAttributeint(nameof(User.Id)) .First(User); User result userParser.Parse(text); Assert.NotNull(result); Assert.Equal(id, result.Id); Assert.Equal(name, result.Name);}2.3 通用性分析特性 XmlSerializer ParseXml同类Xml格式 只支持一种格式 支持多种映射对象 只映射属性 映射属性、字段和构造函数参数映射Xml结构 支持属性、子节点和当前节点 支持属性、子节点和当前节点结果类型 object需要转化 当前类型,直接使用二、ParseXml主要功能读取单个节点1.1 读取原始值使用First方法读取单个节点原始值var summaryReader HandXml.Default.First(“summary”);string summary summaryReader.Parse(xmlReader);1.2 读取强类型值使用First泛型方法读取单个节点强类型值var idParser HandXml.Default.First(“Id”);int id idParser.Parse(xmlReader);读取单个属性2.1 读取原始值使用Attribute方法读取属性原始值支持使用属性名或属性索引var text member name ““F:GenerateConvertTests.Supports.ColumnType.Identity””自增列;var nameReader HandXml.Default.Attribute(“name”).First();string name nameReader.Parse(text);2.2 读取强类型值使用Attribute泛型方法读取属性强类型值支持使用属性名或属性索引var text $“?xml version1.0 encodingutf-8?User Id”“123”Jxj;var idReader HandXml.Default.Attribute(0).Element(“User”).First();int result idReader.Parse(text);3. 读取单个节点名使用Name方法读取节点名var text “?xml version1.0 encodingutf-8?1”;var eachParser HandXml.Default.Name().First();var result eachParser.Parse(text);4. 解析到实体xml反序列化4.1 从子节点解析Entity泛型方法指定实体类型WithItem方法读取子节点原始值并绑到实体WithItem泛型方法读取子节点强类型值并绑到实体var text “?xml version1.0 encodingutf-8?123Jxj20”;// Id、Name和Age节点转化为实体User的属性var userParser HandXml.Default.Entity().WithItem(nameof(User.Id)).WithItem(nameof(User.Name)).WithItem(nameof(User.Age));User result userParser.Get(text);4.2 从属性解析Entity泛型方法指定实体类型WithAttribute方法读取属性原始值并绑到实体WithAttribute泛型方法读取属性强类型值并绑到实体var text $“?xml version1.0 encodingutf-8?User Id”“123” Name““Jxj”” Age““20” /”;// 第一个User节点的属性转化为实体User的属性var userParser HandXml.Default.Entity().WithAttribute(nameof(User.Id)).WithAttribute(nameof(User.Name)).WithAttribute(nameof(User.Age)).Element(nameof(User)).First();User result userParser.Parse(text);4.3 从本节点解析原始值Entity可选参数contentName指定本节点原始值绑定实体的成员名var text $“?xml version1.0 encodingutf-8?User Id”“123” Age““age””Jxj;var config HandXml.Default;// 第一个User节点的属性转化为实体User的属性,User节点的文本转化为实体User的Namevar userParser config.Entity(nameof(User.Name)).WithAttribute(nameof(User.Id)).WithAttribute(nameof(User.Age)).Element(nameof(User)).First();User result userParser.Parse(text);5. 解析集合通过Each方法定义集合解析var text “?xml version1.0 encodingutf-8?1张三2李四3![CDATA[王二]]”;var config HandXml.Default;// 每一个User节点转化为实体Uservar repeatReader config.Entity().WithItem(nameof(User.Id)).WithItem(nameof(User.Name)).Element(nameof(User)).Each();User[] result repeatReader.Get(text).ToArray();6. 解析字典6.1 把列表解析为字典使用Dictionary扩展方法解析字典,acceptDefault可选参数配置是否包含默认值先定义键的解析规则以子节点Id为键,子节点Name为值var text “?xml version1.0 encodingutf-8?1张三23![CDATA[王二]]”;var config HandXml.Default;var dictionaryReader config.Element(nameof(User.Id)).Dictionary(config.Element(“Name”).First());IDictionaryint,string result dictionaryReader.Get(text);6.2 把子节点解析给字典通过Name获取节点名使用Dictionary扩展方法解析字典,acceptDefault可选参数配置是否包含默认值var text “?xml version1.0 encodingutf-8?1张三10”;var config HandXml.Default;var dictionaryReader config.Name().Dictionary(config.Content().First())// 跳过当前User节点.MoveIn().Element(“User”).First();IDictionarystring,string result dictionaryReader.Parse(text);7. 包装类型转化Convert方法转入一个转化器可以变成另一个类型的解析器var text $“?xml version1.0 encodingutf-8?User Id”“123”Jxj;// 第一个User节点的Id属性转化为UserIdvar idReader HandXml.Default.Attribute(“Id”).Element(nameof(User)).First().Convert(id new UserId(id));UserId result idReader.Parse(text);8. 复杂类型通过WithItem可以一个解析器绑定到主解析器的成员上通过WithEache可以把集合解析器绑定到主解析器的成员上8.1 复杂类型的Caseusing var fs new FileStream(“rss.xml”, FileMode.Open, FileAccess.Read);using var xmlReader XmlReader.Create(fs);var config HandXml.Default;// url和title节点分别转化为RssImage实体的Url和Titlevar imageParser config.Entity().WithItem(“url”, nameof(RssImage.Url)).WithItem(“title”, nameof(RssImage.Title));// 每一个item节点的title和link分别转化为RssItem实体的Title和Linkvar itemParser config.Entity().WithItem(“title”, nameof(RssItem.Title)).WithItem(“link”, nameof(RssItem.Link)).Element(“item”).Each();var rssParser config.Entity()// title节点转化为Rss实体的Title.WithItem(“title”, nameof(Rss.Title))// image节点交给imageParser处理,处理的结果转化为Rss实体的Image.WithItem(imageParser, “image”, nameof(Rss.Image))// item节点交给itemParser处理,处理的结果转化为Rss实体的Items.With(itemParser, “item”, nameof(Rss.Items));Rss rss rssParser.Get(xmlReader);8.2 支持绑定子解析器的WithItem重载方法item为子解析器node为调用该解析器的子节点名member为子解析器解析结果绑定实体成员名TItem为子解析器解析结果和实体成员共同的类型如果类型不一致结果会抛异常或被抛弃EntityParser WithItem(IParserXmlReader, TItem item, string node, string member);三、ParseXml的模块化HandXml是ParseXml的配置基座HandXml包含Builders、Converters和DefaultValuesBuilders、Converters和DefaultValues参与到解析Xml的各个过程内通过Builders、Converters和DefaultValues配置可以调整Xml解析的结果和性能class HandXml{////// 构造器提供者///IMemberBuilderProvider Builders { get; }////// 转化器///XmlConvertCacher Converters { get; }////// 默认值提供者///DefaultValueBuilder DefaultValues { get; }}1.1 Builders负责对象构建默认使用反射构建支持配置////// 注册工厂//////////////////TProvider UseCreatorTProvider, TEntity(this TProvider provider, ICreatorIMemberBuilder creator);////// 注册工厂//////////////////TProvider UseCreatorTProvider, TEntity(this TProvider provider, FuncIMemberBuilder func);1.2 Converters负责xml节点原始处理默认使用XmlConvert处理默认支持18种基础类型(bool、byte、short、char、sbyte、ushort、int、uint、long、ulong、float、double、decimal、DateTime、string、Guid、DateTimeOffset和TimeSpan等)支持配置////// 配置转化器////////////HandXml UseTValue, TConverter(TConverter converter);////// 配置转化器/////////HandXml Use(Converterstring, TValue converter);1.3 DefaultValues负责默认值各类型的默认值默认是default支持配置////// 设置默认值////////////DefaultValueBuilder Use(TValue value);四、ParseXml的组件化Xml解析接口IXmlParser对Xml单节点、属性、重复节点或者复杂节点的解析类都实现了IXmlParser////// Xml解析接口//////interface IXmlParser{////// 解析////////////bool TryParse(XmlReader reader, out TResult result);}IXmlParser的主要实现类AttributeReader和ContentReader是基础实现其他类都是通过组装IXmlParser实例来实现通过完美的组件化可以模拟和解析复杂的Xml文件类 作用AttributeReader 读取属性的原始文本ContentReader 读取节点的原始文本PrimitiveReader 调用AttributeReader或ContentReader,使用Converters把文本转化为需要的类型ConvertParserTSource, TDest 把一种类型的解析器封装为另一种类型的解析器FirstReader 解析碰到的第一个该节点EachReader 解析连续重复的该节点DictionaryParserTKey, TValue 解析为字典EntityParser 解析包含多个属性或子节点为实体类型的成员class PrimitiveReader(IXmlParser original, IConverterstring, TPrimitive converter, TPrimitive defaultValue);class ConvertParserTSource, TDest(HandXml xml, IXmlParser original, IConverterTSource, TDest converter, TDest defaultValue);class FirstReader(string element, IXmlParser original, TResult defaultValue);class RepeatReader(HandXml xml, string name, IXmlParser item);class DictionaryParserTKey, TValue(IParserXmlReader, TKey key, IParserXmlReader, TValue value, IEqualityComparer comparer, bool acceptDefault);class EntityParser(HandXml xml, ICreatorIMemberBuilder creator, IMemberParser? content, bool hasItem false);五、ParseXml的可扩展性EntityParser的扩展性EntityParser可以用来处理复杂Xml和复杂类型可扩展性在处理复杂问题时可简化配置或提供性能UserParser扩展EntityParser的示例UserParser封装了对象解析器,提高性能UserParser封装了节点配置,简化使用重写ReadAttributes跳过属性解析提高性能重写ReadItem,使用switch代替字典查找提高性能public class UserParser(HandXml xml): EntityParser(xml, UserBuilder2.Creater, null, true){#region 配置private readonly IXmlParser _id xml.Content();private readonly ContentReader _name xml.Content();private readonly IXmlParser _age xml.Content();#endregion///public override void ReadAttributes(IMemberStore entity, XmlReader reader) { }///public override void ReadItem(IMemberBuilder entity, XmlReader reader, string name){if (entity is UserBuilder2 builder)ReadItem(builder.Original, reader, name);elsebase.ReadItem(entity, reader, name);}////// 使用自定义构造器////////////public void ReadItem(User entity, XmlReader reader, string name){switch (name){case nameof(User.Id):if (_id.TryParse(reader, out var idResult))entity.Id idResult;break;case nameof(User.Name):if (_name.TryParse(reader, out var nameResult))entity.Name nameResult;break;case nameof(User.Age):if (_age.TryParse(reader, out var ageResult))entity.Age ageResult;break;}}}
