分享
 
 
 

用XML,XSL,CSS制作电子图书

王朝html/css/js·作者佚名  2006-12-16
窄屏简体版  字體: |||超大  

当你购买某些出版社出版的科技书籍时,你会发现随书的配套光盘包含有该书的电子版。一些电子图书(e-book)是以一个大的帮助文件(.CHM)的形式出现,而其他的则是一整套的HTML文件形式。这篇文章源于我对建立网络电子图书馆以发布、出版电子图书形式的计算机教程、材料的愿望,以及一些网站给我的灵感,如InformIT.com--网上书籍,以前称为Personal Bookshelf(个人书架)。

运行例程前的准备工作

你必须确认以下的软件已安装在你的操作系统中:

Microsoft XML Parser

Windows Script Host 2.0

Internet Explorer 5.0 or higher

制作电子图书的必要条件

制作电子图书所必须的条件是简单的。我们对用XML和XSL出版电子图书感兴趣。为达到这一目的,我们必须明确以下几点问题:

1. 是用有效的(valid)XML文件还是用格式良好的( well-formed )XML文件?

2. 是把整本书保存成一个XML文件,还是按内容章节把书分为若干个XML文件(i.e. chapter/section/subsection)?

3. 如果把书按内容章节分为若干个XML文件,又如何对每一个XML文件的内容进行划分?

4. 是否需要一个单独的XML文件保存内容目录?

解决方法

解决的方法就是以上问题的答案。

我用XML-Data来设计电子图书的大纲(schema)。XML-Data是一种用XML来描述大纲的标准,因为它比DTD更为直观,所以可以用它来替代DTD。相对与DTD,XML-Data的最大优势在于它的开放模式(open model)允许我们定义事先没有在大纲中定义的新子元素或子属性。你可以在http://www.w3.org/TR/1998/NOTE-XML-data/ 阅读到最新的XML-Data规范。

当我试图用XMLDOM读取XML时遇到了一些问题。因为在〈content〉〈/content〉中的使用了HTML标记符(tag)的缘故,我收到了从XML解析器返回的解析错误。我本可以为每一个要在〈content〉〈/content〉中使用的那些HTML标记符都定义一个〈ElementType〉,这样就可以避免从XML解析器返回的解析错误。但这样做的话就必须要为几乎所有必需的HTML标记符定义〈ElementType〉才能使内容合乎用户的心意。

所以我用名域(namespaces)设计了一个简单的工作区。我在〈content〉〈/content〉中指定了缺省的名域以避免解析错误。然而,如果你在〈content〉〈/content〉中使用任何XML元素的话,这个解决方法将不起作用,因为那些XML元素并非在我指定的缺省名域当中。

此外,我们可以通过象html一样使用名域前缀(namespace prefix)以区分我们自定义的标记符和HTML标记符来解决这一问题。但是,使用名域前缀使得XSL代码复杂化,因为我们必须使自定义的标记符与HTML标记符相匹配:加前缀并把它们转换为HTML标记符。既然我已经为图书设计了XML大纲,使得在〈content〉〈/content〉中没有XML元素出现,因而我们就可以设缺省名域为http://www.w3.org/TR/REC-html40,避免了解析错误的出现。

现在让我回答第二个问题。既然我已决定使用格式良好的XML,那么图书的内容就必须保存在同一个XML文件中,并可以在内容当中使用HTML标记符。对于那些希望用若干个HTML文件分别保存图书的内容,然后只用XML文件来保存实际内容在目录中的链接的人而言,大纲还是有用的。做为选择,你可以选择完全忽视大纲,继续使用格式良好的XML。

我选择以节(section)来作为划分图书内容的单元。用户不会对过细的章节划分感兴趣。同样的,把整章内容就这样显示,会使用户忙于拖动滚动条,也不好。因此,过大或过小的内容划分都不应让用户遇到。以节划分就是一个折中的标准。 当我们把图书保存在一个单独的XML文件当中时,为什么还需要另外一个XML文件来保存图书的目录呢?目录(TOC)可以很容易的用XMLDOM从XML文件当中生成。就象我先前说的,这个目录XML文件包含有内容单元(如:节)与实际HTML文件之间的链接。

既然所有的问题都有了答案,就让我解释以下电子图书的实际执行过程。在下表中,我列出了组成电子图书的各个文件名和各自的用途(这些文件都包含在文后可下载的例程当中):

