分类归档: Programming

编程编程编程。。。

[转]LZW数据压缩算法的原理分析

src:http://www.cnblogs.com/jillzhang/archive/2006/11/06/551298.html

   我希望通过本文的介绍,能给那些目前不太了解lzw算法和该算法在gif图像中应用,但渴望了解它的人一些启发和帮助。抛砖引玉而已,更希望园子里面兄弟提出宝贵的意见。
1.LZW的全称是什么?
   Lempel-Ziv-Welch (LZW).
2. LZW的简介和压缩原理是什么?
   LZW压缩算法是一种新颖的压缩方法,由Lemple-Ziv-Welch 三人共同创造,用他们的名字命名。它采用了一种先进的串表压缩,将每个第一次出现的串放在一个串表中,用一个数字来表示串,压缩文件只存贮数字,则不存贮 串,从而使图象文件的压缩效率得到较大的提高。奇妙的是,不管是在压缩还是在解压缩的过程中都能正确的建立这个串表,压缩或解压缩完成后,这个串表又被丢 弃。
   LZW算法中,首先建立一个字符串表,把每一个第一次出现的字符串放入串表中,并用一个数字来表示,这个数字与此字符串在串表中的位置有关,并将这个数字 存入压缩文件中,如果这个字符串再次出现时,即可用表示它的数字来代替,并将这个数字存入文件中。压缩完成后将串表丢弃。如"print" 字符串,如果在压缩时用266表示,只要再次出现,均用266表示,并将"print"字符串存入串表中,在图象解码时遇到数字266,即可从串表中查出 266所代表的字符串"print",在解压缩时,串表可以根据压缩数据重新生成。
3.在详细介绍算法之前,先列出一些与该算法相关的概念和词汇
   1)’Character’: 字符,一种基础数据元素,在普通文本文件中,它占用1个单独的byte,而在图像中,它却是 一种代表给定像素颜色的索引值。
   2)’CharStream’:数据文件中的字符流。
   3)’Prefix’:前缀。如这个单词的含义一样,代表着在一个字符最直接的前一个字符。一个前缀字符长度可以为0,一个prefix和一个character可以组成一个字符串(string),
   4)’Suffix’: 后缀,是一个字符,一个字符串可以由(A,B)来组成,A是前缀,B是后缀,当A长度为0的时候,代表Root,根
   5)’Code:码,用于代表一个字符串的位置编码
   6)’Entry’,一个Code和它所代表的字符串(string)
4.压缩算法的简单示例,不是完全实现LZW算法,只是从最直观的角度看lzw算法的思想
    对原始数据ABCCAABCDDAACCDB进行LZW压缩
    原始数据中,只包括4个字符(Character),A,B,C,D,四个字符可以用一个2bit的数表示,0-A,1-B,2-C,3-D,从最直观的角度看,原始字符串存在重复字符:ABCCAABCDDAACCDB,用4代表AB,5代表CC,上面的字符串可以替代表示为:45A4CDDAA5DB,这样是不是就比原数据短了一些呢!
5.LZW算法的适用范围
   为了区别代表串的值(Code)和原来的单个的数据值(String),需要使它们的数值域不重合,上面用0-3来代表A-D,那么AB就必须用大于3的 数值来代替,再举另外一个例子,原来的数值范围可以用8bit来表示,那么就认为原始的数的范围是0~255,压缩程序生成的标号的范围就不能为 0~255(如果是0-255,就重复了)。只能从256开始,但是这样一来就超过了8位的表示范围了,所以必须要扩展数据的位数,至少扩展一位,但是这样不是增加了1个字符占用的空间了么?但是却可以用一个字符代表几个字符,比如原来255是8bit,但是现在用256来表示254,255两个数,还是划得来的。从这个原理可以看出LZW算法的适用范围是原始数据串最好是有大量的子串多次重复出现,重复的越多,压缩效果越好。反之则越差,可能真的不减反增了
6.LZW算法中特殊标记
   随着新的串(string)不断被发现,标号也会不断地增长,如果原数据过大,生成的标号集(string table)会越来越大,这时候操作这个集合就会产生效率问题。如何避免这个问题呢?Gif在采用lzw算法的做法是当标号集足够大的时候,就不能增大 了,干脆从头开始再来,在这个位置要插入一个标号,就是清除标志CLEAR,表示从这里我重新开始构造字典,以前的所有标记作废,开始使用新的标记
