Большой XML-файл, XmlDocument невозможен, но должен иметь возможность поиска - PullRequest
3 голосов
/ 17 февраля 2009

Я борюсь с разумным логическим циклом для удаления узлов из файла XML, слишком большого для использования с XPath, поддерживающим классы .NET.

Я пытаюсь заменить одну строку кода, которая была у меня (которая называлась SelectNodes строкой запроса XPath), кодом, который делает то же самое, но использует XmTextReader.

Мне нужно пройти несколько уровней вниз, как показано ранее используемым запросом XPath (который был для справки):

ConfigurationRelease/Profiles/Profile[Name='MyProfileName']/Screens/Screen[Id='MyScreenId']/Settings/Setting[Name='MySettingName']

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

Мне нужно получить узел, проверить узел под ним, чтобы увидеть, соответствует ли значение целевой строке, и затем идти дальше, если это так, или пропустить эту ветку, если это не так.

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

Я думал, что смогу поддерживать некоторые логические значения, например bool ОжидаетProfileName, который устанавливается в значение true, когда я нажимаю на узел профиля. Однако, если это не тот конкретный узел профиля, который я хочу, я не могу выйти из этой ветви.

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

Я хотел бы опубликовать часть файла, но не могу понять, какова структура примерно:

ConfigRelease > Profiles > Profile > Name > Screens > Screen > Settings > Setting > Name

Я буду знать ProfileName, ScreenName и SettingName, и мне нужен узел Setting.

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

Любые советы будут с благодарностью.

UPDATE

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

Файл невелик по большинству стандартов, поэтому система изначально была закодирована для использования XmlDocument. В настоящее время он составляет 4 МБ, что, по-видимому, достаточно велико, чтобы вывести из строя мобильное приложение при загрузке в XmlDocument. Это, вероятно, так же хорошо, как он появился сейчас, так как файл рассчитан на гораздо больший размер. Во всяком случае, сейчас я пробую предложение DataSet, но все еще открыта для других идей.

ОБНОВЛЕНИЕ 2

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

Ответы [ 6 ]

9 голосов
/ 17 февраля 2009

Посмотрите на <a href="http://msdn.microsoft.com/en-us/library/system.xml.xpath.xpathdocument.aspx" rel="noreferrer">XPathDocument</a>.

XPathDocument более легкий, чем XmlDocument, и оптимизирован для запросов XPath, доступных только для чтения.

3 голосов
/ 24 февраля 2009

Хорошо, меня это развлекало, поэтому я взломал код вместе. Это не красиво и только действительно поддерживает этот вариант использования, но я думаю, что он выполняет ту работу, которую вы ищете, и действует как достойная платформа для начала. Я также не проверил это полностью. Наконец, вам нужно изменить код, чтобы он возвращал содержимое (см. Метод Output ()).

Вот код:

using System;

using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;

