在不同的父元素下解析相同类型的XML元素的模式?

时间:2014-12-10 23:51:10

标签: c# 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>

所以我正在建立一个地点和人员列表。作为商业规则,“人”必须与“位置”相关联,但这可以通过两种方式之一完成。通过将它们列为location元素的子元素,从而将它们放在父级位置上,或者在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类型的添加,它会变得更加丑陋。这是否与它得到的一样好,或者是否有一种方法可以重写以更好地分离关注点?

1 个答案:

答案 0 :(得分:1)

  可以重写

以更好地分离关注点吗?

如果这是将会增长并且可扩展的代码,那么我建议使用Interfaces作为操作合同。我相信您感觉代码具有可被利用的相似性,并且通过定义接口,可以创建扩展代码的系统,并允许对数据项进行通用处理,而不管它们的来源如何。


我看到两种不同的类型可以用枚举表示,例如

public enum eOperationType
{
    Person,
    Location
};

每个项目都相似,每个项目都有一个名称和一个eOperationType。所以让我们将其表达为一个界面。合同将要求它返回它是什么 OpType,一个全名,并且必须知道如何处理目标Xml节点。

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

因此,在我们指定Person类或City之前,我们需要为这些类提供合同(以及将来可能无缝替换未来处理的类。)合同将是我们处理类的通用方式,无论它们的类型。请注意,人员或位置现在可以指定在这些接口中与该类不同的其他属性,但仍将共享公共操作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
{

}