Генерирование JSON из XML для использования с Ajax

Используйте XSLT V2 для преобразования ваших XML-данных в JavaScript Object Notation (JSON)

Использование JavaScript-кода для добавления интерактивности в управляемые данными Web-приложения в настоящее время очень актуально. Закодировав ваши данные в виде JavaScript Object Notation (JSON), вы упростите их использование с языком JavaScript. Узнайте о различных подходах к использованию XSLT V2 для генерирования JSON из XML-данных.

Джек Д Херрингтон, главный инженер-программист, Leverage Software Inc.

Джек Д. Херрингтон (Jack D. Herrington) - главный инженер-программист с более чем двадцатилетним опытом работы. Он автор трех книг: "Генерирование кода в действии", "Podcasting Hacks" и "PHP Hacks". Написал более 30 статей. Вы можете связаться с Джеком по адресу jherr@pobox.com.



12.09.2006

Несколько лет назад многие разработчики сделали ставку на XML, XSLT, Extensible HTML (XHTML) и набор основанных на тегах "X"-языков. Сейчас новой страстью является Asynchronous JavaScript and XML (AJAX), и инвесторы обратили взгляд в сторону управляемых данными полнофункциональных Интернет-приложений (Rich Internet Applications), использующих JavaScript-код. Но объединили ли разработчики XML и эту новую технологию?

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

Другим вариантом является разрешение серверу выполнить работу по синтаксическому анализу XML путем конфигурирования его на передачу в браузер данных, закодированных в виде JavaScript-кода, или в более модном стиле - JavaScript Object Notation (JSON). В данной статье я демонстрирую три способа генерирования JSON из XML-данных, используя язык XSLT V2 и процессор Saxon XSLT V2:

  • Простое кодирование.
  • Загрузка данных через вызовы функций.
  • Кодирование объектов.

Введение в JSON

Изучение кодирования данных в виде JSON (на самом деле, являющегося просто подмножеством JavaScript) мы начнем с определения некоторых данных. В листинге 1 приведен пример набора XML-данных - список книг.

Листинг 1. Основная графическая библиотека
<?xml version="1.0" encoding="UTF-8"?>
<books>
    <book id="1">
        <title>Code Generation in Action</title>
        <author><first>Jack</first><last>Herrington</last></author>
        <publisher>Manning</publisher>
    </book>
    <book id="2">
        <title>PHP Hacks</title>
        <author><first>Jack</first><last>Herrington</last></author>
        <publisher>O'Reilly</publisher>
    </book>
    <book id="3">
        <title>Podcasting Hacks</title>
        <author><first>Jack</first><last>Herrington</last></author>
        <publisher>O'Reilly</publisher>
    </book>
</books>

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

В листинге 2 показано, как эти данные выглядели бы в JSON.

Листинг 2. Пример набора данных в JSON
[ { id: 1,
    title: 'Code Generation in Action',
    first: 'Jack',
    last: 'Herrington',
    publisher: 'Manning' },
 ... ]

Квадратные скобки ([]) указывают массив. Фигурные скобки ({}) указывают хэш-таблицу, которая является набором пар имя-значение. В данном случае я создаю массив хэш-таблиц - традиционный метод хранения подобных структурированных данных.

Стоит отметить, что строки кодируются с использованием одинарных или двойных кавычек. Потому, если я хочу закодировать O'Reilly в строку, заключенную в одинарные кавычки, я должен использовать обратный слеш: 'O\'Reilly'. Это делает таблицу стилей XSLT, которую я пишу, немного более интересной.

Я не использую в примере даты, но вы можете закодировать дату двумя способами. Первый способ - это строка, которая должна быть проанализирована позднее. Второй - это объект, например:

publishdate: new Date( 2006, 6, 16, 17, 45, 0 )

Этот код устанавливает значение publishdate в 7/16/2006 5:45:00 p.m..


Простое кодирование

