2014-12-10 56 views
0

于是我找解析,可能看起来像这样的XML文件:解析不同父元素下相同类型的XML元素的模式?

<Locations> 
    <Location Name="California"> 
     <Location Name="Los Angeles"> 
      <Person Name="Harrison Ford"/> 
     </Location> 
    </Location> 
</Locations> 

<People> 
    <Person Name="Jake Gyllenhaal" Location="Los Angeles"/> 
</People> 

所以我建立的地点和人员名单。作为一项商业规则,“人”必须与“地点”相关联,但这可以通过以下两种方式之一来完成。可以将它们列为位置元素的子元素,以便他们可以采用该父级位置,或者在People元素下列出时明确列出它们。现在我处理它是这样的(没有任何错误检查)。

public class Parser 
{ 
    public void Parse(XElement xmlRoot) 
    { 
     IList<Location> locations = new List<Location>(); 
     IList<Person> people = new List<Person>(); 

     var locationParser = new LocationParser(); 

     locations = locationParser.ParseLocations(xmlRoot.Element("Locations"), people); 

     var peopleParser = new PeopleParser(); 

     people = peopleParser.ParsePeople(xmlRoot.Element("People"), locations); 

     // Do stuff with XML read objects. 
    } 
} 

public class PeopleParser 
{ 
    public IList<Person> ParsePeople(XElement peopleRoot, IList<Location> locations) 
    { 
     var xPeople = peopleRoot.Elements("Person"); 
     var people = new List<Person>(); 

     foreach (var person in xPeople) 
     { 
      var locationName = person.Attribute("Location").Value; 

      var location = locations.First(loc => loc.Name.Equals(locationName)); 

      people.Add(this.ParsePerson(person, location)); 
     } 

     return people; 
    } 

    public Person ParsePerson(XElement person, Location location) 
    { 
     var personName = person.Attribute("Name").Value; 

     return new Person(personName, location); 
    } 
} 

public class LocationParser 
{ 
    PeopleParser peopleParser = new PeopleParser(); 

    public IList<Location> ParseLocations(XElement locationRoot, IList<Person> people) 
    { 
     var xLocations = locationRoot.Elements("Location"); 
     var locations = new List<Location>(); 

     foreach (var location in xLocations) 
     { 
      locations.Add(this.ParseLocation(location, people)); 
     } 

     return locations; 
    } 

    public Location ParseLocation(XElement xLocation, IList<Person> people) 
    { 
     var children = new List<Location>(); 

     foreach (var subLocation in xLocation.Elements("Location")) 
     { 
      children.Add(this.ParseLocation(subLocation, people)); 
     } 

     var newLocation = new Location(xLocation.Attribute("Name").Value, children); 

     foreach (var xPerson in xLocation.Elements("Person")) 
     { 
      people.Add(peopleParser.ParsePerson(xPerson, newLocation)); 
     } 

      return newLocation; 
     } 
    } 
} 

此代码是我“丑”,这只是东西变得更依赖XML类型添加了很多丑陋的一个简单的例子。这是否如此好,还是有一种方法可以重写,以更好地分离关注点?

+0

我不明白你的问题。或者为什么一个人被列为两种不同的方式之一,但基本意义相同。 – 2014-12-11 00:11:16

+0

问题是,这可以解析'更好'说例如让PeopleParser离开LocationParser。至于为什么可以用多种方式定义一个人只是添加选项。我主要是在构建我自己的解析器来寻找已经存在的东西,所以我必须遵循创建者的约定。在Wix工具集中查找示例,其中XML元素(如组件)可以在各种不同的元素下声明。 – Thermonuclear 2014-12-11 00:28:46

+0

您需要在名称旁边的Location对象中存储什么信息?路径也很重要(例如:加州/洛杉矶)? – alexm 2014-12-11 00:49:40

回答

1

可以改写为更好的分离关注?

如果这是这是会成长并获得可扩展的代码,那么我建议Interfaces使用的业务合同。我相信你认为代码具有可以利用的相似性,并且通过定义接口,可以创建扩展代码的系统,并允许对数据项进行通用处理,而不管它们的起源如何。


我看到其可以通过一个枚举如

public enum eOperationType 
{ 
    Person, 
    Location 
}; 

