Журнал LinuxFormat - перейти на главную

LXF71:PHP

Материал из Linuxformat
Версия от 18:23, 6 декабря 2008; Yaleks (обсуждение | вклад)

(разн.) ← Предыдущая | Текущая версия (разн.) | Следующая → (разн.)
Перейти к: навигация, поиск

Содержание

SimpleXML и XPath

Пол Хадсон (Paul Hudson) вновь пытается помочь с решенем загадки Sudoku и погружается в изучение XML для дела и удовольствия.

одна из многих латинских поговорок, приписываемых юлию Цезарю, звучит так: «Beati Hispani quibus vivere bibere est». В свободном переводе это значит: «блаженны испанцы, для которых жить – значит пить». И кто с этим не согласится? Но мало кто знает, что эта фраза очень похоже прозвучит в современной Испании. там вы можете сказать “Dichosos los espaсoles, para quienes vivir es beber.” Любой, хорошо знакомый с испанским языком, может посмот- реть на фразу на латыни и догадаться о её значении, благодаря тому факту что испанский язык относится к романской группе – не из-за сво- ей романтичности, а потому что он происходит от разговорного латинско- го языка, на котором говорили римляне (Romans) много лет назад. Удивительно, но в компьютерных науках наблюдается такая же сте- пень стандартизации. Несмотря на то, что мы никак не можем догово- риться, сколько кнопок должно быть на мышке, мы при этом способны создавать системы для обмена данными в понятной форме. XML (the eXtensible Markup Language, расширяемый язык разметки) – это формат данных, основанный на текстовом представлении, позволяющий легко читать и сохранять данные, а так же делиться ими с другими, в том чис- ле и через Интернет. как латынь и языки романской группы, разные XML- схемы (правила, по которым создаются XML-файлы) отличаются друг от друга, но не настолько, чтобы другие стали непонятными, если вам зна- кома только одна из них. В сегодняшней статье мы рассмотрим XML для сохранения кроссвордов: мы будем читать их, записывать и показывать на экране. Мы не собираемся их разгадывать – мы займёмся совершен- но другой проблемой.

Зачем использовать XML

Даже теперь, когда XML присутствует везде, мы всё еще видим разнооб- разие форматов. Причина этого проста – XML несовершенен. он очень многословен, он не имеет строгой типизации, он понятен для человека и поэтому заметно медленнее бинарных форматов данных. Но у него есть свои достоинства. Главные из них – возможность проверить коррект- ность файла, не имея представления о его схеме, поддержка Unicode, возможности самодокументирования и жёсткие синтаксические правила, позволяющие быстро и безошибочно читать XML-файл. В общем, XML силён несмотря на все свои проблемы, потому что он позволяет легко обмениваться данными между различными программа- ми, в том числе и через Интернет. Используя его для сохранения крос- свордов, мы можем быть уверены что при желании другие программы смогу легко воспользоваться ими. Если вы читали о возможностях техно- логии Ajax, то вы уже знаете о возможностях, которые даёт XML при про- граммировании на стороне клиента. Если у вас нет каких-то специфичес- ких требований, для большинства проектов XML подходит лучше всего.

создание простого XML