Я предложу несколько различных способов для JSON-кодирования. Первый является самым простым. Таблица стилей приведена в листинге 3.

Листинг 3. Таблица стилей simple.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
 xmlns:js="http://muttmansion.com">
    
<xsl:output method="text" />

<xsl:function name="js:escape">
<xsl:param name="text" />
<xsl:value-of select='replace( $text, "'", "\\'" )' />
</xsl:function>

<xsl:template match="/">
var g_books = [
<xsl:for-each select="books/book">
<xsl:if test="position() > 1">,</xsl:if> {
id: <xsl:value-of select="@id" />,
name: '<xsl:value-of select="js:escape(title)" />',
first: '<xsl:value-of select="js:escape(author/first)" />',
last: '<xsl:value-of select="js:escape(author/last)" />',
publisher: '<xsl:value-of select="js:escape( publisher )" />'
}</xsl:for-each>
];
</xsl:template>

</xsl:stylesheet>

Для того чтобы понять таблицу стилей, легче сначала просмотреть получаемые результаты, показанные в листинге 4.

Листинг 4. Результат, получаемый из simple.xsl
var g_books = [
 {
id: 1,
name: 'Code Generation in Action',
first: 'Jack',
last: 'Herrington',
publisher: 'Manning'
}, {
id: 2,
name: 'PHP Hacks',
first: 'Jack',
last: 'Herrington',
publisher: 'O\'Reilly'
}, {
id: 3,
name: 'Podcasting Hacks',
first: 'Jack',
last: 'Herrington',
publisher: 'O\'Reilly'
}
];

Здесь я устанавливаю переменную под названием g_books в массив, содержащий три хэш-таблицы. Каждая хэш-таблица содержит информацию о книге. Снова взгляните на листинг 3. Вы увидите, что первый шаблон является шаблоном, соответствующим пути "/", и первым шаблоном, применяемым ко входному набору данных. Этот шаблон использует цикл for-each для прохода по каждой книге. Затем он использует тег <value-of> для вывода текста из данных в выходной JavaScript-код.

Для строк я использую пользовательскую функцию js:escape(), которая определена прямо перед шаблоном. Эта функция использует регулярное выражение для изменения одинарной кавычки на одинарную кавычку с предшествующим ей обратным слешем.

Последним важным элементом является тег <xsl:output>, который указывает процессору, что я хочу вывести текст, а не XML. Для того чтобы увидеть, работает ли процесс, я скомпоновал простой .html-файл, ссылающийся на получаемый из таблицы стилей XSL результат, который я сохранил в файле simple.js. HTML приведен в листинге 5.

Листинг 5. Фал simple.html
<html>
<head>
<title>Simple JS loader</title>
<script src="simple.js"></script>
</head>
<body>
<script>
document.write( "Found "+g_books.length+" books" );
</script>
</body>
</html>

.html-файл просто загружает закодированный JavaScript-код, используя первый тег <script>. Затем второй тег <script> записывает длину массива в страницу браузера, как показано на рисунке 1.

Рисунок 1. Результат отображения simple.html
Рисунок 1. Результат отображения simple.html

Отлично! Файл данных содержит три книги, и соответствующий JavaScript-файл содержит три книги. Все работает!


Загрузка через функции

Первый пример является довольно простым и может подойти во многих ситуациях, но есть некоторые проблемы. Первая проблема - нет указания того, когда данные заканчивают загружаться. Это не является проблемой, когда данные загружаются статически, как в данной странице. Но когда страница создает тег <script> "на лету" для загрузки данных по запросу, важно знать, когда этот тег <script> завершает работу. Одним из наилучших способов узнать это - сделать так, чтобы закодированные данные активизировали JavaScript-функцию вместо простой установки данных.