namespace XPathInCE
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                if (args.Length != 2)
                {
                    ShowUsage();
                }
                else
                {
                    Extract(args[0], args[1]);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("{0} was thrown", ex.GetType());
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
            }

            Console.WriteLine("Press ENTER to exit");
            Console.ReadLine();
        }

        private static void Extract(string filePath, string queryString)
        {
            if (!File.Exists(filePath))
            {
                Console.WriteLine("File not found! Path: {0}", filePath);
                return;
            }

            XmlReaderSettings settings = new XmlReaderSettings { IgnoreComments = true, IgnoreWhitespace = true };
            using (XmlReader reader = XmlReader.Create(filePath, settings))
            {
                XPathQuery query = new XPathQuery(queryString);
                query.Find(reader);
            }
        }

        static void ShowUsage()
        {
            Console.WriteLine("No file specified or incorrect number of parameters");
            Console.WriteLine("Args must be: Filename XPath");
            Console.WriteLine();
            Console.WriteLine("Sample usage:");
            Console.WriteLine("XPathInCE someXmlFile.xml ConfigurationRelease/Profiles/Profile[Name='MyProfileName']/Screens/Screen[Id='MyScreenId']/Settings/Setting[Name='MySettingName']");
        }

        class XPathQuery
        {
            private readonly LinkedList<ElementOfInterest> list = new LinkedList<ElementOfInterest>();
            private LinkedListNode<ElementOfInterest> currentNode;

            internal XPathQuery(string query)
            {
                Parse(query);
                currentNode = list.First;
            }

            internal void Find(XmlReader reader)
            {
                bool skip = false;
                while (true)
                {
                    if (skip)
                    {
                        reader.Skip();
                        skip = false;
                    }
                    else
                    {
                        if (!reader.Read())
                        {
                            break;
                        }
                    }
                    if (reader.NodeType == XmlNodeType.EndElement
                            && String.Compare(reader.Name, currentNode.Previous.Value.ElementName, StringComparison.CurrentCultureIgnoreCase) == 0)
                    {
                        currentNode = currentNode.Previous ?? currentNode;
                        continue;
                    }
                    if (reader.NodeType == XmlNodeType.Element)
                    {
                        string currentElementName = reader.Name;
                        Console.WriteLine("Considering element: {0}", reader.Name);

                        if (String.Compare(reader.Name, currentNode.Value.ElementName, StringComparison.CurrentCultureIgnoreCase) != 0)
                        {
                            // don't want
                            Console.WriteLine("Skipping");
                            skip = true;
                            continue;
                        }
                        if (!FindAttributes(reader))
                        {
                            // don't want
                            Console.WriteLine("Skipping");
                            skip = true;
                            continue;
                        }

                        // is there more?
                        if (currentNode.Next != null)
                        {
                            currentNode = currentNode.Next;
                            continue;
                        }

                        // we're at the end, this is a match! :D
                        Console.WriteLine("XPath match found!");
                        Output(reader, currentElementName);
                    }
                }
            }

            private bool FindAttributes(XmlReader reader)
            {
                foreach (AttributeOfInterest attributeOfInterest in currentNode.Value.Attributes)
                {
                    if (String.Compare(reader.GetAttribute(attributeOfInterest.Name), attributeOfInterest.Value,
                                       StringComparison.CurrentCultureIgnoreCase) != 0)
                    {
                        return false;
                    }
                }
                return true;
            }

            private static void Output(XmlReader reader, string name)
            {
                while (reader.Read())
                {
                    // break condition
                    if (reader.NodeType == XmlNodeType.EndElement
                        && String.Compare(reader.Name, name, StringComparison.CurrentCultureIgnoreCase) == 0)
                    {
                        return;
                    }

                    if (reader.NodeType == XmlNodeType.Element)
                    {
                        Console.WriteLine("Element {0}", reader.Name);
                        Console.WriteLine("Attributes");
                        for (int i = 0; i < reader.AttributeCount; i++)
                        {
                            reader.MoveToAttribute(i);
                            Console.WriteLine("Attribute: {0} Value: {1}", reader.Name, reader.Value);
                        }
                    }

                    if (reader.NodeType == XmlNodeType.Text)
                    {
                        Console.WriteLine("Element value: {0}", reader.Value);
                    }
                }
            }

            private void Parse(string query)
            {
                IList<string> elements = query.Split('/');
                foreach (string element in elements)
                {
                    ElementOfInterest interestingElement = null;
                    string elementName = element;
                    int attributeQueryStartIndex = element.IndexOf('[');
                    if (attributeQueryStartIndex != -1)
                    {
                        int attributeQueryEndIndex = element.IndexOf(']');
                        if (attributeQueryEndIndex == -1)
                        {
                            throw new ArgumentException(String.Format("Argument: {0} has a [ without a corresponding ]", query));
                        }
                        elementName = elementName.Substring(0, attributeQueryStartIndex);
                        string attributeQuery = element.Substring(attributeQueryStartIndex + 1,
                                    (attributeQueryEndIndex - attributeQueryStartIndex) - 2);
                        string[] keyValPair = attributeQuery.Split('=');
                        if (keyValPair.Length != 2)
                        {
                            throw new ArgumentException(String.Format("Argument: {0} has an attribute query that either has too many or insufficient = marks. We currently only support one", query));
                        }
                        interestingElement = new ElementOfInterest(elementName);
                        interestingElement.Add(new AttributeOfInterest(keyValPair[0].Trim().Replace("'", ""),
                            keyValPair[1].Trim().Replace("'", "")));
                    }
                    else
                    {
                        interestingElement = new ElementOfInterest(elementName);
                    }

                    list.AddLast(interestingElement);
                }
            }

            class ElementOfInterest
            {
                private readonly string elementName;
                private readonly List<AttributeOfInterest> attributes = new List<AttributeOfInterest>();

                public ElementOfInterest(string elementName)
                {
                    this.elementName = elementName;
                }

                public string ElementName
                {
                    get { return elementName; }
                }

                public List<AttributeOfInterest> Attributes
                {
                    get { return attributes; }
                }

                public void Add(AttributeOfInterest attribute)
                {
                    Attributes.Add(attribute);
                }
            }

            class AttributeOfInterest
            {
                private readonly string name;
                private readonly string value;

                public AttributeOfInterest(string name, string value)
                {
                    this.name = name;
                    this.value = value;
                }

                public string Value
                {
                    get { return value; }
                }

                public string Name
                {
                    get { return name; }
                }
            }
        }
    }
}

