C # Найти и заменить узлы XML - PullRequest
4 голосов
/ 22 июля 2011

Редактировать: Я решил использовать подход LINQ to XML (см. Ответ ниже), который был рекомендован, и все работает, КРОМЕ ТОГО, что я не могу заменить измененные записи записями из инкрементного файла , Мне удалось заставить программу работать, просто удалив полный файловый узел, а затем добавив инкрементный узел. Есть ли способ просто поменять их местами? Кроме того, хотя это решение очень хорошо, есть ли способ уменьшить использование памяти без потери кода LINQ? Это решение может все еще работать, но я хотел бы пожертвовать временем, чтобы уменьшить использование памяти.


Я пытаюсь взять два файла XML (полный файл и инкрементный файл) и объединить их вместе. Файл XML выглядит следующим образом:

<List>
    <Records>
        <Person id="001" recordaction="add">
            ...
        </Person>
    </Records>
</List>

Атрибутом recordaction также может быть «chg» для изменений или «del» для удалений. Основная логика моей программы:

1) Считайте полный файл в XmlDocument.

2) Считайте инкрементный файл в XmlDocument, выберите узлы с помощью XmlDocument.SelectNodes (), поместите эти узлы в словарь для облегчения поиска.

3) Выберите все узлы в полном файле, выполните цикл и сверяйте каждый со словарем, содержащим добавочные записи. Если recordaction = "chg" или "del" добавить узел в список, то удалите все узлы из списка XmlNodeList, которые находятся в этом списке. Наконец, добавьте recordaction = "chg" или "add" записей из инкрементного файла в полный файл.

4) Сохраните файл XML.

У меня серьезные проблемы с шагом 3. Вот код этой функции:

private void ProcessChanges(XmlNodeList nodeList, Dictionary<string, XmlNode> dictNodes)
    {
        XmlNode lastNode = null;
        XmlNode currentNode = null;
        List<XmlNode> nodesToBeDeleted = new List<XmlNode>();

        // If node from full file matches to incremental record and is change or delete, 
        // mark full record to be deleted.
        foreach (XmlNode fullNode in fullDocument.SelectNodes("/List/Records/Person"))
        {
            dictNodes.TryGetValue(fullNode.Attributes[0].Value, out currentNode);
            if (currentNode != null)
            {
                if (currentNode.Attributes["recordaction"].Value == "chg"
                    || currentNode.Attributes["recordaction"].Value == "del")
                {
                    nodesToBeDeleted.Add(currentNode);
                }
            }
            lastNode = fullNode;
        }

        // Delete marked records
        for (int i = nodeList.Count - 1; i >= 0; i--)
        {
            if(nodesToBeDeleted.Contains(nodeList[i]))
            {
                nodeList[i].ParentNode.RemoveChild(nodesToBeDeleted[i]);
            }
        }

        // Add in the incremental records to the new full file for records marked add or change.
        foreach (XmlNode weeklyNode in nodeList)
        {
            if (weeklyNode.Attributes["recordaction"].Value == "add"
                || weeklyNode.Attributes["recordaction"].Value == "chg")
            {
                fullDocument.InsertAfter(weeklyNode, lastNode);
                lastNode = weeklyNode;
            }
        }
    }

Передаваемый XmlNodeList - это просто все инкрементные записи, которые были выбраны из инкрементного файла, а словарь - это те же самые узлы, но и ключ по идентификатору, поэтому мне не пришлось перебирать все инкрементные записи каждый раз. Прямо сейчас программа умирает на этапе «Удалить отмеченные записи» из-за индексации за пределами. Я почти уверен, что «Добавить в инкрементные записи» тоже не работает. Есть идеи? Также было бы неплохо сделать несколько предложений по повышению эффективности. Потенциально я мог столкнуться с проблемой, потому что она считывает файл размером 250 МБ, объем которого в памяти увеличивается до 750 МБ, поэтому мне было интересно, есть ли более простой способ перейти от узла к узлу в полном файле. Спасибо!

1 Ответ

5 голосов
/ 22 июля 2011

Вот пример того, как вы можете сделать это с помощью LINQ-to-XML.Словарь не нужен:

using System.Xml.Linq;

// Load the main and incremental xml files into XDocuments
XDocument fullFile = XDocument.Load("fullfilename.xml");
XDocument incrementalFile = XDocument.Load("incrementalfilename.xml");    

// For each Person in the incremental file
foreach (XElement person in incrementalFile.Descendants("Person")) {

    // If the person should be added to the full file
    if (person.Attribute("recordaction").Value == "add") {
        fullFile.Element("List").Element("Records").Add(person); // Add him
    }

    // Else the person already exists in the full file
    else {
        // Find the element of the Person to delete or change
        var personToChange =
                (from p in fullFile.Descendants("Person")
                    where p.Attribute("id").Value == person.Attribute("id").Value
                    select p).Single();

        // Perform the appropriate operation
        switch (person.Attribute("recordaction").Value) {
            case "chg":
                personToChange.ReplaceWith(person);
                break;
            case "del":
                personToChange.Remove();
                break;
            default:
                throw new ApplicationException("Unrecognized attribute");
        }
    }
}// end foreach

// Save the changes to the full file
fullFile.Save("fullfilename.xml");

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

EDIT: исправлен случай "chg" для использования personToChange.ReplaceWith(person) вместо personToChange = person.Последний ничего не заменяет, так как он просто перемещает ссылку от базового документа.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...