Начало:
1.
Общение с ЖЖ из скрипта по протоколу HTTP(S), ч.1: документация2.
Общение с ЖЖ из скрипта по протоколу HTTP(S), ч.2: URL-адрес входа, интерфейсы3.
Выбор веб-клиента для общения с ЖЖ из скрипта, PowerShell, моделирование4.
PowerShell: особенности использования командлета «Invoke-WebRequest»5.
Общение с ЖЖ из скрипта по протоколу HTTP(S), ч.3: первая успешная связь6.
PowerShell: помещаем тело HTTP-ответа ЖЖ в хеш-таблицу (интерфейс «flat») Окружение: операционная система «Windows 10», программа-оболочка «PowerShell» версии 7.
Напомню, в
одном из предыдущих постов я получил по сети от веб-сервиса ЖЖ («LiveJournal») по
протоколу общения с ЖЖ (через интерфейс «XML-RPC») на базе протокола HTTP(S) в теле HTTP-ответа строку в формате
XML:
блок кода 1
PS C:\> $Response.Content
auth_schemec0server_time1674771448challengec0:1674770400:1048:60:yeM13Zf4UeujVPDIapTv:03d7b6a66990e95ba17ced533b9b98d2expire_time1674771508
Наверное, сразу стоит отметить, что формат данных, передаваемых по интерфейсу «XML-RPC», можно рассматривать как некую надстройку над форматом XML, потому что для удобной работы с данными по интерфейсу «XML-RPC» недостаточно инструментов работы с форматом XML, нужна дополнительная обработка (дополнительные функции, может, дополнительная библиотека функций). В программе-оболочке «PowerShell» по умолчанию нет таких дополнительных функций для работы с данными в формате «XML-RPC», но существуют сторонние библиотеки с такими функциями (я пока что не буду пользоваться этими сторонними библиотеками). Поэтому будем пока что обходиться теми инструментами для работы с форматом XML, которые есть в программе-оболочке «PowerShell» по умолчанию.
Я сейчас рассматриваю два удобных инструмента для работы с форматом XML в программе-оболочке «PowerShell»: «
System.Xml» и «
System.Xml.Linq» - это наборы классов (и не только) из платформы «.NET», все прелести которой, напомню, легко доступны из языка «PowerShell». Я на данный момент плохо разбираюсь в этих наборах и не очень понимаю, почему их существует два для одной и той же области применения. Но буду пытаться использовать их для работы с форматом XML.
Визуализация XML-дерева
С точки зрения программы строка, показанная в блоке кода выше, ясна и понятна. Поэтому в скрипте (программе) сразу можно приступить к обработке этой строки, то есть получению из нее хеш-таблицы, как мы это делали в
предыдущем посте для строки, полученной по интерфейсу «flat». Но для того, чтобы объяснения в этом посте были понятны человеку, думаю, следует привести полученную строку в удобочитаемый для человека вид (в скрипте мы это делать не будем). Вот как это можно сделать с помощью одного из классов набора «System.Xml.Linq»:
блок кода 2
PS C:\> [System.Xml.Linq.XDocument]::Parse($Response.Content).ToString()
auth_scheme
c0
server_time
1674771448
challenge
c0:1674770400:1048...
expire_time
1674771508
Я заменил часть значения параметра «challenge» многоточием, чтобы код влез в формат поста. Думаю, понятно, что в программе-оболочке «PowerShell» это значение будет доступно полностью.
Люди, привыкшие работать с HTML-деревом или с XML-деревом, легко прочитают содержимое блока кода выше. Полученная визуализация XML-дерева помогает понять, как работать с этими данными дальше. Формат этого XML-дерева описан в
спецификации протокола «XML-RPC».
Получение и исследование XML-дерева
Вернемся к строке в блоке кода 1. В терминах формата XML эта строка содержит XML-документ. Для работы с XML-документом в наборе классов «
System.Xml» есть класс «
System.Xml.XmlDocument». У названия этого класса есть псевдоним (alias) - «xml». Используя этот псевдоним, преобразуем нашу строку в объект класса «System.Xml.XmlDocument»:
PS C:\> $xml = [xml]$Response.Content
Теперь в переменной $xml содержится объект класса «System.Xml.XmlDocument» и к этой переменной мы можем применять методы данного класса, а также пользоваться свойствами, присущими объектам данного класса.
Кстати, при изучении объектов рекомендую пользоваться методом «
GetType» класса «
System.Object». Этот класс является корнем системы классов платформы «.NET», поэтому метод «GetType» наследуется всеми классами от корневого класса «System.Object». Например, этот метод можно использовать для изучения наших данных следующим образом:
PS C:\> ($Response.Content).GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object
PS C:\> $xml = [xml]$Response.Content
PS C:\> $xml.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False XmlDocument System.Xml.XmlNode
Из блока кода выше видно, как наша строка (объект класса «System.String») трансформировалась в объект класса «System.Xml.XmlDocument». Понимание того, какого типа (класса) у нас переменная, дает понимание того, какие методы и свойства можно использовать у данной переменной.
В строении полученного XML-документа, содержащего XML-дерево, можно разобраться следующим образом:
PS C:\> $xml
xml methodResponse
--- --------------
version="1.0" encoding="UTF-8" methodResponse
PS C:\> $xml.methodResponse
params
------
params
PS C:\> $xml.methodResponse.params
param
-----
param
И так далее, погружаясь всё глубже. На каждом шаге погружения можно проверять тип (класс) текущего объекта с помощью метода «GetType», как было показано ранее. После нескольких этапов этого погружения и проверки типов (классов) становится понятно, что XML-дерево состоит из XML-узлов разного типа (например, XML-элементы, текстовые узлы, узлы-комментарии и тому подобное, точно так же, как и у HTML-дерева). XML-узлы-дети записаны в свойства XML-узлов-родителей, у XML-узлов-детей есть свои XML-узлы-дети и так далее, так XML-дерево постепенно выстраивается целиком.
Конкретно для нашего XML-дерева объект в переменной $xml имеет класс «System.Xml.XmlDocument». XML-узлы methodResponse, params, param, value и struct имеют класс «
System.Xml.XmlElement».
Тут всё понятно и логично. Но что происходит, когда у XML-узла-родителя есть несколько XML-узлов-детей с одним и тем же именем? В нашем случае, к примеру, у XML-узла struct имеется несколько XML-узлов-детей с одним и тем же именем member. В этом случае в свойство member записывается не один объект, а массив объектов, каждый из которых имеет класс «System.Xml.XmlElement».
Получение хеш-таблицы из XML-дерева в скрипте
Опираясь на понимание структуры XML-дерева, полученное с помощью исследования, описанного выше, можно написать нужную функцию для преобразования исходной строки с текстом в формате XML в хеш-таблицу. Вот какая функция для такого преобразования у меня в итоге получилась:
function toHash($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
}
Эта функция во многом похожа на функцию, которую я написал в
предыдущем посте для интерфейса «flat».
Конструкция $arr[$i].name, думаю, должна быть понятна из объяснений, данных выше; это название (name) параметра (member). А вот конструкция $arr[$i].value.FirstChild."#text", наверное, требует дополнительных пояснений.
Думаю, понятно, что $arr[$i].value - это значение (value) параметра (member). Но обратите внимание на XML-дерево, визуализированное в блоке кода 2 выше. Там видно, что у XML-узлов с названием value есть дополнительный XML-узел-ребенок, у которого может быть либо название int, либо название string. Если бы я осуществлял доступ к этому дополнительному XML-узлу-ребенку по его названию, то пришлось бы написать более сложное выражение с применением оператора ветвления if.
Ранее у меня
был опыт работы с HTML-деревом, из которого я помнил, что у узла может присутствовать свойство для доступа к первому ребенку этого узла. Я поискал такое свойство в документации класса «
System.Xml.XmlElement» и в этой документации такое свойство не нашел. Однако, я уже знал, что этот класс имеет следующую схему наследования:
Object -> XmlNode -> XmlLinkedNode -> XmlElement
Поэтому я поискал еще в документации к классам-предкам класса «System.Xml.XmlElement» и нужное свойство с названием «
FirstChild» нашлось у класса «
System.Xml.XmlNode», это свойство наследуется классом «System.Xml.XmlElement» и поэтому я могу его использовать в конструкции $arr[$i].value.FirstChild. Таким образом, я избегаю в скрипте лишнего ветвления и обращаюсь к XML-узлу-ребенку не по имени, а как к первому ребенку (в принципе, у XML-узла $arr[$i].value нашего XML-дерева есть только один ребенок).
У полученного таким образом XML-элемента есть свойство с названием #text, которое содержит нужное нам значение. Из-за того, что в названии свойства содержится специальный символ знака решетки #, пришлось взять название этого свойства в кавычки.
Проверим работу полученной функции:
PS C:\> toHash($Response.Content)
Name Value
---- -----
challenge c0:1674770400:1048...
auth_scheme c0
expire_time 1674771508
server_time 1674771448
Всё сработало успешно, хеш-таблица получена.
Продолжение следует.