Это тестовый ввод, который я использовал:

<?xml version="1.0" encoding="utf-8" ?>
<ConfigurationRelease>
  <Profiles>
    <Profile Name ="MyProfileName">
      <Screens>
        <Screen Id="MyScreenId">
          <Settings>
            <Setting Name="MySettingName">
              <Paydirt>Good stuff</Paydirt>
            </Setting>
          </Settings>
        </Screen>
      </Screens>
    </Profile>
    <Profile Name ="SomeProfile">
      <Screens>
        <Screen Id="MyScreenId">
          <Settings>
            <Setting Name="Boring">
              <Paydirt>NOES you should not find this!!!</Paydirt>
            </Setting>
          </Settings>
        </Screen>
      </Screens>
    </Profile>
    <Profile Name ="SomeProfile">
      <Screens>
        <Screen Id="Boring">
          <Settings>
            <Setting Name="MySettingName">
              <Paydirt>NOES you should not find this!!!</Paydirt>
            </Setting>
          </Settings>
        </Screen>
      </Screens>
    </Profile>
    <Profile Name ="Boring">
      <Screens>
        <Screen Id="MyScreenId">
          <Settings>
            <Setting Name="MySettingName">
              <Paydirt>NOES you should not find this!!!</Paydirt>
            </Setting>
          </Settings>
        </Screen>
      </Screens>
    </Profile>
  </Profiles>
</ConfigurationRelease>

И это вывод, который я получил.

C:\Sandbox\XPathInCE\XPathInCE\bin\Debug>XPathInCE MyXmlFile.xml ConfigurationRe
lease/Profiles/Profile[Name='MyProfileName']/Screens/Screen[Id='MyScreenId']/Set
tings/Setting[Name='MySettingName']
Considering element: ConfigurationRelease
Considering element: Profiles
Considering element: Profile
Considering element: Screens
Considering element: Screen
Considering element: Settings
Considering element: Setting
XPath match found!
Element Paydirt
Attributes
Element value: Good stuff
Considering element: Profile
Skipping
Considering element: Profile
Skipping
Considering element: Profile
Skipping
Press ENTER to exit

Я запустил его на рабочем столе, но это был CF 2.00 .exe, который я сгенерировал, поэтому он должен нормально работать на CE. Как вы можете видеть, он пропускается, когда не совпадает, поэтому не будет проходить весь файл.

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

2 голосов
/ 25 февраля 2009

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

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

Его вердикт заключается в том, что мы должны были реализовать собственный формат файла (с индексированием), оптимизированный для размера и скорости запроса.

Итак, проблема приостановлена ​​до тех пор, пока эта работа не будет одобрена и должным образом определена.

Конец пока.

2 голосов
/ 17 февраля 2009

Попробуйте загрузить файл в набор данных:

DataSet ds = new Dataset();
ds.ReadXml("C:\MyXmlFile.xml")

Тогда вы можете использовать linq для поиска.

0 голосов
/ 09 марта 2009

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

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

Недостатком является то, что это немного пользовательского программирования.

Положительным моментом является то, что вы будете читать только то, что вас интересует, и обрабатывать XML-документ в соответствии с вашими требованиями. Вы также можете начать обработку результатов перед завершением прохождения документа. Это отлично подходит для запуска рабочих потоков на основе содержимого документа. Пример: вы можете взять все содержимое элемента в качестве корня другого XML-документа, а затем загрузить его отдельно (использовать xpath и т. Д.). Вы можете скопировать содержимое в буфер, а затем передать его работнику для обработки и т. Д.

Я использовал это давно, используя libxml2 для C, но также есть привязки C # (и многие другие языки).

0 голосов
/ 09 марта 2009

Загрузка его в набор данных не будет работать - это займет еще больше памяти.

Когда сталкивался с подобным, я использовал XmlReader и строил индекс в памяти во время загрузки. Я представил индекс, а затем, когда пользователь нажимает на ссылку или активирует поиск, затем я перечитываю документ XML, снова с помощью XmlReader, и загружаю соответствующее подмножество.

Это звучит кропотливо, и я думаю, в некотором смысле. Он обменивает циклы процессора на память. Но это работает, и приложение достаточно отзывчиво. Размер данных всего 2 МБ, не такой большой. но я получал OOM с набором данных. Затем я пошел к XmlSerializer, и это какое-то время работало, но снова я ударил OOM. Поэтому я, наконец, вернулся к этой вещи с пользовательским индексом.

...