Эта концепция является важной, поэтому я вернусь назад и потрачу некоторое время на объяснение того, почему вы должны иметь возможность загружать данные через динамически сгенерированные теги <script>. Получение данных с сервера после загрузки страницы является сердцевиной Web 2.0. Одним из способов сделать это - использовать механизм AJAX для загрузки XML посредством вызова сервера. Однако, в целях защиты, этот AJAX-механизм ограничен получением данных из одного домена. Это не важно в большинстве ситуаций, но иногда вы хотели бы выполнить ваш JavaScript-код на страницах других людей (например, Google Maps).

Единственным способом получения данных с сервера в этом случае является использование динамической загрузки тегов <script>. Самым лучшим способом узнать о том, когда тег <script> загружается, является сценарий, в котором тег <script> возвращает вызов функции вместо простой загрузки данных. В листинге 6 показаны данные, закодированные в вызове функции.

Листинг 6. Function1.js
AddBooks( [
{
id: 1,
name: 'Code Generation in Action',
first: 'Jack',
last: 'Herrington',
publisher: 'Manning'
}, {
id: 2,
name: 'PHP Hacks',
first: 'Jack',
last: 'Herrington',
publisher: 'O\'Reilly'
}, {
id: 3,
name: 'Podcasting Hacks',
first: 'Jack',
last: 'Herrington',
publisher: 'O\'Reilly'
}
] );

В листинге 7 приведен соответствующий .html-файл.

Листинг 7. Function1.html
<html>
<head>
<title>Function 1 JS loader</title>
<script>
var g_books = [];
function AddBooks( books ) { g_books = books; }
</script>
<script src="function1.js"></script>
<script src="drawbooks.js"></script>
</head>
<body>
<script>drawbooks( g_books );</script>
</body>
</html>

Я рассмотрю функцию drawbooks через пару секунд. Но что здесь важно - увидеть, как страница определяет функцию AddBooks, которая затем вызывается сценарием в файле function1.js. Эта функция AddBooks определяет, что делать с данными. И функция AddBooks, будучи вызванной, указывает странице, что тег <script> был правильно загружен, а его загрузка завершилась.

Для создания файла function1.js я сделал только небольшие изменения в таблице стилей, показанные в листинге 8.

Листинг 8. Таблица стилей function1.xsl
<xsl:template match="/">
AddBooks( [
<xsl:for-each select="books/book">
<xsl:if test="position() > 1">,</xsl:if> {
id: <xsl:value-of select="@id" />,
name: '<xsl:value-of select="js:escape(title)" />',
first: '<xsl:value-of select="js:escape(author/first)" />',
last: '<xsl:value-of select="js:escape(author/last)" />',
publisher: '<xsl:value-of select="js:escape( publisher )" />'
}</xsl:for-each>
] );
</xsl:template>

То есть, вместо простой установки переменной я теперь активизирую функцию. Это единственное изменение.

Вернемся к странице. Я использовал функцию drawbooks для создания таблицы книг, для того чтобы можно было убедиться в том, что данные закодированы правильно и выглядят корректно. Функция определена в файле drawbooks.js, который представлен в листинге 9.

Листинг 9. Drawbooks.js
function drawbooks( books )
{
 var elTable = document.createElement( 'table' );
 for( var b in books )
 {
  var elTR = elTable.insertRow( -1 );
  var elTD1 = elTR.insertCell( -1 );
  elTD1.appendChild( document.createTextNode( books[b].id ) );
  var elTD2 = elTR.insertCell( -1 );
  elTD2.appendChild( document.createTextNode( books[b].name ) );
  var elTD3 = elTR.insertCell( -1 );
  elTD3.appendChild( document.createTextNode( books[b].first ) );
  var elTD4 = elTR.insertCell( -1 );
  elTD4.appendChild( document.createTextNode( books[b].last ) );
  var elTD5 = elTR.insertCell( -1 );
  elTD5.appendChild( document.createTextNode( books[b].publisher ) );
 }
 document.body.appendChild( elTable );
}

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