文件名

用途

EBookSchema.xml

包含用XML-Data 设计的电子图书大纲(Schema)

EBook.xml

保存在XML文件当中的完整的电子图书

GenerateEBook.js

从EBook.xml 中提取数据生成目录文件和图书内容的 HTML文件的JavaScript 脚本。目录文件本身就是一个可以用EbookLinks.xsl转换为HTML的XML文件。图书的内容按节保存在单独的HTML文件中,并以各节的ID号来命名HTML文件。

EBook.js

包含客户端脚本和目录、内容显示的事件句柄的JavaScript脚本

EBook.asp

连接目录文件和内容文件的ASP文件

EBookContent.asp

负责根据用户的选择显示相关内容的ASP文件

EBookLinks.xsl

用于显示目录的XSL样式文件

EBookContent.xsl

用于显示内容的XSL样式文件

EBook.css

用于显示目录的CSS样式文件

EBookContent.css

用于显示内容的CSS样式文件

EBookGenerator.bat

用Windows Script Host运行GenerateEBook.js的批处理文件

XML-Data开放模式下的电子图书XML大纲

以下为部分用XML-Data编写的电子图书XML大纲:

〈?xml version='1.0'?〉

〈Schema name="book" xmlns="urn:schemas-microsoft-com:xml-data"

xmlns:dt="urn:schemas-microsoft-com:datatypes" 〉

〈AttributeType name="appendix" dt:type="string" required="no"/〉

〈ElementType name="author" content="eltOnly"〉...〈/ElementType〉

〈ElementType name="authors" content="eltOnly"〉...〈/ElementType〉

〈ElementType name="publisher" content="eltOnly"〉...〈/ElementType〉

〈ElementType name="subsection" content="mixed"〉...〈/ElementType〉

〈ElementType name="section" content="eltOnly"〉...〈/ElementType〉

〈ElementType name="chapter" content="eltOnly"〉...〈/ElementType〉

〈ElementType name="part" content="eltOnly"〉...〈/ElementType〉

〈ElementType name="book" content="eltOnly" 〉...〈/ElementType〉

...

〈/Schema〉

从以上的例子可以看出,以上每一个〈ElementType〉都是其下一层子元素的集合体:

〈ElementType name="id" content="textOnly"/〉

〈ElementType name="title" content="textOnly"/〉

〈ElementType name="isbn" content="textOnly"/〉

〈ElementType name="edition" content="textOnly"/〉

〈ElementType name="copyright" content="textOnly"/〉

〈ElementType name="number" content="textOnly"/〉

〈ElementType name="name" content="textOnly"/〉

〈ElementType name="content" content="mixed"/〉

例如,被命名为“book”的〈ElementType〉在大纲中是这样描述的:

〈ElementType name="book" content="eltOnly" 〉

〈element type="id" minOccurs="1" maxOccurs="1"/〉

〈element type="title" minOccurs="1" maxOccurs="1"/〉

〈element type="isbn" minOccurs="1" maxOccurs="1"/〉

〈element type="edition" minOccurs="1" maxOccurs="1"/〉

〈element type="copyright" minOccurs="1" maxOccurs="1"/〉

〈element type="authors" minOccurs="1" maxOccurs="1"/〉

〈element type="publisher" minOccurs="1" maxOccurs="1"/〉

〈element type="part" minOccurs="1" maxOccurs="*"/〉

〈/ElementType〉

||||||电子图书的XML结构

设计电子图书的XML结构并非是件难事。很明显,任何电子图书都可以以分层树(hierarchical tree)的形式表现,分层树可以包含如下的节点:

Part

Chapter

Section

Sub Section

Content

以上的节点都有一些共同的特性(properties),如:

Id

Title

Number

接着就是确定是将这些特性(properties)设置为各个节点的属性(attributes),还是设置为各个节点的子元素(sub-elements)。我决定把这些特性设置为各个节点的子元素。根元素很明显就会用〈book〉。

一本电子图书的XML结构就可以用如下的形式表现:

〈book〉

〈id〉 〈/id〉

〈title〉 〈/title〉

〈isbn〉 〈/isbn〉

〈edition〉 〈/edition〉

〈copyright〉 〈/copyright〉

〈authors〉

〈author〉

〈name〉〈/name〉

〈/author〉

〈/authors〉

〈publisher〉

〈name〉 〈/name〉