其中的每一项是各自具有一个名称和一个eOperationType类似表示两种不同类型的。所以让我们把它作为一个接口来表达。合同将要求它返回什么它是OpType,一个完整的名称,并且必须知道如何处理一个目标Xml节点。我们指定一个Person类或City我们需要为这些类提供合同(以及任何未来的类可能无缝替换那些在未来处理

public interface IOperation 
{ 
    string FullName { get; set; } 
    eOperationType OpType { get; } 
    void ProcessXml(XElement node); 
} 

所以之前,本合同将是我们处理的通用方式类无论其类型。需要注意的是这个人或位置现在可以指定哪些是不同的,以在这些接口该类其他属性,但仍会共用的IOperation的常用操作。

public interface IPerson : IOperation 
{ 

} 

public interface ILocation : IOperation 
{ 

} 

这给了我们什么?我们现在可以创建一个类,它将接受一个XML节点并通过接口表示它。让我们来看看人

public class Person : IPerson 
{ 

    public string FullName { get; set; } 
    public eOperationType OpType { get { return eOperationType.Person; } } 
    public void ProcessXml(XElement node) 
    { 
     var attr = node.Attributes().First (atr => atr.Name == "Name"); 
     FullName = attr.Value.ToString(); 
    } 

} 

现在我们需要的是将在一个IOperation并返回我们需要的类的实例的通用方法。这里是一个通用的方法:

public static class XmlOperations 
{ 
    public static T GetData<T>(XElement data) where T : IOperation 
    { 
     var clone = Activator.CreateInstance<T>(); 

     clone.ProcessXml(data); 

     return clone; 
    } 
} 

现在在简单的例子,我们可以得到所有的人(后来的位置可以增加),如:

var doc = XDocument.Parse(GetData()); 

var People = 
doc.Descendants(eOperationType.Person.ToString()) 
    .Select (ele => XmlOperations.GetData<Person>(ele)); 

的人现在是哈里森和杰克:

enter image description here

所以在这一点上,我们可以创建一个实现ILocation的Location类。既然它也实现了IOperation,我们可以重用通用的xml处理。从那里我们可以采用这个基本的通用实现,并且无论如何都可以模拟它;但是通过指定原子操作,代码之间的相互作用增加了,并且由于合同中发现的泛化扩展了重用。


以下是完整的linqpad程序

void Main() 
{ 
    var doc = XDocument.Parse(GetData()); 

    var People = 
      doc.Descendants(eOperationType.Person.ToString()) 
       .Select (ele => XmlOperations.GetData<Person>(ele)); 

    People.Dump(); // Linqpad extension to display data. 

} 

public static class XmlOperations 
{ 
    public static T GetData<T>(XElement data) where T : IOperation 
    { 
     var clone = Activator.CreateInstance<T>(); 

     clone.ProcessXml(data); 

     return clone; 
    } 
} 


public class Person : IPerson 
{ 
    public string FullName { get; set; } 
    public eOperationType OpType { get { return eOperationType.Person; } } 
    public void ProcessXml(XElement node) 
    { 
     var attr = node.Attributes().First (atr => atr.Name == "Name"); 
     FullName = attr.Value.ToString(); 
    } 

} 


public string GetData() 
{ 
return @"<Data> 
<Locations> 
    <Location Name=""California""> 
     <Location Name=""Los Angeles""> 
      <Person Name=""Harrison Ford""/> 
     </Location> 
    </Location> 
</Locations> 

<People> 
    <Person Name=""Jake Gyllenhaal"" Location=""Los Angeles""/> 
</People> 
</Data>"; 
} 


public enum eOperationType 
{ 
    Person, 
    Location 
}; 


public interface IOperation 
{ 
    string FullName { get; set; } 
    eOperationType OpType { get; } 
    void ProcessXml(XElement node); 
} 

public interface IPerson : IOperation 
{ 

} 

public interface ILocation : IOperation 
{ 

} 
+0

哇,谢谢你,这是一个非常彻底的答案,虽然我仍然在围绕着这里发生的一切,我喜欢这些想法。现在我只需要玩一下,看看我可以如何让位置插入到人员中。 – Thermonuclear 2014-12-11 16:51:23

+0

@Thermonuclear我不明白你如何处理数据,所以我没有实现'Location'类;但通过使用接口并找到共同的属性和操作,将所有对象(无论是个人还是位置)的“List ”与每个项绑定到枚举的能力是在Linq查询中使用的强大功能。 – OmegaMan 2014-12-11 16:58:32