Чтение XML

Перебор элементов XML-документа

Для чтения XML-документа предназначен экземпляр типа ЧтениеXml. В общем случае, читать XML-документ можно из экземпляра типа, производного от ПотокЧтения. Мы будем рассматривать чтение документа из файла. В качестве примера файла рассмотрим фрагмент манифеста универсального приложения ОС Windows. Это достаточно сложный XML-документ, который позволит увидеть основные особенности чтения таких документов.

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Package IgnorableNamespaces="uap mp build"
	xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
	xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
	xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
	xmlns:build="http://schemas.microsoft.com/developer/appx/2015/build">
  
  <mp:PhoneIdentity
		PhoneProductId="A588A326-FD4F-441C-83B2-AA0B8554C548"
		PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
  <Properties>
    <DisplayName>ms-resource:AppName</DisplayName>
    <PublisherDisplayName>1C LLC</PublisherDisplayName>
    <Logo>icon_50x50.png</Logo>
  </Properties>
  <Resources>
    <Resource Language="EN-US"/>
  </Resources>
  <Capabilities>
    <uap:Capability Name="appointments"/>
    <uap:Capability Name="contacts"/>
    <Capability Name="internetClient"/>
    <Capability Name="privateNetworkClientServer"/>
    <Capability Name="internetClientServer"/>
    <uap:Capability Name="musicLibrary"/>
    <uap:Capability Name="picturesLibrary"/>
    <uap:Capability Name="removableStorage"/>
    <uap:Capability Name="videosLibrary"/>
    <DeviceCapability Name="location"/>
    <DeviceCapability Name="webcam"/>
    <DeviceCapability Name="microphone"/>
    <DeviceCapability Name="proximity"/>
  </Capabilities>
  <build:Metadata>
    <build:Item Name="cl.exe" Version="19.16.27030.1 built by: vcwrkspc"/>
    <build:Item Name="VisualStudio" Version="15.0"/>
    <build:Item Name="OperatingSystem" Version="6.3.9600.16384 (winblue_rtm.130821-1623)"/>
    <build:Item Name="Microsoft.Build.AppxPackage.dll" Version="15.0.28307.104"/>
    <build:Item Name="ProjectGUID" Value="{15630E4C-5D90-44AB-9CF0-FBC27700DDAA}"/>
    <build:Item Name="OptimizingToolset" Value="None"/>
    <build:Item Name="TargetRuntime" Value="Native"/>
    <build:Item Name="Microsoft.Windows.UI.Xaml.Build.Tasks.dll" Version="15.0.28307.102"/>
    <build:Item Name="WindowsMobile" Version="10.0.17763.0"/>
    <build:Item Name="MakePri.exe" Version="10.0.17763.132 (WinBuild.160101.0800)"/>
  </build:Metadata>
</Package>
Рассматриваемый экземпляр читает XML-документ строго последовательно. Каждый элемент имеет свое имя, которое отображается в свойство Имя экземпляра ЧтениеXml. Простейший пример такого чтения будет выглядеть примерно следующим образом:
метод метод ЧтениеXml(): Массив<Строка>
    пер ВременныйКаталог = СредаИсполнения.ПолучитьПеременную("temp")
    пер Файл = новый Файл(ВременныйКаталог + "\\manifest.xml")
    пер СодержимоеXML: Массив<Строка>
    пер Чтение = новый ЧтениеXml(Файл.ОткрытьПотокЧтения())
    пока Чтение.Следующий()
        // Читаем имя текущего узла
        СодержимоеXML.Добавить("Имя узла - " + Чтение.Имя)
    ;
    возврат СодержимоеXML
;

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

Имя узла - Package
Имя узла - mp:PhoneIdentity
Имя узла - mp:PhoneIdentity
Имя узла - Properties
Имя узла - DisplayName
Имя узла - 
Имя узла - DisplayName
Имя узла - PublisherDisplayName
Имя узла - 
Имя узла - PublisherDisplayName
Имя узла - Logo
Имя узла - 
Имя узла - Logo
Имя узла - Properties
...

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