Поддержка XML в PHP находится в очень неустойчивом положении. С момента её появления было создано множество разных реализаций, несколько переработок и серия расширений, предназначенных для чте- ния и записи XML. Последнее из них, SimpleXML (простой XML), так называется потому, что предназначено для представления XML-файла в виде простых переменных PHP. Вот пример XML-файла под названием requiem.xml: <requiem> <line> <latin>Confutatis maledictis</latin> <english>When the wicked are confounded</english> </line> <line> <latin>Flammis acribus addictis</latin> <english>Doomed to flames of woe unbounded</english> </line> </requiem> В этом примере корневым элементом является requiem. он содер- жит два элемента line, в которых в свою очередь находятся latin и english – как видите, тут нет ничего мудрёного. Мы можем разобрать этот файл и напечатать результат разбора с помощью двух строк на PHP: <?php $file = simplexml_load_file(“requiem.xml”); var_dump($file); ?> Функция var_dump даёт прекрасную возможность разобраться, как что-то работает в PHP, так как она позволяется наглядно увидеть данные, содержащиеся в переменной. Вот, что выводит наша программа: object(SimpleXMLElement)#1 (1) { [“line”]=> array(2) { [0]=> object(SimpleXMLElement)#2 (2) { [“latin”]=> string(21) “Confutatis maledictis” [“english”]=> string(30) “When the wicked are confounded” } [1]=> object(SimpleXMLElement)#3 (2) { [“latin”]=> string(25) “Flammis acribus addictis” [“english”]=> string(33) “Doomed to flames of woe unbounded” } } } Понимание этой структуры – это ключ к использованию SimpleXML. Корневой объект SimpleXMLElement содержит только одну пере- менную, line. О на является массивом из двух элементов (с номерами 0 и 1), соответствующих двум элементам <line> в исходном файле. Эти элементы, в свою очередь, являются объектами SimpleXMLElement, содержащими строковые переменные latin и english с соответствующи- ми данными из исходного XML. Итак, вызов функции simplexml_load_ file вернул нам смесь из объектов и массивов, соответствующую струк- туре XML-файла и содержащий все его данные в обычных переменных php. Вывод var_dump познавателен, но непригоден для нормальной работы, так что давайте перепишем нашу программу так, чтобы она печа- тала только английский текст: <?php $file = simplexml_load_file(“requiem.xml”); foreach($file->line as $line) { echo $line->english, “\n”; } ?> Обратите внимание на то, как мы используем $file->line и $line- >english. Т ак как $file и $line – объекты, мы можем получить доступ к их переменным при помощи оператора ->. Ничего не мешает вам тракто- вать объекты как массивы. Например, использовав их внутри цикла foreach, можно перебирать все поля объекта по одному, словно это эле- менты массива. Но лучше всё же мысленно различать объекты и масси- вы, так как SimpleXML использует «массивоподобный» метод для пред- ставления атрибутов. Например, если изменить первую строчку нашего XML следующим образом: <requiem key=”D”> то теперь с разобранным файлом вы можете сделать следующее: $file = simplexml_load_file(“requiem.xml”); print $file[“key”]; Этот код отобразит значение атрибута key корневого элемента. Синтаксис $file->key не сработает, так как он обозначает обращение ко вложенному элементу <key>, которого не существует. Да, это похоже на чёрную магию, мы привыкли к тому что операторы массивов работают по другому. Всё дело в том, что здесь приходится работать с объектами SimpleXMLElement, а не с обычными плоскими массивами.

XML на входе, объекты на выходе

Как вы только что могли убедиться, SimpleXML позволяет вам работать с XML-файлами так же, как с объектами и массивами, так что вы можете игнорировать семантику XML и сосредоточиться на правильной обработ- ке данных. Эта простота использования простирается вплоть до возмож- ности изменять значения переменных, поскольку раз уж у вас есть объекты и массивы php, то вы можете делать с ними всё, что пожелаете. Что более интересно, после внесения изменений вы можете экспортировать результат обратно в XML. Для демонстрации этого мы используем новый XML файл: <park> <squirrel name=”Squirly”> <nuts>320</nuts> </squirrel> <squirrel name=”Nick”> <nuts>0</nuts> </squirrel> </park> И так, в нашем парке (park) есть две белки (squirrel) по имени Squirly и Nick. У Squirly много орехов (nuts), а у Nick’а ничего нет. Чтобы это исправить, воспользуемся умением SimpleXML изменять значения пере- менных на лету. Сделать это действительно просто: <?php $park = simplexml_load_file(“squirrels.xml”); $park->squirrel[1]->nuts = 10; print $park->asXML(); ?> Этот сценарий превращает парк с белками в обычный набор объек- тов и массивов, получает доступ к одной из белок (второй, так как эле- менты нумеруются начиная с нуля, то есть squirrel[1] – это Nick) и изме- няет число её орехов. Важной частью является вызов asXML, так как он позволяет превратить наши объекты снова в XML, и это лучший XML чем тот, с которого мы начинали: <?xml version=”1.0”?> <park> <squirrel name=”Squirly”> <nuts>320</nuts> </squirrel> <squirrel name=”Nick”> <nuts>10</nuts> </squirrel> </park> Nick обзавёлся орехами, а весь файл – заголовком с обозначением версии, которого у нас не было раньше. Метод asXML доступен для любого объекта SimpleXMLElement, так что вы можете получить XML одного только Nick-а при помощи $park->squirrel[1]->asXML(). Обратите внимание: при использовании asXML для любого другого объекта, кроме корневого, XML-заголовок с версией не вставляется, вы получите только кусочек XML-кода. После получения текстовой строки в нужном формате воспользуйтесь функцией file_put_contents для сохранения её в файле по вашему выбору.

Для того, чтобы увеличить возможности XML, было разработано множество стандартов, самый известный из которых – Xpath. он делает XML более похожим на SQL – теперь вы можете не просто использовать его для хранения информации, но и выполнять запросы. По своей сути Xpath – это способ вытащить нужную часть XML, указав путь к ней (похо- жий на путь к файлу в файловой системе). В PHP это достигается воз- вращением массива объектов SimpleXMLElement, соответствующих вашему запросу.

первые шаги по Xpath

