Контакты

Разбор XML-данных. Разбор XML-данных Обработка XML данных

Расширяемый язык разметки XML представляет собой набор правил для кодирования документов в машиночитаемой форме. XML является популярным форматом для обмена данными в Интернете. Сайты, которые часто обновляют свой контент, например, новостные сайты или блоги, часто предоставляют XML канал, чтобы внешние программы были в курсе изменений контента. Отправка и разбор XML-данных является общей задачей для приложений с сетевым подключением. Этот урок объясняет, как выполнить разбор XML документов и использовать их данные.

Выбор синтаксического анализатора

Анализ канала

Первым шагом в разборе канала является решение о том, в какие полях данные вы заинтересованы. Анализатор извлекает заданные поля и игнорирует все остальное.

Вот фрагмент канала, который будет разбираться в примере приложения. Каждый пост на StackOverflow.com появляется в канале, как entry тег, который содержит несколько вложенных тегов:

newest questions tagged android - Stack Overflow ... ... http://stackoverflow.com/q/9439999 0 Where is my data file? cliff2310 http://stackoverflow.com/users/1128925 2012-02-25T00:30:54Z 2012-02-25T00:30:54Z

I have an Application that requires a data file...

... ...

Пример приложения извлекает данные из entry тега и его вложенных тегов title , link , и summary .

Создание экземпляра синтаксического анализатора

Следующим шагом является создание экземпляра синтаксического анализатора и запуск процесса разбора. В этом фрагменте анализатор инициализируется так, чтобы не обрабатывать пространства имен, а также использовать предоставленный InputStream в качестве входных данных. Процесс разбора запускается с помощью вызова nextTag() и вызывает readFeed() метод, который извлекает и обрабатывает данные, в которых заинтересовано приложение:

Public class StackOverflowXmlParser { // We don"t use namespaces private static final String ns = null; public List parse(InputStream in) throws XmlPullParserException, IOException { try { XmlPullParser parser = Xml.newPullParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); parser.setInput(in, null); parser.nextTag(); return readFeed(parser); } finally { in.close(); } } ... }

Вычитать канал

readFeed() метод делает фактическую работу по обработке канала. Элементы, помеченные тэгом "entry", являются отправной точкой для рекурсивной обработки канала. Если следующий тег не entry тег, он пропускается. После того, как вся "лента" была рекурсивно обработана, readFeed() возвращает List содержащий записи (в том числе вложенные элементы данных), которые извлекаются из канала. Этот List затем возвращается анализатором.

Private List readFeed(XmlPullParser parser) throws XmlPullParserException, IOException { List entries = new ArrayList (); parser.require(XmlPullParser.START_TAG, ns, "feed"); while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG) { continue; } String name = parser.getName(); // Starts by looking for the entry tag if (name.equals("entry")) { entries.add(readEntry(parser)); } else { skip(parser); } } return entries; }

Разбор XML

Шаги для разбора XML канала следующие:

Этот фрагмент показывает, как анализатор анализирует entry, title, link, и summary.

Public static class Entry { public final String title; public final String link; public final String summary; private Entry(String title, String summary, String link) { this.title = title; this.summary = summary; this.link = link; } } // Parses the contents of an entry. If it encounters a title, summary, or link tag, hands them off // to their respective "read" methods for processing. Otherwise, skips the tag. private Entry readEntry(XmlPullParser parser) throws XmlPullParserException, IOException { parser.require(XmlPullParser.START_TAG, ns, "entry"); String title = null; String summary = null; String link = null; while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG) { continue; } String name = parser.getName(); if (name.equals("title")) { title = readTitle(parser); } else if (name.equals("summary")) { summary = readSummary(parser); } else if (name.equals("link")) { link = readLink(parser); } else { skip(parser); } } return new Entry(title, summary, link); } // Processes title tags in the feed. private String readTitle(XmlPullParser parser) throws IOException, XmlPullParserException { parser.require(XmlPullParser.START_TAG, ns, "title"); String title = readText(parser); parser.require(XmlPullParser.END_TAG, ns, "title"); return title; } // Processes link tags in the feed. private String readLink(XmlPullParser parser) throws IOException, XmlPullParserException { String link = ""; parser.require(XmlPullParser.START_TAG, ns, "link"); String tag = parser.getName(); String relType = parser.getAttributeValue(null, "rel"); if (tag.equals("link")) { if (relType.equals("alternate")){ link = parser.getAttributeValue(null, "href"); parser.nextTag(); } } parser.require(XmlPullParser.END_TAG, ns, "link"); return link; } // Processes summary tags in the feed. private String readSummary(XmlPullParser parser) throws IOException, XmlPullParserException { parser.require(XmlPullParser.START_TAG, ns, "summary"); String summary = readText(parser); parser.require(XmlPullParser.END_TAG, ns, "summary"); return summary; } // For the tags title and summary, extracts their text values. private String readText(XmlPullParser parser) throws IOException, XmlPullParserException { String result = ""; if (parser.next() == XmlPullParser.TEXT) { result = parser.getText(); parser.nextTag(); } return result; } ... }

Пропуск элементов, который вам не нужны

Одним из шагов XML разбора описанном выше, синтаксический анализатор пропускает теги, в которых мы не заинтересованы. Ниже представлен код синтаксического анализатора skip() метода:

Private void skip(XmlPullParser parser) throws XmlPullParserException, IOException { if (parser.getEventType() != XmlPullParser.START_TAG) { throw new IllegalStateException(); } int depth = 1; while (depth != 0) { switch (parser.next()) { case XmlPullParser.END_TAG: depth--; break; case XmlPullParser.START_TAG: depth++; break; } } }

Вот как это работает:

  • Метод генерирует исключение, если текущее событие не START_TAG .
  • Он потребляет START_TAG , и все события, вплоть до END_TAG .
  • Чтобы убедиться, что он останавливается на правильном END_TAG , а не на первом встречном теге после оригинального START_TAG , он отслеживает глубину вложенности.

Таким образом, если текущий элемент имеет вложенные элементы, значение depth не будет равно 0 пока анализатор не обработает все события между оригинальным START_TAG и его соответствующим END_TAG . Например, рассмотрим как анализатор пропускает элемент, который имеет 2 вложенных элемента, и :

  • В первый проход по while циклу, следующий тег, который анализатор встречает после это START_TAG для
  • Во второй проход по while циклу, следующий тег, который встречает анализатор, это END_TAG
  • В третий проход по while циклу, следующий тег, который встречает анализатор, это START_TAG . Значение depth увеличивается до 2.
  • В четвертый проход по while циклу, следующий тег, который встречает анализатор, это END_TAG . Значение depth уменьшается до 1.
  • На пятый и последний проход по while циклу, следующий тег, который встречает анализатор, это END_TAG . Значение depth уменьшается до 0, что указывает на то, что элемент был успешно пропущен.

Обработка XML данных

Пример приложения получает и анализирует XML канал в AsyncTask . Обработка выполняется вне основного потока пользовательского интерфейса. Когда обработка завершена, приложение обновляет пользовательский интерфейс в основной деятельности(NetworkActivity).

Во фрагменте представленном ниже, loadPage() метод делает следующее:

  • Инициализирует строковую переменную значением URL, указывающим на XML канал.
  • Если настройки пользователя и подключение к сети позволяют, вызывает new DownloadXmlTask().execute(url) . Это создает новый DownloadXmlTask объект(AsyncTask подкласс) и выполняет его execute() метод, который загружает и анализирует канал и возвращает строковый результат, который будет отображаться в пользовательском интерфейсе.
public class NetworkActivity extends Activity { public static final String WIFI = "Wi-Fi"; public static final String ANY = "Any"; private static final String URL = "http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest"; // Whether there is a Wi-Fi connection. private static boolean wifiConnected = false; // Whether there is a mobile connection. private static boolean mobileConnected = false; // Whether the display should be refreshed. public static boolean refreshDisplay = true; public static String sPref = null; ... // Uses AsyncTask to download the XML feed from stackoverflow.com. public void loadPage() { if((sPref.equals(ANY)) && (wifiConnected || mobileConnected)) { new DownloadXmlTask().execute(URL); } else if ((sPref.equals(WIFI)) && (wifiConnected)) { new DownloadXmlTask().execute(URL); } else { // show error } }
  • doInBackground() выполняет метод loadXmlFromNetwork() . Он передает URL канала в качестве параметра. Метод loadXmlFromNetwork() получает и обрабатывает канал. Когда он заканчивает обработку, он передает обратно результирующую строку.
  • onPostExecute() принимает возвращенную строку и отображает её в пользовательском интерфейсе.
// Implementation of AsyncTask used to download XML feed from stackoverflow.com. private class DownloadXmlTask extends AsyncTask { @Override protected String doInBackground(String... urls) { try { return loadXmlFromNetwork(urls); } catch (IOException e) { return getResources().getString(R.string.connection_error); } catch (XmlPullParserException e) { return getResources().getString(R.string.xml_error); } } @Override protected void onPostExecute(String result) { setContentView(R.layout.main); // Displays the HTML string in the UI via a WebView WebView myWebView = (WebView) findViewById(R.id.webview); myWebView.loadData(result, "text/html", null); } }

Ниже приведен метод loadXmlFromNetwork() , который вызывается из DownloadXmlTask . Он делает следующее:

  1. Создает экземпляр StackOverflowXmlParser . Он также создает переменные для List Entry объектов(entries), и title , url , и summary , для хранения значений, извлеченных из XML канала, для этих полей.
  2. Вызывает downloadUrl() , который загружает канал и возвращает его как InputStream .
  3. Использует StackOverflowXmlParser для разбора InputStream . StackOverflowXmlParser заполняет List entries данными из канала.
  4. Обрабатывает entries List , и объединяет в себе данные канала с HTML разметкой.
  5. Возвращает HTML строку, отображаемую в пользовательском интерфейсе главной деятельности, AsyncTask в методе onPostExecute() .
// Uploads XML from stackoverflow.com, parses it, and combines it with // HTML markup. Returns HTML string. private String loadXmlFromNetwork(String urlString) throws XmlPullParserException, IOException { InputStream stream = null; // Instantiate the parser StackOverflowXmlParser stackOverflowXmlParser = new StackOverflowXmlParser(); List entries = null; String title = null; String url = null; String summary = null; Calendar rightNow = Calendar.getInstance(); DateFormat formatter = new SimpleDateFormat("MMM dd h:mmaa"); // Checks whether the user set the preference to include summary text SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); boolean pref = sharedPrefs.getBoolean("summaryPref", false); StringBuilder htmlString = new StringBuilder(); htmlString.append("

" + getResources().getString(R.string.page_title) + "

"); htmlString.append("" + getResources().getString(R.string.updated) + " " + formatter.format(rightNow.getTime()) + ""); try { stream = downloadUrl(urlString); entries = stackOverflowXmlParser.parse(stream); // Makes sure that the InputStream is closed after the app is // finished using it. } finally { if (stream != null) { stream.close(); } } // StackOverflowXmlParser returns a List (called "entries") of Entry objects. // Each Entry object represents a single post in the XML feed. // This section processes the entries list to combine each entry with HTML markup. // Each entry is displayed in the UI as a link that optionally includes // a text summary. for (Entry entry: entries) { htmlString.append("

" + entry.title + "

"); // If the user set the preference to include summary text, // adds it to the display. if (pref) { htmlString.append(entry.summary); } } return htmlString.toString(); } // Given a string representation of a URL, sets up a connection and gets // an input stream. private InputStream downloadUrl(String urlString) throws IOException { URL url = new URL(urlString); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setReadTimeout(10000 /* milliseconds */); conn.setConnectTimeout(15000 /* milliseconds */); conn.setRequestMethod("GET"); conn.setDoInput(true); // Starts the query conn.connect(); return conn.getInputStream(); }
публикация данной статьи разрешена только со ссылкой на сайт автора статьи

В этой статье я покажу пример, как разобрать большой XML файл. Если на вашем сервере (хостинге) не запрещено увеличение времени работы скрипта, то можно разбирать XML файл весом хоть гигабайты, сам лично разбирал только файлы от озона весом 450 мегабайт.

При разборе больших XML файлов возникает две проблемы:
1. Не хватает памяти.
2. Не хватает выделенного времени для работы скрипта.

Вторую проблему с временем решить можно, если сервером это не запрещено.
А вот проблему с памятью решить сложно, даже если речь идет о своем сервере, то ворочать файлы по 500 мегабайт не очень просто а уж на хостинге и на VDS увеличить память просто не получится.

В PHP существует несколько встроенных вариантов обработки XML - SimpleXML, DOM, SAX.
Все эти варианты подробно описаны во многих статьях с примерами, но все примеры демонстрируют работу с полным XML документом.

Вот один из примеров, получаем объект из XML файла

Теперь можно обрабатывать этот объект, НО...
Как видно, весь XML файл считывается в память, затем все разбирается в объект.
То есть все данные попадают в память и если выделенной памяти мало, то скрипт останавливается.

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

Вот теоретический пример разбора большого XML файла.
Этот скрипт читает по одному символу из файла, собирает эти данные в блоки и отправляет в разборщик XML.
Такой подход полностью решает проблему с памятью и не вызывает нагрузки, но усугубляет проблему со временем. Как попытаться решить проблему со временем, читайте ниже.

Function webi_xml ($file )
{

########
### функция работы с данными

{
print $data ;
}
############################################



{
print $name ;
print_r ($attrs );
}


## функция закрывающих тегов
function endElement ($parser , $name )
{
print $name ;
}
############################################

($xml_parser , "data" );

// открываем файл
$fp = fopen ($file , "r" );

$perviy_vxod = 1 ; $data = "" ;



{

$simvol = fgetc ($fp ); $data .= $simvol ;


if($simvol != ">" ) { continue;}


echo "

break;
}

$data = "" ;
}
fclose ($fp );

Webi_xml ("1.xml" );

?>

В этом примере я все сложил в одну функцию webi_xml() и в самом низу видно ее вызов.
Сам скрипт состоит из трех основных функций:
1. Функция которая ловит открытие тега startElement()
2. Функция которая ловит закрытие тега endElement()
3. И функция получения данных data() .

Предположим что содержимое файла 1.xml некий рецепт



< title > Простой хлеб
< ingredient amount = "3" unit = "стакан" > Мука
< ingredient amount = "0.25" unit = "грамм" > Дрожжи
< ingredient amount = "1.5" unit = "стакан" > Тёплая вода
< ingredient amount = "1" unit = "чайная ложка" > Соль
< instructions >
< step > Смешать все ингредиенты и тщательно замесить .
< step > Закрыть тканью и оставить на один час в тёплом помещении .
< step > Замесить ещё раз , положить на противень и поставить в духовку .
< step > Посетить сайт сайт


Начинаем все с вызова общей функции webi_xml ("1.xml" );
Дальше в этой функции стартует разборщик и все имена тегов переводим в верхний регистр, чтобы все теги имели одинаковый регистр.

$xml_parser = xml_parser_create ();
xml_parser_set_option ($xml_parser , XML_OPTION_CASE_FOLDING , true );

Теперь указываем какие функции будут работать для отлова открытия тега, закрытия и обработки данных

xml_set_element_handler ($xml_parser , "startElement" , "endElement" );
xml_set_character_data_handler ($xml_parser , "data" );

Дальше идет открытие указанного файла, перебор файла по одному символу и каждый символ добавляется в строковую переменную пока не будет найден символ > .
Если это самое первое обращение к файлу, то попутно будет удалено все что будет лишним в начале файла, все что стоит до , именно с такого тега должен начинаться XML.
В первый раз строковая переменная соберет в себе строку

И отправит ее в разборщик
xml_parse ($xml_parser , $data , feof ($fp )) ;
После обработки данных строковая переменная сбрасыватеся и снова начинается сбор данных в строку и во второй раз сформируется строка

В третий
</b><br> в четвертый <br><b> Простой хлеб

Обратите внимание, что строковая переменная всегда формируется по законченному тегу > и не обязательно посылать разбощику открытый и закрытый тег с данными например
Простой хлеб
Данному обработчику важно получить целый не разбитый тег, хоть один открытый, а в следущий шаг закрытый тег, или сразу получить 1000 строк файла, не важно, главное чтобы тег не разрывался, например

le>Простой хлеб
Так отправить данные обработчику нельзя, так как тег разорвался.
Вы можете придумать свою методику посылания данных в обработчик, например собирать по 1 мегабайту данных и отправлять в обработчик для повышения скорости, только следите чтобы теги всегда завершались, а данные можно разрывать
Простой </b><br><b>хлеб

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

Теперь рассмотрим каким образом эти данные обрабатываются и как их получить.

Начинаем с функции открывающих тегов startElement ($parser , $name , $attrs )
Предположим, что обработка дошла до строки
< ingredient amount = "3" unit = "стакан" > Мука
Тогда внутри функции переменная $name будет равна ingredient то есть название открытого тега (до закрытия тега дело еще не дошло).
Так же в данном случае будет доступен массив атрибутов этого тега $attrs , в котором будут данные amount = "3" и unit = "стакан" .

После этого пошла обработка данных открытого тега функцией data ($parser , $data )
В переменной $data будет все, что находится между открывающим и закрывающим тегом, в нашем случае это текст Мука

И завершается обработка нашей строки функцией endElement ($parser , $name )
Это название закрытого тега, в нашем случае $name будет равна ingredient

А после этого опять пошло все по кругу.

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

Function webi_xml ($file )
{
global $webi_depth ; // счетчик, для отслеживания глубины вложенности
$webi_depth = 0 ;
global $webi_tag_open ; // будет содержать массив открытых в данный момент тегов
$webi_tag_open = array();
global $webi_data_temp ; // этот массив будет содержать данные одного тега

####################################################
### функция работы с данными
function data ($parser , $data )
{
global $webi_depth ;
global $webi_tag_open ;
global $webi_data_temp ;
// добавляем данные в массив с указанием вложенности и открытого в данный момент тега
$webi_data_temp [ $webi_depth ][ $webi_tag_open [ $webi_depth ]][ "data" ].= $data ;
}
############################################

####################################################
### функция открывающих тегов
function startElement ($parser , $name , $attrs )
{
global $webi_depth ;
global $webi_tag_open ;
global $webi_data_temp ;

// если уровень вложенности уже не нулевой, значит один тег уже открыт
// и данные из него уже в массиве, можно их обработать
if ($webi_depth )
{




" ;

print "
" ;
print_r ($webi_tag_open ); // массив открытых тегов
print "


" ;

// после обработки данных удаляем их для освобождения памяти
unset($GLOBALS [ "webi_data_temp" ][ $webi_depth ]);
}

// теперь пошло открытие следующего тега и дальше обработка его произойдет на следующем шаге
$webi_depth ++; // увеличиваем вложенность

$webi_tag_open [ $webi_depth ]= $name ; // добавляем открытый тег в массив информаци
$webi_data_temp [ $webi_depth ][ $name ][ "attrs" ]= $attrs ; // теперь добавляем атрибуты тега

}
###############################################

#################################################
## функция закрывающих тегов
function endElement ($parser , $name ) {
global $webi_depth ;
global $webi_tag_open ;
global $webi_data_temp ;

// здесь начинается обработка данных, например добаление в базу, сохранение в файл и т.д.
// $webi_tag_open содержит цепочку открытых тегов по уровню вложенности
// например $webi_tag_open[$webi_depth] содержит название открытого тега чья информация сейчас обрабатывается
// $webi_depth уровень вложенности тега
// $webi_data_temp[$webi_depth][$webi_tag_open[$webi_depth]]["attrs"] массив атрибутов тега
// $webi_data_temp[$webi_depth][$webi_tag_open[$webi_depth]]["data"] данные тега

Print "данные " . $webi_tag_open [ $webi_depth ]. "--" .($webi_data_temp [ $webi_depth ][ $webi_tag_open [ $webi_depth ]][ "data" ]). "
" ;
print_r ($webi_data_temp [ $webi_depth ][ $webi_tag_open [ $webi_depth ]][ "attrs" ]);
print "
" ;
print_r ($webi_tag_open );
print "


" ;

Unset($GLOBALS [ "webi_data_temp" ]); // после обработки данных удаляем массив с данными целиком, так как произошло закрытие тега
unset($GLOBALS [ "webi_tag_open" ][ $webi_depth ]); // удаляем информацию об этом открытом теге... так как он закрылся

$webi_depth --; // уменьшаем вложенность
}
############################################

$xml_parser = xml_parser_create ();
xml_parser_set_option ($xml_parser , XML_OPTION_CASE_FOLDING , true );

// указываем какие функции будут работать при открытии и закрытии тегов
xml_set_element_handler ($xml_parser , "startElement" , "endElement" );

// указываем функцию для работы с данными
xml_set_character_data_handler ($xml_parser , "data" );

// открываем файл
$fp = fopen ($file , "r" );

$perviy_vxod = 1 ; // флаг для проверки первого входа в файл
$data = "" ; // сюда собираем частями данные из файла и отправляем в разборщик xml

// цикл пока не найден конец файла
while (! feof ($fp ) and $fp )
{
$simvol = fgetc ($fp ); // читаем один символ из файла
$data .= $simvol ; // добавляем этот символ к данным для отправки

// если символ не завершающий тег, то вернемся к началу цикла и добавим еще один символ к данным, и так до тех пор, пока не будет найден закрывающий тег
if($simvol != ">" ) { continue;}
// если закрывающий тег был найден, теперь отправим эти собранные данные в обработку

// проверяем, если это первый вход в файл, то удалим все, что находится до тега // так как иногда может встретиться мусор до начала XML (корявые редакторы, либо файл получен скриптом с другого сервера)
if($perviy_vxod ) { $data = strstr ($data , "

// теперь кидаем данные в разборщик xml
if (! xml_parse ($xml_parser , $data , feof ($fp ))) {

// здесь можно обработать и получить ошибки на валидность...
// как только встретится ошибка, разбор прекращается
echo "
XML Error: " . xml_error_string (xml_get_error_code ($xml_parser ));
echo " at line " . xml_get_current_line_number ($xml_parser );
break;
}

// после разбора скидываем собранные данные для следующего шага цикла.
$data = "" ;
}
fclose ($fp );
xml_parser_free ($xml_parser );
// удаление глобальных переменных
unset($GLOBALS [ "webi_depth" ]);
unset($GLOBALS [ "webi_tag_open" ]);
unset($GLOBALS [ "webi_data_temp" ]);

Webi_xml ("1.xml" );

?>

Весь пример сопроводил комментариями, теперь тестируйте и экспериментируйте.
Обратите внимание, в функции работы с данными в массив данные не просто вставляются, а именно добавляются с помощью ".=" так как данные могут поступать не в целом виде и если сделать просто присвоение, то время от времени вы будете получать данные кусками.

Ну вот и все, теперь при обработке файла любого размера памяти хватит, а вот время работы скрипта увеличить можно несколькими способами.
В начало скрипта вставьте функцию
set_time_limit (6000 );
или
ini_set ("max_execution_time" , "6000" );

Либо добавьте в файл.htaccess текст
php_value max_execution_time 6000

Данные примеры увеличат время работы скрипта до 6000 секунд.
Увеличить время подобным образом можно только в выключенном безопасном режиме.

Если у вас есть доступ к редактированию php.ini можете увеличить время с помощью
max_execution_time = 6000

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

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

Используя расширение SimpleXML в PHP, которое было добавлено еще в PHP 5.0, работать с XML очень легко и просто. В этой статье я покажу вам, как это делать.

Основы использования

Давайте начнем со следующего примера languages.xml :


>

> 1972>
> Dennis Ritchie>
>

> 1995>
> Rasmus Lerdorf>
>

> 1995>
> James Gosling>
>
>

Данный XML-документ содержит список языков программирования с некоторой информацией о каждом языке: год его реализации и имя его создателя.

Первый шаг заключается в загрузке XML с использованием функций либо simplexml_load_file() , либо simplexml_load_string() . Как понятно из названия функций, первая загружает XML из файла, а вторая будет загружать XML из строки.

Обе функции считывают все дерево DOM в память и возвращают объект SimpleXMLElement . В приведенном выше примере, объект сохраняется в переменной $languages. Вы можете использовать функции var_dump() или print_r() , чтобы получить подробную информацию о возвращенном объект, если хотите.

SimpleXMLElement Object
[ lang] => Array
[ 0 ] => SimpleXMLElement Object
[ @ attributes] => Array
[ name] => C
[ appeared] => 1972
[ creator] => Dennis Ritchie
[ 1 ] => SimpleXMLElement Object
[ @ attributes] => Array
[ name] => PHP
[ appeared] => 1995
[ creator] => Rasmus Lerdorf
[ 2 ] => SimpleXMLElement Object
[ @ attributes] => Array
[ name] => Java
[ appeared] => 1995
[ creator] => James Gosling
)
)

Данный XML содержит корневой элемент languages , внутри которого находятся три элемента lang. Каждый элемент массива соответствует элементу lang в XML-документе.

Вы можете получить доступ к свойствам объекта при помощи оператора -> . Например, $languages->lang вернет вам объект SimpleXMLElement, который соответствует первому элементу lang . Этот объект содержит два свойства: appeared и creator.

$languages -> lang [ 0 ] -> appeared ;
$languages -> lang [ 0 ] -> creator ;

Вывести на экран список языков и показать их свойства можно очень легко при помощи стандартного цикла, такого как foreach .

foreach ($languages -> lang as $lang ) {
printf (
"" ,
$lang [ "name" ] ,
$lang -> appeared ,
$lang -> creator
) ;
}

Обратите внимание, как я получил доступ к имени атрибута элемента lang, чтобы получить название языка. Таким образом Вы можете получить доступ к любому атрибуту элемента представленого в виде объекта SimpleXMLElement.

Работа с пространствами имен

Во время работы с XML различных веб-сервисов вы не раз столкнетесь с пространствами имен элементов. Давайте изменим наш languages.xml , чтобы показать пример использования пространства имен:



xmlns:dc =>

> 1972>
> Dennis Ritchie>
>

> 1995>
> Rasmus Lerdorf>
>

> 1995>
> James Gosling>
>
>

Теперь элемент creator помещается в пространстве имен dc , который указывает на http://purl.org/dc/elements/1.1/. Если вы попытаетесь распечатать создателей языка, используя наш предыдущий код, то он не будет работать. Для того, чтобы читать пространства имен элементов вам необходимо использовать один из следующих подходов.

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

$dc = $languages -> lang [ 1 ] - > children("http://purl.org/dc/elements/1.1/" ) ;
echo $dc -> creator ;

Метод children() принимает пространство имен и возвращает дочерние элементы, которые начинаются с префикса. Он принимает два аргумента, первый из которых является пространством имен XML, и второй необязательный аргумент, который по умолчанию равен false . Если второй аргумент установлен как TRUE, пространство имен будет рассмотрено как префикс. Если FALSE, то пространство имен будет рассмотрено как пространство имен URL.

Второй подход состоит в чтении имен URI из документа и использование их при обращении к пространству имен элементов. На самом деле это лучший способ доступа к элементам, потому что вы не должны быть жестко привязаны к URI.

$namespaces = $languages -> getNamespaces (true ) ;
$dc = $languages -> lang [ 1 ] -> children ($namespaces [ "dc" ] ) ;

echo $dc -> creator ;

Метод GetNamespaces() возвращает массив имен префиксов и связанные с ними URI. Он принимает дополнительный параметр, который по умолчанию равен false . Если вы установите его как true , то этот метод будет возвращать имена, используемые в родительских и дочерних узлах. В противном случае, он находит пространства имен, используемые только в родительском узле.

Теперь вы можете пройтись по списку языков следующим образом:

$languages = simplexml_load_file ("languages.xml" ) ;
$ns = $languages -> getNamespaces (true ) ;

foreach ($languages -> lang as $lang ) {
$dc = $lang -> children ($ns [ "dc" ] ) ;
printf (
"

%s появился в %d и был создан %s .

" ,
$lang [ "name" ] ,
$lang -> appeared ,
$dc -> creator
) ;
}

Практический пример - Парсинг видео-канала с YouTube

Давайте рассмотрим пример, который получает RSS-feed с канала YouTube и отображает ссылки на все видео из него. Для этого нужно обратится по следующему адресу:

http://gdata.youtube.com/feeds/api/users/xxx/uploads

URL возвращает список последних видео с данного канала в формате XML. Мы распарсим XML и получим следующую информацию для каждого видео:

  • Ссылка на видео
  • Миниатюра
  • Название

Мы начнем с поиска и загрузки XML:

$channel = "Имя_канала" ;
$url = "http://gdata.youtube.com/feeds/api/users/" . $channel . "/uploads" ;
$xml = file_get_contents ($url ) ;

$feed = simplexml_load_string ($xml ) ;
$ns = $feed -> getNameSpaces (true ) ;

Если вы посмотрите на XML-feed, то вы можете увидеть, что там есть несколько элементов entity , каждый из которых хранит подробную информацию о конкретном видео с канала. Но мы используем только миниатюры изображений, адрес видео и название. Эти три элемента являются потомками элемента group , который, в свою очередь, является дочерним для entry :

>

>



Title…>

>

>

Мы просто пройдемся по всем элементам entry , и для каждого из них извлечем необходимую информацию. Обратите внимание, что player, thumbnail и title находятся в пространстве имен media. Таким образом, мы должны действовать, как в предыдущем примере. Мы получаем имена из документа и используем пространство имен при обращении к элементам.

foreach ($feed -> entry as $entry ) {
$group = $entry -> children ($ns [ "media" ] ) ;
$group = $group -> group ;
$thumbnail_attrs = $group -> thumbnail [ 1 ] -> attributes () ;
$image = $thumbnail_attrs [ "url" ] ;
$player = $group -> player -> attributes () ;
$link = $player [ "url" ] ;
$title = $group -> title ;
printf ("

" ,
$player , $image , $title ) ;
}

Заключение

Теперь, когда вы знаете, как использовать SimpleXML для разбора XML-данных, вы можете улучшить свои навыки путем анализа различных XML-каналов с различными API. Но важно учитывать, что SimpleXML считывает весь DOM в память, так что если вы парсите большой набор данных, то вы можете столкнуться с нехваткой памяти. Чтобы узнать больше о SimpleXML прочтите документацию .


Если у Вас возникли вопросы, то для скорейшего получения ответа рекомендуем воспользоваться нашим


Автор: Arseny Kapoulkine
Дата публикации: 21 Сентября 2012 г.
Перевод: А.Панин
Дата перевода: 10 Ноября 2013 г.

Введение

XML является стандартизированным языком разметки, для которого устанавливается ряд правил кодирования иерархически структурированных документов в читаемом человеком текстовом формате. Стандарт XML получил широкое распространение и применяется для формирования как очень компактных простых документов (таких, как запросы SOAP), так и многогигабайтных документов (применяемых проектом OpenStreetMap) со сложными зависимостями между данными (COLLADA). Для обработки документов XML пользователям обычно требуется специальная библиотека: она должна реализовывать механизм разбора документов XML, который преобразовывает документ из текстового во внутреннее представление. Стандарт XML является компромиссным решением в плане скорости разбора, пригодности для чтения пользователем, а также сложности кода для разбора - следовательно, наличие быстрой системы разбора документов XML может повлиять на предпочтительный выбор формата XML в качестве формата для хранения данных приложения.

В данной главе описываются различные приемы, направленные на повышение производительности описанной системы разбора и позволившие автору разработать чрезвычайно производительную систему разбора с использованием языка программирования C++: pugixml . Хотя эти приемы были использованы для системы разбора документов XML, большая их часть может быть применена в системах разбора документов других форматов или даже в абсолютно не связанных с описанными программных компонентах (например, алгоритмы управления памятью широко применяются и в не связанных с системами разбора текстовых документов областях).

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

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

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

  • При использовании систем разбора на основе SAX (Simple API for XML - простой API для XML) пользователь получает в свое расположение программный компонент, ожидающий поток данных документа в качестве входных данных и предоставляющий несколько функций обратного вызова, таких, как "открытие тэга", "закрытие тэга", "символ внутри тэга". Система разбора использует функции обратного вызова в процессе обработки данных документа. Необходимый для разбора контекст ограничен глубиной дерева текущего элемента, что подразумевает значительное снижение требований к памяти. Этот тип системы разбора может использоваться для обработки передаваемых в потоковом режиме документов в том случае, когда в каждый момент времени доступна только часть документа.
  • Pull-разбор аналогичен разбору на основе SAX в плане самого процесса - в каждый момент времени производится обработка одного элемента документа, но метод управления процессом разбора изменен: в системе разбора на основе SAX управление процессом разбора осуществляется самой системой с помощью функций обратного вызова, в то время, как при pull-разборе пользователь управляет процессом разбора с помощью похожего на итератор объекта.
  • При использовании систем разбора на основе DOM (Document Object Model - объектная модель документа) пользователь передает системе разбора полный документ в виде буфера или потока текстовых данных, на основе которого система разбора генерирует в памяти объектное представление всего дерева элементов документа, использующее отдельные объекты для каждого определенного элемента или атрибута XML, а также набор допустимых операций (например, "получение всех дочерних элементов этого узла"). Библиотека pugxml использует эту модель.

Выбор модели системы разбора обычно зависит от размера документа и его структуры. Так как библиотека pugixml производит разбор на основе модели DOM, она эффективна для документов, которые:

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

Архитектурные решения в pugixml

При разработке Библиотеки pugixml внимание в значительной степени уделялось проблеме создания представления DOM, так как хотя и были доступны такие быстрые и легковесные системы разбора на основе SAX (как Expat), все доступные для промышленного использования системы разбора XML на основе DOM во время создания pugixml (2006 год) были либо не слишком легковесными, либо не слишком быстрыми. Исходя из этого, главной целью процесса разработки puixml является создание очень быстрой легковесной библиотеки для выполнения манипуляций с документами XML на основе модели DOM.

Понравилась статья? Поделитесь ей