2014-12-10 111 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