Каждый элемент XML имеет открывающий и закрывающий тег. При этом оба этих тега в теле документа имеют одинаковое имя: <tag>текст</tag>. Закрывающий тег несколько отличается, но это нам сейчас не интересно. Экземпляр ЧтениеXml читает наш документ последовательно. При чтении отдельно читается начало элемента и отдельно — его окончание. А так как имена в открывающем и закрывающем тегах одинаковые, мы видим две строки с одним именем. Соответственно, если строка с именем только одна, то скорее всего, до закрывающего тега еще не дошли.

Чтобы определить, какой элемент в данный момент считан из нашего документа, тип ЧтениеXml предоставляет свойство ВидУзла. С помощью этого свойства мы можем проверить наше предыдущее утверждение. Перепишем строку СодержимоеXML.Добавить("Имя узла - " + Чтение.Имя) из примера:

СодержимоеXML.Добавить("Имя узла - " + Чтение.Имя + ", тип - " + Чтение.ВидУзла)

В результате работы нашего примера в массиве СодержимоеXML будут следующие элементы (приведен фрагмент того же размера):

Имя узла - Package, тип - StartElement
Имя узла - mp:PhoneIdentity, тип - StartElement
Имя узла - mp:PhoneIdentity, тип - EndElement
Имя узла - Properties, тип - StartElement
Имя узла - DisplayName, тип - StartElement
Имя узла - , тип - Text
Имя узла - DisplayName, тип - EndElement
Имя узла - PublisherDisplayName, тип - StartElement
Имя узла - , тип - Text
Имя узла - PublisherDisplayName, тип - EndElement
Имя узла - Logo, тип - StartElement
Имя узла - , тип - Text
Имя узла - Logo, тип - EndElement
Имя узла - Properties, тип - EndElement
...
Мы видим, что тип элемента (или узла XML-документа) в каждой строке вывода изменяется. В текущем примере мы видим три разных значения:
  • StartElement — таким образом описывается начало элемента.
  • EndElement — таким образом описывается окончание элемента.
  • Text — таким типом отмечается содержимое узла XML-документа, если это содержимое не является другим элементом.
Наши утверждения, высказанные ранее, подтвердились практически. Также получили объяснения элементы, которые не имеют имени. В XML-документе узлы, которые имеют содержимое, выглядят следующим образом:
<tag>содержимое</tag>
В то же время не все узлы такое содержимое имеют. Для того чтобы определить, имеет узел содержимое или нет, предназначено свойство ЧтениеXml.ИмеетЗначение.

Наш пример, модифицированный для того, чтобы выводить содержимое для элементов, которые его содержат, выглядит следующим образом:

метод ЧтениеXml(): Массив<Строка>
    пер ВременныйКаталог = СредаИсполнения.ПолучитьПеременную("temp")
    пер Файл = новый Файл(ВременныйКаталог + "\\manifest.xml")
    пер СодержимоеXML: Массив<Строка>

    пер Чтение = новый ЧтениеXml(Файл.ОткрытьПотокЧтения())
    пока Чтение.Следующий()
        // Читаем имя текущего узла
        СодержимоеXML.Добавить("Имя узла - " + Чтение.Имя + ", тип - " + Чтение.ВидУзла)
        если Чтение.ИмеетЗначение
            СодержимоеXML.Добавить("Содержимое: " + Чтение.Значение)
        ;
    ;
    возврат СодержимоеXML
;

Массив СодержимоеXML будет содержать следующие элементы:

Имя узла - Package, тип - StartElement
Имя узла - mp:PhoneIdentity, тип - StartElement
Имя узла - mp:PhoneIdentity, тип - EndElement
Имя узла - Properties, тип - StartElement
Имя узла - DisplayName, тип - StartElement
Имя узла - , тип - Text
Содержимое: ms-resource:AppName
Имя узла - DisplayName, тип - EndElement
Имя узла - PublisherDisplayName, тип - StartElement
Имя узла - , тип - Text
Содержимое: 1C LLC
Имя узла - PublisherDisplayName, тип - EndElement
Имя узла - Logo, тип - StartElement
Имя узла - , тип - Text
Содержимое: icon_50x50.png
Имя узла - Logo, тип - EndElement
Имя узла - Properties, тип - EndElement
...