Представляю вам новый XML, books.xml: <books> <author nationality=”British” name=”Jane Austen”> <book>Pride And Prejudice</book> <book>Sense And Sensibility</book> </author> <author nationality=”Colombian” name=”Gabriel Garcia Marquez”> <book>Cien anos de soledad</book> <book>El coronel no tiene quien le escriba</book> </author> <author nationality=”British” name=”David Baddiel”> <book>Time For Bed</book> <book>The Secret Purposes</book> </author> </books> он содержит трёх авторов (<author>), из которых двое англичане, и для всех троих указаны их книги. С помощью Xpath мы можем запросить список всех книг: <?php $authors = simplexml_load_file(“books.xml”); $books = $authors->XPath(“//book”); foreach($books as $book) { echo $book, “\n”; } ?> Запрос «//book» ищет элемент <book> в любом месте дерева XML. Часть запроса // значит “искать везде”, что для функции xpath значит «найти все корневые элементы <book>, найти все <book> внутри элемен- та, содержащего <author>, найти все <book> внутри элементов <author>», то есть найти <book> вообще везде, и вернуть все найденные элементы в одном массиве. Затем мы передаём полученный массив в цикл foreach и печатаем его. Есть один любопытный момент: если вместо print использовать var_dump($book), вы увидите, что в переменной на самом деле хранится SimpleXMLElement. Это часть той же магии, которая позволяет работать с объектами SimpleXML как с массивами. В нашем случае это магия функции __tostring(), позволяющая использо- вать объект в операторе print как обычную строку. Если вы хотите добыть конкретную книгу, Xpath поможет вам и в этом. Например, добавьте следующий код к books.xml сразу перед стро- кой </books>: <library name=”British Library”> <book> <title>The Peloponnesian War</title> <author>Donald Kagan</title> </book> <book> <title>The Peloponnesian War</title> <author>Thucidydes</author> </book> </library> теперь элементов <book> у нас стало больше, но они разные – эле- менты внутри <author> обозначают, какие книги этими авторами были написаны, а <book> внутри < library> - это список книг, имеющихся в биб- лиотеке. Если по-прежнему использовать строку поиска //book, то мы получим их все, вне зависимости от того, есть эта книга в библиотеке или нет. Для выполнения более точного поиска можно воспользоваться иерархией XML: $books = $authors->XPath(“/books/library/book”); Этот запрос вернёт все книги в библиотеке, не затронув книги авто- ров. Помните, что он по прежнему возвращает массив из SimpleXMLElement, то есть вы получите объекты со свойствами title и author. Чтобы получить только заголовки, можно воспользоваться запро- сом /books/library/book/title, но если нам надо и заголовки и авторов, то лучше запросить именно книги, а затем воспользоваться объектно- ориентированным синтаксисом: <?php $all_books = simplexml_load_file(“books.xml”); $library_books = $all_books->XPath(“/books/library/book”); foreach($library_books as $book) { echo “{$book->title} was written by {$book->author}\n”; } ?> XML нельзя назвать быстрым, но XPath – уже можно, так как в момент его использования XML преобразован во внутреннее представле- ние, поиск по которому происходит сравнительно легко.

разделяй и запрашивай

XPath позволяет фильтровать полученный массив элементов с помощью небольшого набора условий. Например, мы можем запросить список только английских авторов. <?php $all_books = simplexml_load_file(“books.xml”); $british_authors = $all_books->XPath(‘/books/author[@nationality=”British” ]’); foreach($british_authors as $author) { echo “{$author[“name”]} is British.\n”; } ?> ключевая часть запроса заключена в квадратные скобки. Мы указы- ваем, что хотим получить элемент author, вложенный в books, а в квад- ратных скобках содержится фильтр, которому должны удовлетворять наши элементы. @nationality обоозначает “Выбрать только такие эле- менты, у которых параметр nationality...” и далее написано условие “=British”. Это условие позволяет ограничить поиск только английскими авторами. Символ @ имеет важное значение, без него условие применя- лось бы к вложенным элементам, а не к атрибутам. Например, запрос books/author[book=”Cien anos de soledad”] вернёт “Gabriel Garcia Marquez”.

кроме проверки на равенство вы можете использовать стандартные условия <, >, <=, >= и !=, а так же соединять несколько условий при помощи or или and.

$club_1830_eligible = $holidaymakers->XPath(/books/author[@age>=18 or @age<=30]);

кроме того, условия можно заключать в скобки, примерно так: /books/author[@age>=18 and (@name=”Jim” or @ name=”Bob”)].

кроме операторов сравнения, в условиях XPath доступны простые математические действия. В их число входят +, -, *, div и mod. Вот несколько примеров Xpath-запросов с фильтрованием:

$blessed = $people->XPath('//person[nationality="Spanish"]');
$meaning_of_life = $earth->XPath('//monkeys[@favouritenumber = 7 * 6]');
$a_grade_freshmen = $university->XPath('/students/student[@year = 1 and grades > 80.0'];
$dangerous = $people->XPath('//adults[@iq = @shoesize]'); // обратите внимание, мы можем сравнивать значение атрибутов между собой
$squirrels_with_comedy _oversized_tails = $animals->XPath('//squirrels[@tail > @body-length * 6]');
$offenders = $people->XPath('//people/[@outstanding_penalty = true()]'); //возвращает записи, имеющие параметр outstanding_penalty, пустой или с любым значением
$good_wines = $wines->XPath('/drinks/alcoholic/wines/wine[@year mod 2 = 1 and (@country="Australia" or @country="France")]');

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

Кроссворды

теперь, когда мы представляем, как работает SimpleXML, самое время обратиться к задаче хранения кроссвордов. кроссворды можно рассматривать как большие сетки (как и Sudoku), в которых каждый элемент должен быть заполнен в процессе решения (опять же, полная аналогия с Sudoku). так что задачи практически совпадают.

Существует два способа хранения таких данных. “Чистое” решение – это хранение каждой клеточки в отдельном XML-элементе. Другой вариант – сделать один элемент в XML-файле, который состоит из символов, образующих сетку. Давайте рассмотрим сначала второй вариант как более простой:

<crossword>
<title>My Excellent Crossword</title>
<grid>
-l--t-
-linux
-a--l-
-m--i-
-agape
------
</grid>
</crossword>

Чтобы загрузить такой кроссворд, надо получить текст элемента grid и затем разобрать его посимвольно. Но это не очень хорошее решение. как хранить номера слов в кроссворде? как добавить вопросы? как сохранять состояние решения? Для решения всех этих проблем нам потребуется “правильный” XML, в котором каждый квадратик описывается отдельным элементом. Эти квадратные элементы могут иметь следующие атрибуты:

  • type– тип квадратика, black(чёрный) или white (белый).
  • number– номер слова (если есть)
  • direction– направление слова, down (вниз), across (вправо) или both (оба), если есть.
  • downclue– вопрос для слова, идущего вниз, если есть.
  • acrossclue– вопрос для слова, идущего вправо, если есть.
  • correctanswer– буква, которая должна быть тут.
  • currentanswer– буква, которую сюда написал отгадчик.
  • guessedanswer– буква, в которой отгадчик не уверен.

Мы отсортируем элементы так, что сначала укажем первую строчку, потом вторую и так далее. Элемент <grid> будет хранить свойства – автора, сложность (номер от 1 до 4, 4 – самый сложный), и размер сетки ( 6 значит шесть квадратиков вправо и вниз).

Чтобы сэкономить пространство в журнале, я приведу пример крос- сворда 3x3:

<grid author=”Paul Hudson” difficulty=”1” size=”3”>
<square type=”white” number=”1” direction=”both” downclue=”Water stopper” acrossclue=”Four-legged cathater” correct=”d” current=”” guessed=”” />
<square type=”white” correct=”o” current=”” guessed=”” />
<square type=”white” number=”2” direction=”down” downclue=”Water stopper” correct=”g” current=”” guessed=”” />
<square type=”white” correct=”a” current=”” guessed=”” />
<square type=”black” />
<square type=”white” correct=”o” current=”” guessed=”” />
<square type=”white” number=”3” direction=”across” acrossclue=”Crazily annoyed” correct=”m” current=”” guessed=”” />
<square type=”white” correct=”a” current=”” guessed=”” />
<square type=”white” correct=”d” current=”” guessed=”” />
</grid>

Сохранив этот код в файле crossword.xml, мы можем использовать следующий PHP-код для печати всех правильных ответов:

<?php
$crossword = simplexml_load_file("crossword.xml");
$i = 0; // square counter
foreach($crossword->square as $square) {
if ($square["type"] == "white") {
print $square["correct"];
} else {
print " ";
}
++$i;
if ($i % $crossword['size'] == 0) print "\n";
}
?>

Вложенный в цикл второй оператор if отвечает за правильную расстановку символов перевода строки. Элементы XML загружаются в порядке очереди, так что нам нужно прервать строку после символа номер $crossword[“size”] или любого, кратного ему.

И это всё? Да, нашей целью было только отображение кроссворда, а не программа для их решения или генерации (подсказка – для этого можно воспользоваться одним из множества бесплатных словарей в web), и у нас нет красивого графического интерфейса пользователя. я оставляю первые две задачи на ваше усмотрение, а вот с третьей постараюсь вам помочь в следующем выпуске.

До встречи!

Персональные инструменты
купить
подписаться
Яндекс.Метрика