Автор — Грэг Валтерс (Greg Walters)

Возможно, вы слышали об XML, однако можете и не знать, что это такое. В этом месяце нашу лекцию мы посвятим XML. Цели лекции:

  • познакомить вас с тем, что такое XML;
  • показать, как читать и записывать XML-файлы в ваших приложениях;
  • подготовить к серьёзному проекту с использованием XML в следующий раз.

Итак, давайте поговорим об XML. XML означает «EXtensible Markup Language» («Расширяемый язык разметки»). Он достаточно похож на HTML. Формат разработан как способ хранения и эффективной передачи данных через интернет или другим образом. XML – это по существу текстовой файл, отформатированный с использованием ваших собственных тэгов и имеющий хорошие средства самодокументирования. Являясь текстом, он может быть сжат для более быстрой и легкой передачи данных. В отличие от HTML, XML сам по себе действий не производит и не связан с визуальным представлением данных. Как я отмечал, XML не требует набора стандартных тэгов: вы можете создавать свои собственные.

Давайте взглянем на типичный пример XML-файла.

<root>
	<node1> Данные… </node1>
	<node2 attribute=«something»>Данные Узла 2</node2>
	<node3>
		<node3sub1> ещё данные </node3sub1>
	</node3>
</root>

Первое, что обращает на себя внимание, – это отступы. На самом деле они просто облегчают восприятие человеком. XML-файл будет рабочим, даже если он выглядит так:

<root><node1> Данные… </node1><node2 attribute=«something»> Данные Узла 2 </node2><node3><node3sub1>ещё данные</node3sub1></node3></root>

Тэги внутри угловых скобок «< >» отвечают некоторым правилам. Они должны состоять из одного слова. Каждому открывающему тэгу (например, <root>) должен соответствовать закрывающий, начинающийся с «/». Кроме того, тэги чувствительны к регистру: <node>, <Node>, <NODE> и <NodE> – различны, и к каждому из них должен быть свой закрывающий тэг. Наименования тэгов могут содержать буквы, цифры и другие символы, но не могут начинаться с цифры или знака препинания. Избегайте знаков «-», «.», «:» в именах тэгов, так как некоторые программы могут рассматривать их как команды или свойства объекта. Помимо этого, запятые зарезервированы для некоторых других целей. Тэги также можно называть элементами.

Каждый XML-файл – это древовидная структура, начинающаяся с корня и ответвляющаяся от него. Он ДОЛЖЕН иметь корневой элемент – родитель для всех других элементов в файле. Вернёмся к нашему примеру. От корня отходят три дочерних элемента: node1, node2 и node3. Дочерние элементы имеют общий корень, а node3 является родителем для node3sub1.

Рассмотрим второй узел node2. Обратите внимание, что вдобавок к обычной информации внутри угловых скобок содержится атрибут (attribute). В настоящее время многие разработчики избегают использования атрибутов, так как без них элементы эффективнее и удобнее в использовании, но вы обнаружите, что атрибуты до сих пор используются. Мы познакомимся с ними позже.

Ниже приведён полезный пример.

Имеется корневой элемент «people», содержащий два дочерних – «person». Каждый элемент «person» содержит 6 дочерних элементов: «firstname», «lastname», «gender», «address», «city» и «state». На первый взгляд, XML-файл можно рассматривать как базу данных (вспомните несколько последних лекций), и это верное предположение. Некоторые приложения используют XML-файлы как простую структуру базы данных. Не составит труда написать программу для чтения XML-файла. Она должна открыть файл, прочитать его построчно и, в зависимости от элемента, использовать заключённые в нём данные, а затем закрыть файл. Однако есть и более эффективные способы.