这时候又有一个问题出现,足够大是多大?这个标号集的大小为比较合适呢?理论上是标号集大小越大,则压缩比率就越高,但开销也越高。 一般根据处理速度和内存空间连个因素来选定。GIF规范规定的是12位,超过12位的表达范围就推倒重来,并且GIF为了提高压缩率,采用的是变长的字 长。比如说原始数据是8位,那么一开始,先加上一位再说,开始的字长就成了9位,然后开始加标号,当标号加到512时,也就是超过9为所能表达的最大数据 时,也就意味着后面的标号要用10位字长才能表示了,那么从这里开始,后面的字长就是10位了。依此类推,到了2^12也就是4096时,在这里插一个清 除标志,从后面开始,从9位再来。
GIF规定的清除标志CLEAR的数值是原始数据字长表示的最大值加1,如果原始数据字长是8,那么清除标志就是256,如果原始数据字长为4那么就是16。另外GIF还规定了一个结束标志END,它的值是清除标志CLEAR再加1。由于GIF规定的位数有1位(单色图),4位(16色)和8位(256色),而1位的情况下如果只扩展1位,只能表示4种状态,那么加上一个清除标志和结束标志就用完了,所以1位的情况下就必须扩充到3位。其它两种情况初始的字长就为5位和9位。此处参照了http://blog.csdn.net/whycadi/
7.用lzw算法压缩原始数据的示例分析
   输入流,也就是原始的数据为:255,24,54,255,24,255,255,24,5,123,45,255,24,5,24,54………………
   这个正好可以看到是gif文件中像素数组的一部分,如何对它进行压缩
   因为原始数据可以用8bit来表示,故清除标志Clear=255+1 =256,结束标志为End=256+1=257,目前标号集为
   0 1 2 3 ………………………………………………………………………255 CLEAR END
   第一步,读取第一个字符为255,在标记表里面查找,255已经存在,我们已经认识255了,不做处理
   第二步,取第二个字符,此时前缀为A,形成当前的Entry为(255,24),在标记集合不存在,我们并不认识255,24好,这次你小子来了,我就记住你,把它在标记集合中标记为258,然后输出前缀A,保留后缀24,并作为下一次的前缀(后缀变前缀)
第三步,取第三个字符为54,当前Entry(24,54),不认识,记录(24,54)为标号259,并输出24,后缀变前缀
    第四部:取第四个字符255,Entry=(54,255),不认识,记录(54,255)为标号260,输出54,后缀变前缀
   第五步   取第5个字符24,entry=(255,24),啊,认识你,这不是老258么,于是把字符串规约为258,并作为前缀
   第六步 取第六个字符255,entry=(258,255),不认识,记录(258,255)为261,输出258,后缀变前缀
   …….
一直处理到最后一个字符,
用一个表记录处理过程
   CLEAR=256,END=257

第几步 前缀 后缀 Entry 认识(Y/N) 输出 标号
1 255 (,255)
2 255 24 (255,24)       N 255 258
3 24 54 (24,54)       N 24 259
4 54 255    (54,255)       N 54 260
5 255 24 (255,24)       Y
6 258 255 (258,255)       N 258 261
7 255 255 (255,255)       N 255 262

…..
上面这个示例有些不能完整体现,另外一个例子是
原输入数据为:A B A B A B A B B B A B A B A A C D A C D A D C A B A A A B A B …..
采用LZW算法对其进行压缩,压缩过程用一个表来表述为:
注意原数据中只包含4个character,A,B,C,D
用两bit即可表述,根据lzw算法,首先扩展一位变为3为,Clear=2的2次方+1=4; End=4+1=5;
初始标号集因该为

0 1 2 3 4 5
A B C D Clear End

而压缩过程为:

第几步 前缀 后缀 Entry 认识(Y/N) 输出 标号
1 A (,A)
2 A B (A,B)       N A 6
3 B A (B,A)       N B 7
4 A B    (A,B)       Y
5 6 A (6,A)       N 6 8
6 A B (A,B)       Y
7 6 A (6,A)      Y
8 8 B (8,B)        N 8 9
9 B B (B,B)        N B 10
10 B B (B,B)        Y
11 10 A (10,A)        N 10 11
12 A B (A,B)        Y

…..
当进行到第12步的时候,标号集应该为

0 1 2 3 4 5 6 7 8 9 10 11
A B C D Clear End AB BA 6A 8B BB 10A

8.LZW算法的伪代码实现

1STRING = get input character
2WHILE there are still input characters DO
3     CHARACTER = get input character
4     IF STRING+CHARACTER is in the string table then
5         STRING = STRING+character
6     ELSE
7         output the code for STRING
8         add STRING+CHARACTER to the string table
9         STRING = CHARACTER
10     END of IF
11END of WHILE
12output the code for STRING
13

9.LZW算法的流程图
没有安visio,画了一个,比较难看,

Read: 1064

[转]如何修复 “Internet Explorer 无法打开 Internet 站点….”

src:http://www.nirmaltv.com/2007/08/08/how-to-fix-internet-explorer-cannot-open-the-internet-site-operation-aborted-error/

