Уменьшите дублирование кода при поиске значений в XML с помощью XPath - PullRequest
1 голос
/ 05 марта 2012

Я пишу некоторый код для анализа xml-файла следующего формата (для простоты урезан)

<?xml version="1.0" encoding="UTF-8"?>
<ship name="Foo">
 <base_type>Foo</base_type>
 <GFX>fooGFX</GFX>
 ....
</ship>

Я использую словарь, состоящий из пар ключей параметра и и xpath для значения, и запрашиваю его, чтобы загрузить все значения в различные переменные. Тем не менее, я заметил, что будет много дублирования кода. Фактически, для каждого значения, которое я хочу получить, мне нужно будет написать еще одну строку с почти идентичным кодом. Вот мой код:

class Ship
{
    public Ship()
    {
        paths = new Dictionary<string, string>();
        doc = new XmlDocument();
        //Define path to various elements
        paths.Add("name", "/ship/@name");
        paths.Add("base_type", "/ship/base_type");
        paths.Add("GFX", "/ship/GFX");
    }
    public void LoadFile(string filename)
    {// Loads the file and grabs the parameters
        doc.Load(filename);
        Name = doc.SelectSingleNode(paths["name"]).Value;
        Base_type = doc.SelectSingleNode(paths["base_type"]).Value;
        GFX = doc.SelectSingleNode(paths["GFX"]).Value;
    }

    public Dictionary<string, string> paths; //The XPaths to the various elements, define them in constructor
    public XmlDocument doc;
    public string Name;
    public string Base_type;
    public string GFX;
}

Обратите внимание на дублирование здесь:

variable = doc.SelectSingleNode(paths["variable_name"]).value. 

Там будет еще много переменных, поэтому этот раздел будет массовым.

Есть ли способ упростить это? Если бы это был C ++, я бы, вероятно, попробовал указатели, но я знаю, что они не рекомендуются для использования в C #, так есть ли подобный способ?

Я бы искал что-то, что мог бы дать список имен переменных и путей к ним, и чтобы код извлекал все значения и загружал их в переменную в каком-то цикле или что-то в этом роде. Я хочу использовать XPath, потому что я ожидаю, что формат этого файла может периодически меняться.

Есть идеи?

Заранее спасибо.

РЕДАКТИРОВАТЬ: Я также хотел бы иметь возможность изменить эти данные и сохранить их обратно. Я не против сохранения всего нового дерева в случае необходимости, но было бы неплохо изменить данные на месте, если это возможно. Мне не нужно решение для модификации, но мне просто нужно открыть эту опцию.

Ответы [ 3 ]

2 голосов
/ 05 марта 2012

Одним из хороших способов достижения заселенности объекта является использование XmlSerializer.Deserialize () .

Примерно так:

namespace TestSerialization
{
    using System;
    using System.IO;
    using System.Xml;
    using System.Xml.Serialization;

    public class TestSerialization
    {
        static void Main(string[] args)
        {
            string theXml =
@"<ship name='Foo'>
 <base_type>Foo</base_type>
 <GFX>fooGFX</GFX>
</ship>";
            Ship s = Ship.Create(theXml);

            // Write out the properties of the object.
            Console.Write(s.Name + "\t" + s.GFX);
        }
    }

    [XmlRoot("ship")]
    public class Ship
    {
        public Ship() { }

        public static Ship Create(string xmlText)
        {
            // Create an instance of the XmlSerializer specifying type.
            XmlSerializer serializer = new XmlSerializer(typeof(Ship));

            StringReader sr = new StringReader(xmlText);

            XmlReader xreader = new XmlTextReader(sr);

            // Use the Deserialize method to restore the object's state.
            return (Ship)serializer.Deserialize(xreader);
        }

        [XmlAttribute("name")]
        public string Name;

        [XmlElement("base_type")]
        public string Base_type;

        public string GFX;
    }
}

ОБНОВЛЕНИЕ : ОП добавил дополнительный вопрос:

Я также хотел бы иметь возможность изменить эти данные и сохранить их обратно. я не препятствует сохранению всего нового дерева при необходимости

Просто используйте метод XmlSerializer.Serialize() .