В следующем примере мы воспользуемся модулем библиотеки под названием ElementTree. Его можно получить непосредственно из Synaptic, установив python-elementtree. Однако я предпочитаю устанавливать с сайта ElementTree (http://effbot.org/downloads/#elementtree) и непосредственно загружать файл-исходник (elementtree-1.2.6-20050316.tar.gz). После загрузки я с помощью менеджера пакетов извлекаю его во временную папку. Затем в этой папке выполняю «sudo python setup.py install». Эта команда помещает файлы в общую папку python, поэтому в дальнейшем у меня есть возможность использовать модуль и в python 2.5, и в 2.6. Итак, приступим к работе! Создайте папку, которая будет содержать код этого месяца, скопируйте приведённые выше данные в XML-формате в ваш любимый текстовой редактор и сохраните их в эту папку под именем «xmlsample1.xml».

Поговорим о нашем коде. В первую очередь хочется протестировать установленный модуль ElementTree.

import elementtree.ElementTree as ET

tree = ET.parse('xmlsample1.xml')

ET.dump(tree)

Запустив эту программу, мы получим нечто похожее на то, что представлено ниже.

Всё, что делает программа, – позволяет модулю ElementTree открыть файл, разбить на структурные элементы и сохранить в памяти в таком виде. Ничего сверхъестественного.

Теперь заменим наш код на следующий:

import elementtree.ElementTree as ET

tree = ET.parse('xmlsample1.xml')

person = tree.findall('.//person')

for p in person:

for dat in p:

print "Элемент: %s - Данные: %s" %(dat.tag,dat.text)

Запустите код снова. Результат будет таким:

/usr/bin/python -u »/home/greg/Documents/articles/xml/reader1.py»

  • Элемент: firstname - Данные: Саманта
  • Элемент: lastname - Данные: Фэроу
  • Элемент: gender - Данные: Женский
  • Элемент: address - Данные: ул. Мэйн, д. 123
  • Элемент: city - Данные: Дэнвер
  • Элемент: state - Данные: Колорадо
  • Элемент: firstname - Данные: Стив
  • Элемент: lastname - Данные: Левон
  • Элемент: gender - Данные: Мужской
  • Элемент: address - Данные: Бульвар Арапахо, 332120
  • Элемент: city - Данные: Дэнвер
  • Элемент: state - Данные: Колорадо

Теперь каждая порция данных выводится напротив имени тэга. Эти данные можно легко распечатать. Итак, посмотрим, что делает программа. Модуль ElementTree проанализировал файл и поместил результаты в объект tree. Затем ElementTree нашёл все вхождения тэга person. В нашем примере таких элементов оказалось два, но их могло быть 1 или 1000. Элемент person – дочерний к корневому элементу people. Все данные были разбиты на порции. Затем мы создали простой цикл, перебирающий объекты person. Вложенный в него цикл перебирает данные каждого элемента person, и на экран выводятся результаты, показывающие имя элемента (.tag) и данные (.text).

Рассмотрим более жизненный пример. Я со своей семьёй играю в Геокэшинг. Для тех, кто не знает, Геокэшинг – это «охота за сокровищами», когда «гики» используют мобильные устройства с GPS, чтобы найти нечто, спрятанное кем-то другим. Они публикуют GPS-координаты на веб-сайте, иногда с подсказками, а другие вводят координаты в свои GPS-навигаторы и пытаются найти тайник. По данным Википедии во всём мире существует более миллиона действующих точек кэшинга, таким образом, вероятно, они найдутся и поблизости от вас. Для поиска таких мест я использую два веб-сайта: http://www.geocaching.com/ и http://navicache.com/. Есть и другие сайты, но эти два – наиболее крупные.

Файлы, которые содержат информацию о каждом месте геокэшинга, – это обычно XML-файлы. Существуют программы, которые получают такие данные и передают их в устройство GPS. Некоторые из них действуют как приложения баз данных – позволяют отслеживать вашу деятельность, иногда с использованием карт. Теперь сконцентрируемся на анализе загружаемых файлов.

Я зашел на Navicache и обнаружил последний добавленный тайник в Техасе. Информация из файла показана на предыдущей странице.

Скопируем данные и сохраним их в файл «Cache.loc». Перед началом составления программы внимательно рассмотрим файл.

Первая строка обычно сообщает, что это проверенный XML-файл, её можно проигнорировать. Следующая строка, начинающаяся с «loc», является корневым элементом и содержит атрибуты «version» и «src». Выше я отмечал, что атрибуты иногда используются в файлах. В дальнейшем мы ещё будем иметь дело с ними в этом файле. Корневой элемент тут также может быть проигнорирован. Следующая строка содержит тэг дочернего элемента – маршрутной точки (waypoint). (Маршрутная точка – это место расположения, где можно найти тайник). Из этого элемента можно получить важные сведения: наименование тайника, координаты (долготу и широту), тип тайника и ссылку на интернет-страницу с дополнительной информацией. Элемент с именем содержит кучу полезной информации, но проводить её структурный анализ придётся самостоятельно. Давайте теперь создадим программу для чтения и отображения файла cache.loc, назвав её readacache.py. Начнём с импорта модуля и команд для структурного анализа из предыдущего примера.

import elementtree.ElementTree as ET

tree = ET.parse('Cache.loc')

Нам необходимо получить только данные, находящиеся внутри тэга waypoint. Для этого мы используем функцию .find внутри модуля ElementTree. Результаты работы будут сохранены в объекте w.

w = tree.find('.//waypoint')

Затем нам необходимо просмотреть все данные, для чего используется цикл for. В теле цикла мы ищем элементы name (имя), coord (координаты), type (тип) и link (ссылка). На основании содержащихся в тэге данных, мы извлекаем информацию, чтобы распечатать её в дальнейшем.

for w1 in w:
	if w1.tag == "name":

Просмотрим, какие данные можно извлечь из тэга name.

<name id="N02CAC"><![CDATA[Возьмите Фотографии озера 
Виноградной лозы, сделанные g_phillips

Тайник открыт: не ограничено

Тип тайника: обычный

Размер: обычный

Сложность: 1.5

Местность: 2.0]]></name>

Это одна очень длинная строка. Идентификатор id установлен как атрибут. Имя тайника – это часть строки после «CDATA» и до «Тайник открыт:». Мы разделим строку на несколько более мелких частей.

newstring = oldstring[startposition:endposition]

Итак, приведенный код можно использовать для извлечения необходимой информации.

Затем нам следует извлечь идентификатор, который содержится в атрибуте id тэга name. Проверим, имеются ли какие-либо атрибуты (а как мы знаем, они имеются), следующим образом:

if w1.keys():
	for name,value in w1.items():
		if name == 'id':
			CacheID = value

Перейдём к работе с тэгами координат, типа и ссылки; соответствующий код показан ниже. В итоге мы выведем полученную информацию, используя код, размещенный в нижнем правом углу. Правее – полный листинг программы.

Теперь вы получили достаточно информации, чтобы суметь реализовать код для чтения большинства XML-файлов. Как всегда, вы можете получить полный код этой лекции на моём веб-сайте http://www.thedesignatedgeek.com.

В следующей лекции мы применим наши знания формата XML для получения и вывода на терминал сведений с замечательного сайта погоды. Наслаждайтесь!


Коды:

#1

<people>
    <person>
        <firstname>Samantha</firstname>
        <lastname>Pharoh</lastname>
        <gender>Female</gender>
        <address>123 Main St.</address>
        <city>Denver</city>
        <state>Colorado</state>
    </person>
    <person>
        <firstname>Steve</firstname>
        <lastname>Levon</lastname>
        <gender>Male</gender>
        <address>332120 Arapahoe Blvd.</address>
        <city>Denver</city>
        <state>Colorado</state>
    </person>
</people>

#2

/usr/bin/python -u  "/home/greg/Documents/articles/xml/reader1.py"

<people>
    <person>
        <firstname>Samantha</firstname>
        <lastname>Pharoh</lastname>
        <gender>Female</gender>
        <address>123 Main St.</address>
        <city>Denver</city>
        <state>Colorado</state>
    </person>
    <person>
        <firstname>Steve</firstname>
        <lastname>Levon</lastname>
        <gender>Male</gender>
        <address>332120 Arapahoe Blvd.</address>
        <city>Denver</city>
        <state>Colorado</state>
    </person>
</people>

#3

<?xml version="1.0" encoding="ISO-8859-1"?>
<loc version="1.0" src="NaviCache">
   <waypoint>
      <name id="N02CAC"><![CDATA[Take Goofy 
      Pictures at Grapevine Lake  by g_phillips
Open Cache: Unrestricted
Cache Type: Normal
Cache Size: Normal
Difficulty: 1.5
Terrain   : 2.0]]></name>
      <coord  lat="32.9890166666667"  lon="-97.0728833333333" />
      <type>Geocache</type>
      <link 
      text="Cache Details">
      http://www.navicache.com/cgi-bin/db/displaycache2.pl
      ?CacheID=11436</link>
   </waypoint>
</loc>

#4

# Get text of cache name up to the phrase "Open Cache: "
CacheName = w1.text[:w1.text.find
("Open Cache: ")-1]
# Get the text between "Open Cache: " and "Cache Type: "
OpenCache = w1.text[w1.text.find
("Open Cache: ")+12:w1.text.find("Cache Type: ")-1]
# More of the same
CacheType = w1.text[w1.text.find
("Cache Type: ")+12:w1.text.find
("Cache Size: ")-1]
CacheSize = w1.text[w1.text.find
("Cache Size: ")+12:w1.text.find
("Difficulty: ")-1]
Difficulty= w1.text[w1.text.find
("Difficulty: ")+12:w1.text.find
("Terrain   : ")-1]
Terrain =   w1.text[w1.text.find
("Terrain   : ")+12:]

#5

elif w1.tag == "coord":
   if w1.keys():
       for name,value in w1.items():
          if name == "lat":
               Lat = value
          elif name == "lon":
               Lon = value
elif w1.tag == "type":
   GType = w1.text
elif w1.tag == "link":
   if w1.keys():
      for name, value in w1.items():
          Info = value
   Link = w1.text
   
print "Cache Name: ",CacheName
print "Cache ID: ",CacheID
print "Open Cache: ",OpenCache
print "Cache Type: ",CacheType
print "Cache Size: ",CacheSize
print "Difficulty: ", Difficulty
print "Terrain: ",Terrain
print "Lat: ",Lat
print "Lon: ",Lon
print "GType: ",GType
print "Link: ",Link

#6

import elementtree.ElementTree as ET
tree = ET.parse('Cache.loc')
w = tree.find('.//waypoint')
for w1 in w:
    if w1.tag == "name":
        # Get text of cache name up to the phrase "Open Cache: "
        CacheName = w1.text[:w1.text.find
        ("Open Cache: ")-1]
        # Get the text between 
        "Open Cache: " and "Cache Type: "
        OpenCache = w1.text[w1.text.find
        ("Open Cache: ")+12:w1.text.find
        ("Cache Type: ")-1]
        # More of the same
        CacheType = w1.text[w1.text.find
        ("Cache Type: ")+12:w1.text.find
        ("Cache Size: ")-1]
        CacheSize = w1.text[w1.text.find
        ("Cache Size: ")+12:w1.text.find
        ("Difficulty: ")-1]
        Difficulty= w1.text[w1.text.find
        ("Difficulty: ")+12:w1.text.find
        ("Terrain   : ")-1]
        Terrain =   w1.text[w1.text.find
        ("Terrain   : ")+12:]
        if w1.keys():
            for name,value in w1.items():
                if name == 'id':
                    CacheID = value
    elif w1.tag == "coord":
        if w1.keys():
            for name,value in w1.items():
                if name == "lat":
                    Lat = value
                elif name == "lon":
                    Lon = value
    elif w1.tag == "type":
        GType = w1.text
    elif w1.tag == "link":
        if w1.keys():
            for name, value in w1.items():
                Info = value
        Link = w1.text
print "Cache Name: ",CacheName
print "Cache ID: ",CacheID
print "Open Cache: ",OpenCache
print "Cache Type: ",CacheType
print "Cache Size: ",CacheSize
print "Difficulty: ", Difficulty
print "Terrain: ",Terrain
print "Lat: ",Lat
print "Lon: ",Lon
print "GType: ",GType
print "Link: ",Link
print "="*25
 
print "finished"