IBM®
Перейти к тексту
    в России и странах СНГ [изменить]    Условия использования
 
 
   
    Главная страница    Продукты    Услуги и решения    Поддержка и загрузка    Мой профиль    
Перейти к тексту

developerWorks Россия  >  XML  >

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

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

developerWorks
Опции документа

Опции документа, требующие включения JavaScript, не отображаются

Обсудить

Исходные тексты примера


Выскажите мнение об этой странице

Помогите нам улучшить содержание


Уровень сложности: средний

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

12.09.2006

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

Несколько лет назад многие разработчики сделали ставку на 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.zip7KBHTTP
Информация о методах загрузки


Ресурсы

Научиться

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

Обсудить


Об авторе

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




Выскажите мнение об этой странице


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



 


 


 


Поделиться этой статьей:

забобрить забобрить memori сохранить в memori




В начало


IBM обладает всеми авторскими правами касательно информации, расположенной на developerWorks. Использование информации приведенной на этом ресурсе без явного письменного разрешения от IBM или первоначального автора запрещены. Если Вы желаете использовать информацию с developerWorks, пожалуйста воспользуйтесь регистрационной формой для того, чтобы связаться с нами запрос на использование материалов developerWorks Россия.
    IBM в России Конфиденциальность Контакты