Цикл и редактирование через дочерние элементы XML на основе значений

#excel #xml #vba

#excel #xml #vba

Вопрос:

У меня есть интерфейс для циклического просмотра дочерних элементов XML и их редактирования. Что-то вроде этого:

XML

XML-файл выглядит следующим образом:

  <?xml version="1.0"?>
    <catalog>
       <query id="bk100">
          <question>Do we have Docker security?</question>
          <answer>Yes</answer>
          <comment>None</comment>
          <genre>Cloud</genre>
       </query>
       <query id="bk101">
          <question>Do we have cloud security</question>
          <answer>Yes</answer>
          <comment>None</comment>
          <genre>SCPC</genre>
       </query>
       <query id="bk100">
          <question>Do we have Kubernetos security?</question>
          <answer>Yes</answer>
          <comment>None</comment>
          <genre>Cloud</genre>
       </query>
    </catalog>
  

Я считываю и сохраняю дочерние элементы как таковые в Global variabes :

 xmlUrl = ThisWorkbook.Path amp; "Blah.xml"
oXMLFile.Load (xmlUrl)
Set QuestionNodes = oXMLFile.SelectNodes("/catalog/query/question/text()")
  

Теперь, когда пользователь выбирает Genre из внутреннего интерфейса (например, используя поле со списком или что-то еще), SCPC я хочу, чтобы кнопки next и previous позволяли просто перебирать вопросы и ответы (и редактировать их) в Genre SCPC

так, например, псевдо-реализация для `Следующей кнопки» будет выглядеть как:

 'Next XML Node Iterartor
Private Sub btnNextEntry_Click()
   Interate Where GenreNodes(i).NodeValue = "SCPC"
        txtQuestion.Value = QuestionNodes(i).NodeValue
        Pause 'When the user clicks Next again, the Next Node Data Is Showed
End Sub
  

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

Есть какие-либо советы, идеи, как я могу справиться с этим? Спасибо.

Комментарии:

1. Создайте функцию, которая возвращает коллекцию узлов, специфичных для жанра, из вашего глобального QuestionNodes , затем используйте кнопки для навигации по этой отфильтрованной коллекции. Кроме того, разделите пользовательский интерфейс для редактирования на LoadNode и SaveNode методы — тогда у вас будут единственные точки соприкосновения между вашими узлами и пользовательским интерфейсом, который вы можете вызывать из других ваших методов.

2. @TimWilliams Я не понимаю, как это сделать, какими должны быть типы данных и реализация для сохранения? Какие-либо указания? Что я планировал использовать <query id="bk100"> свойство id в качестве первичного ключа, а затем использовать коллекцию для хранения всего ids для определенного жанра, но я понял, что это будет очень медленно. Также большой проблемой будет, когда я попытаюсь реализовать функцию сохранения: (

3. @TimWilliams о разделении LoadNode и SaveNode пользовательского интерфейса — не могли бы вы рассказать немного подробнее? Я не могу этого понять.

4. LoadNode передается узлу и заполняет текстовые поля пользовательской формы значениями дочерних элементов узла. SaveNode выполняется обратное.

5. @TimWilliams Не могли бы вы, пожалуйста, дать какой-нибудь псевдокод? Для передачи данных между формами я обычно использую временный файл. Это действительно помогло бы мне.

Ответ №1:

Использование Set QuestionNodes = oXMLFile.SelectNodes("/catalog/query/question/text()") для списка вопросов затрудняет фильтрацию, чем это должно быть. Проще использовать список узлов запроса, а затем обращаться к дочерним узлам по мере необходимости.

Итак, если вы хотите перечислить все узлы, используйте:

 Dim queryNodes As IXMLDOMNodeList
' ...
Set queryNodes = oXmlFile.SelectNodes("/catalog/query")
  

и затем вы могли бы работать со значениями дочерних узлов следующим образом:

 Dim node As IXMLDOMNode

For Each node In queryNodes
    Debug.Print "Q: " amp; node.SelectSingleNode("question").Text amp; vbCrLf amp; _
        "A: " amp; node.SelectSingleNode("answer").Text amp; vbCrLf amp; _
        "C: " amp; node.SelectSingleNode("comment").Text amp; vbCrLf amp; _
        "G: " amp; node.SelectSingleNode("genre").Text amp; vbCrLf amp; vbCrLf
Next node
  

Если затем вы хотели работать только с узлами, жанр которых «SCPC», тогда это просто случай изменения queryNodes списка, например:

 Set queryNodes = oXmlFile.SelectNodes("/catalog/query[genre='SCPC']")
  

Код для доступа к дочерним узлам не меняется только потому, что мы по-другому отфильтровали список. Все изменения содержатся в том, как мы создаем queryNodes список. Код для обновления queryNodes может быть вызван из обработчика событий для выпадающего списка, который позволяет пользователю выбирать жанр.

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

 Sub printNode(node As IXMLDOMNode)

Debug.Print "Q: " amp; node.SelectSingleNode("question").Text amp; vbCrLf amp; _
    "A: " amp; node.SelectSingleNode("answer").Text amp; vbCrLf amp; _
    "C: " amp; node.SelectSingleNode("comment").Text amp; vbCrLf amp; _
    "G: " amp; node.SelectSingleNode("genre").Text amp; vbCrLf amp; vbCrLf

End Sub
  

Чтобы управлять тем, какой узел отображается через ваш интерфейс, используйте Item свойство queryNodes списка. Первый узел — это queryNodes.Item(0) , следующий — это queryNodes.Item(1) и так далее.

Если мы используем переменную с именем position для отслеживания того, где мы находимся в списке, тогда предыдущая кнопка в вашем интерфейсе должна производить position = position - 1 , а ваша следующая кнопка должна производить position = position 1 .

Итак, как только пользователь нажимает «Предыдущий» или «Следующий», мы бы обновили, position а затем вызвали printNode queryNodes.Item(position) . Всегда есть вероятность, что мы вышли за пределы либо начала, либо конца списка, и это можно проверить с помощью If Not queryNodes.Item(position) Is Nothing , прежде чем мы попытаемся вызвать printNode .

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

saveNode Функция была бы просто обратной этой — скопируйте значение каждого поля в вашем интерфейсе в свойство text соответствующего дочернего узла

Комментарии:

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

2. Для всех, кому интересно, вот как вы пишете это в коде: oXmlFile.SelectNodes("/catalog/query[genre='" amp; var_name amp; "']")