Кроме безусловного чтения следующего элемента, с помощью экземпляра ЧтениеXml можно читать элементы какого-то заранее известного имени. Чтение будет выполняться без учета иерархии элементов. Для того, чтобы выполнить такое чтение, необходимо использовать метод СледующийДо(). Параметром метода является имя элемента, начало которого необходимо прочитать. После того как выполнено позиционирование на требуемый элемент, необходимо вызвать метод Следующий(), а затем снова вызвать метод СледующийДо(). Пример демонстрирует чтение элементов с именем Capability. Всего их три.

метод ЧтениеXmlОпределенногоЭлемента(): Массив<Строка>
    пер ВременныйКаталог = СредаИсполнения.ПолучитьПеременную("temp")
    пер Файл = новый Файл(ВременныйКаталог + "\\manifest.xml")
    пер СодержимоеXML: Массив<Строка>
    
    пер Чтение = новый ЧтениеXml(Файл.ОткрытьПотокЧтения())
    пока Чтение.СледующийДо("Capability")
        СодержимоеXML.Добавить("Имя узла - " + Чтение.Имя + ", тип - " + Чтение.ВидУзла)
        если Чтение.ИмеетЗначение
            СодержимоеXML.Добавить("Содержимое: " + Чтение.Значение)
        ;
        Чтение.Следующий()
    ;
    возврат СодержимоеXML
;

Eсли какой-либо узел нам не нужен, мы можем пропустить все содержимое этого узла и возобновить чтение с узла, который следует в нашем XML-документе после пропускаемого узла. Узел пропускается полностью, включая все вложенные узлы, если таковые имеются. Для того чтобы пропустить узел, необходимо использовать метод Пропустить(). В следующем примере мы дойдем до узла <Capabilities>, пропустим его и сразу окажемся на стартовом элементе узла <build:Metadata>.

метод ЧтениеXmlСПропускомЭлемента(): Массив<Строка>
    пер ВременныйКаталог = СредаИсполнения.ПолучитьПеременную("temp")
    пер Файл = новый Файл(ВременныйКаталог + "\\manifest.xml")
    пер СодержимоеXML: Массив<Строка>
    пер Чтение = новый ЧтениеXml(Файл.ОткрытьПотокЧтения())

    пер ЕщеЕстьЭлементы = Чтение.Следующий()

    пока ЕщеЕстьЭлементы
        если Чтение.ВидУзла == ВидУзлаXml.НачалоЭлемента
            если Чтение.Имя == "Capabilities"
                // Пропускаем этот узел!
                ЕщеЕстьЭлементы = Чтение.Пропустить()
                продолжить
            иначе
                СодержимоеXML.Добавить("Имя узла - " + Чтение.Имя + ", тип - " + Чтение.ВидУзла)
            ;
        ;
        ЕщеЕстьЭлементы = Чтение.Следующий()
    ;
    возврат СодержимоеXML
;

Также при переборе элементов полезным может оказаться метод ЭтоПустойЭлемент(), который позволяет понять, что у текущего узла нет значения. Другими словами, метод ЭтоПустойЭлемент() вернет значение Истина для узла вида <tag/>.

Чтение атрибутов узла XML-документа как данных различных типов

Узлы XML-документа могут иметь один или несколько атрибутов. Зачастую смысловая информация узла расположена именно в атрибутах, а не в содержимом элемента. Для того чтобы работать с атрибутами узла XML, встроенный язык предоставляет несколько методов, среди которых:
  • КоличествоАтрибутов() — позволяет получить количество атрибутов у данного элемента.
  • ИмяАтрибута() — позволяет получить имя атрибута по индексу.
  • ЗначениеАтрибута() — позволяет получить значение атрибута по имени атрибута.
  • ЗначениеАтрибутаПоИндексу() — позволяет получить значение атрибута по индексу атрибута.

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

