当你购买某些出版社出版的科技书籍时,你会发现随书的配套光盘包含有该书的电子版。一些电子图书(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上实现电子图书馆颇有价值的工具、手段。