Рисунок 2. Результат function1.html
Рисунок 2. Результат function1.html

Теперь я могу взглянуть на результат и увидеть, что все из оригинального .xml-файла было переведено в JavaScript-код правильно, и что данные были переданы в функцию AddData и добавлены на страницу должным образом.


Улучшение методики вызова функции

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

Листинг 10. Function2.js
AddBook( {
id: 1,
name: 'Code Generation in Action',
first: 'Jack',
last: 'Herrington',
publisher: 'Manning'
}  );
AddBook( {
id: 2,
name: 'PHP Hacks',
first: 'Jack',
last: 'Herrington',
publisher: 'O\'Reilly'
}  );
...

На .html-странице необходимо сделать лишь небольшие изменения, показанные в листинге 11.

Листинг 11. Function2.html
...
<script>
var g_books = [];
function AddBook( book ) { g_books.push( book ); }
</script>
...

XSLT изменяется так, что вызов функции теперь размещается в теле цикла for-each. В листинге 12 приведена обновленная таблица стилей.

Листинг 12. function2.xsl
...
<xsl:template match="/">
<xsl:for-each select="books/book">
AddBook( {
id: <xsl:value-of select="@id" />,
name: '<xsl:value-of select="js:escape(title)" />',
first: '<xsl:value-of select="js:escape(author/first)" />',
last: '<xsl:value-of select="js:escape(author/last)" />',
publisher: '<xsl:value-of select="js:escape( publisher )" />'
}  );</xsl:for-each>
</xsl:template>
...

Это изменение может показаться необязательным для данного примера. Но если ваш оригинальный набор XML-данных имеет много различных типов данных, передача каждого из них в отдельном вызове функции упрощает как XSL, так и JavaScript-код страницы, а также облегчает обслуживание.


Закодированные объекты

Для небольших страниц использование JavaScript-функций является нормальным. Но для больших проектов вы определенно захотите использовать некоторые объектно-ориентированные возможности языка JavaScript. Да, язык JavaScript поддерживает объекты, и делает это хорошо.

В листинге 13 показано, как выглядит создание объектов с данными.

Листинг 13. Object1.js
g_books.push( new Book( {
id: 1,
name: 'Code Generation in Action',
first: 'Jack',
last: 'Herrington',
publisher: 'Manning'
}  ) );
g_books.push( new Book( {
id: 2,
name: 'PHP Hacks',
first: 'Jack',
last: 'Herrington',
publisher: 'O\'Reilly'
}  ) );

В данном случае я просто добавляю объекты Book в массив g_books. Создание JavaScript-объекта аналогично созданию объекта в языках программирования Java™, C# или C++: оператор new, за которым следует имя класса. Затем в круглых скобках передаются аргументы. В данном случае я передаю одну хэш-таблицу со значениями. Я мог точно так же разбить их на отдельные параметры.

Код для создания этого объекта показан в листинге 14.

Листинг 14. Object1.xsl
<xsl:template match="/">
<xsl:for-each select="books/book">
g_books.push( new Book( {
id: <xsl:value-of select="@id" />,
name: '<xsl:value-of select="js:escape(title)" />',
first: '<xsl:value-of select="js:escape(author/first)" />',
last: '<xsl:value-of select="js:escape(author/last)" />',
publisher: '<xsl:value-of select="js:escape( publisher )" />'
}  ) );</xsl:for-each>
</xsl:template>

Более интересный код имеется на странице, где определяется класс Book. Эта страница показана в листинге 15.

Листинг 15. object1.html
...
<script>
var g_books = [];
function Book( data )
{
  for( var d in data ) { this[d] = data[d]; }
}
</script>
...

Конструктор класса Book выполняет итерацию по данным хеш-таблицы. Для каждого ключа создается экземпляр переменной объекта с этим именем и данными. Для функции drawbooks никаких изменений не требуется, поскольку все объекты имеют те же ключи и значения, что и оригинальные хеш-таблицы. Язык JavaScript не делает различий в доступе к именованным значениям в хеш-таблице и к именованным значениям объекта.