〈/publisher〉

〈part〉

〈id〉 〈/id〉

〈number〉 〈/number〉

〈title〉 〈/title〉

〈chapter〉

〈id〉 〈/id〉

〈number〉 〈/number〉

〈title〉 〈/title〉

〈section〉

〈id〉 〈/id〉

〈number〉 〈/number〉

〈title〉 〈/title〉

〈subsection〉

〈id〉 〈/id〉

〈title〉 〈/title〉

〈content〉 〈/content〉

〈/subsection〉

〈/section〉

〈/chapter〉

〈/part〉

〈/book〉

为什么需要ID号?

设置ID号对于识别XML文档的一些主要部分如章、节、小节来说是相当有必要的。使用ID号,我们就可以很容易的通过ID号来对以ID号命名的、包含图书内容的HTML文件进行定位。

创建图书目录

因为我们预先为电子图书定义了XML结构,所以XMLDOM可以解析各个节点并创建图书目录。创建目录的函数如下所示:(其中的参数xmlNodeList代表节点〈part〉的列表,strFileName代表目录文件的名称)

function GenerateTableOfContents(xmlNodeList, strFileName) {

var xmlNode = null;

var fso = WScript.CreateObject("Scripting.FileSystemObject");

var tf = null;

var i,j,k,t;

var xmlString = '〈?xml version="1.0"?〉\n〈toc〉\n';

var xmlChapterNodeList;

var xmlChapterNode;

var xmlSectionNodeList;

var xmlSectionNode;

xmlString = xmlString + xmlBookTitle + '\n';

WScript.Echo("Creating " + strFileName + "...");

for(i=0; i〈xmlNodeList.length; i++){

xmlNode = xmlNodeList.item(i);

if(xmlNode.hasChildNodes() == true &&

xmlNode.firstChild.nodeTypeString == "element") {

xmlString = xmlString + "〈" + xmlNode.nodeName + "〉\n" +

xmlNode.childNodes(0).xml + "\n" +

xmlNode.childNodes(1).xml + "\n" +

xmlNode.childNodes(2).xml + "\n";

xmlChapterNodeList = xmlNode.selectNodes(strChapterNodeName);

for(j=0; j〈xmlChapterNodeList.length; j++) {

xmlChapterNode = xmlChapterNodeList.item(j);

xmlString = xmlString + "〈" + xmlChapterNode.nodeName;

for(t=0; t〈xmlChapterNode.attributes.length; t++)

xmlString = xmlString + ' ' +

xmlChapterNode.attributes.item(t).xml ;

xmlString = xmlString + "〉\n" + xmlChapterNode.childNodes(0).xml +

"\n" + xmlChapterNode.childNodes(1).xml + "\n" +

xmlChapterNode.childNodes(2).xml + "\n"

xmlSectionNodeList = xmlChapterNode.selectNodes(strSectionNodeName);

for(k=0; k〈xmlSectionNodeList.length; k++) {

xmlSectionNode = xmlSectionNodeList.item(k);

xmlString = xmlString + "〈" + xmlSectionNode.nodeName + "〉\n"

+ xmlSectionNode.childNodes(0).xml + "\n" +

xmlSectionNode.childNodes(1).xml + "\n" +

xmlSectionNode.childNodes(2).xml + "\n" +

"〈/" + xmlSectionNode.nodeName + "〉\n";

}

xmlString = xmlString + "〈/" + xmlChapterNode.nodeName + "〉\n" ;

}

xmlString = xmlString + "〈/" + xmlNode.nodeName + "〉\n";

}

}

xmlString = xmlString + "〈/toc〉";

tf = fso.CreateTextFile(strFileName, true);

tf.Write(xmlString);

tf.Close();

WScript.Echo(strFileName + " created");

}

输出文件的根节点是〈toc〉。这个函数中的3个循环分别用于访问〈part〉节点列表、该〈part〉节点中的〈chapter〉节点列表、以及该〈part〉节点和〈chapter〉节点中的〈section〉节点列表。每一个循环的第一条语句把该节点的id和title保存成一个字符串。这个字符串变量就与id和title所指的XML节点产生联系。最后这个字符串变量就通过strFileName保存到一个XML文件中。

||||||创建包含图书内容的HTML文件

包含图书内容的HTML文件由保存在〈content〉〈/content〉标记符之间的文本生成而来。创建包含图书内容的HTML文件的函数如下所示:

function GenerateContent(xmlNodeList, xmlStyleSheet) {

var xmlNode = null;

var xmlElem = WScript.CreateObject("Microsoft.XMLDOM");

var xmlResult = WScript.CreateObject("Microsoft.XMLDOM");

var fso = WScript.CreateObject("Scripting.FileSystemObject");

var tf = null;

var i;

var strFileName;

var strOutput;

for(i=0;i〈xmlNodeList.length;i++) {

xmlNode = xmlNodeList.item(i);

if(xmlNode.hasChildNodes() == true &&

xmlNode.firstChild.nodeTypeString == "element") {

strFileName = xmlNode.childNodes.item(0).text + ".htm";

xmlElem.loadXML( xmlNode.xml );

xmlElem.transformNodeToObject(xmlStyleSheet, xmlResult);

strOutput = xmlResult.xml.replace(/〈html〉/ig,"");

strOutput = strOutput.replace(/〈\/html〉/ig,"");

strOutput = strOutput.replace(/

strOutput = strOutput.replace(/>/ig,"〉");

tf = fso.CreateTextFile( strFileName, true);

tf.Write(strOutput);

tf.Close();

}

}

}

该函数有两个参数,其中xmlNodeList代表〈section〉节点列表, xmlStyleSheet代表应用于xmlNodeList的样式(StyleSheet)。就象我们在“为什么需要ID号?”这节内容中叙述的一样,HTML文件名就是〈section〉节点的ID号加上“.htm”的扩展名。〈section〉节点的XML内容通过给定的样式转换为XHTML,然后以指定的文件名保存到HTML文件中。

生成电子图书

以上叙述的两个过程在以下的代码中通过调用GenerateTableOfContents()和GenerateContent()函数实现:

try {

var WSHShell = WScript.CreateObject("WScript.Shell");

var args = WScript.Arguments;

var count = args.count();

if (count 〈 5) {

WSHShell.Popup("usage: GenerateEBook.js 〈filename〉[.xml] " +

"〈Content-Style-FileName〉[.xsl] " +

"〈Intermediate-FileName〉[.xml] " +

"〈Link-Style-FileName〉[.xsl] 〈Link-FileName〉[.htm]");

WScript.Quit();

}

var xml = args(nXMLFileName);

var styleContentFileName = args(nContentStyleFileName);

var intermediateFileName = args(nIntermediateFileName);

var styleLinkFileName = args(nLinkStyleFileName);

var outputFileName = args(nLinkFileName);

// Load the XML file

var source = WScript.CreateObject("Microsoft.XMLDOM");

source.async = false;

source.load(xml);

WScript.Echo(source.parseError.reason);

WScript.Echo("Processing " + xml );

var xmlDoc = source.documentElement;

var xmlBookTitle = xmlDoc.childNodes(2).xml;

// get the desired root nodes for the contents

var xmlNodeContentList = xmlDoc.getElementsByTagName(strSectionNodeName);

// get the root nodes for the chapters

var xmlNodeChapterList = xmlDoc.getElementsByTagName(strChapterNodeName);

// get the root nodes for the parts

var xmlNodePartList = xmlDoc.getElementsByTagName( strPartNodeName );

var fso = WScript.CreateObject("Scripting.FileSystemObject");

var tfChapter = null;

//Create the result objects for chapter and content

var styleContent = WScript.CreateObject("Microsoft.XMLDOM");

// Load the XSL for the content

styleContent.async = false;

styleContent.load(styleContentFileName);

WScript.Echo(styleContent.parseError.reason);

GenerateContent( xmlNodeContentList, styleContent );

WScript.Echo("The Content.htm files are generated");

intermediateFileName = intermediateFileName + ".xml";

GenerateTableOfContents( xmlNodePartList, intermediateFileName );

WScript.Echo("The Table of Contents XML file is generated");

// Reuse the source object to load the generated TOC xml file

var intermediate = WScript.CreateObject("Microsoft.XMLDOM");

intermediate.load(intermediateFileName);

// load the stylesheet for the chapter

var styleLink = null;

styleLink = WScript.CreateObject("Microsoft.XMLDOM");

styleLink.async = false;

styleLink.load(styleLinkFileName);

WScript.Echo(styleLinkFileName + " loaded");

//object to hold the result

var resultLink = WScript.CreateObject("Microsoft.XMLDOM");

// Translate the XML using XSL into HTML

intermediate.transformNodeToObject(styleLink, resultLink);

outputFileName = outputFileName + ".htm";

WScript.Echo("Creating " + outputFileName );

tfChapter = fso.CreateTextFile(outputFileName, true);

tfChapter.Write(resultLink.xml.replace(/\/〉/ig,"〉"));

tfChapter.Close();

}

catch(e) {

WScript.Echo("An error has occured: [" + e.number + "] " +

e.description);

}

以上所示代码是GenerateEBook.js的关键所在。函数GenerateTableOfContents()和GenerateContent()被调用以生成包含图书内容的HTML文件和图书目录的XML文件。图书目录的XML文件通过XSL文件,转换为一个HTML文件,该HTML文件包含了相关章节内容的超链接。

为运行例程,你不必直接执行该GenerateEBook.js文件,你可以点击EBookGenerator.bat这个批处理文件来运行例程。

||||||用户界面

电子图书的用户界面包含左右两个框架,分别命名为frameLink 和frameContent。 左框架(frameLink)以超链接树的形式显示各个章、节和小节的标题。用户可以点击标题在右框架(frameContent)中显示内容。当页面第一次被装载时,用户只能看到大的标题。Expand All 和 Collapse All按钮给用户提供了方便。

为什么设置Scrolling=YES?

我在左右两个框架中都把SCROLLING属性设置为YES。你或许并不喜欢见到滚动条,用户界面的标准是要求在需要的时候滚动条才出现。但我这样做是有原因的。

如果SCROLLING属性设置为AUTO,当用户把鼠标移到框架frameLink中的超链接上时,超链接上的文字在两框架的交接处不会显示出来,因为框架结构认为文字后的空格是为滚动条所保留的,即使滚动条并未出现。当你点击一个链接,把鼠标移到链接上时,超链接右边的一些超出框架宽度的文字就会移到下一行显示。但可笑的就是即使你把鼠标移离这个已经点击了的链接,那些超出框架宽度的文字却依然保持原样,还在下一行显示。如果滚动条始终都显示的话,那这个问题就不会出现。

客户端脚本

客户端脚本实现以下特征:

高亮显示当前的选择

低亮显示前一次选择

当用户点击节点时展开和收缩节点

展开和收缩所有的节点

考虑到这篇文章的长度,在这里就不列出客户端脚本的代码了,但你可以随着这篇文章的逻辑思路来读懂ebook.js文件中的代码。

用XSL 和 CSS 修饰输出数据

我们已用XML为图书内容做好了准备。现在我们将关注XSL文件。CSS使工作变得容易,因为我们可以事先定义显示的风格。

用于生成目录的XSL文件如下所示:

〈?xml version="1.0"?〉

〈xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl"〉

〈xsl:template match="/"〉

〈HTML〉〈HEAD〉

〈TITLE〉〈xsl:value-of select="toc/title"/〉〈/TITLE〉

〈SCRIPT LANGUAGE="JScript" TYPE="text/javascript" SRC="EBook.js"〉〈/SCRIPT〉

〈LINK REL="stylesheet" TYPE="text/css" HREF="EBook.css" /〉〈/HEAD〉

〈BODY TOPMARGIN="0" LEFTMARGIN="0" MARGINHEIGHT="0" MARGINWIDTH="0"

BGCOLOR="#ffffff" TEXT="#000000"〉〈NOBR〉

〈P〉〈B〉〈UL〉

〈LI CLASS="clsAll"〉〈IMG WIDTH="16" BORDER="0" SRC="bo.gif"〉

〈A HREF="javascript:ExpandAll('UL')" TITLE="Expand All"〉Expand All〈/A〉

〈IMG WIDTH="16" BORDER="0" SRC="bs.gif"〉

〈A HREF="javascript:CollapseAll('UL')" TITLE="Collapse All"〉

Collapse All〈/A〉

〈/LI〉〈/UL〉〈/B〉〈/P〉

〈UL〉

〈xsl:for-each select="toc"〉

〈xsl:apply-templates /〉

〈/xsl:for-each〉

〈/UL〉

〈/NOBR〉〈/BODY〉

〈/HTML〉

〈/xsl:template〉

〈!-- 〈xsl:template match="toc"〉

〈xsl:for-each select="part"〉

〈xsl:apply-templates /〉

〈/xsl:for-each〉

〈/xsl:template〉 --〉

〈xsl:template match="part"〉

〈LI CLASS="clsHasKids"〉

〈IMG WIDTH="16"〉

〈xsl:attribute name="SRC"〉〈xsl:choose〉〈xsl:when test="@nodeImgPath"〉

〈xsl:value-of select="@nodeImgPath"/〉〈/xsl:when〉

〈xsl:otherwise〉bs.gif〈/xsl:otherwise〉〈/xsl:choose〉〈/xsl:attribute〉

〈A〉

〈xsl:attribute name="TITLE"〉〈xsl:value-of select="title" /〉

〈/xsl:attribute〉

〈xsl:attribute name="HREF"〉javascript:PseudoLink()〈/xsl:attribute〉

Part 〈xsl:value-of select="number"/〉-〈xsl:value-of select="title" /〉

〈/A〉

〈/LI〉〈UL〉

〈xsl:apply-templates/〉

〈/UL〉

〈/xsl:template〉

〈xsl:template match="chapter"〉

〈LI CLASS="clsHasKids"〉

〈IMG WIDTH="16"〉〈xsl:attribute name="SRC"〉〈xsl:choose〉

〈xsl:when test="@nodeImgPath"〉〈xsl:value-of select="@nodeImgPath"/〉

〈/xsl:when〉〈xsl:otherwise〉bs.gif〈/xsl:otherwise〉

〈/xsl:choose〉〈/xsl:attribute〉

〈A TARGET="Right"〉〈xsl:attribute name="TITLE"〉

〈xsl:value-of select="title" /〉〈/xsl:attribute〉

〈xsl:attribute name="HREF"〉EBookContent.asp?Chap=

〈xsl:value-of select="number"/〉&Sec=0〈/xsl:attribute〉

〈xsl:choose〉〈xsl:when test="@appendix"〉Appendix

〈xsl:value-of select="@appendix"/〉〈/xsl:when〉

〈xsl:otherwise〉Chapter 〈xsl:value-of select="number"/〉

〈/xsl:otherwise〉〈/xsl:choose〉-〈xsl:value-of select="title" /〉

〈/A〉

〈/LI〉〈UL〉

〈xsl:apply-templates/〉

〈/UL〉

〈/xsl:template〉

〈xsl:template match="section"〉

〈xsl:if test="title[.!='default']"〉

〈LI〉〈IMG CLASS="clsNoHand" WIDTH="16"〉〈xsl:attribute name="SRC"〉

〈xsl:choose〉〈xsl:when test="@nodeImgPath"〉

〈xsl:value-of select="@nodeImgPath"/〉〈/xsl:when〉

〈xsl:otherwise〉dc.gif〈/xsl:otherwise〉〈/xsl:choose〉〈/xsl:attribute〉

〈A TARGET="Right"〉〈xsl:attribute name="TITLE"〉

〈xsl:value-of select="title" /〉〈/xsl:attribute〉

〈xsl:attribute name="HREF"〉EBookContent.asp?Chap=

〈xsl:eval language="JavaScript"〉chapterNum(this)〈/xsl:eval〉

&Sec=〈xsl:value-of select="number"/〉〈/xsl:attribute〉

〈xsl:value-of select="title" /〉

〈/A〉

〈/LI〉〈UL〉

〈xsl:apply-templates /〉

〈/UL〉

〈/xsl:if〉

〈/xsl:template〉

〈xsl:script〉

〈![CDATA[

function chapterNum(e) {

if (e) {

return e.parentNode.childNodes(1).text;

}

}

]]〉

〈/xsl:script〉

〈/xsl:stylesheet〉

这个样式文件负责将目录的XML转换为HTML。让我们注意一下〈xsl:template match="section"〉这一段XSL模板(template)。它生成HTML以显示dc.gif,然后生成以章节数为检索字串的、指向EbookContent.asp的超链接。

||||||用于修饰内容的XSL文件如下所示:

〈?xml version="1.0"?〉

〈xsl:stylesheet xmlns:xsl="uri:xsl"〉

〈xsl:template match="/"〉

〈HTML〉

〈xsl:for-each select="section"〉

〈xsl:apply-templates/〉

〈/xsl:for-each〉

〈/HTML〉

〈/xsl:template〉

〈xsl:template match="text()"〉

〈xsl:value-of/〉

〈/xsl:template〉

〈xsl:template match="subsection/title"〉

〈xsl:if test="text()[.!='default']"〉

〈H4〉〈xsl:apply-templates/〉〈/H4〉

〈/xsl:if〉

〈/xsl:template〉

〈xsl:template match="section/title"〉

〈xsl:if test="text()[.!='default']"〉

〈H3〉〈xsl:apply-templates/〉〈/H3〉

〈/xsl:if〉

〈/xsl:template〉

〈xsl:template match="content"〉〈xsl:apply-templates/〉〈/xsl:template〉

〈xsl:template match="subsection"〉〈xsl:apply-templates/〉〈/xsl:template〉

〈xsl:template match="B | b | I | i | P | p | UL | ul | LI | li | OL | ol"〉

〈xsl:copy〉〈xsl:apply-templates/〉〈/xsl:copy〉

〈/xsl:template〉

〈xsl:template match="A | a | TABLE | table | TR | tr | TD | td"〉

〈xsl:copy〉

〈xsl:for-each select="@*"〉

〈xsl:attribute〉

〈xsl:value-of/〉

〈/xsl:attribute〉

〈/xsl:for-each〉

〈xsl:apply-templates/〉

〈/xsl:copy〉

〈/xsl:template〉

〈/xsl:stylesheet〉

让我们看一看这个XSL文件是如何对〈content〉 〈/content〉中的HTML标记符进行处理的。XSL文件有一段〈xsl:template match=""〉是用于匹配〈content〉 〈/content〉中所有HTML标记符,〈xsl:copy〉则将〈content〉 〈/content〉中所有HTML标记符原封不动地复制到输出数据中,没有进行转换。对于那些带有属性的HTML标记符,XSL文件用〈xsl:for-each〉遍历其属性,然后把属性写到输出数据中。 电子图书生成器

包含命令行的EBookGenerator.bat为你运行例程提供了方便。你可以有选择地指定运行电子图书的IIS虚拟目录。如果你未指定IIS虚拟目录, EBookGenerator.bat将在显示一段信息后在当前目录生成所需文件。

这个批处理文件用Windows Script Host运行GenerateEBook.js,目录和HTML文件生成之后就把文件复制到指定的目录:

@Echo Off

if %1. == . goto ShowUsage

cscript GenerateEBook.js EBook.xml EBookContent.xsl EBookTOC

EBookLinks.xsl EBookLinks

Echo.

Echo Copying required files to %1

for %%a in ( *.htm *.gif EBook.* EBookContent.* EBookToc.xml EBookSchema.xml

) do copy %%a %1 〉 nul

Echo Successfully copied the files

goto End

:ShowUsage

Echo.

Echo Usage: EBookGenerator [Target Directory]

Echo Target Directory is the virtual directory for e-book application

Echo If Target Directory is not specified, the Generator will

Echo generate the e-book application files in the Current Folder.

Echo.

cscript GenerateEBook.js EBook.xml EBookContent.xsl EBookTOC EBookLinks.xsl EBookLinks

:End

||||||设计粘合XML和XSL文件的ASP文件

最后,我们将关注例程中的两个ASP文件,Ebook.asp 和EbookContent.asp 。Ebook.asp生成两个框架(frameLink 和 frameContent),并装载EbookLinks.htm和EbookContent.asp。

〈% @LANGUAGE="JSCRIPT" %〉

〈HTML〉

〈%

try

{

// The code depends on EBookTOC.xml

var source = Server.CreateObject("Microsoft.XMLDOM");

source.async = false;

var strTOCFilePath = "EBookTOC.xml";

source.load(Server.MapPath(strTOCFilePath))

var xmlDoc = source.documentElement;

var strTitle = xmlDoc.childNodes(0).text;

var strSectionFileName="EBookContent.asp?Chap=1&Sec=0";

strLinkFileName = "EBookLinks.htm";

%〉

〈TITLE〉〈%=strTitle%〉〈/TITLE〉

〈FRAMESET cols="250,*" frameborder="1" framespacing="2" topmargin="0"

leftmargin="0" marginheight="0" marginwidth="0" border="1"

bordercolor="#BE008E"〉

〈FRAME src="〈%= strLinkFileName%〉" name="framelink"

style="border-right:solid #be008e 1px; border-top:solid #AE00AE 1px;"

scrolling="yes" topmargin="0" leftmargin="0" marginheight="0"

marginwidth="0" frameborder="0" border="0"〉

〈FRAME src="〈%= strSectionFileName%〉" name="frameContent"

style="border-left: groove #BE008E 2px; border-top: solid #AE00AE 1px;"

frameborder="no" border="0" bordercolor="#ae00ae" scrolling="yes"〉

〈%

}

catch(e) {

Response.Write(e.number + " " + e.description);

}

%〉

〈/FRAMESET〉

〈/HTML〉

一旦Ebook.asp完成它的功能,控制权就转移到EbookContent.asp。EbookContent.asp如下所示,读取XMLTOC.xml并按章定位相应的部分。

〈% @LANGUAGE="JSCRIPT" %〉

〈HTML〉〈HEAD〉

〈SCRIPT language="JScript" type="text/javascript" src="EBook.js"〉〈/SCRIPT〉

〈LINK rel="stylesheet" type="text/css" href="EBookContent.css" /〉

〈/HEAD〉

〈%

try

{

// The code requires EBookTOC.xml

var source = Server.CreateObject("Microsoft.XMLDOM");

source.async = false;

var strTOCFilePath = "EBookTOC.xml";

source.load(Server.MapPath(strTOCFilePath))

var strChapter;

var strSection;

if(Request.QueryString.Count == 2 )

{

strChapter = new String(Request.QueryString("Chap"));

strSection = new String(Request.QueryString("Sec"));

}

else

{

strChapter = "1";

strSection = "0";

}

var xmlDoc = source.documentElement;

var strTitle = xmlDoc.childNodes(0).text;

%〉

〈TITLE〉〈%=strTitle%〉〈/TITLE〉

〈BODY style="margin-left: 20px; margin-top: 20px" topmargin="0"

leftmargin="0" marginheight="0" marginwidth="0" bgcolor="#FFFFFF"

text="#000000"〉

〈%

var xmlChapterNodeList = xmlDoc.getElementsByTagName("chapter");

var xmlChapterNode;

var strLinkFileName="";

var strSectionFileName="";

var xmlSectionNodeList;

var xmlSectionNode;

var strHTML = "";

for(var i=0;i〈xmlChapterNodeList.length;i++) {

xmlChapterNode = xmlChapterNodeList.item(i);

if( xmlChapterNode.childNodes(1).text == strChapter ) {

if(strSection == "0") {

if( xmlChapterNode.attributes.length 〉 0 )

strHTML = "〈H1〉" + "Appendix " +

xmlChapterNode.attributes.item(0).text ;

else

strHTML = "〈H1〉" + "Chapter " + strChapter;

strHTML = strHTML + " " + xmlChapterNode.childNodes(2).text +

"〈/H1〉";

}

xmlSectionNodeList = xmlChapterNode.selectNodes("section");

for(var j=0;j〈xmlSectionNodeList.length;j++) {

xmlSectionNode = xmlSectionNodeList.item(j);

if( xmlSectionNode.childNodes(1).text == strSection ) {

strSectionFileName = xmlSectionNode.childNodes(0).text + ".htm";

break;

}

}

break;

}

}

strLinkFileName = "EBookLinks.htm";

var fso = Server.CreateObject("Scripting.FileSystemObject");

var ForReading = 1;

var txtContentFile = fso.OpenTextFile(

Server.MapPath(strSectionFileName),ForReading);

Response.Write(strHTML + txtContentFile.ReadAll());

}

catch(e)

{

Response.Write(e.number + " " + e.description);

}

%〉

〈/BODY〉

〈/HTML〉

改进

虽然我们通过大量的代码来实现电子图书,但还有许多的工作可以做。例如,我们可以进一步改进以实现诸如全文搜索、全文索引的功能。

对于那些对使用有效的(valid)XML感兴趣的人而言,只要去掉〈content〉标记符并生成以相应的〈id〉作为文件名的HTML文件即可。修改EbookLinks.xsl使HREF的值变成实际的HTML文件名,以取代EbookContent.asp。还要记得为每一个你生成的HTML文件使用EbookContent.CSS来修饰。

结论

这篇文章展示了用XML,XSL,CSS把任何已印刷好的书籍转换电子图书,或由草稿直接生成电子图书的过程是多么的容易。我们都知道XML很适合书籍在网络上出版、发表的需要,而这只是XML应用的一个例子。电子图书是在Internet上实现电子图书馆颇有价值的工具、手段。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有