Вот типичный пример его использования:

  // Create an XmlTextWriter using a FileStream.
  Stream fs = new FileStream(filename, FileMode.Create);
  XmlWriter writer = 
  new XmlTextWriter(fs, Encoding.Unicode);
  // Serialize using the XmlTextWriter.
  serializer.Serialize(writer, yourObject);
  writer.Close();
0 голосов
/ 05 марта 2012

Одним из способов будет определение собственного пользовательского типа атрибута для сопоставления свойства с селектором XPath, определение автоматических свойств для ваших переменных, которые должны быть сопоставлены с селектором XPath, и их декорирование с использованием вашего пользовательский атрибут. Например:

[MapTo("/ship/@name")]
public string Name { get; set; }

[MapTo("/ship/base_type")]
public string BaseType { get; set; }

Затем, после загрузки вашего XML-документа, напишите цикл, который использует отражение, чтобы перебрать каждое из этих свойств и установить их значение на основе их связанного селектора XPath. Например, предполагая, что пользовательский атрибут объявлен следующим образом:

[AttributeUsage(AttributeTargets.Property)]
public class MapToAttribute : Attribute
{
    public string XPathSelector { get; private set; }

    public MapToAttribute(string xPathSelector)
    {
        XPathSelector = xPathSelector;
    }
}

Тогда ваш цикл, который выполняет сопоставление, предполагая, что он находится где-то внутри метода экземпляра в классе, содержащем ваши сопоставленные свойства (если нет, замените this на целевой объект), будет выглядеть так:

foreach (var property in this.GetType().GetProperties())
{
    var mapToAttribute = property.GetCustomAttributes(typeof(MapToAttribute), false).SingleOrDefault() as MapToAttribute;
    if (mapToAttribute != null)
    {
        property.SetValue(this, doc.SelectSingleNode(mapToAttribute.XPathSelector).Value);
    }
}
0 голосов
/ 05 марта 2012

Это может показаться странным для вас, но если вы собираетесь использовать огромное количество данных, то лично я бы выбрал решение для автоматически генерируемого кода.

Значение: сопоставить все ваши имена XML -> с их выражениями XPath -> для отображения имен и иметь что-то, что автоматически сгенерирует класс данных для вас.

Например:

Допустим, входной файл имеет формат CSV (или разделенный табуляцией и т. Д.):

Отображаемое, XMLField, XPathExpr
Имя, фамилия, / корабль / @ имя
Base_type, base_type, / корабль / base_type
GFX, GFX, / корабль / GFX

Теперь вам понадобится некоторый процесс, который будет автоматически генерировать из него код. Для этого вы можете разработать служебную программу для C #, использовать некоторый язык сценариев, например Perl, или даже создать какой-нибудь перевод XSL.

Конечный результат будет выглядеть примерно так:

class AutoGeneratedShipData
{
    public AutoGeneratedShipData(XmlDocument xmlDoc)
    {
        // Code initialization like in your sample
    }

    public string Name ...
    public string Base_type ...
    public string GFX ...
}

Вы можете продолжить и добавить сериализацию, поддержку INotifyPropertyChanged и другие украшения, которые вы считаете нужными.

Другой подход

Было бы использовать отражение для загрузки данных в свойства, но для этого вам нужно будет вручную создать свойство для каждого элемента данных, а также отображение данных.

Вот пример:

LoadData(xmlDoc, "Name", "/ship/@name");
LoadData(xmlDoc, "Base_type", "/ship/base_type");
LoadData(xmlDoc, "GFX", "/ship/GFX");

Где LoadData() будет что-то вроде:

private void LoadData(XmlDocument xmlDoc, Dictionary<string, string> propertyNameToXPathMap)
{
    foreach ( PropertyInfo pi in this.GetType().GetProperties() )
    {
        // Is the property mapped to an xpath?
        if ( propertyNameToXPathMap.ContainsKey(pi.Name) )
        {
            string sPathExpression = propertyNameToXPathMap[pi.Name];

            // Extract the Property's value from XML based on the xpath expr.
            string value = xmlDoc.SelectSingleNode(sPathExpression).Value;

            // Set this object's property's value
            pi.SetValue(this, value, null);
        }
    }
}
  • Обратите внимание, что я игнорирую ваш paths словарь, потому что не вижу особой роли для него.
...