Чтение 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. Простейший пример такого чтения будет выглядеть примерно следующим образом:
метод Скрипт()
    пер ВременныйКаталог = СредаИсполнения.ПолучитьПеременную("temp")
    пер Файл = новый Файл(ВременныйКаталог + "\\manifest.xml")
    пер Чтение = новый ЧтениеXml(Файл.ОткрытьПотокЧтения())
    пока Чтение.Следующий()
        Консоль.Записать("Имя узла - " + Чтение.Имя)
    ;
;

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

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

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

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

Чтобы определить, какой элемент в данный момент считан из нашего документа, тип ЧтениеXml предоставляет свойство ТипУзла. С помощью этого свойства мы может проверить наше предыдущее утверждение:

метод Скрипт()
    пер ВременныйКаталог = СредаИсполнения.ПолучитьПеременную("temp")
    пер Файл = новый Файл(ВременныйКаталог + "\\manifest.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.ИмеетЗначение.

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

метод Скрипт()
    пер ВременныйКаталог = СредаИсполнения.ПолучитьПеременную("temp")
    пер Файл = новый Файл(ВременныйКаталог + "\\manifest.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, которых в нашем примере находится три элемента.

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

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

метод Скрипт()
    пер ВременныйКаталог = СредаИсполнения.ПолучитьПеременную("temp")
    пер Файл = новый Файл(ВременныйКаталог + "\\manifest.xml")
    пер Чтение = новый ЧтениеXml(Файл.ОткрытьПотокЧтения())
    пока Чтение.Следующий()
        если Чтение.ВидУзла == ВидУзлаXml.НачалоЭлемента
            Консоль.Записать("Имя узла - " + Чтение.Имя + ", тип - " + Чтение.ВидУзла)
            если Чтение.Имя == "Capabilities"
                Консоль.Записать("Пропускаем этот узел!")
                Чтение.Пропустить()
            ;
        ;
    ;
;

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

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

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

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

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

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

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

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

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

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

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

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

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