How to Fix “Internet Explorer Cannot Open the Internet Site- Operation Aborted” Error

Of late few of my blog readers using Internet explorer pointed out to me that there was some error happening while loading my blog in IE. Around 25% of my readers use Internet Explorer to read my blog. I check my blog frequently in IE and many times I too encountered this error, but initially I thought it was some error in my browser. The error is really frustrating as it shows only in IE and not in Firefox or Opera or any other browser for that matter.

The nature of error is that it shows up when the site is loads completely. This is what you see when the error occurs.

Internet Explorer Error

Once you click OK, the page is replaced with “The Page cannot be Displayed” error. How to fix this error?

A bit of Googling gave me some idea into this error. This error can happen because of many reasons. Microsoft has even a patch for solving this error in IE.

1. This error can happen if Google Map API is present in the code. If you are getting this error because of Google Map API, then the fix for the problem is available here.

2. If you are not using Google API and still getting the error, then it could be because of this reason.

It is not possible append to the BODY element from script that isn’t a direct child to the BODY element

If there are any Javascripts running inside the body tag or inside a table which is directly a child of body, then most of the times this error is bound to happen. The solution is to move the script to the top or bottom of the body tag or even moving it after the body. The script can also be put inside a function and then calling it from window.onload. Another solution to this problem is to add defer=”defer” in the script tag.

I checked my code for scripts which were running inside the body tag and was a direct child to it. Then I moved all my Javascript codes to bottom of the body tag so that it will not interfere while parsing the page. I tested my blog in IE after I made the change and I could see the difference, the error was not happening any more. I checked in different versions of IE to confirm. In case you are using IE and still getting the error on my blog, please do inform me.

Read: 2038

[转]如何正确的理解CSS的float浮动属性?

src:http://www.52css.com/article.asp?id=228

首先我们了解到,CSS网页布局的原理,就是按照HTML代码中对象声明的顺序,以流布局的方式来显示它,而流布局就不得不说到float浮动技术, 在HTML中的所有对象,默认分为两种:块元素(block element)、内联元素(inline element),虽然也存在着可变元素,但只 是随上下文关系确定该元素是块元素或者内联元素。关于块元素和内联元素可以参考这里。

其实CSS的float属性,作用就是改变块元素(block element)对象的默认显示方式。block对象设置了float属性之后,它将不再独自占据一行。可以浮动到左侧或右侧,关于float属性的详细说明可以参考这里。

需要引起你重视的是,float属性不是你所想象的那么简单,不是通过这一篇文字的说明,就能让你完全搞明白它的工作原理的,我们需要在实践中不断的总结经验,应对所出现的问题。我们通过下面的这个小例子,来说明它的基本工作情况。

我们看下面的CSS代码:

div css xhtml xml Example Source Code Example Source Code [www.52css.com]
.left{
        background-color:#cccccc;
        border:2px solid #333333;
        width:200px;
        height:100px;
}
.leftfloat{
        background-color:#cccccc;
        border:2px solid #333333;
        width:200px;
        height:100px;
        float:left;
}
.right{
        background-color:#cccccc;
        border:2px solid #333333;
        height:100px;
}

left和right为不作任何浮动的类。leftfloat向左浮动的类。

我们再看看xhtml代码:

div css xhtml xml Example Source Code Example Source Code [www.52css.com]
<div class="left">div left float:none</div>
<div class="right">div right [www.52css.ocm]</div>
<div class="leftfloat">div left float:left</div>
<div class="right">div right [www.52css.ocm]</div>
<span class="left">span left float:none</span>
<span class="right">span right</span>

我们看运行效果:

div css xhtml xml Source Code to Run Source Code to Run [www.52css.com]
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>www.52css.com</title> <style type="text/css"> <!– .left{ background-color:#cccccc; border:2px solid #333333; width:200px; height:100px; } .leftfloat{ background-color:#cccccc; border:2px solid #333333; width:200px; height:100px; float:left; } .right{ background-color:#cccccc; border:2px solid #333333; height:100px; } –> </style> </head> <body> <div class="left">div left float:none</div> <div class="right">div right [www.52css.ocm]</div> <div class="leftfloat">div left float:left</div> <div class="right">div right [www.52css.ocm]</div> <span class="left">span left float:none</span> <span class="right">span right</span> </body> </html>
    [ 可先修改部分代码 再运行查看效果 ]

我们看(1)和(2):容器(1)没有任何浮动,占据了一整行,将(2)挤到了下面一行。而且(2)也占据了一整行的位置。
我们看(3)和(4):容器(3)声明了左浮动,容器(4)浮动到了它的右侧。实现了这两个容器处于同一行的情况。
我们看(5)和(6):容器(5)和(6)是span元素,也就是内联元素(inline element),自然的处于同一行。

Read: 825

[转]IE中标签使用float属性 后背景不能正常显示原因及解决

src:http://hi.baidu.com/anjingmin/profile

IE浏览器对于CSS的支持很不完整,如下代码,在FireFox能够正常显示出背景,但是在IE中却不可以。
HTML:
          
<div class=”titlebody”>
            
<div class=”cqsub”>
              
<div class=”cqsubtitle”>培养方案</div>
           
</div>
            
<div class=”cqsub”>
              
<div class=”cqsubtitle”>学籍指导</div>
            
</div>
          
</div>

CSS:
.cqsub{
width:50%;
float:left;
}
.titlebody{
background-image:url(‘../img/subb.jpg’);
background-repeat:repeat-y;
}

由于在cqsub类中使用了float:left;属性,而IE在使用这个属性的时候,如果没有定义上层的高度,在计算的时候会出现错误,导致定义的背景图片不能显示。

解决方法:在.titlebody中添加height:1px;的定义,迫使IE浏览器重新计算其高度,从而正常显示背景。但是此时FireFox中会老老实实地采用高度为1px的定义,这显然有问题的。因此还必须使用CSS Hack方法,添加代码:

[xmlns] .titlebody{
height:auto;
}

这行代码只能由FireFox解释,对IE6.0及以下版本完全透明,从而达到兼容的目的。

以上代码在FireFox,IE6.0,DreamWeaver8中调试通过。
G.K语:…. TNND 个恶心的Internet Explorer 搞得我累死, 弄了一晚上才找到解决办法 实在是郁闷之极

Read: 1040

PHP 中的 XML 拉模式解析

src:http://www.wujianrong.com/archives/2007/04/php_xml.html

PHP 5 引入了新的类 XMLReader,用于读取可扩展标记语言(Extensible Markup Language,XML)。与 SimpleXML 或文档对象模型(Document Object Model,DOM)不同,XMLReader 以流模式进行操作。即它从头到尾读取文档。在文档后面的内容编译完成之前,可以先处理已编译好的文档前面的内容,从而实现非常快速、非常高效、非常节省地使用内存。需要处理的文档越大,这个特点就越发重要。

libxml

这里所说的 XMLReader API 位于 Gnome Project 中用于 C 和 C++ 的 libxml 库之上。实际上 XMLReader 只是在 libxml 的 XmlTextReader API 之上的很薄的 PHP 层。XmlTextReader 本身是模仿 .NET 的 XmlTextReader 类和 XmlReader 类,尽管并不具有与这些类相似的代码。

与 Simple API for XML (SAX) 不同,XMLReader 是推解析器,而不是拉解析器。这意味着程序是可以控制的。您将告诉解析器何时获取下一个文档片段,而不是在解析器看到文档后告诉您所看到的内容。您将请求内容,而不是对内容进行反应。从另一个角度来考虑这个问题:XMLReader 是 Iterator 设计模式的实现,而不是 Observer 设计模式的实现。

示例问题

先从简单例子开始讨论。假定正在编写 PHP 脚本,用来接收 XML-RPC 请求并生成响应。更具体一些,假定请求如清单 1 所示。文档的根元素是 methodCall,它包含 methodName 元素和 params 元素。方法的名称是 sqrtparams 元素包含一个 param 元素,param 元素包含 doubledouble 的平方根是希望得到的值。没有使用名称空间。

清单 1. XML-RPC 请求


<?xml version="1.0"?>
<methodCall>
<methodName>sqrt</methodName>
<params>
<param>
<value><double>36.0</double></value>
</param>
</params>
</methodCall>

下面是 PHP 脚本需要完成的工作:

  1. 检查方法名,如果不是 sqrt(它是该脚本懂得如何处理的惟一方法),则生成错误响应。
  2. 找到参数,如果参数不存在或参数类型错误,则生成错误响应。
  3. 另外,计算平方根。
  4. 在表单中返回结果,如清单 2 所示。

清单 2. XML-RPC 响应


<?xml version="1.0"?>
<methodResponse>
<params>
<param>
<value><double>6.0</double></value>
</param>
</params>
</methodResponse>

下面我们逐步展开说明。

初始化解析器并载入文档

第一步是创建新的解析器对象。创建操作很简单:

$reader = new XMLReader();

接着,需要为它提供一些用于解析的数据。对于 XML-RPC,这是超文本传输协议(Hypertext Transfer Protocol,HTTP)请求的原始主体。然后可以将该字符串传递到读取器的 XML() 函数:

填充原始发送数据

如果发现 $HTTP_RAW_POST_DATA 是空的,则将以下代码行添加到 php.ini 文件:

always_populate_raw_post_data = On
$request = $HTTP_RAW_POST_DATA;
$reader->XML($request);

可以解析任何字符串,无论它是从何处获取的。例如,可以是程序中的一串文字或从本地文件读取。还可以使用 open() 函数从外部 URL 载入数据。例如,下面的语句准备解析其中一个 Atom 提要:

$reader->XML('http://www.cafeaulait.org/today.atom');

无论是从何处获取原始数据,现在已建立了阅读器并为解析做好准备。

读取文档

read() 函数使解析器前进到下一个标记。最简单的方法是在 while 循环中遍历整个文档:

while ($reader->read()) {
// processing code goes here...
}

完成遍历后,关闭解析器以释放它所持有的任何资源,并且重置解析器以便用于下一个文档:

$reader->close();

在循环内部,将解析器放置在特殊节点上:元素的起点、元素的终点、文本节点、注释等等。通过检查以下属性,可以发现解析器正在查看的内容:

  • localName 是本地的、未带前缀的节点名。
  • name 是可能的节点前缀名。对于像注释这种没有名称的节点,包括 #comment#text#document 等等,与 DOM 中的一样。
  • namespaceURI 是节点名称空间的统一资源标识符(Uniform Resource Identifier,URI)。
  • nodeType 是代表节点类型的整数 —— 例如,2 代表属性节点,7 代表处理指令。
  • prefix 是节点的名称空间前缀。
  • value 是节点的下一个文本内容。
  • 如果节点有文本值,hasValue 值为 true;否则,值为 false。

当然,并非所有节点类型都具有所有这些属性。例如,文本节点、CDATA 部件、注释、处理指令、属性、空格、文档类型和 XML 声明具有值。而其它节点类型(最重要的是元素和文档)则没有值。通常,程序将使用 nodeType 属性来断定它所查找的内容,然后做出适当的响应。清单 3 展示了简单的 while 循环,该循环使用这些函数来打印它所查看的内容。清单 4 展示了将清单 1 输入程序后的输出。

清单 3. 解析器所查看的内容

while ($reader->read()) {
echo $reader->name;
if ($reader->hasValue) {
echo ": " . $reader->value;
}
echo "n";
}

清单 4. 清单 3 的输出

methodCall
#text:

methodName
#text: sqrt
methodName
#text:

params
#text:

param
#text:

value
double
#text: 10
double
value
#text:

param
#text:

params
#text:

methodCall

大多数程序并非这么简单。它们接受特定格式的输入,并以某种方式来处理输入。在 XML-RPC 例子中,仅需要读取输入中的一个元素:double 元素,该元素应该只有一个。为此,查找名称为 double 的元素的起点:

if ($reader->name == "double" 
&& $reader->nodeType == XMLReader::ELEMENT) {
// ...
}

该元素可能有单个文本子节点,可以通过将解析器前进到下一个节点来进行读取,如下所示:

if ($reader->name == "double" && $reader->nodeType == XMLReader::ELEMENT) {
$reader->read();
respond($reader->value);
}

在这里 respond() 函数构建了 XML-RPC 响应并将它发送到客户机。但是,在展示上述操作前,还有一些事情需要处理。不能绝对保证请求文档中的 double 元素仅包含一个文本节点。可能包含多个文本节点,以及注释和处理指令。例如,可能看起来像以下代码:

<value><double>
<!--value follows-->6.<!--fractional part next-->0
</double></value>

嵌套元素

该模式存在一个潜在的缺陷。嵌套的 double 元素(例如 <double>6<double>1.2</double></double>) 将违背该算法。然而它将成为无效的 XML-RPC;并且不久您将看到如何使用 RELAX NG 验证来拒绝所有此类文档。在诸如可扩展超文本标记语言(Extensible Hypertext Markup Language,XHTML)之类的文档类型中,允许相同元素互相包含(例如 table 元素包含在另一个 table 元素中),因此您还需要知道元素的深度,从而确保结束标记与开始标记之间进行正确匹配。

一个健壮的解决方案需要获得 double 元素的所有文本子节点,将它们连接起来,并且仅将结果转换为 double。必须小心避免任何注释或可能出现的其它非文本节点。这有一点复杂,但并不是十分复杂,如清单 5 所示。

清单 5. 累积来自一个元素的所有文本内容

while ($reader->read()) {
if ($reader->nodeType == XMLReader::TEXT
|| $reader->nodeType == XMLReader::CDATA
|| $reader->nodeType == XMLReader::WHITESPACE
|| $reader->nodeType == XMLReader::SIGNIFICANT_WHITESPACE) {
$input .= $reader->value;
}
else if ($reader->nodeType == XMLReader::END_ELEMENT
&& $reader->name == "double") {
break;
}
}

您可以暂时忽略文档中的其它任何内容。(稍后我将添加更多的错误处理。)

构建响应

正如它的名称所暗示的,XMLReader 仅仅用于读取。相应的 XMLWriter 类正在开发中,但还不能投入到生产。幸运的是,写入 XML 比读取 XML 要容易得多。首先,应使用 header() 函数来设置响应的媒体类型。对于 XML-RPC 来说,媒体类型是 application/xml。例如:

header('Content-type: application/xml');

通常直接将内容显示在页面上,如清单 6 中的 respond() 函数所示。

清单 6. Echo XML

function respond($input) {

echo "<?xml version='1.0'?>
<methodResponse>
<params>
<param>
<value><double>" .
sqrt($input)
. "</double></value>
</param>
</params>
</methodResponse>";

}

甚至可以将响应的文字部分直接嵌入 PHP 页面中,就像使用 HTML 时一样。清单 7 展示了该技术。

清单 7. 文字表示的 XML

function respond($input) {

?><?xml version='1.0'?>
<methodResponse>
<params>
<param>
<value><double>"<?php
echo sqrt($input);
?>
</double></value>
</param>
</params>
</methodResponse>
<?php
}

错误处理

到现在为止,一直隐含假定输入文档是格式规范的文档。但是不能保证情况都是如此。像任何 XML 解析器一样,只要发现一个规范格式错误,XMLReader 就必须停止处理。如果是这样的话,read() 函数将返回 false。

从理论上讲,解析器将报告数据直到发现第一个错误。但是在对小型文档进行试验时,几乎是立刻显示错误信息。底层解析器将预解析大块文档,对它进行缓存,然 后每次分发出一小块文档。因此往往会过早地检查错误。出于安全考虑,不要假定在发现第一个规范格式错误之前能够解析内容。此外,也不要假设解析错误出现之 前看不到任何内容。如果希望只接受完整的、格式规范的文档,那么请确保在看到文档终点之前脚本不能进行任何不可逆操作。

如果解析器检测到规范格式错误,那么 read() 函数将显示如下错误消息(如果启用了详细错误报告,且位于开发服务器上时):

<br />
<b>Warning</b>: XMLReader::read() [<a href='function.read'>function.read</a>]:
< value><double>10</double></value> in <b>/var/www/root.php</b>
on line <b>35</b><br />

您可能不希望将它复制到用户所看到的 HTML 页面中。更好的方法是在 $php_errormsg 环境变量中捕获错误消息。为此,需要启用 php.ini 文件中的 track_errors 配置选项:

track_errors = On

默认情况下,track_errors 选项是关闭的;这在 php.ini 中是显式指定的,因此请确保更改了该行代码。如果提早在 php.ini 中添加了上述一行代码(正如最初我所进行的操作),则后面的 track_errors = Off 代码将重写先前的代码。

该程序仅将响应发送到完整的、格式良好的输入。(也是有效的,不过将实现这点。)因此您需要等待,直到完成了文档的解析(已经跳出 while 循环)。这时,检查是否设置了 $php_errormsg 变量。如果没有进行设置,则文档是格式良好的文档,然后发送 XML-RPC 响应消息。如果设置了该变量,则文档不是格式良好的文档,并发送 XML-RPC 错误响应。如果有人请求负数的平方根,也将发送错误响应。清单 8 展示以上操作。

清单 8. 检查文档格式是否良好


// set up the request
$request = $HTTP_RAW_POST_DATA;
error_reporting(E_ERROR | E_WARNING | E_PARSE);
if (isset($php_errormsg)) unset(($php_errormsg);
// create the reader
$reader = new XMLReader();
// $reader->setRelaxNGSchema("request.rng");
$reader->XML($request);

$input = "";
while ($reader->read()) {
if ($reader->name == "double" && $reader->nodeType == XMLReader::ELEMENT) {

while ($reader->read()) {
if ($reader->nodeType == XMLReader::TEXT
|| $reader->nodeType == XMLReader::CDATA
|| $reader->nodeType == XMLReader::WHITESPACE
|| $reader->nodeType == XMLReader::SIGNIFICANT_WHITESPACE) {
$input .= $reader->value;
}
else if ($reader->nodeType == XMLReader::END_ELEMENT
&& $reader->name == "double") {
break;
}
}
break;
}
}

// make sure the input was well-formed
if (isset($php_errormsg) ) fault(21, $php_errormsg);
else if ($input < 0) fault(20, "Cannot take square root of negative number");
else respond($input);

这是 XML 流处理中简单的常见模式。解析器将填写一个数据结构,当完成文档时该数据结构将起作用。通常数据结构要比文档本身简单。这里所使用的数据结构尤其简单:一个字符串。

验证

libxml 版本

libxml 的早期版本中,RELAX NG 有一些严重错误,XMLReader 取决于 libxml 库。请确保所使用的版本至少是 2.06.26 版。很多系统(包括 Mac OS X Tiger)捆绑了较早的、有错误的 libxml 版本。

到目前为止,对于验证数据是否位于所预期的地方,并没有给予关注。实现该验证的最简单的方法是检查文档的模式。XMLReader 支持 RELAX NG 模式语言;清单 9 展示了简单的 RELAX NG 模式,用于这个特定的 XML-RPC 请求表单。

清单 9. XML-RPC 请求

<element name="methodCall" xmlns="http://relaxng.org/ns/structure/1.0" 
datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
<element name="methodName">
<value>sqrt</value>
</element>
<element name="params">
<element name="param">
<element name="value">
<element name="double">
<data type="double"/>
</element>
</element>
</element>
</element>
</element>

可以使用 setRelaxNGSchemaSource() 将模式作为一串文字直接嵌入 PHP 脚本,或者使用 setRelaxNGSchema() 从外部文件或 URL 读取模式。例如,假定清单 9 位于 sqrt.rng 文件中,下面将展示如何载入模式:

reader->setRelaxNGSchema("sqrt.rng")

在开始解析文档 之前,执行上述操作。解析器在进行读取时将检查文档的模式。若要检查文档是否有效,则调用 isValid(),如果文档是有效的(目前为止),则返回 true,否则,返回 false。清单 10 展示了完整的程序,包括所有错误处理。这样将接受任何合法输入,然后返回正确的值,而且将拒绝所有不正确的请求。我还添加了 fault() 方法,当发生故障时将发送 XML-RPC 错误响应。

清单 10. 完整的 XML-RPC 平方根服务器

<?php
header('Content-type: application/xml');

// try grammar
$schema = "<element name='methodCall'
xmlns='http://relaxng.org/ns/structure/1.0'
datatypeLibrary='http://www.w3.org/2001/XMLSchema-datatypes'>
<element name='methodName'>
<value>sqrt</value>
</element>
<element name='params'>
<element name='param'>
<element name='value'>
<element name='double'>
<data type='double'/>
</element>
</element>
</element>
</element>
</element>";


if (!isset($HTTP_RAW_POST_DATA)) {
fault(22, "Please make sure always_populate_raw_post_data = On in php.ini");
}
else {

// set up the request
$request = $HTTP_RAW_POST_DATA;
error_reporting(E_ERROR | E_WARNING | E_PARSE);
// create the reader
$reader = new XMLReader();
$reader->setRelaxNGSchema("request.rng");
$reader->XML($request);

$input = "";
while ($reader->read()) {
if ($reader->name == "double" && $reader->nodeType == XMLReader::ELEMENT) {

while ($reader->read()) {
if ($reader->nodeType == XMLReader::TEXT
|| $reader->nodeType == XMLReader::CDATA
|| $reader->nodeType == XMLReader::WHITESPACE
|| $reader->nodeType == XMLReader::SIGNIFICANT_WHITESPACE) {
$input .= $reader->value;
}
else if ($reader->nodeType == XMLReader::END_ELEMENT
&& $reader->name == "double") {
break;
}
}
break;
}
}

if (isset($php_errormsg) ) fault(21, $php_errormsg);
else if (! $reader->isValid()) fault(19, "Invalid request");
else if ($input < 0) fault(20, "Cannot take square root of negative number");
else respond($input);

$reader->close();
}


function respond($input)
{
?>
<methodResponse>
<params>
<param>
<value><double><?php
echo sqrt($input);
?></double></value>
</param>
</params>
</methodResponse>
<?php
}


function fault($code, $message)
{

echo "<?xml version='1.0'?>
<methodResponse>
<fault>
<value>
<struct>
<member>
<name>faultCode</name>
<value><int>" . $code . "</int></value>
</member>
<member>
<name>faultString</name>
<value>
<string>" . $message . "</string>
</value>
</member>
</struct>
</value>
</fault>
</methodResponse>";

}

属性

在正常的推解析期间不会看到属性。若要读取属性,请停止在元素的起点处,通过名称或编号来请求特定属性。

将需要的属性名称传递到 getAttribute(),以便在当前元素上查找该属性的值。例如,下面的语句请求当前元素的 id 属性:

$id = $reader->getAttribute("id");

如果属性位于名称空间中 —— 例如,xlink:href —— 则调用 getAttributeNS(),将本地名称和名称空间 URI 分别作为第一个和第二个参数进行传递。(前缀是无关紧要的。)例如,下面的语句将请求 http://www.w3.org/1999/xlink/ 名称空间中 xlink:href 属性的值:

$href = $reader->getAttributeNS("href", "http://www.w3.org/1999/xlink/");

如果属性不存在,那么这两种方法都将返回空字符串。(这是不正确的。它们应该返回 null。当前设计很难区分值为空字符串的属性和值根本不存在的属性。)

属性次序

在 XML 文档中,属性次序并不重要,并且不受解析器的保护。这里用于属性索引的编号仅仅是为了方便起见。不能保证开始标记中的第一个属性就是属性 1,第二个就是属性 2 等等。不要编写依赖于属性次序的代码。

如果仅希望了解元素上的所有属性,并且事先并不知道属性名,那么当读取器位于元素上时,调用 moveToNextAttribute()。一旦解析器位于属性节点上,就可以读取属性的名称、名称空间以及元素所使用的相同属性的值。例如,以下代码片段将打印当前元素的所有属性:

if ($reader->hasAttributes and $reader->nodeType == XMLReader::ELEMENT) {
while ($reader->moveToNextAttribute()) {
echo $reader->name . "='" . $reader->value . "'n";
}
echo "n";
}

对于 XML API 来说非常难得的是,XMLReader 允许从元素的起点 或终点 读取属性。为了避免重复计算,确认代码类型是 XMLReader::ELEMENT 而不是 XMLReader::END_ELEMENT 是很重要的,后者也可能拥有属性。

结束语

XMLReader 是添加到 PHP 程序员工具箱中的一个很有用的工具。与 SimpleXML 不同,它是处理所有文档(而不是部分文档)的完整 XML 解析器。与 DOM 不同,它可以处理大于可用内存的文档。与 SAX 不同,它将程序置于控制之下。如果 PHP 程序需要接受 XML 输入,则 XMLReader 是很值得考虑的一个工具。

参考资料
学习

    * 您可以参阅本文在 developerWorks 全球网站上的 英文原文。

    * XMLReader 的官方文档:阅读 PHP 5 手册。

    * PHP XMLReader 类:阅读有关 libxml 的 C XMLReader API 之上的这个薄层的更多内容。

    * .NET 的 Ajax System.Xml.XmlTextReader:查看该 API 的灵感。

    * XML in a Nutshell(Elliotte Rusty Harold 和 W. Scott Means,O’Reilly,2005):在这本小而全面的书籍中深入研究了 XML —— 可以作为全面介绍和参考资料。

    * Java 编程中的 XML-RPC(Roy Miller,developerWorks,2004 年 1 月):为应用程序间通信的最简路径编写的 XML-RPC 客户机和服务器。

    * PHP 中的 SimpleXML 处理(Elliotte Rusty Harold,developerWorks,2006 年 10 月):使用 SimpleXML 扩展,在 PHP 中使用 SimpleXML 编写特定于标记的 RSS 阅读器。

    * 使用 RELAX NG 反击,第 1 部分(David Mertz,developerWorks,2003 年 2 月):使用 RELAX NG 来创建强大的、简练的、语义简单易懂的类,用于描述有效 XML 实例。

    * Design Patterns(Addison-Wesley,1995 年):深入研究 Gang of Four 的力作中对 Observer 和 Iterator 设计模式的阐述。

    * IBM XML 认证:了解如何才能成为一名 IBM 认证的 XML 及相关技术的开发人员。

    * XML 技术库:查看 developerWorks 中国网站 XML 专区,获得大量技术文章、技巧、教程、标准以及 IBM 红皮书。

    * developerWorks 技术活动和网络广播:随时关注这些会议中的技术进展。

获得产品和技术

    * 使用 IBM 试用版软件:构建您的下一个开发项目,可直接从 developerWorks 下载。

讨论

    * XML 专区讨论论坛:参与任何一个面向 XML 的论坛。

    * 通过参与 developerWorks blogs 加入 developerWorks 社区。

关于作者

Photo of Elliot Rusty Harold       

Elliotte Harold 出生在新奥尔良,现在他还定期回老家喝一碗美味的秋葵汤。但目前他和妻子 Beth、他们的狗 Shayna、猫 Charm 和 Marjorie 定居在布鲁克林附近的 Prospect Heights。他是 Polytechnic 大学的计算机科学副教授,讲授 Java 和面向对象编程。他的 Cafe au Lait Web 站点是 Internet 上最受欢迎的独立 Java 站点之一,子站点 Cafe con Leche 是最受欢迎的 XML 站点之一。他的著作包括 Effective XML、 Processing XML with Java、 Java Network Programming 和 The XML 1.1 Bible。他的最新著作是 Java I/O, 第二版。他目前从事 XOM API 处理 XML、Jaxen XPath 引擎和 Jester 测试覆盖工具的研究。

Read: 972