|
第11章 嵌入非XML数据不是世界上的所有数据都为XML格式。实际上,可以大胆地说世界上积累下来的数据大部分都不是XML格式。大量数据按无格式文本、HTML和微软的Word格式保存,这里只举出三种常用的非XML格式。在理论上说,如果有兴趣且财力允许的情况下,至少这些数据的大部分可以重写为XML格式,但也不是所有的数据都可以。例如把图像编码为XML格式就将导致处理效率极端低下。 XML提供三种结构:记号、不可析外部实体和处理指令,通常用于处理非XML格式数据。记号描述非XML格式数据;不可析外部实体提供与非XML格式数据实际位置的链接;处理指令给出如何观看这些数据的信息。 本章叙述的具体内容尚有许多争议。尽管我所说的每一个事情都符合XML 1.0规范,但是不是所有人都同意上述观点。肯定可以写出一个XML文档,文档中没有使用注解和外部对象,仅有一些简单的处理说明。可以先跳过本章内容,在后面发现有必要了解这方面内容时,再返回到这一章。 本章的主要内容如下: · 记号
11.1 记号在XML文档中使用非XML格式数据将会遇到的第一个问题是识别数据格式,并通知XML应用程序如何读出和显示这些非XML格式数据。例如,企图在屏幕上画出MP3声音文件就是不合适宜的。 在一个有限的范围内只利用一套固定的用于特定种类的外部实体的标记,就可在单一应用程序中解决外部非XML数据的读取和显示问题。例如,如果全部图片数据都通过IMAGE元素嵌入,全部声音数据通过AVDIO元素嵌入;那么开发一个知道如何处理这两个元素的浏览器并不是一件很难的事。实际上这正是HTML采用的方法,可是这样的方法不允许文档作者为了能更加清楚地描述它们所需的内容,而创建新的标记;例如PERSON元素碰巧就有一个PHOTO属性,该属性指向那人的JPEG格式图片。 再者,没有一个应用程序可理解所有可能的文件格式。大多数Web浏览器可以管理和读出GIF、JPEG、PNG图像文件,或许还包括一些其他格式的图像文件;但是它们在EPS、TIFF、FITS文件前都束手无策,对于是几百种普遍和特殊的图像格式就更加力不从心了。图11-1的对话框或许再熟悉不过了。 图11-1 当Netscape Navigator无法识别一种文件类型时将发生的事情 理想的情况是希望文档会通知应用程序外部实体的格式,因此不必去依赖应用程序来识别文件类型,或是靠具有魔力的数字或者是并不可靠的文件扩展名。此外,如果应用程序自身无法处理这种格式的图像,也可以为应用程序提供一些关于什么程序可用来显示图像的线索。 记号提供了部分解决这个问题的方法(尽管不能获得很好的支持)。记号描述非XML数据的格式。在DTD中,NOTATION声明规定特殊的数据类型。DTD在与元素、属性和实体同一层次上声明记号。每个记号声明都包含一个名字和一个外部标识符,语法结构如下: <!NOTATION name SYSTEM "externalID"> name为文档中使用的特殊格式的标识符;externalID就是用来标识记号的有意义的字符串。例如,实体GIF图像的记号可以使用MIME类型: <!NOTATION GIF SYSTEM "Image/gif"> 也可以使用PUBLIC代替SYSTEM标识符,这样做就必须提供public ID和URL。例如: <!NOTATION GIF PUBLIC "-//IETF// NONSGML Media Type image/gif//EN" "http://www.isi.edu/in-notes/iana/assignments/media-types/image/gif"> 对于如何准确地作出外部标识,还存在激烈的争论。像图像/gif、文本/HTML之类的MIME类型是一种可能性;另一个建议是选择URL,或者其他的标准文档定位方式——像http://www.w3.org/TR/REC-html140/。第三个选择是使用正式的国际标准——如表示日期和时间的ISO 8601标准。某些情况下,可能ISBN或者国会图书馆为文献文档编目的方法更为适用。此外还有其余许多选择。 选取何种方式,取决于对文档生命期的期望值。例如,如果选择不普遍的格式,就不能依赖每个月都会改变的URL方式;如果希望文档在100年内都具有活跃的生命力,那么就该考虑使用在100年中都具有意义的标识符,而不是使用仅具有10年生命力的技术。 也可以使用记号来描述插入到文档中数据。例如,研究下面的DATA元素: <DATE>05-07-06</DATE> 05-07-06到底表示哪一天?是公元1906年5月7日还是公元1906年7月5日?答案取决于是按美国格式还是欧洲格式理解这个日期。甚至也可能是2006年5月7日或者2006年7月5日。或者是公元6年5月7日,是西方鼎盛时期的罗马帝国的秋天和中国的汉朝。也有可能这个日期根本不是公元纪年,而是犹太历、穆斯林历法或者中国的农历。没有更多的信息,就无法确定其真实的意义。 为了避免这样混淆不清的情况,ISO 8601标准为表示日期规定了一个精确的方法。应用这种方法,在XML中,公元2006年7月5日写为20060705,或者是如下格式: <DATE>20060705</DATE> 这种格式不是与每个人的想法都相同,对所有人都具有同等程度的迷惑性,不偏向任何一种文化(实际上仍然偏向西方传统日历)。 在DTD中声明记号,并且用记号属性描述嵌入XML文档中的非XML数据的格式。接着再研究日期的例子,清单11-1定义两种日期记号:ISO 8601和美国惯用格式。然后将NOTATION类型必需的FORMAT属性添加到每一个DATE元素中,用来描述特定元素的结构。 清单11-1:ISO 8601和美国惯用格式DATE元素 <?xml version="1.0" standalone="yes"?> <!DOCTYPE SCHEDULE [ <!NOTATION ISODATE SYSTEM "http://www.iso.ch/cate/d15903.html"> <!NOTATION USDATE SYSTEM "http://es.rice.edu/ES/humsoc/Galileo/Things/gregorian_calendar .html"> <!ELEMENT SCHEDULE (APPOINTMENT*)> <!ELEMENT APPOINTMENT (NOTE, DATE, TIME?)> <!ELEMENT NOTE (#PCDATA)> <!ELEMENT DATE (#PCDATA)> <!ELEMENT TIME (#PCDATA)> <!ATTLIST DATE FORMAT NOTATION (ISODATE | USDATE) #IMPLIED> ]> <SCHEDULE> <APPOINTMENT> <NOTE>Deliver presents</NOTE> <DATE FORMAT="ISDATE">12-25-1999</DATE> </APPOINTMENT> <APPOINTMENT> <NOTE>Party like it s 1999</NOTE> <DATE FORMAT="ISODATE">19991231</DATE> </APPOINTMENT> </SCHEDULE> 记号不能强制作者使用记号描述的格式。因此需要提供除XML基本方法以外的几种语言方案——但是在相信作者能正确描述日期的简单应用场合,记号方法是有效的。 11.2 不可析外部实体对所有的数据,特别是非文本数据,XML格式都不是理想的格式。例如,可以按下面所示的方式,把位图图像的每一个像素存为一个XML元素: <PIXEL X="32"Y="28" COLOR="FF5E32"/> 可是,这肯定不是一个好主意。任何微小的错误都会导致气球图像文件的比例严重失衡。XML现在和将来都永远不可能让XML文档具有访问数据的能力,因此无法把所有数据按XML编码。 一个典型的Web页面可以引用GIF和JPEG图像、JAVA小程序、ActiveX控件、各种类型的声音等等。在XML中,因为XML处理器不会去尝试理解非XML格式的数据块,所以把这些数据块称为不可析实体。至多XML处理器通知应用程序存在这样的实体,并且为应用程序提供实体名和实体可能包含的内容(可是这并不是必须执行的动作)。 HTML页面通过各种定制的标记嵌入非HTML实体。图片由具有SRC属性的<IMG>标记引用,SRC属性提供图像文件的URL;JAVA程序由具有CLASS和CODEBASE属性的<APPLET>标记包括,CLASS和CODEBASE属性指向JAVA程序保存的文件和目录;<OBJECT>标记来嵌入CODEBASE属性引用,可从中找到目标数据的URI。每一种情况下,特定的预定义标记表示一种特定的内容。预定义属性包含其内容的URL。 XML应用程序可以但不是必须这样运作,实际上,除了特意为保持与落后的HTML之间的兼容性之外,大部分XML应用程序都不这样做。相反,XML应用程序使用不可析外部实体引用这些内容。不可析外部实体提供与非XML数据的实际位置的链接。接着文档中特定的元素利用其ENTITY属性与实体相连。 11.2.1 声明不可析实体回忆第9章的内容,外部实体的声明看起来如下面的形式: <!ENTITY SIG SYSTEM "http://metalab.unc.edu/xml/signature.xml"> 可是,仅在URL指明的外部实体恰好为完整的XML文档的时候,才能接受这种格式。如果外部实体不是XML,则不得不使用NDATA关键字指定实体类型。例如,为了用LOGO名字连接GIF格式文件logo.gif,就需在DTD中放置如下的ENTITY声明: <!ENTITY LOGO SYSTEM "logo.gif" NDATA GIF> 声明中的最终名字必须是DTD中声明的记号名,如本例中的GIF。记号将GIF类的名称与某种类型的外部标识符联系起来,外部标识符标识某种格式。如MIME类型、ISO标准式或者是格式规格的URL。例如,GIF的记号类似下面的形式: <!NOTATION GIF SYSTEM "image/gif"> 通常,作为习惯的表示方法,可以使用绝对或相对的URL指向外部实体。例如: <!ENTITY LOGO SYSTEM "http://metalab.unc.edu/xml/logo.gif" NDATA GIF> <!ENTITY LOGO SYSTEM "/xml/logo.gif" NDATA GIF> <!ENTITY LOGO SYSTEM "../logo.gif" NDATA GIF> 11.2.2 嵌入不可析实体不能与用通用实体引用嵌入可析实体一样,在文档中的任意位置简单地嵌入不可析实体。例如,清单11-2就是一个不合法的是XML文档,因为LOGO是不可析实体。如果这里的LOGO是可析实体,本例就为有效的XML文档。 清单11-2:试图用通用实体引用嵌入不可析实体的无效XML文档 <?xml version="1.0" standalone="no"?> <!DOCTYPE DOCUMENT [ <!ELEMENT DOCUMENT ANY> <!ENTITY LOGO SYSTEM "http://metalab.unc.edu/xml/logo.gif" NDATA GIF> <!NOTATION GIF SYSTEM "image/gif" ]> <DOCUMENT> &LOGO; </DOCUMENT> 为了嵌入不可析实体,不采用如&LOGO;通用实体引用的方法;而是声明一个元素,把该元素作为不可析实体的占位符(例如IMAGE)。然后声明IMAGE元素属性SOURCE为ENTITY类型,SOURCE属性仅提供不可析实体名。如清单11-3所示。 清单11-3:正确嵌入不可析实体的合法的XML文档 <?xml version="1.0" standalone="no"?> <!DOCTYPE DOCUMENT [ <!ELEMENT DOCUMENT ANY> <!ENTITY LOGO SYSTEM "http://metalab.unc.edu/xml/logo.gif" NDATA GIF> <!NOTATION GIF SYSTEM "image/gif" <!ELEMENT IMAGE EMPTY> <!ATTLIST IMAGE SOURNE ENTITY #REQUIRED> ]> <DOCUMENT> <IMAGE SOURNE="LOGO"/> </DOCUMENT> 等到应用程序读取XML文档时,就可认出这个不可析实体且显示出来。应用程序也可能不显示不可析实体(当用户使图像载入失效时,Web浏览器将选择不显示图像)。 这些例子表明:空元素就像是为不可析实体准备的容器,可是这不是必须采用的方法。例如,假设有一个基于XML的公司ID系统,就是保安人员使用的查寻进入建筑物的人的系统;PERSON元素拥有NAME、PHONE、OFFICE、EMPLOYEE_ID子类和PHOTO ENTITY属性,如清单11-4所示。 清单11-4:具有PHOTO ENTITY属性的非空PERSON元素 <?xml version="1.0" standalone="no"?> <!DOCTYPE PERSON [ <!ELEMENT PERSON (NAME, EMPLOYEE_ID, PHONE, OFFICE)> <!ELEMENT NAME (#PCDATA)> <!ELEMENT EMPLOYEE_ID (#PCDATA)> <!ELEMENT PHONE (#PCDATA)> <!ELEMENT OFFICE (#PCDATA)> <!NOTATION JPEG SYSTEM "image/jpg" <!ENTITY ROGER SYSTEM "rogers.jpg" NDATA JPEG> <!ATTLIST PERSON PHOTO ENTITY #REQUIRED> ]> <PERSON PHOTO="ROGER"> <NAME>Jim Rogers</ NAME> <EMPLOYEE_ID>4534</EMPLOYEE_ID> <PHONE>X396</PHONE> <OFFICE>RH 415A</OFFICE> </PERSON> 这个例子看起来有点做作。实际上,使一个带有SOURCE属性的空DHOTO元素的成为PERSON元素的子元素,而不是PERSON元素的属性。再者,或许可以把这个DTD分割为内部和外部的子集。如清单11-5所示,外部子集声明元素、记号和属性。这些都是可以被不同的文档共享的部分。但是,实体从一个文档到另一个文档会发生改变,因此最好把实体放在如清单11-6显示的文档的内部DTD子集中。 清单11-5:外部DTD子集person.dtd <!ELEMENT PERSON ( NAME, EMPLOYEE_ID, PHONE, OFFICE, PHOTO)> <!ELEMENT NAME (#PCDATA)> <!ELEMENT EMPLOYEE_ID (#PCDATA)> <!ELEMENT PHONE (#PCDATA)> <!ELEMENT OFFICE (#PCDATA)> <!ELEMENT PHOTO EMPTY> <!NOTATION JPEG SYSTEM "image/jpeg"> <!ATTLIST PHOTO SOURCE ENTITY #REQUIRED> 清单11-6:内含非空PERSON元素和一个内部DTD子集的文档 <?xml version="1.0" standalone="no"?> <!DOCTYPE PERSON [ <!ENTITY % PERSON _DTD SYSTEM "person.dtd"> %PERSON_DTD; <!ENTITY ROGER SYSTEM "rogers.jpg" NDATA JPEG> ]> <PERSON> <NAME>Jim Rogers</NAME> <EMPLOYEE_ID>4534</EMPLOYEE_ID> <PHONE>X396</PHONE> <OFFICE>RH 415A</OFFICE> <PHOTO SOURCE="ROGER"/> </PERSON> 11.2.3 嵌入多个不可析实体在某些特殊场合下,一个单一的属性甚至一个标识号,可能需要引用不止一个的不可析实体。就可以声明占位符元素的属性为ENTITIES类型。ENTITIES属性值由空格分隔的多个不可析实体名组成,每个实体名都指向一个外部非XML格式数据资源,并且必须在DTD中声明所有实体。例如,可以用这种方法编写一个以幻灯放映元素来切换不同的图片,DTD需要如下形式的声明: <!ELEMENT SLIDESHOW EMPTY> <!ATTLIST SLIDESHOW SOURCES ENTITIES #REQUIRED> <!NOTATION JPEG SYSTEM "image/jpeg" <!ENTITY HARM SYSTEM "charm.jpg" NDATA JPEG> <!ENTITY MARJORIE SYSTEM "marjorie.jpg" NDATA JPEG> <!ENTITY POSSUM SYSTEM "possum.jpg" NDATA JPEG> <!ENTITY BLUE SYSTEM "blue.jpg" NDATA JPEG> 然后,在文档中需要幻灯放映出现的位置上,就可插入如下标记: <SLIDESHOW SOUR ES="CHARM MARJORIE POSSUM BLUE"> 必须再次强调,这不是一个XML处理器(甚至任意处理器)可自动理解的具有魔力的方案,这仅仅是一种技巧,在嵌入文档中的非XML数据时,浏览器和其余应用程序可能采用也可能不采用的技术。 11.3 处理指令指令经常过多地应用于支持HTML的私有范围,如服务端嵌入、浏览器定制脚本语言、数据库模板和其余许多超出HTML标准范围的项目。出于这些目的而使用注释的好处是:其余系统可以简单地忽略它们无法理解的外来数据。这种方法的不利之处在于:剥离了注释的文档可能不再是原来的文档了,并且仅仅作为文档的注释会被误解为这些私有范围的输入数据。为了避免滥用注释,XML提供了处理指令的方法,作为在文件中嵌入信息的明确机制,用于私有的应用程序而不是XML语法分析器或浏览器。其余用途包括,处理指令可以提供关于如何查看不可析外部实体的附加信息。 处理指令就是位于<? 和?>标记之间的一行文本。处理指令中的文本只需要如下句法结构,以XML名开头,其后紧跟空格,空格后为数据。XML名可以是应用程序的实际名字(如latex),或者是在DTD中指向应用程序的记号名(例如LATEX),在DTD中的LATEX声明具有如下形式: <!NOTATION LATEX SYSTEM "/usr/local/bin/latex"> 甚至这个名字可以是可被应用程序识别的其他名字。对于使用处理指令的应用程序来说,各个细节部分是非常明确的。确实,大部分依赖处理指令的应用程序在处理指令的内容上利用更多的结构。例如,研究如下在IBM的Bean Markup Language中使用的处理指令: <?bmlpi register demos.calculator.EventSourceText2Int?> 使用处理指令的应用程序名字为bmlpi。赋予应用程序的数据为字符串register demos.calculator.EventSourceText2Int,这些数据将包含全部合格的Java类名。这就告诉名为bmlpi的应用程序使用Java类demos.calculator.EventSourceText2Int,将操作事件转换为整数。如果bmlpi在读取文档是遇到这个处理指令,将载入类demos.calculator.EventSourceText2Int,从此往后利用该类元素将事件转化为整数。 如果这听起来很明确也很详细的话,那是因为它们原来就是如此。处理指令不是文档的通用结构部分,它们为特定的应用程序提供额外的明确的信息,而不是为所有读取该文档的应用程序提供信息。如果其余一些应用程序在读取文档时遇到这个说明,它们将简单地跳过这些说明。 处理指令除了不能位于标记或者CDATA字段之内,可以放在XML文档中的任意部位。它们可以位于序进程、DTD、元素内容中,甚至可在文档结束标记之后。因为处理指令不是元素,所以不会影响文档的树型结构。没有必要打开或者关闭处理指令,也没有必要考虑它们在其他元素中的嵌套问题。处理指令不是标记,不会对元素进行限定。 到此我们已经很熟悉了一个处理指令的例子:xml-stylesheet处理指令把样式单与文档相结合: <?xml-stylesheet type="text/xsl" href="baseball.xsl"?> 虽然这些例子中的处理指令位于序进程中,但是处理指令可以在文档的任意位置出现。因为处理指令不是元素,所以没有必要声明为包含它们元素的子类元素。 以字符串xml开头的处理指令在XML规范中留作特殊的用途。此外,在处理指令中,可以自由使用除文档结束标记符(?>)外的任意名字和任意文本字符串。例如,下面的例子就是完全有效的处理指令: <?gcc HelloWorld.c ?> <?acrobat document="passport.pdf"?> <?Dave remember to replace this one?> 请记住XML处理器不会对处理说明进行任何处理,仅仅是把他们传送给应用程序。应用程序决定如何处理这些说明。大部分应用程序简单地跳过他们无法理解的处理说明。 有些时候了解不可析实体的类型还是不够的。还需要了解应用程序如何运行和查看实体,以及需要提供给应用程序的参数是什么。 这些信息都可以通过处理指令来提供。因为处理指令所包含的数据没有什么限制,所以在制定说明时就相对容易一些,这些说明是决定记号中列出的外部程序将采取什么行为。 这样的处理指令可以是查看数据块的程序名,也可以是几千字节的配置信息。应用程序和文档的作者当然必须采用同样的方法来确定何种不可析外部实体采取何种处理指令。清单11-7显示一个方案,该方案使用一个处理指令和PDF记号来通知Acrobat Reader关于物理纸张的PDF格式,以便Acrobat Reader显示PDF的内容。 清单11-7:在XML中嵌入PDF文档 <?xml version="1.0" standalone="yes"?> <!DOCTYPE PAPER [ <!NOTATION PDF PUBLIC "-//IETF//NONSGML Media Type application/pdf//EN" "http://www.isi.edu/in-notes/iana/assignments/media-types/ application/pdf"> <!ELEMENT PAPER (TITLE, AUTHOR+, JOURNAL, DATE_RECEIVED, VOLUME, ISSUE, PAGES)> <!ATTLIST PAPER CONTENTS ENTITY #IMPLIED> <!ENTITY PRLTAO000081000024005270000001 SYSTEM "http://ojps.aip.org/journal_cgi/getpdf?KEY=PRLTAO&cvips=PR LTA0000081000024005270000001" NDATA PDF> <!ELEMENT AUTHOR (#PNDATA)> <!ELEMENT JOURNAL (#PNDATA)> <!ELEMENT YEAR (#PNDATA)> <!ELEMENT TITLE (#PNDATA)> <!ELEMENT DATE_RE EIVED (#PNDATA)> <!ELEMENT VOLUME (#PNDATA)> <!ELEMENT ISSUE (#PNDATA)> <!ELEMENT PAGES (#PNDATA)> ]> <?PDF acroread?> <PAPER CONTENTS="PRLTAO000081000024005270000001"> <TITLE>Do Naked Singularities Generically Occur in Generalized Theories of Gravity?</TITLE> <AUTHOR>Kengo Maeda</AUTHOR> <AUTHOR>Takashi Torii</AUTHOR> <AUTHOR>Makoto Narita</AUTHOR> <JOURNAL>Physical Review Letters</JOURNAL> <DATE_RE EIVED>19 August 1998</DATE_RE EIVED> <VOLUME>81</VOLUME> <ISSUE>24</ISSUE> <PAGES>5270-5273</PAGES> </PAPER> 任何时候都该记住不是所有的处理器程序都会以你希望的方式去对待这个例子。实际上,大部分处理器都不会。可是,从让一个应用程序支持PDF文件和其余非XML媒体类型的角度来说,这也是一个值得考虑的方法。 11.4 DTD的条件部分在创建DTD和文档的时候,或许需要文档中没有反映DTD的部分作一些注释。除了直接使用注释,也可以把DTD中的特定声明组放置在IGNORE指令中的方式,从而忽略该声明组。句法结构如下: <![ IGNORE declarations that are ignored ]]> 像通常一样,空格不会对句法结构产生实质性的影响,但是必须保证开始符(<![IGNORE)和结束符(]]>)占单独的一行,以便阅读。 可以忽略任意声明或一组声明——元素、实体、属性甚至包括其他的IGNORE块,但是必须忽略整个声明。IGNORE的构造必须完整包含从DTD中移走的全部声明。不能仅忽略声明的某个局部(例如在不可析实体声明中的NDATA GIF)。 也可以指定包括声明的某个特定部分,也就是说不忽略的部分。INCLUDE指示的句法结构与IGNORE相似,但是关键词不同: <![ INCLUDE declarations that are included ]]> 当INCLUDE位于IGNORE之内的时候,INCLUDE和声明都被忽略。当IGNORE位于INCLUDE内,位于IGNORE之内的声明依然被忽略。换一种说法就是INCLUDE不会覆盖IGNORE。 上述给出的情形中,或许会为INCLUDE的存在表示奇怪。简单地移走INCLUDE块,仅留下它们的内容,没有任何DTD会发生改变。INCLUDE好像完全是多余的。可是,对于无法单独使用IGNORE的参数实体引用的情形中,同时应用IGNORE和INCLUDE不失为一个灵巧的方法。首先定义一个如下的参数实体引用: <!ENTITY % fulldtd "IGNORE"> 将元素包裹在下列结构中,就可将其忽略: <![ %fulldtd; declarations ]]> %fulldtd;参数实体引用求出的值为IGNORE,因此声明被忽略。现在,假设对一个单词作出修改,把fulldtd从IGNORE改为INCLUDE,如下所示: <!ENTITY % fulldtd "INCLUDE"> 所有的IGNORE块立即转换为INCLUDE块。实际上,就像是一系列开关,可以打开或者关闭声明块。 在本例中,仅使用了一个开关——fulldtd。可以在DTD中的多个IGNORE/INCLUDE块中使用这种开关。也可以拥有许多可根据不同条件选择开或关的不同IGNORE/INCLUDE块。 当设计其余DTD内含的DTD时,这种能力特别有用。通过改变参数实体开关值,最后的DTD可以改变嵌入的DTD行为。 11.5 本章小结在本章中,学习了如何通过记号、不可析外部实体和处理指令的方法,把非XML数据与XML文档相结合。具体地说,学习了下述概念: · 记号描述非XML数据的格式。
在本书的后面几个部分可以看到更多拥有DTD的文档的例子。但是关于DTD的最基本的句法结构和用法,在本章中已经讨论完毕。在本书的第三部分,我们开始讨论XML的样式语言,从下一章的级联样式单(第一级)开始。 |