метод ЧтениеАтрибутовXml(): Массив<Строка>
    пер ВременныйКаталог = СредаИсполнения.ПолучитьПеременную("temp")
    пер Файл = новый Файл(ВременныйКаталог + "\\manifest.xml")
    пер СодержимоеXML: Массив<Строка>
    пер Чтение = новый ЧтениеXml(Файл.ОткрытьПотокЧтения())
    
    пока Чтение.Следующий()
        СодержимоеXML.Добавить("Имя узла - " + Чтение.Имя + ", тип - " + Чтение.ВидУзла)
        если Чтение.ИмеетЗначение
            СодержимоеXML.Добавить("Содержимое: " + Чтение.Значение)
        ;
        если Чтение.ВидУзла != ВидУзлаXml.НачалоЭлемента
            продолжить
        ;
        для индекс = 0 по Чтение.КоличествоАтрибутов() - 1
            пер ИмяАтрибута = Чтение.ИмяАтрибута(индекс)
            СодержимоеXML.Добавить("\тАтрибут: " + ИмяАтрибута +
                                   " = " + Чтение.ЗначениеАтрибута(ИмяАтрибута))
        ;
    ;
    возврат СодержимоеXML
;

Результат работы этого примера будет следующим:

Имя узла - Package, тип - StartElement
	Атрибут: IgnorableNamespaces = uap mp build
Имя узла - mp:PhoneIdentity, тип - StartElement
	Атрибут: PhoneProductId = A588A326-FD4F-441C-83B2-AA0B8554C548
	Атрибут: PhonePublisherId = 00000000-0000-0000-0000-000000000000
Имя узла - mp:PhoneIdentity, тип - EndElement
Имя узла - Properties, тип - StartElement
Имя узла - DisplayName, тип - StartElement
Имя узла - , тип - Text
Содержимое: ms-resource:AppName
Имя узла - DisplayName, тип - EndElement
Имя узла - PublisherDisplayName, тип - StartElement
Имя узла - , тип - Text
Содержимое: 1C LLC
Имя узла - PublisherDisplayName, тип - EndElement
Имя узла - Logo, тип - StartElement
Имя узла - , тип - Text
Содержимое: icon_50x50.png
Имя узла - Logo, тип - EndElement
Имя узла - Properties, тип - EndElement
...
Метод ЗначениеАтрибута() возвращает значение типа Строка. Однако может возникнуть потребность в чтении атрибутов как данных других типов. Существует методы для чтения атрибутов элементов XML-документа, которые возвращают значения следующих типов:
  • Ууид / Число / Булево
  • Байты / БайтыBase64
  • Время / ДатаВремя / Дата / Длительность / Момент

Все эти методы начинаются с префикса ЗначениеАтрибутаКак. Например, для того чтобы прочитать атрибут элемента как Число, следует использовать метод:

ЗначениеАтрибутаКакЧисло(Имя: Строка, ПространствоИмен: Строка? = Неопределено): Число

Чтение содержимого узла XML-документа как данных различных типов

Для чтения содержимого элемента предназначены свойства типа ЧтениеXml ИмеетЗначение и Значение. Первое свойство позволяет узнать, что элемент имеет содержимое, а второе свойство позволяет его считать. Однако значение возвращается исключительно в текстовом виде. Это не всегда бывает удобно. Зачастую может быть известно заранее, что содержимое того или иного элемента XML-документа имеет конкретный тип. Тип ЧтениеXml обладает методами, которые позволяют считывать значения некоторых типов. Все эти методы начинаются с префикса ПрочитатьСодержимоеКак:

ПрочитатьСодержимоеКакБайты()
Читает содержимое узла как шестнадцатеричное число и преобразует к значению типа Байты
ПрочитатьСодержимоеКакБайтыBase64()
Читает содержимое узла как число в записи Base64 и преобразует к значению типа Байты
ПрочитатьСодержимоеКакБулево()
Данный метод считывает значение узла и преобразует его к типу Булево:
  • Текст true и 1 преобразуются в значение Истина
  • Текст false и 0 преобразуются в значение Ложь
