時(shí)下,非常流行使用 JavaScript 代碼為數(shù)據(jù)驅(qū)動(dòng)的 Web 應(yīng)用程序添加互動(dòng)性。若能將數(shù)據(jù)編碼成
JavaScript Object Notation(JSON)的格式,您就可以更輕松地通過 JavaScript
語言使用它。通過本文,發(fā)掘使用 XSLT V2 從 XML 數(shù)據(jù)生成 JSON 的幾種不同方法。
 |
請(qǐng)?jiān)L問 Ajax 技術(shù)資源中心,這是有關(guān) Ajax 編程模型信息的一站式中心,包括很多文檔、教程、論壇、blog、wiki 和新聞。任何 Ajax 的新信息都能在這里找到。
|
|
幾年前,許多開發(fā)人員很看好 XML、XSLT、Extensible HTML
(XHTML)和其他一些基于標(biāo)記的語言。現(xiàn)在,Asynchronous
JavaScript and XML(AJAX)成了新的熱點(diǎn),人們又將目光轉(zhuǎn)向了使用 JavaScript 代碼的數(shù)據(jù)驅(qū)動(dòng)的富
Internet 應(yīng)用程序。但是開發(fā)人員是否已經(jīng)消除了 XML 和這一新技術(shù)之間的鴻溝呢?
當(dāng)
然,您可以在 Web 客戶機(jī)中使用 XML 解析器來讀取數(shù)據(jù),但這種做法會(huì)帶來兩個(gè)問題。第一,出于安全方面的原因,XML
數(shù)據(jù)只能從與此頁面相同的那個(gè)域中讀取。這雖然不是什么大的限制因素,但它的確會(huì)引起部署方面的問題,還會(huì)阻礙 DHTML
小部件的創(chuàng)建。第二,讀取和解析 XML 會(huì)非常慢。
另一種做法是讓服務(wù)器執(zhí)行 XML 的解析工作,方法是設(shè)置服務(wù)器,使之向?yàn)g覽器發(fā)送以 JavaScript 代碼或時(shí)下流行的
JavaScript Object Notation(JSON)編碼的數(shù)據(jù)。本文將展示如下三種使用 XSLT V2 語言和 Saxon XSLT V2 處理器從 XML 數(shù)據(jù)生成 JSON 的技巧:
- 簡(jiǎn)單編碼
- 通過函數(shù)調(diào)用加載數(shù)據(jù)
- 編碼對(duì)象
JSON 簡(jiǎn)介
要學(xué)習(xí)如何將數(shù)據(jù)編碼成 JSON(它只是 JavaScript 的一個(gè)子集),最好的方法是從數(shù)據(jù)開始。清單 1 顯示了書籍列表的一個(gè)示例 XML 數(shù)據(jù)集。
清單 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>
|
這個(gè)數(shù)據(jù)集很簡(jiǎn)單,只包含三本書,每本書都具有惟一的 ID、書名、作者姓名及出版商的名字。(沒錯(cuò),我只選擇了我自己的書作為數(shù)據(jù)集,但能怨我嗎?這些書實(shí)在是不可多得的節(jié)日和生日禮物。)
清單 2 顯示了這些數(shù)據(jù)在 JSON 中的效果。
清單 2. JSON 中的示例數(shù)據(jù)集
[ { id: 1,
title: 'Code Generation in Action',
first: 'Jack',
last: 'Herrington',
publisher: 'Manning' },
... ]
|
方括號(hào) ([]) 表明這是一個(gè)數(shù)組。大括號(hào) ({}) 則表明這是一個(gè)散列表,該散列表由一組名稱和值對(duì)組成。在本例中,我創(chuàng)建了一個(gè)散列表的數(shù)組 —— 用來存儲(chǔ)這類結(jié)構(gòu)式數(shù)據(jù)的一種常見方法。
另外一點(diǎn)值得注意的是字符串是通過單引號(hào)或雙引號(hào)被編碼的。所以,如果我想用單引號(hào)編碼 O'Reilly
,我就必須使用反斜杠對(duì)它進(jìn)行轉(zhuǎn)義:'O\'Reilly'
。
這讓我編寫的這個(gè) XSLT 樣式表更為有趣了一些。
我并未在本例中放上任何日期,但您也可以通過如下兩種方法來編碼日期。第一種方法是將日期作為字符串,該字符串必須在后面被解析。第二種方法是將日期作為一個(gè)對(duì)象,比如:
publishdate: new Date( 2006, 6, 16, 17, 45, 0 )
|
這段代碼將 publishdate
的值設(shè)置為6/16/2006
5:45:00 p.m.。
簡(jiǎn)單編碼
接下來我將陸續(xù)介紹 JSON 編碼的幾種技巧。第一種也是其中最簡(jiǎn)單的一種,此樣式表如 清單 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
的變量設(shè)置為一個(gè)包含三個(gè)散列表的數(shù)組,每個(gè)散列表包含關(guān)于該書的信息。再回過頭來看看 清單 3,您會(huì)發(fā)現(xiàn)第一個(gè)模板匹配 "/" 路徑,它也是首先應(yīng)用到輸入數(shù)據(jù)集的模板,該模板使用 for-each
循環(huán)來遍歷每本書。之后,它使用 <value-of>
標(biāo)記來將文本從該數(shù)據(jù)輸出到 JavaScript 輸出代碼。
對(duì)于字符串,我使用名為 js:escape()
的定制函數(shù),它在模板之前定義。該函數(shù)使用一個(gè)正則表達(dá)式將一個(gè)單引號(hào)標(biāo)記更改為帶有反斜杠的單引號(hào)標(biāo)記。
最后一個(gè)重要的元素是 <xsl:output>
標(biāo)記,它告知處理器要輸出的是文本而不是 XML。要檢驗(yàn)此過程是否可以正常工作,我加入了一個(gè) simple .html 文件,該文件引用我在 simple.js 保存的 XSL 樣式表的輸出。這個(gè) 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 文件使用 <script>
標(biāo)記簡(jiǎn)單地加載已編碼了的 JavaScript 代碼。之后,第二個(gè)
<script>
標(biāo)記將數(shù)組的長(zhǎng)度寫出到瀏覽器頁面,如 圖 1 所示。
圖 1. simple.html 的輸出
好了!數(shù)據(jù)文件包含三本書,相應(yīng)的 JavaScript 文件也包含三本書。它真的可以工作!
通過函數(shù)加載
上述第一個(gè)示例很簡(jiǎn)單,而且在大多數(shù)情況下可以發(fā)揮其作用,但它存在一些問題。第一個(gè)問題是對(duì)于數(shù)據(jù)何時(shí)被加載沒有任何提示。如果數(shù)據(jù)是像頁面那樣被靜態(tài)加載的,這不成問題。但是如果頁面動(dòng)態(tài)創(chuàng)建了一個(gè) <script>
標(biāo)記來按需加載數(shù)據(jù),那么就很有必要知道 <script>
標(biāo)記是何時(shí)完成的。實(shí)現(xiàn)此功能的最好的方法是讓編碼了的數(shù)據(jù)調(diào)用一個(gè) JavaScript 函數(shù),而不是只設(shè)置數(shù)據(jù)。
這個(gè)概念很重要,所以我將花一些時(shí)間來介紹一下為什么您必須要通過動(dòng)態(tài)生成的 <script>
標(biāo)記來加載數(shù)據(jù)。頁面加載后,從服務(wù)器獲得數(shù)據(jù)是 Web 2.0 的核心功能。一種方法是使用 AJAX 機(jī)制通過到服務(wù)器的調(diào)用來加載
XML。然而,出于安全性的原因,AJAX 機(jī)制只限于從單一域獲取數(shù)據(jù)。這在大多數(shù)情況下都沒有問題,但有時(shí),您可能需要 JavaScript
代碼運(yùn)行在他人的頁面上(例如,Google Maps)。
在這種情況下從服務(wù)器獲得數(shù)據(jù)的惟一方法是通過動(dòng)態(tài)加載 <script>
標(biāo)記。獲悉 <script>
標(biāo)記何時(shí)加載的最好的方法是讓 <script>
標(biāo)記返回的腳本調(diào)用函數(shù)而不是簡(jiǎn)單地加載數(shù)據(jù)。清單 6 顯示了在函數(shù)調(diào)用中編碼的數(shù)據(jù)。
清單 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 給出了相應(yīng)的 .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>
|
稍后將詳細(xì)介紹 drawbooks
函數(shù)。這里重要的是了解一下頁面如何定義 AddBooks
函數(shù),該函數(shù)隨后會(huì)由 function1.js 文件中的腳本調(diào)用。該 AddBooks
函數(shù)負(fù)責(zé)處理數(shù)據(jù)。而且被調(diào)用的 AddBooks
函數(shù)會(huì)向頁面指示
<script>
標(biāo)記被正確加載,并已加載完成。
要?jiǎng)?chuàng)建 function1.js 文件,我只對(duì)樣式表稍微做了一點(diǎn)修改,如 清單 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>
|
這里,我調(diào)用了一個(gè)函數(shù),而不是簡(jiǎn)單地設(shè)置一個(gè)變量。這就是我所做的惟一更改。
回到頁面,我使用了 drawbooks
函數(shù)來構(gòu)建書的表格,這樣我就能夠確認(rèn)數(shù)據(jù)被正確編碼和正確顯示。此函數(shù)是在 drawbooks.js 內(nèi)定義的,如 清單 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 );
}
|
這個(gè)簡(jiǎn)單函數(shù)創(chuàng)建了一個(gè)表格節(jié)點(diǎn),然后循環(huán)訪問書的列表并為每本書創(chuàng)建一行,為每個(gè)數(shù)據(jù)元素分配一個(gè)單元格。此頁面上的代碼的結(jié)果如 圖 2 所示。
圖 2. function1.html 的結(jié)果
現(xiàn)在我就可以查看一下此頁面的輸出并確認(rèn)來自原始
.xml 文件的一切均已被正確轉(zhuǎn)換成 JavaScript 代碼,且數(shù)據(jù)被發(fā)送到 AddData
函數(shù)并被正確添加到頁面。
細(xì)化函數(shù)調(diào)用技術(shù)
我很喜歡函數(shù)調(diào)用這一技術(shù),但我并不贊同將所有圖書數(shù)據(jù)都放入一個(gè)塊中。另一種方式是為每條記錄采用一個(gè)調(diào)用,如 清單 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'
} );
...
|
對(duì) .html 頁面只需做少許修改,如 清單 11 所示。
清單 11. Function2.html
...
<script>
var g_books = [];
function AddBook( book ) { g_books.push( book ); }
</script>
...
|
這里更改了 XSLT,以使函數(shù)調(diào)用駐留在 for-each
循環(huán)體內(nèi)。清單 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>
...
|
對(duì)這個(gè)給定示例來說,這種更改看起來有些隨意。但如果原始的 XML 數(shù)據(jù)集有多種數(shù)據(jù)類型,要為每種類型分配一個(gè)單獨(dú)的函數(shù)調(diào)用會(huì)使 XSL 和頁面上的 JavaScript 代碼更為簡(jiǎn)單、更易于維護(hù)。
編碼對(duì)象
對(duì)小的頁面來講,使用 JavaScript 函數(shù)沒有問題。但對(duì)于大型項(xiàng)目,就需要使用 JavaScript 語言的一些面向?qū)ο筇匦浴J堑模琂avaScript 語言可以處理對(duì)象而且可以處理得很好。
清單 13 顯示了如何創(chuàng)建帶有數(shù)據(jù)的對(duì)象。
清單 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'
} ) );
|
在本例中,我只簡(jiǎn)單地向名為 g_books
的數(shù)組添加了 Book
對(duì)象。JavaScript 的對(duì)象創(chuàng)建與 Java™、C#
或 C++
編程語言的對(duì)象創(chuàng)建十分相似。都是一個(gè) new 操作符后跟一個(gè)類名。參數(shù)放到隨后的括號(hào)內(nèi)。在本例中,我傳入了一個(gè)帶值的單一散列表,并將其分割成單獨(dú)的一些參數(shù)。
創(chuàng)建此對(duì)象的代碼如 清單 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>
|
此頁面內(nèi)最值得注意的是定義 Book
類的那部分代碼。清單 15
顯示了該頁面。
清單 15. object1.html
...
<script>
var g_books = [];
function Book( data )
{
for( var d in data ) { this[d] = data[d]; }
}
</script>
...
|
Book
類的構(gòu)造函數(shù)循環(huán)訪問散列表的所有數(shù)據(jù)。對(duì)于每個(gè)鍵,會(huì)在對(duì)象上創(chuàng)建一個(gè)具有對(duì)象名稱和數(shù)據(jù)的實(shí)例變量。不需要對(duì)
drawbooks
函數(shù)做任何修改,因?yàn)閷?duì)象都有與原始的散列表相同的鍵和值。JavaScript 語言并不區(qū)分訪問的是散列表內(nèi)的命名值還是對(duì)象上的命名值。
當(dāng)然,Book
類應(yīng)該有像 set
和 get
這樣的訪問程序。
清單 16 顯示了我是如何對(duì) JavaScript 數(shù)據(jù)進(jìn)行編碼的。
清單 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' );
...
|
沒錯(cuò),這有些大同小異。它也是先創(chuàng)建一個(gè)對(duì)象,設(shè)置其值,然后將它添加到數(shù)組,等等。首先,我對(duì)樣式表做了一些較大的修改,如 清單 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>
...
|
我定義了一個(gè)新的名為 createbook
的函數(shù),該函數(shù)構(gòu)建 book 對(duì)象,并由用于每本書的模板調(diào)用。createbook
函數(shù)還是調(diào)用 escape
函數(shù)來確保字符串被正確編碼。
考慮到 HTML 方面,我必須向 Book
類添加更多方法以便編碼后的 JavaScript 代碼能夠調(diào)用它們。這些新方法如 清單 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>
...
|
原型機(jī)制是 JavaScript 語言所特有的。該語言中的每個(gè)對(duì)象都是具有其自己的數(shù)據(jù)和函數(shù)的單獨(dú)實(shí)體,可獨(dú)立設(shè)置。某個(gè)類的每個(gè)對(duì)象都有相同的原型。所以,為了創(chuàng)建可由所有類共享的方法,我在原型之上設(shè)置了函數(shù),而不僅僅是在對(duì)象上。
結(jié)束語
您
可以使用幾種技巧來將存儲(chǔ)在 XML 內(nèi)的數(shù)據(jù)編碼成 JavaScript 代碼形式。您編碼數(shù)據(jù)的方式取決于您的 Web 2.0
應(yīng)用程序的整體設(shè)計(jì),還取決于數(shù)據(jù)出現(xiàn)在頁面上時(shí)對(duì)這些數(shù)據(jù)作何打算。關(guān)鍵的一點(diǎn)是要充分利用所生成的動(dòng)態(tài) JavaScript 語言。
下載
描述 | 名字 | 大小 | 下載方法 |
本文使用的示例代碼 |
x-xml2json-samplecode.zip |
7KB |
HTTP |
參考資料
學(xué)習(xí)