PowerShell: улучшаю преобразование данных из XML-RPC в хеш-таблицу

Feb 12, 2023 00:03

Ранее в этой серии постов:
...
15. PowerShell и ЖЖ: аутентификация challenge-response, ч.2 (практика через интерфейс «flat»)
16. PowerShell и ЖЖ: аутентификация challenge-response, ч.3 (практика через интерфейс «XML-RPC»)
17. PowerShell и ЖЖ: аутентификация с «cookie», интерфейс «flat»

Окружение: операционная система «Windows 10», программа-оболочка «PowerShell» версии 7.

Работая из скрипта на языке PowerShell с веб-сервером ЖЖ («Живого Журнала») через интерфейс «XML-RPC», я в нескольких предыдущих постах ( например) написал следующие две функции для преобразования ответа веб-сервера ЖЖ из формата «XML-RPC» в более удобную для дальнейшей работы с данными хеш-таблицу (ассоциативный массив):

function toHashTable1($str) {
$xml = [xml]$str
$arr = $xml.methodResponse.params.param.value.struct.member
$hash = @{}
for ($i = 0; $i -lt $arr.Length; $i++) {
$hash[$arr[$i].name] = $arr[$i].value.FirstChild."#text"
}
return $hash
}

function toHashTable2($str) {
$xml = [xml]$str
$arr = $xml.methodResponse.params.param.value.struct.member
$events = ($arr.where{ $_.name -eq "events" })[0] # добавлено
$arr = $events.value.array.data.value.struct.member # добавлено
$hash = @{}
for ($i = 0; $i -lt $arr.Length; $i++) {
$hash[$arr[$i].name] = $arr[$i].value.FirstChild."#text"
}
return $hash
}

Пример использования этих функций в дальнейшем коде (многоточиями обозначен другой код, на данный момент нам неинтересный):

...
$params = toHashTable1($Response.Content)
...
$params = toHashTable2($Response.Content)
...

В чем проблема этих функций

Вторая функция отличается от первой только дополнительными двумя строками (эти строки помечены комментарием в блоке кода выше). Интуитивно понятно, что из этих двух функций лучше сделать одну универсальную. Когда я писал эти функции, меня больше интересовало подтверждение на практике принципиальной возможности работы с веб-сервером ЖЖ из программы-оболочки «PowerShell», поэтому я писал эти функции на скорую руку, понимая, что в будущем их придется переписать.

Главная проблема этих функций в том, что они негибкие, не могут подстроиться к входящей строке в формате «XML-RPC». Эти функции могут обработать только те структуры данных в формате «XML-RPC», для которых они были написаны первоначально. На практике мы можем получить от веб-сервера ЖЖ данные со структурой в формате «XML-RPC», организованной по-другому, и эти функции тогда не справятся (придется дописывать третью, четвертую и так далее функции, если продолжать действовать в том же стиле).

Улучшенная одна универсальная функция вместо двух не универсальных

Понятно, что нахождение нужного XML-узла с хеш-таблицей (struct) нужно вынести в параметр функции. Сначала я хотел сделать что-то вроде передачи в функцию нужного блока инструкций (script block) для нахождения нужного XML-узла, но потом решил почитать об обработке данных в формате XML побольше и узнал, что существует язык запросов к элементам XML-документа, который называется «XPath». Насколько я понимаю, этот язык как раз и придумали для решения в том числе таких проблем, как описанная выше в этом посте.

В программе-оболочке «PowerShell» для работы с данными в формате XML с помощью языка запросов XPath есть специальный командлет «Select-Xml». Этот командлет может принимать на вход как объект класса «System.Xml.XmlDocument», так и данные в формате XML в виде обычной строки (объект класса «System.String»). Как можно увидеть в блоках кода выше, в описываемых функциях я сначала преобразую полученную строку в XML-документ (объект класса «System.Xml.XmlDocument»). Но при работе с командлетом «Select-Xml» это можно не делать, а сразу начинать работу со строкой, что уже дает небольшое упрощение функций.

Итак, вот какой код одной универсальной функции вместо двух описанных выше у меня получился:

function toHashTable($strXml, $xpath) {
$arr = (Select-Xml -Content $strXml -XPath $xpath).node
$hash = @{}
for ($i = 0; $i -lt $arr.Length; $i++) {
$hash[$arr[$i].name] = $arr[$i].value.FirstChild."#text"
}
return $hash
}

Как видно из блока кода выше, на вход обновленной функции «toHashTable» теперь подается не один параметр со строкой, а два параметра. Первый параметр содержит ту же строку, что и параметр двух старых функций. Во второй параметр мы должны будем передать строку с запросом на языке запросов XPath. Внутри функции «toHashTable» ее два входящих параметра сразу передаются на вход командлету «Select-Xml».

Командлет «Select-Xml» возвращает объект класса «Microsoft.PowerShell.Commands.SelectXmlInfo» (если результатом запроса на языке запросов XPath является один XML-узел) или массив объектов этого же класса (если результатом запроса на языке запросов XPath является множество XML-узлов). Каждый такой объект содержит в своем свойстве «Node» один XML-узел, соответствующий запросу на языке запросов XPath. Таким образом, после первой строки обновленной функции:

$arr = (Select-Xml -Content $strXml -XPath $xpath).node

Мы получаем в переменной $arr тот же результат, что и в старых функциях. Поэтому остаток обновленной функции в точности совпадает с той же частью старых функций. В переменную $arr попадает один объект класса «System.Xml.XmlNode» (если результатом запроса является один XML-узел) или массив объектов этого класса (если результатом запроса является множество XML-узлов).

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

function toHashTable($strXml, $xpath) {
$arr = (Select-Xml -Content $strXml -XPath $xpath).node
$hash = @{}
if ($arr.GetType().FullName -eq "System.Xml.XmlElement") {
$hash[$arr.name] = $arr.value.FirstChild."#text"
} else {
for ($i = 0; $i -lt $arr.Length; $i++) {
$hash[$arr[$i].name] = $arr[$i].value.FirstChild."#text"
}
}
return $hash
}

После этого я задумался, а зачем, собственно, тут реализована такая вещь, что командлет может возвращать либо один объект, либо массив объектов? (Можно же было в случае одного объекта возвратить массив с одним элементом-объектом.) И понял, что это сделано для использования в конвейере (pipeline) из нескольких командлетов. То есть в блоке кода выше я просто «изобрел велосипед», вместо того, чтобы использовать конвейер. Я переписал функцию следующим образом с использованием конвейера и командлета «ForEach-Object»:

function toHashTable($strXml, $xpath) {
$arr = (Select-Xml -Content $strXml -XPath $xpath).node
$hash = @{}
$res = $arr | ForEach-Object { $hash[$_.name] = $_.value.FirstChild."#text" }
return $hash
}

Командлет «ForEach-Object» без проблем принимает с конвейера хоть один объект (XML-узел), хоть массив объектов (множество XML-узлов). Это здорово упрощает нашу функцию. Переменная $_ - это встроенная переменная, в ней хранится очередной объект, пришедший с конвейера. В итоге функция сократилась всего до четырех строк. Можно ее минимизировать и дальше, но меня пока устраивает, как она выглядит сейчас.

Как использовать обновленную функцию

...
$xpath = "/methodResponse/params/param/value/struct/*"
$params = toHashTable $Response.Content $xpath
...
$xpath = "/methodResponse/params/param/value/struct/member/value/array/data/value/struct/*"
$params = toHashTable $Response.Content $xpath
...

Как видно из блока кода выше, использование несколько усложнилось, так как теперь нам нужно еще передавать в функцию «toHashTable» второй параметр, содержащий запрос на языке запросов XPath. Раньше этот запрос был фиксированно реализован в коде функций, что помешало бы в будущем, при появлении запросов со структурой, не предусмотренной в старых функциях.

Из примеров, показанных в блоке кода выше, можно понять, как пользоваться языком запросов XPath. Для быстрого начального входа в этот язык достаточно прочитать русскоязычную статью википедии по языку XPath. Там даны довольно понятные объяснения, мне хватило для того, чтобы начать пользоваться языком запросов XPath. Для серьезной работы с этим языком запросов, вероятно, понадобится почитать что-нибудь пообъемнее.

Инструмент, Образование, Сайтостроение, Программирование, Английский язык, ЖЖ

Previous post Next post
Up