ПрочитатьСодержимоеКакВремя()
Читает содержимое узла и преобразует к значению типа Время
ПрочитатьСодержимоеКакДатаВремя()
Читает содержимое узла и преобразует к значению типа ДатаВремя
ПрочитатьСодержимоеКакДату()
Читает содержимое узла и преобразует к значению типа Дата
ПрочитатьСодержимоеКакДлительность()
Читает содержимое узла и преобразует к значению типа Длительность
ПрочитатьСодержимоеКакМомент(ЧасовойПояс = ЧасовойПояс.Текущий())
Читает содержимое узла и преобразует к значению типа Момент. Если у содержимого узла не указан часовой пояс, для преобразования будет использован часовой пояс, указанный в аргументе ЧасовойПояс
ПрочитатьСодержимоеКакУуид()
Читает содержимое узла и преобразует к значению типа Ууид
ПрочитатьСодержимоеКакЧисло()
Читает содержимое узла и преобразует к типу Число
Примечание: При использовании вышеперечисленных методов, перед преобразованием от значения автоматически отрезаются пробельные символы в начале и конце.

Пространства имен

Имена элементов и атрибутов в XML-документ могут быть квалифицированными и неквалифицированными (локальными). Квалифицированное имя уникально в рамках XML-документа. Квалифицированное имя состоит из префикса, определяющего пространство имен, и локального имени. Префикс и локальное имя разделяются символом двоеточия (":"). Когда XML-документ читается с помощью типа ЧтениеXml, то программист может получить доступ к любой части имени элемента или атрибута:

  • Для элемента:
    • Свойство Имя. Содержит квалифицированное имя текущего узла. Квалифицированное имя состоит из префикса и локального имени (разделенного символом двоеточия).
    • Свойство Префикс. Содержит префикс пространства имен для текущего узла.
    • Свойство ЛокальноеИмя. Содержит локальное имя текущего узла документа.
    • Свойство ПространствоИмен. Данное свойство позволяет получить URI пространства имен. Префикс этого пространства имен можно получить с помощью свойства Префикс.
  • Для атрибута:
    • Метод ИмяАтрибута(Индекс: Число). Возвращает квалифицированное имя атрибута по индексу Индекс. Квалифицированное имя состоит из префикса и локального имени (разделенного символом двоеточия).
    • Метод ПрефиксАтрибута(Индекс: Число). Возвращает префикс пространства имен для атрибута с указанным индексом.
    • Метод ЛокальноеИмяАтрибута(Индекс: Число). Возвращает локальное имя атрибута по индексу Индекс.
    • Метод ПространствоИменАтрибута(Индекс: Число). Данный метод позволяет получить URI пространства имен атрибута по индексу Индекс. Префикс пространства имен атрибута можно получить с помощью метода ПрефиксАтрибута(Индекс: Число).

Далее приведен пример программного кода, который отображает всю информацию об узлах и атрибутах XML-документа, связанную с пространствами имен. Будут выведены квалифицированное имя, префикс имени, локальное имя и URI пространства имен.

метод ЧтениеXmlСПространствомИмен(): Массив<Строка>
    пер ВременныйКаталог = СредаИсполнения.ПолучитьПеременную("temp")
    пер Файл = новый Файл(ВременныйКаталог + "\\manifest.xml")
    пер СодержимоеXML: Массив<Строка>

    пер Чтение = новый ЧтениеXml(Файл.ОткрытьПотокЧтения())
    пока Чтение.Следующий()
        если Чтение.ВидУзла == ВидУзлаXml.НачалоЭлемента
            СодержимоеXML.Добавить("Имя узла: " + Чтение.Имя)
            СодержимоеXML.Добавить("\тПрефикс: " + Чтение.Префикс)
            СодержимоеXML.Добавить("\тЛокальное имя: " + Чтение.ЛокальноеИмя)
            СодержимоеXML.Добавить("\тURI пространства имен: " + Чтение.ПространствоИмен)
            для индекс = 0 по Чтение.КоличествоАтрибутов() - 1
                СодержимоеXML.Добавить("\т\тИмя атрибута: " + Чтение.ИмяАтрибута(индекс))
                СодержимоеXML.Добавить("\т\т\тПрефикс: " + Чтение.ПрефиксАтрибута(индекс))
                СодержимоеXML.Добавить("\т\т\тЛокальное имя: " + Чтение.ЛокальноеИмяАтрибута(индекс))
                СодержимоеXML.Добавить("\т\т\тURI пространства имен: " + Чтение.ПространствоИменАтрибута(индекс))
            ;
        ;    
    ;
    возврат СодержимоеXML
;