设为首页
加入收藏
站内地图
旧版入口
当前位置:首页 > 站长学院 > 网络编程 > XML

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

作者:佚名 出处:网络转载 时间:07-26 点击:

内容载入中...
当你购买某些出版社出版的科技书籍时,你会发现随书的配套光盘包含有该书的电子版。一些电子图书(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="/upload/2006-12-20/200612209312990457.gif"〉
  〈A HREF="javascript:ExpandAll("UL")" TITLE="Expand All"〉Expand All〈/A〉
  〈IMG WIDTH="16" BORDER="0" SRC="/upload/2006-12-20/200612209312994216.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上实现电子图书馆颇有价值的工具、手段。

收藏本文:
】【打印页面】【推荐给朋友】【关闭窗口
<< 上一篇 :DTD元素属性介绍

站长学院

推荐信息