Конечно же, класс Book должен, на самом деле, иметь методы доступа, такие как set и get. В листинге 16 показано, как мне нравится кодировать JavaScript-данные.

Листинг 16. Object2.js
var  b1  = new Book();
b1.setId ( 1 );
b1.setTitle ( 'Code Generation in Action' );
b1.setFirst ( 'Jack' );
b1.setLast ( 'Herrington' );
b1.setPublisher ( 'Manning' );
g_books.push( b1 );

var  b2  = new Book();
b2.setId ( 2 );
b2.setTitle ( 'PHP Hacks' );
...

Да, так лучше. Я создам объект, установлю его значения, затем добавлю его в массив и т.д. Для начала я сделаю некоторые изменения в таблице стилей, как показано в листинге 17.

Листинг 17. Object2.xsl
...
<xsl:function name="js:createbook">
<xsl:param name="book" />
<xsl:variable name="b" select="concat( 'b', $book/@id )" />
var <xsl:value-of select="$b" /> = new Book();
<xsl:value-of select="concat( $b, '.setId' )" />
( <xsl:value-of select="$book/@id" /> );
<xsl:value-of select="concat( $b, '.setTitle' )" />
( '<xsl:value-of select="js:escape( $book/title )" />' );
<xsl:value-of select="concat( $b, '.setFirst' )" />
( '<xsl:value-of select="js:escape( $book/author/first )" />' );
<xsl:value-of select="concat( $b, '.setLast' )" />
( '<xsl:value-of select="js:escape( $book/author/last )" />' );
<xsl:value-of select="concat( $b, '.setPublisher' )" />
( '<xsl:value-of select="js:escape( $book/publisher )" />' );
</xsl:function>
    
<xsl:template match="/">
<xsl:for-each select="books/book">
<xsl:value-of select="js:createbook(.)" />
g_books.push( b<xsl:value-of select="@id" /> );
</xsl:for-each>
</xsl:template>
...

Я определил новую функцию createbook, которая создает объект book и вызывается шаблоном для каждой книги. Функция createbook по-прежнему вызывает функцию escape, чтобы гарантировать правильное кодирование строк.

С точки зрения HTML я должен добавить больше методов в класс Book, для того чтобы закодированный JavaScript-код мог вызывать их. Эти новые методы показаны в листинге 18.

Листинг 18. Object2.html
...
<script>
var g_books = [];
function Book() { }
Book.prototype.setId = function( val ) { this.id = val; }
Book.prototype.setTitle = function( val ) { this.name = val; }
Book.prototype.setFirst = function( val ) { this.first = val; }
Book.prototype.setLast = function( val ) { this.last = val; }
Book.prototype.setPublisher = function( val ) { this.publisher = val; }
</script>
...

Механизм прототипирования в языке JavaScript несколько специфичен. Каждый объект в этом языке представляет собой отдельную сущность со своими собственными данными и функциями, которые вы можете устанавливать независимо. Каждый объект определенного класса имеет один и тот же прототип (prototype). Поэтому, для создания методов, используемых совместно всеми классами, я устанавливаю функцию в прототипе, а не просто в объекте.


Заключение

Вы можете использовать любой из нескольких способов кодирования данных, записанных в XML-формате, в виде JavaScript-кода. То, как вы кодируете данные, зависит от дизайна вашего приложения Web 2.0 и от того, как вы намереваетесь использовать данные на странице. Главным является наилучшее использование динамического языка JavaScript.


Загрузка

ОписаниеИмяРазмер
Пример кода, используемого в данной статьеx-xml2json-samplecode.zip7KB

Ресурсы

Научиться

Получить продукты и технологии

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=XML
ArticleID=171311
ArticleTitle=Генерирование JSON из XML для использования с Ajax
publish-date=09122006