Attachment 'kgp.xml'
Download 1 <?xml version="1.0" encoding="utf-8"?>
2 <chapter id="kgp">
3 <?dbhtml filename="xml_processing/index.html"?>
4 <title>&xml; Processing</title>
5 <titleabbrev id="kgp.numberonly">第九章</titleabbrev>
6 <section id="kgp.divein">
7 <title>接触</title>
8 <para>下面两章是关于 &python; 中 &xml; 处理的。如果你已经知道一个 &xml; 文档的样子,比如它是由结构化标记构成的,这些标记形成了层次模型的元素,等等这些知识都是有帮助的。如果你不明白这些,这里有<ulink url="&url_xmltutorial;">很多 &xml; 教程</ulink> 能够解释这些基础知识。</para>
9 <para>如果你对XML不是很感兴趣,你还是应该读一下这些章节,它们涵盖了不少重要的主题比如 &python; 包,Unicode,命令行参数以及如何使用 &getattr; 进行方法分发。</para>
10 <para>Being a philosophy major is not required, although if you have ever had the misfortune of being subjected to the writings of Immanuel Kant, you will appreciate the example program a lot more than if you majored in something useful, like computer science.</para>
11 <abstract>
12 <title/>
13 <para>处理 &xml; 有两种基本的方式。一种叫做 &sax;(<quote>Simple &api; for &xml;</quote>),它的工作方式是,一次读出一点 &xml; 内容,然后对发现的每一个元素调用一个方法。(如果你读了 <xref linkend="dialect"/>,这应该听起来很熟悉,因为这是 &sgmllib_modulename; 工作的方式。)另一种方式叫做 &dom; (<quote>Document Object Model</quote>),它的工作方式是,一次性读入整个 &xml; 文档,然后使用 &python; 类创建一个内部表示形式(以树结构进行连接)。&python; 拥有这两种解析方式的标准模块,但是本章只涉及 &dom;。</para>
14 </abstract>
15 <para>下面是一个完整的 &python; 程序,它根据 &xml; 格式定义的上下文无关语法生成伪随机输出。如果你不明白是什么意思,不用担心,下面两章中将会深入的检视这个程序的输入和输出。</para>
16 <example>
17 <title>&kgp_filename;</title>
18 ¶_download;
19 <programlisting>
20 &kgp_doc;
21 &kgp_import;
22
23 &kgp_debug;
24
25 &kgp_exception;
26
27 &kgp_classdef;
28 &kgp_classdoc;
29
30 &kgp_init;
31 &kgp_initgrammar;
32 &kgp_initsource;
33 &kgp_refresh;
34
35 &kgp_privateload;
36 &kgp_loaddoc;
37 &kgp_loadopen;
38 &kgp_loadparse;
39 &kgp_loadclose;
40 &kgp_loadreturn;
41
42 &kgp_loadgrammardef;
43 &kgp_loadgrammardoc;
44 &kgp_loadgrammar;
45 &kgp_initrefs;
46 &kgp_forref;
47 &kgp_refid;
48
49 &kgp_loadsourcedef;
50 &kgp_loadsourcedoc;
51 &kgp_loadsource;
52
53 &kgp_defaultsourcedef;
54 &kgp_defaultsourcedoc;
55 &kgp_initxrefs;
56 &kgp_forxref;
57 &kgp_xrefid;
58 &kgp_xrefkeys;
59 &kgp_standalonexrefs;
60 &kgp_ifstandalone;
61 &kgp_raiseerror;
62 &kgp_returndefaultsource;
63
64 &kgp_resetdef;
65 &kgp_resetdoc;
66 &kgp_initpieces;
67 &kgp_initcapitalizenextword;
68
69 &kgp_refreshdef;
70 &kgp_refreshdoc;
71 &kgp_reset;
72 &kgp_parse;
73 &kgp_output;
74
75 &kgp_outputdef;
76 &kgp_outputdoc;
77 &kgp_returnoutput;
78
79 &kgp_randomchildelementdef;
80 &kgp_randomchildelementdoc;
81 &kgp_choices;
82 &kgp_chosen;
83 &kgp_ifdebug;
84 &kgp_printchoices;
85 &kgp_printchosen;
86 &kgp_returnchosen;
87
88 &kgp_parsedef;
89 &kgp_parsedoc;
90 &kgp_getparsemethod;
91 &kgp_parsemethod;
92
93 &kgp_parsedocumentdef;
94 &kgp_parsedocumentdoc;
95 &kgp_parsedocument;
96
97 &kgp_parsetextdef;
98 &kgp_parsetextdoc;
99 &kgp_text;
100 &kgp_ifcapitalizenextword;
101 &kgp_appendcapital;
102 &kgp_appendrest;
103 &kgp_resetcapital;
104 &kgp_elsecapitalizenextword;
105 &kgp_appendnormal;
106
107 &kgp_parseelementdef;
108 &kgp_parseelementdoc;
109 &kgp_gethandlermethod;
110 &kgp_handlermethod;
111
112 &kgp_parsecommentdef;
113 &kgp_parsecommentdoc;
114 &kgp_pass;
115
116 &kgp_doxrefdef;
117 &kgp_doxrefdoc;
118 &kgp_id;
119 &kgp_parsexref;
120
121 &kgp_dopdef;
122 &kgp_dopdoc;
123 &kgp_keys;
124 &kgp_ifclass;
125 &kgp_sentence;
126 &kgp_setcapitalizenextword;
127 &kgp_ifchance;
128 &kgp_chance;
129 &kgp_rolldice;
130 &kgp_elsechance;
131 &kgp_setdoit;
132 &kgp_ifdoit;
133 &kgp_parsep;
134
135 &kgp_dochoicedef;
136 &kgp_dochoicedoc;
137 &kgp_parsechoice;
138
139 &kgp_usagedef;
140 &kgp_usagecode;
141
142 &kgp_maindef;
143 &kgp_initmaingrammar;
144 &kgp_trygetopt;
145 &kgp_getopt;
146 &kgp_exceptgetopt;
147 &kgp_errorusage;
148 &kgp_errorexit;
149 &kgp_foropt;
150 &kgp_ifh;
151 &kgp_helpusage;
152 &kgp_helpexit;
153 &kgp_ifd;
154 &kgp_globaldebug;
155 &kgp_setdebug;
156 &kgp_ifg;
157 &kgp_setmaingrammar;
158
159 &kgp_setdefaultsource;
160
161 &kgp_createkantgenerator;
162 &kgp_nowrap;
163
164 &kgp_ifmain;
165 &kgp_main;
166 </programlisting>
167 </example>
168 <example>
169 <title>&toolbox_filename;</title>
170 <programlisting>
171 &tb_doc;
172
173 &tb_openanythingdef;
174 &tb_openanythingdoc;
175 &tb_ifhasattr;
176 &tb_returnsource;
177
178 &tb_stdin;
179 &tb_stdinimport;
180 &tb_stdinreturn;
181
182 &tb_urllibcomment;
183 &tb_importurllib;
184 &tb_tryurllib;
185 &tb_urlopen;
186 &tb_excepturllib;
187 &tb_urllibpass;
188
189 &tb_nativecomment;
190 &tb_trynative;
191 &tb_native;
192 &tb_exceptnative;
193 &tb_nativepass;
194
195 &tb_stringiocomment;
196 &tb_importstringio;
197 &tb_stringio;
198 </programlisting>
199 </example>
200 <para>独立运行程序 &kgp_filename; ,它会解析 &kantxml_filename; 中默认的基于 &xml; 的语法,并以康德的风格打印出几段有哲学价值的段落来。</para>
201 <example>
202 <title>Sample output of &kgp_filename;</title>
203 <screen><prompt>[you@localhost kgp]$ python kgp.py</prompt>
204 <computeroutput> As is shown in the writings of Hume, our a priori concepts, in
205 reference to ends, abstract from all content of knowledge; in the study
206 of space, the discipline of human reason, in accordance with the
207 principles of philosophy, is the clue to the discovery of the
208 Transcendental Deduction. The transcendental aesthetic, in all
209 theoretical sciences, occupies part of the sphere of human reason
210 concerning the existence of our ideas in general; still, the
211 never-ending regress in the series of empirical conditions constitutes
212 the whole content for the transcendental unity of apperception. What
213 we have alone been able to show is that, even as this relates to the
214 architectonic of human reason, the Ideal may not contradict itself, but
215 it is still possible that it may be in contradictions with the
216 employment of the pure employment of our hypothetical judgements, but
217 natural causes (and I assert that this is the case) prove the validity
218 of the discipline of pure reason. As we have already seen, time (and
219 it is obvious that this is true) proves the validity of time, and the
220 architectonic of human reason, in the full sense of these terms,
221 abstracts from all content of knowledge. I assert, in the case of the
222 discipline of practical reason, that the Antinomies are just as
223 necessary as natural causes, since knowledge of the phenomena is a
224 posteriori.
225 The discipline of human reason, as I have elsewhere shown, is by
226 its very nature contradictory, but our ideas exclude the possibility of
227 the Antinomies. We can deduce that, on the contrary, the pure
228 employment of philosophy, on the contrary, is by its very nature
229 contradictory, but our sense perceptions are a representation of, in
230 the case of space, metaphysics. The thing in itself is a
231 representation of philosophy. Applied logic is the clue to the
232 discovery of natural causes. However, what we have alone been able to
233 show is that our ideas, in other words, should only be used as a canon
234 for the Ideal, because of our necessary ignorance of the conditions.
235
236 [...snip...]</computeroutput></screen>
237 </example>
238 <para>当然这是胡言乱语。噢,不完全是胡言乱语。它在句法和语法上都是正确的(尽管非常罗嗦--康德可不是你们所说的踩得到点上的那种人)。其中一些实际上是正确的(或者至少康德可能会认同的事情),其中一些则明显是错误的,大部分只是语无伦次。但所有内容都是符合康德的风格。
239 </para>
240 <para>让我重复一遍,如果你现在或曾经主修哲学专业,这会非常、非常有趣。</para>
241 <para>关于这个程序的有趣之处在于没有一点内容是属于康德的。所有的内容都来自于上下文无关语法文件&kantxml_filename;。如果你要程序使用不同的语法文件(可以在命令行中指定),输出信息将完全不同。</para>
242 <example>
243 <title>&kgp_filename; 的简单输出</title>
244 <screen><prompt>[you@localhost kgp]$ python kgp.py -g binary.xml</prompt>
245 <computeroutput>00101001</computeroutput>
246 <prompt>[you@localhost kgp]$ python kgp.py -g binary.xml</prompt>
247 <computeroutput>10110100</computeroutput></screen>
248 </example>
249 <para>在本章后面的内容中,你将近距离的观察语法文件的结构。现在,你只要知道语法文件定义了输出信息的结构,而 &kgp_filename; 程序读取语法规则并随机确定哪些单词插入哪里。
250 </para>
251 </section>
252 <section id="kgp.packages">
253 <?dbhtml filename="xml_processing/packages.html"?>
254 <title>包</title>
255 <abstract>
256 <para>实际上解析一个 &xml; 文档是很简单的:只要一行代码。但是,在你接触那行代码前,需要暂时岔开一下,讨论一下包。</para>
257 </abstract>
258 <example>
259 <title>载入一个 &xml; 文档 (偷瞥一下)</title>
260 <screen>
261 &prompt;<userinput>from xml.dom import minidom</userinput> <co id="kgp.packages.1.1"/>
262 &prompt;<userinput>xmldoc = minidom.parse('~/diveintopython/common/py/kgp/binary.xml')</userinput></screen>
263 <calloutlist>
264 <callout arearefs="kgp.packages.1.1">
265 <para>这个语法你之前没有见过。它看上去很像我们所知并且喜欢的 &frommoduleimport; ,但是<literal>"."</literal> 使得它好像不止是import那么简单。事实上,&xml_packagename; 是我们所知的包,&dom_packagename; 是&xml_packagename; 中嵌套的包,而 &minidom_modulename; 是 &xmldom_packagename; 中的模块。</para>
266 </callout>
267 </calloutlist>
268 </example>
269 <para>听起来挺复杂的,其实不是。看一下确切的实现可能会有帮助。包不过是模块的目录;嵌套包是子目录。一个包(或一个嵌套包)中的模块也只是 <filename class="headerfile">.py</filename> 文件罢了,永远都是,只是它们是在一个子目录中,而不是在你的 &python; 安装环境的主 <filename class="directory">lib/</filename> 目录下。</para>
270 <example>
271 <title>包的文件布局</title>
272 <screen><computeroutput>&python;21/ &python; 安装根目录 (可执行文件的所在地)
273 |
274 +--lib/ 库目录 (标准库模块的所在地)
275 |
276 +-- xml/ xml包 (实际上目录中还有其它东西)
277 |
278 +--sax/ xml.sax包 (也只是一个目录)
279 |
280 +--dom/ xml.dom包 (包含 minidom.py)
281 |
282 +--parsers/ xml.parsers包 (内部使用)</computeroutput></screen>
283 </example>
284 <para>所以你说<literal>from xml.dom import minidom</literal>,&python; 认为它的意思是<quote>在 &xml_packagename; 目录中查找 &dom_packagename; 目录,然后在<emphasis>其</emphasis>中查找 &minidom_modulename; 模块,接着导入它并以 &minidom_modulename; 命名 </quote>。但是 &python; 更聪明;你不仅可以导入包含在一个包中的所有模块,还可以从包的模块中有选择地导入指定的类或者函数。语法都是一样的; &python; 会根据包的布局理解你的意思,然后自动进行正确的导入。
285 </para>
286 <example>
287 <title>包也是模块</title>
288 <screen>&prompt;<userinput>from xml.dom import minidom</userinput> <co id="kgp.packages.2.1"/>
289 &prompt;<userinput>minidom</userinput>
290 <computeroutput><module 'xml.dom.minidom' from 'C:\Python21\lib\xml\dom\minidom.pyc'></computeroutput>
291 &prompt;<userinput>minidom.Element</userinput>
292 <computeroutput><class xml.dom.minidom.Element at 01095744></computeroutput>
293 &prompt;<userinput>from xml.dom.minidom import Element</userinput> <co id="kgp.packages.2.2"/>
294 &prompt;<userinput>Element</userinput>
295 <computeroutput><class xml.dom.minidom.Element at 01095744></computeroutput>
296 &prompt;<userinput>minidom.Element</userinput>
297 <computeroutput><class xml.dom.minidom.Element at 01095744></computeroutput>
298 &prompt;<userinput>from xml import dom</userinput> <co id="kgp.packages.2.3"/>
299 &prompt;<userinput>dom</userinput>
300 <computeroutput><module 'xml.dom' from 'C:\Python21\lib\xml\dom\__init__.pyc'></computeroutput>
301 &prompt;<userinput>import xml</userinput> <co id="kgp.packages.2.4"/>
302 &prompt;<userinput>xml</userinput>
303 <computeroutput><module 'xml' from 'C:\Python21\lib\xml\__init__.pyc'></computeroutput></screen>
304 <calloutlist>
305 <callout arearefs="kgp.packages.2.1">
306 <para>这里你正从一个嵌套包(&xmldom_packagename;)中导入一个模块(&minidom_modulename;)。结果就是 &minidom_modulename; 被导入到了你(程序)的<link linkend="dialect.locals">命名空间</link>中,为了能够引用 &minidom_modulename; 模块中的类(比如 &element_classname;),你必须在它们的类名前面加上模块名。</para>
307 </callout>
308 <callout arearefs="kgp.packages.2.2">
309 <para>这里你正从一个来自嵌套包(&xmldom_packagename;)的模块(&minidom_modulename;)中导入一个类(&element_classname;)。结果就是 &element_classname; 直接导入到了你(程序)的命名空间中。注意,这样做并不会干扰以前的导入;现在 &element_classname; 类可以用两种方式引用了(但其实是同一个类)。</para>
310 </callout>
311 <callout arearefs="kgp.packages.2.3">
312 <para>这里你正在导入 &dom_packagename; 包(&xml_packagename; 的一个嵌套包),并将其作为自己或者内部的一个模块。一个包的任何层次都可以视为一个模块,一会就会看到。它甚至可以拥有自己的属性和方法,就像你在前面看到过的模块。</para>
313 </callout>
314 <callout arearefs="kgp.packages.2.4">
315 <para>这里你正在将根层次的 &xml_packagename; 包作为一个模块导入。</para>
316 </callout>
317 </calloutlist>
318 </example>
319 <para>那么如何才能导入一个包(它不过是磁盘上的一个目录)并使其成为一个模块(它总是在磁盘上的一个文件)呢?答案就是神奇的 &init_filename; 文件。你明白了吧,包不只是目录,它们是包含一个特殊文件 &init_filename; 的目录。这个文件定义了包的属性和方法。例如,&xmldom_packagename; 包含了 &node_classname; 类,它在<filename>xml/dom/__init__.py</filename>中有所定义。当你将一个包作为模块导入(比如从 &xml_packagename; 导入 &dom_packagename;)的时候,实际上导入了它的 &init_filename; 文件。</para>
320 <note>
321 <title>What makes a package</title>
322 <para>一个包是一个其中带有特殊文件 &init_filename; 的目录。&init_filename; 文件定义了包的属性和方法。其实它可以什么也不定义;可以只是一个空文件,但是必须要存在。如果 &init_filename; 不存在,这个目录就仅仅是一个目录,而不是一个包,它就不能被导入或者包含其它的模块和嵌套包。</para>
323 </note>
324 <para>那为什么非得用包呢?恩,它们提供了在逻辑上将相关模块归为一组的方法。不使用其中带有 &sax_packagename; 和 &dom_packagename; 的 &xml_packagename; 包,作者也可以选择将所有的 &sax_packagename; 功能放入 <filename>xmlsax.py</filename>中,并将所有的 &dom_packagename; 功能放入 <filename>xmldom.py</filename>中,或者干脆将所有东西放入单个模块中。但是这样可能不实用(在写到这儿时,&xml; 包已经超过了3000行代码)并且很难管理(独立的源文件意味着多个人可以同时在不同的地方进行开发)。</para>
325 <para>如果你发现自己正在用 &python; 编写一个大型的子系统(或者,很有可能,当你意识到你的小型子系统已经成长为一个大型子系统时),你应该花费些时间设计一个好的包架构。它是 &python; 所擅长的事情之一,所以应该好好利用它。</para>
326 </section>
327 <section id="kgp.parse">
328 <?dbhtml filename="xml_processing/parsing_xml.html"?>
329 <title>&xml; 解析</title>
330 <abstract>
331 <para>正如我说的,实际解析一个 &xml; 文档是非常简单的:只要一行代码。从这里出发到哪儿去就是你自己的事了。</para>
332 </abstract>
333 <example>
334 <title>载入一个 &xml; 文档 (这次是真的)</title>
335 <screen>
336 &prompt;<userinput>from xml.dom import minidom</userinput> <co id="kgp.parse.1.1"/>
337 &prompt;<userinput>xmldoc = minidom.parse('~/diveintopython/common/py/kgp/binary.xml')</userinput> <co id="kgp.parse.1.2"/>
338 &prompt;<userinput>xmldoc</userinput> <co id="kgp.parse.1.3"/>
339 <computeroutput><xml.dom.minidom.Document instance at 010BE87C></computeroutput>
340 &prompt;<userinput>print xmldoc.toxml()</userinput> <co id="kgp.parse.1.4"/>
341 <computeroutput><?xml version="1.0" ?>
342 <grammar>
343 <ref id="bit">
344 <p>0</p>
345 <p>1</p>
346 </ref>
347 <ref id="byte">
348 <p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/>\
349 <xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p>
350 </ref>
351 </grammar></computeroutput></screen>
352 <calloutlist>
353 <callout arearefs="kgp.parse.1.1">
354 <para>正如在<link linkend="kgp.packages">上一章节</link>看到的,该语句从 &xmldom_packagename; 包中导入 &minidom_modulename; 模块。</para>
355 </callout>
356 <callout arearefs="kgp.parse.1.2">
357 <para>这就是进行所有工作的一行代码:&minidomparse_functionname; 接收一个参数并返回 &xml; 文档解析后的表示形式。这个参数可以是很多东西;在本例中,它只是我本地磁盘上一个 &xml; 文档的文件名。(为了继续执行,你需要将路径改为指向下载的例子所在的目录。)但是你也可以传入一个 <link linkend="fileinfo.files">文件对象</link>,或甚至是一个<link linkend="dialect.extract.urllib">类似文件的对象</link>。这样你就可以在本章后面好好利用这一灵活性了。</para>
358 </callout>
359 <callout arearefs="kgp.parse.1.3">
360 <para>从 &minidomparse_functionname; 返回的对象是一个 &document_classname; 对象,它是 &node_classname; 类的一个子对象。这个&document_classname; 对象是联锁的 &python; 对象的一个复杂树状结构的根层次,这些 &python; 对象完整表示了传给 &minidomparse_functionname; 的 &xml; 文档。</para>
361 </callout>
362 <callout arearefs="kgp.parse.1.4">
363 <para>&toxml_functionname; 是 &node_classname; 类的一个方法(因此可以在从 &minidomparse_functionname; 中得到的 &document_classname; 对象上使用)。&toxml_functionname; 打印出了 &node_classname; 表示的 &xml;。对于 &document_classname; 节点,这样就会打印出整个 &xml; 文档。</para>
364 </callout>
365 </calloutlist>
366 </example>
367 <para>现在内存中已经有了一个 &xml; 文档了,你可以开始遍历它了。</para>
368 <example id="kgp.parse.gettingchildnodes.example">
369 <title>获取子节点</title>
370 <screen>
371 &prompt;<userinput>xmldoc.childNodes</userinput> <co id="kgp.parse.2.1"/>
372 <computeroutput>[<DOM Element: grammar at 17538908>]</computeroutput>
373 &prompt;<userinput>xmldoc.childNodes[0]</userinput> <co id="kgp.parse.2.2"/>
374 <computeroutput><DOM Element: grammar at 17538908></computeroutput>
375 &prompt;<userinput>xmldoc.firstChild</userinput> <co id="kgp.parse.2.3"/>
376 <computeroutput><DOM Element: grammar at 17538908></computeroutput></screen>
377 <calloutlist>
378 <callout arearefs="kgp.parse.2.1">
379 <para>每个 &node_classname; 都有一个 &childnodes_attr; 属性,它是一个 &node_classname; 对象的列表。一个 &document_classname; 只有一个子节点,即 &xml; 文档的根元素(在本例中,是 &grammarnode; 元素)。</para>
380 </callout>
381 <callout arearefs="kgp.parse.2.2">
382 <para>为了得到第一个(在本例中,只有一个)子节点,只要使用正规的列表语法。回想一下,其实这里没有发生什么特别的;这只是一个由正规 &python; 对象构成的正规 &python; 列表。</para>
383 </callout>
384 <callout arearefs="kgp.parse.2.3">
385 <para>鉴于获取某个节点的第一个子节点是有用而且常见的行为,所以 &node_classname; 类有一个 &firstchild_attr; 属性,它和<literal>childNodes[0]</literal>具有相同的语义。(还有一个 &lastchild_attr; 属性,它和<literal>childNodes[-1]</literal>具有相同的语义。)</para>
386 </callout>
387 </calloutlist>
388 </example>
389 <example>
390 <title>&toxml_functionname; 用于任何节点</title>
391 <screen>
392 &prompt;<userinput>grammarNode = xmldoc.firstChild</userinput>
393 &prompt;<userinput>print grammarNode.toxml()</userinput> <co id="kgp.parse.3.1"/>
394 <computeroutput><grammar>
395 <ref id="bit">
396 <p>0</p>
397 <p>1</p>
398 </ref>
399 <ref id="byte">
400 <p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/>\
401 <xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p>
402 </ref>
403 </grammar></computeroutput></screen>
404 <calloutlist>
405 <callout arearefs="kgp.parse.3.1">
406 <para>由于 &toxml_functionname; 方法是定义在 &node_classname; 类中的,所以对任何 &xml; 节点都是可用的,不仅仅是 &document_classname; 元素。</para>
407 </callout>
408 </calloutlist>
409 </example>
410 <example id="kgp.parse.childnodescanbetext.example">
411 <title>子节点可以是文本</title>
412 <screen>
413 &prompt;<userinput>grammarNode.childNodes</userinput> <co id="kgp.parse.4.1"/>
414 <computeroutput>[<DOM Text node "\n">, <DOM Element: ref at 17533332>, \
415 <DOM Text node "\n">, <DOM Element: ref at 17549660>, <DOM Text node "\n">]</computeroutput>
416 &prompt;<userinput>print grammarNode.firstChild.toxml()</userinput> <co id="kgp.parse.4.2"/>
417 <computeroutput>
418
419 </computeroutput>
420 &prompt;<userinput>print grammarNode.childNodes[1].toxml()</userinput> <co id="kgp.parse.4.3"/>
421 <computeroutput><ref id="bit">
422 <p>0</p>
423 <p>1</p>
424 </ref></computeroutput>
425 &prompt;<userinput>print grammarNode.childNodes[3].toxml()</userinput> <co id="kgp.parse.4.4"/>
426 <computeroutput><ref id="byte">
427 <p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/>\
428 <xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p>
429 </ref></computeroutput>
430 &prompt;<userinput>print grammarNode.lastChild.toxml()</userinput> <co id="kgp.parse.4.5"/>
431 <computeroutput>
432
433 </computeroutput></screen>
434 <calloutlist>
435 <callout arearefs="kgp.parse.4.1">
436 <para>查看 &binaryxml_filename; 中的 &xml; ,你可能会认为 &grammarnode; 只有两个子节点,即两个 &refnode; 元素。但是你忘记了一些东西:硬回车!在<literal>'<grammar>'</literal>之后,第一个<literal>'<ref>'</literal>之前是一个硬回车,并且这个文本算作 &grammarnode; 元素的一个子节点。类似的,在每个<literal>'</ref>'</literal>之后都有一个硬回车;它们都被当作子节点。所以<literal>grammar.childNodes</literal>实际上是一个有5个对象的列表:3个 &text_classname; 对象和两个 &element_classname; 对象。</para>
437 </callout>
438 <callout arearefs="kgp.parse.4.2">
439 <para>第一个子节点是一个 &text_classname; 对象,它表示在<literal>'<grammar>'</literal>标记之后、第一个<literal>'<ref>'</literal>标记之后的硬回车。</para>
440 </callout>
441 <callout arearefs="kgp.parse.4.3">
442 <para>第二个子节点是一个 &element_classname; 对象,表示了第一个 &refnode; 元素。</para>
443 </callout>
444 <callout arearefs="kgp.parse.4.4">
445 <para>第四个子节点是一个 &element_classname; 对象,表示了第二个 &refnode; 元素。</para>
446 </callout>
447 <callout arearefs="kgp.parse.4.5">
448 <para>最后一个子节点是一个 &text_classname; 对象,表示了在<literal>'</ref>'</literal>结束标记之后、<literal>'</grammar>'</literal> 结束标记之前的硬回车。</para>
449 </callout>
450 </calloutlist>
451 </example>
452 <example>
453 <title>Drilling down all the way to text</title>
454 <screen>
455 &prompt;<userinput>grammarNode</userinput>
456 <computeroutput><DOM Element: grammar at 19167148></computeroutput>
457 &prompt;<userinput>refNode = grammarNode.childNodes[1]</userinput> <co id="kgp.parse.5.1"/>
458 &prompt;<userinput>refNode</userinput>
459 <computeroutput><DOM Element: ref at 17987740></computeroutput>
460 &prompt;<userinput>refNode.childNodes</userinput> <co id="kgp.parse.5.2"/>
461 <computeroutput>[<DOM Text node "\n">, <DOM Text node " ">, <DOM Element: p at 19315844>, \
462 <DOM Text node "\n">, <DOM Text node " ">, \
463 <DOM Element: p at 19462036>, <DOM Text node "\n">]</computeroutput>
464 &prompt;<userinput>pNode = refNode.childNodes[2]</userinput>
465 &prompt;<userinput>pNode</userinput>
466 <computeroutput><DOM Element: p at 19315844></computeroutput>
467 &prompt;<userinput>print pNode.toxml()</userinput> <co id="kgp.parse.5.3"/>
468 <computeroutput><p>0</p></computeroutput>
469 &prompt;<userinput>pNode.firstChild</userinput> <co id="kgp.parse.5.4"/>
470 <computeroutput><DOM Text node "0"></computeroutput>
471 &prompt;<userinput>pNode.firstChild.data</userinput> <co id="kgp.parse.5.5"/>
472 <computeroutput>u'0'</computeroutput></screen>
473 <calloutlist>
474 <callout arearefs="kgp.parse.5.1">
475 <para>正如你在前面的例子中看到的,第一个<sgmltag>ref</sgmltag>元素是<literal>grammarNode.childNodes[1]</literal>,因为childNodes[0]是一个代表硬回车的 &text_classname; 节点。</para>
476 </callout>
477 <callout arearefs="kgp.parse.5.2">
478 <para><sgmltag>ref</sgmltag>元素有它自己的子节点集合,一个表示硬回车,一个独立表示空格的,一个用于<sgmltag>p</sgmltag>元素的,诸如此类。</para>
479 </callout>
480 <callout arearefs="kgp.parse.5.3">
481 <para>你甚至可以在这里使用 &toxml_functionname; 方法,深深嵌套在文档中。</para>
482 </callout>
483 <callout arearefs="kgp.parse.5.4">
484 <para><sgmltag>p</sgmltag>元素只有一个子节点(在这个例子中,你无法知道这一点,但是如果你不信,可以看看<literal>pNode.childNodes</literal>),而且它是表示单字符<literal>'0'</literal>的一个 &text_classname; 节点。</para>
485 </callout>
486 <callout arearefs="kgp.parse.5.5">
487 <para>&text_classname; 节点的<literal>.data</literal>属性可以向你提供文本节点真正代表的字符串。但是字符串前面的<literal>'u'</literal>是什么意思呢?答案将自己专门有一部分来论述。</para>
488 </callout>
489 </calloutlist>
490 </example>
491 </section>
492 <section id="kgp.unicode">
493 <?dbhtml filename="xml_processing/unicode.html"?>
494 <title>Unicode</title>
495 <abstract>
496 <title/>
497 <para>Unicode 是一个系统,用来表示世界上所有不同语言的字符。当 &python; 解析一个 &xml; 文档时,所有的数据都是以unicode的形式保存在内存中的。</para>
498 </abstract>
499 <para>一会儿你就会了解,但首先,先看一些背景知识。</para>
500 <formalpara>
501 <title>历史注解</title>
502 <para>在unicode之前,对于每一种语言都存在独立的字符编码系统,每个系统都使用相同的数字(0-255)来表示这种语言的字符。一些语言(象俄语)对于如何表示相同的字符还有几种有冲突的标准;另一些语言(象日语)拥有太多的字符,需要多个字符集。在系统之间进行文档交流是困难的,因为对于一台计算机来说,没有方法可以识别出文档的作者使用了哪种编码模式;计算机看到的只是数字,并且这些数字可以表示不同的东西。接着考虑到试图将这些文档存放到同一个地方(比如在同一个数据库表中);你需要在每段文本的旁边保存字符的编码,并且确保在传递文本的同时将编码也进行传递。接着考虑多语言文档,即在同一文档中使用了不同语言的字符。(比较有代表性的是使用转义符来进行模式切换;扑,我们处于俄语 koi8-r 模式,所以字符 241 表示这个;扑,现在我们处于 Mac 希腊语模式,所以字符 241 表示其它什么。等等。)这些就是unicode被设计出来要解决的问题。
503 </para>
504 </formalpara>
505 <para>为了解决这些问题,unicode用一个 2 字节数字表示每个字符,从 0 到 65535。<footnote><para>这一点,很不幸<emphasis>仍然</emphasis> 过分简单了。现在unicode已经扩展用来处理古老的汉字、韩文和日文文本,它们有太多不同的字符,以至于2字节的unicode系统不能全部表示。但当前 &python; 不支持超出范围的编码,并且我不知道是否有正在计划进行解决的项目。对不起,你已经到了我经验的极限了。</para></footnote> 每个 2 字节数字表示至少在一种世界语言中使用的一个唯一字符。(在多种语言中都使用的字符具有相同的数字码。)这样就确保每个字符一个数字,并且每个数字一个字符。Unicode数据永远不会模棱两可。</para>
506 <para>当然,仍然还存在着所有那些遗留的编码系统的情况。例如,7位 &ascii;,它可以将英文字符存诸为从0到127的数值。(65是大写字母<quote><literal>A</literal></quote>,97是小写字母<quote><literal>a</literal></quote>,等等。)英语有着非常简单的字母表,所以它可以完全用7位 &ascii; 来表示。象法语、西班牙语和德语之类的西欧语言都使用叫做ISO-8859-1的编码系统(也叫做<quote>latin-1</quote>),它使用7位 &ascii; 字符表示从0到127的数字,但接着扩展到了128-255的范围来表示象n上带有一个波浪线(241),和u上带有两个点(252)的字符。Unicode使用同7位 &ascii; 码一样的字符表示0到127,同ISO-8859-1一样的字符表示128到255,接着使用剩余的数字,256到65535,扩展到表示其它语言的字符。</para>
507 <para>在处理unicode数据时,在某些地方你可能需要将数据转换回这些遗留编码系统之一。例如,为了同其它一些计算机系统集成,这些系统期望它的数据使用一种特定的单字节编码模式,或将数据打印输出到一个非unicode识别终端或打印机。或将数据保存到一个明确指定编码模式的 &xml; 文档中。</para>
508 <para>在了解这个注解之后,让我们回到 &python;上来。</para>
509 <para>从2.0版本开始,&python; 在整个语言的基础上已经支持unicode。&xml; 包使用unicode来保存所有解析了的 &xml; 数据,而且你可以在任何地方使用unicode。</para>
510 <example>
511 <title>unicode介绍</title>
512 <screen>
513 &prompt;<userinput>s = u'Dive in'</userinput> <co id="kgp.unicode.1.1"/>
514 &prompt;<userinput>s</userinput>
515 <computeroutput>u'Dive in'</computeroutput>
516 &prompt;<userinput>print s</userinput> <co id="kgp.unicode.1.2"/>
517 <computeroutput>Dive in</computeroutput></screen>
518 <calloutlist>
519 <callout arearefs="kgp.unicode.1.1">
520 <para>为了创建一个unicode字符串而不是通常的 &ascii; 字符串,要在字符串前面加上字母<quote><literal>u</literal></quote>。注意这个特殊的字符串没有任何非 &ascii; 的字符。这样很好;unicode是 &ascii; 的一个超集(一个非常大的超集),所以任何正常的 &ascii; 都可以以unicode形式保存起来。</para>
521 </callout>
522 <callout arearefs="kgp.unicode.1.2">
523 <para>在打印字符串时,&python; 试图将字符串转换为你的默认编码,通常是 &ascii; 。(过会儿有更详细的说明。)因为组成这个unicode字符串的字符都是 &ascii; 字符,打印结果与打印正常的 &ascii; 字符串是一样的;转换是无缝的,而且如果你没有注意到<varname>s</varname>是一个unicode字符串的话,你永远也不会注意到两者之间的差别。</para>
524 </callout>
525 </calloutlist>
526 </example>
527 <example>
528 <title>存储非 &ascii; 字符</title>
529 <screen>
530 &prompt;<userinput>s = u'La Pe\xf1a'</userinput> <co id="kgp.unicode.2.1"/>
531 &prompt;<userinput>print s</userinput> <co id="kgp.unicode.2.2"/>
532 <computeroutput role="traceback">&traceback;
533 UnicodeError: ASCII encoding error: ordinal not in range(128)</computeroutput>
534 &prompt;<userinput>print s.encode('latin-1')</userinput> <co id="kgp.unicode.2.3"/>
535 <computeroutput>La Peña</computeroutput></screen>
536 <calloutlist>
537 <callout arearefs="kgp.unicode.2.1">
538 <para>unicode真正的优势,理所当然的是它保存非 &ascii; 字符的能力,例如西班牙语的<quote><literal>ñ</literal></quote>(<literal>n</literal>上带有一个波浪线)。用来表示波浪线n的unicode字符编码是十六进制的<literal>0xf1</literal> (十进制的241),你可以象这样输入:<literal>\xf1</literal></para>
539 </callout>
540 <callout arearefs="kgp.unicode.2.2">
541 <para>还记得我说过 &print; 函数会尝试将unicode字符串转换为 &ascii;,这样就可以打印它了吗?嗯,在这里将不会起作用,因为你的unicode字符串包含非 &ascii; 字符,所以 &python; 会引发<errorname>UnicodeError异常。</errorname></para>
542 </callout>
543 <callout arearefs="kgp.unicode.2.3">
544 <para>这儿就是将unicode转换为其它编码模式起作用的地方。<varname>s</varname>是一个unicode字符串,但 &print; 只能打印正常的字符串。为了解决这个问题,我们调用 <function>encode</function> 方法(它可以用于每个unicode字符串)将unicode字符串转换为指定编码模式的正常字符串。我们向此函数传入一个参数。在本例中,我们使用 <literal>latin-1</literal> (也就是大家知道的 <literal>iso-8859-1</literal>),它包括带波浪线的n(然而缺省的 &ascii; 编码模式不包括,因为它只包含数值从 0 到 127 的字符)。</para>
545 </callout>
546 </calloutlist>
547 </example>
548 <para>还记得我说过:一旦需要从一个unicode得到一个正常字符串,&python;通常默认将unicode转换成 &ascii; 吗?嗯,这个默认编码模式是一个可以定制的选项。</para>
549 <example>
550 <title><filename>sitecustomize.py</filename></title>
551 <programlisting>
552 # sitecustomize.py <co id="kgp.unicode.3.1"/>
553 # this file can be anywhere in your Python path,
554 # but it usually goes in ${pythondir}/lib/site-packages/
555 import sys
556 sys.setdefaultencoding('iso-8859-1') <co id="kgp.unicode.3.2"/>
557 </programlisting>
558 <calloutlist>
559 <callout arearefs="kgp.unicode.3.1">
560 <para><filename>sitecustomize.py</filename>是一个特殊的脚本;&python; 会在启动的时候导入它,所以在其中的任何代码都将自动运行。就像注解中提到的那样,它可以放在任何地方(只要 &import; 能够找到它),但是通常它位于 &python; 的<filename>lib</filename>目录的<filename>site-packages</filename>目录中。</para>
561 </callout>
562 <callout arearefs="kgp.unicode.3.2">
563 <para>恩,<function>setdefaultencoding</function> 函数设置默认编码。&python; 会在任何需要自动将unicode字符串强制转换为正规字符串的地方,使用这个编码模式。</para>
564 </callout>
565 </calloutlist>
566 </example>
567 <example>
568 <title>设置默认编码的效果</title>
569 <screen>
570 &prompt;<userinput>import sys</userinput>
571 &prompt;<userinput>sys.getdefaultencoding()</userinput> <co id="kgp.unicode.4.1"/>
572 <computeroutput>'iso-8859-1'</computeroutput>
573 &prompt;<userinput>s = u'La Pe\xf1a'</userinput>
574 &prompt;<userinput>print s</userinput> <co id="kgp.unicode.4.2"/>
575 <computeroutput>La Peña</computeroutput></screen>
576 <calloutlist>
577 <callout arearefs="kgp.unicode.4.1">
578 <para>这个例子假设你已经按前一个例子中的改动对<filename>sitecustomize.py</filename>文件做了修改,并且已经重启了 &python;。如果你的默认编码还是<literal>'ascii'</literal>,可能你就没有正确设置<filename>sitecustomize.py</filename> 文件,或者是没有重新启动 &python;。默认的编码只会在 &python; 启动的时候改变;之后就不能改变了。(由于一些古怪的编程技巧,我没有马上深入,你甚至不能在 &python; 启动之后调用<function>sys.setdefaultencoding</function>函数。仔细研究<filename>site.py</filename>,并搜索<quote><literal>setdefaultencoding</literal></quote>去发现为什么吧。)</para>
579 </callout>
580 <callout arearefs="kgp.unicode.4.2">
581 <para>现在默认的编码模式已经包含了你在字符串中使用的所有字符,&python; 对字符串的自动强制转换和打印就不存在问题了。</para>
582 </callout>
583 </calloutlist>
584 </example>
585 <example>
586 <title>指定<filename>.py</filename>文件的编码</title>
587 <para>如果你打算在你的 &python; 代码中保存非 &ascii; 字符串,你需要在每个文件的顶端加入编码声明来指定每个<filename>.py</filename>文件的编码。这个声明定义了<filename>.py</filename>文件的编码为UTF-8:</para>
588 <programlisting>
589 #!/usr/bin/env python
590 # -*- coding: UTF-8 -*-
591 </programlisting>
592 </example>
593 <para>现在,想想 &xml; 中的编码应该是怎样的呢?不错的是,每一个 &xml; 文档都有指定的编码。重复一下,ISO-8859-1是西欧语言存放数据的流行编码方式。KOI8-R是俄语流行的编码方式。编码,如果指定了的话,都在 &xml; 文档的首部。</para>
594 <example>
595 <title><filename>russiansample.xml</filename></title>
596 <screen><computeroutput>
597 <?xml version="1.0" encoding="koi8-r"?> </computeroutput><co id="kgp.unicode.5.1"/><computeroutput>
598 <preface>
599 <title>Предисловие</title> </computeroutput><co id="kgp.unicode.5.2"/><computeroutput>
600 </preface></computeroutput></screen>
601 <calloutlist>
602 <callout arearefs="kgp.unicode.5.1">
603 <para>这是从一个真实的俄语 &xml; 文档中提取出来的示例;它就是这本书俄语翻译版的一部分。注意,编码<literal>koi8-r</literal>是在首部指定的。</para>
604 </callout>
605 <callout arearefs="kgp.unicode.5.2">
606 <para>这些是古代斯拉夫语的字符,就我所知,它们用来拼写俄语单词<quote>Preface</quote>。如果你在一个正常文本编辑器中打开这个文件,这些字符非常象乱码,因为它们使用了<literal>koi8-r</literal>编码模式进行编码,但是却以<literal>iso-8859-1</literal>编码模式进行显示。</para>
607 </callout>
608 </calloutlist>
609 </example>
610 <example>
611 <title>解析<filename>russiansample.xml</filename></title>
612 <screen>
613 &prompt;<userinput>from xml.dom import minidom</userinput>
614 &prompt;<userinput>xmldoc = minidom.parse('russiansample.xml')</userinput> <co id="kgp.unicode.6.1"/>
615 &prompt;<userinput>title = xmldoc.getElementsByTagName('title')[0].firstChild.data</userinput>
616 &prompt;<userinput>title</userinput> <co id="kgp.unicode.6.2"/>
617 <computeroutput>u'\u041f\u0440\u0435\u0434\u0438\u0441\u043b\u043e\u0432\u0438\u0435'</computeroutput>
618 &prompt;<userinput>print title</userinput> <co id="kgp.unicode.6.3"/>
619 <computeroutput role="traceback">&traceback;
620 UnicodeError: ASCII encoding error: ordinal not in range(128)</computeroutput>
621 &prompt;<userinput>convertedtitle = title.encode('koi8-r')</userinput> <co id="kgp.unicode.6.4"/>
622 &prompt;<userinput>convertedtitle</userinput>
623 <computeroutput>'\xf0\xd2\xc5\xc4\xc9\xd3\xcc\xcf\xd7\xc9\xc5'</computeroutput>
624 &prompt;<userinput>print convertedtitle</userinput> <co id="kgp.unicode.6.5"/>
625 <computeroutput>Предисловие</computeroutput></screen>
626 <calloutlist>
627 <callout arearefs="kgp.unicode.6.1">
628 <para>我假设在这里你将前一个例子以<filename>russiansample.xml</filename>为名保存在当前目录中。也出于完整性的考虑,我假设你已经删除了<filename>sitecustomize.py</filename>文件,将缺省编码改回到<literal>'ascii'</literal>,或至少将<function>setdefaultencoding</function>一行注释起来了。</para>
629 </callout>
630 <callout arearefs="kgp.unicode.6.2">
631 <para>注意<sgmltag>title</sgmltag>标记的文本数据(现在在<varname>title</varname>变量中,幸亏有 &python; 函数的常串联,我快速地将它跳过去,并且在下一节之前不会进行解释)--在 &xml; 文档的<sgmltag>title</sgmltag>元素中的文本数据是以unicode保存的。</para>
632 </callout>
633 <callout arearefs="kgp.unicode.6.3">
634 <para>打印title是不可能的,因为这个unicode字符串包哈了非 &ascii; 字符,所以 &python; 不能把它转换为 &ascii; 因为它无法理解。</para>
635 </callout>
636 <callout arearefs="kgp.unicode.6.4">
637 <para>你能够,但是,显式的将它转换为<literal>koi8-r</literal>,在本例中,我们得到一个(正常,非unicode)单字节字符的字符串(<literal>f0</literal>, <literal>d2</literal>, <literal>c5</literal>,等等),它是初始unicode字符串中字符<literal>koi8-r</literal>-编码的版本。</para>
638 </callout>
639 <callout arearefs="kgp.unicode.6.5">
640 <para>打印<literal>koi8-r</literal>编码的字符串有可能会在你的屏幕上显示为乱码,因为你的 &python; &ide; 将这些字符作为
641 <literal>iso-8859-1</literal>的编码进行解析,而不是<literal>koi8-r</literal>编码。但是,至少它们能打印。(并且,如果你仔细看,当在一个不支持unicode的文本编辑器中打开最初的 &xml; 文档时,会看到相同的乱码。 &python; 在解析 &xml; 文档时,将它从<literal>koi8-r</literal>转换到了unicode,你只不过是将它转换回来。)</para>
642 </callout>
643 </calloutlist>
644 </example>
645 <para>总结一下,如果你以前从没有看到过unicode,倒是有些唬人,但是在 &python; 处理unicode数据真是非常容易。如果你的 &xml; 文档都是7位的 &ascii;(像本章中的例子),你差不多永远都不用考虑unicode。&python; 在进行解析时会将 &xml; 文档中的 &ascii; 数据转换为unicode,在任何需要的时候强制转换回为 &ascii;,你甚至永远都不用注意。但是如果你要处理其它语言的数据,&python; 已经准备好了。</para>
646 <itemizedlist role="furtherreading">
647 <title>进一步阅读</title>
648 <listitem><para><ulink url="&url_unicode;">Unicode.org</ulink>是unicode标准的主页,包含了一个简要的<ulink url="&url_unicodetech;">技术简介</ulink>。</para></listitem>
649 <listitem><para><ulink url="&url_unicodetutorial;">Unicode教程</ulink>有更多关于如何使用 &python; unicode函数的例子,包括甚至在并不真的需要时如何将unicode强制转换为 &ascii;。</para></listitem>
650 <listitem><para><ulink url="http://www.python.org/peps/pep-0263.html">PEP 263</ulink>涉及了何时、如何在你的<filename>.py</filename>文件中定义字符的更多细节。</para></listitem>
651 </itemizedlist>
652 </section>
653 <section id="kgp.search">
654 <?dbhtml filename="xml_processing/searching.html"?>
655 <title>搜索元素</title>
656 <abstract>
657 <title/>
658 <para>通过一步步访问每一个节点的方式遍历 &xml; 文档可能很乏味。如果你正在寻找些特别的东西,又恰恰它们深深埋入了你的 &xml; 文档,有个捷径让你可以快速找到它:&getelementsbytagname_functionname; 。</para>
659 </abstract>
660 <para>在这部分,将使用 &binaryxml_filename; 语法文件,它看上去是这样的:</para>
661 <example>
662 <title>&binaryxml_filename;</title>
663 <screen><computeroutput><?xml version="1.0"?>
664 <!DOCTYPE grammar PUBLIC "-//diveintopython.org//DTD Kant Generator Pro v1.0//EN" "kgp.dtd">
665 <grammar>
666 <ref id="bit">
667 <p>0</p>
668 <p>1</p>
669 </ref>
670 <ref id="byte">
671 <p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/>\
672 <xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p>
673 </ref>
674 </grammar></computeroutput></screen>
675 </example>
676 <para>它有两个 &refnode;,<literal>'bit'</literal>和<literal>'byte'</literal>。一个<literal>位</literal>是<literal>'0'</literal>或者<literal>'1'</literal>,而一个<literal>字节</literal>是8个<literal>位</literal>。</para>
677 <example>
678 <title>&getelementsbytagname_functionname; 介绍</title>
679 <screen>
680 &prompt;<userinput>from xml.dom import minidom</userinput>
681 &prompt;<userinput>xmldoc = minidom.parse('binary.xml')</userinput>
682 &prompt;<userinput>reflist = xmldoc.getElementsByTagName('ref')</userinput> <co id="kgp.search.1.1"/>
683 &prompt;<userinput>reflist</userinput>
684 <computeroutput>[<DOM Element: ref at 136138108>, <DOM Element: ref at 136144292>]</computeroutput>
685 &prompt;<userinput>print reflist[0].toxml()</userinput>
686 <computeroutput><ref id="bit">
687 <p>0</p>
688 <p>1</p>
689 </ref></computeroutput>
690 &prompt;<userinput>print reflist[1].toxml()</userinput>
691 <computeroutput><ref id="byte">
692 <p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/>\
693 <xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p>
694 </ref>
695 </computeroutput></screen>
696 <calloutlist>
697 <callout arearefs="kgp.search.1.1">
698 <para>&getelementsbytagname_functionname; 接收一个参数,即要找的元素的名称。它返回一个 &element_classname; 对象的列表,列表中的对象都是有指定名称的 &xml; 元素。在本例中,你能找到两个<literal>ref</literal>元素。</para>
699 </callout>
700 </calloutlist>
701 </example>
702 <example>
703 <title>每个元素都是可搜索的</title>
704 <screen>
705 &prompt;<userinput>firstref = reflist[0]</userinput> <co id="kgp.search.2.1"/>
706 &prompt;<userinput>print firstref.toxml()</userinput>
707 <computeroutput><ref id="bit">
708 <p>0</p>
709 <p>1</p>
710 </ref></computeroutput>
711 &prompt;<userinput>plist = firstref.getElementsByTagName("p")</userinput> <co id="kgp.search.2.2"/>
712 &prompt;<userinput>plist</userinput>
713 <computeroutput>[<DOM Element: p at 136140116>, <DOM Element: p at 136142172>]</computeroutput>
714 &prompt;<userinput>print plist[0].toxml()</userinput> <co id="kgp.search.2.3"/>
715 <computeroutput><p>0</p></computeroutput>
716 &prompt;<userinput>print plist[1].toxml()</userinput>
717 <computeroutput><p>1</p></computeroutput></screen>
718 <calloutlist>
719 <callout arearefs="kgp.search.2.1">
720 <para>继续前面的例子,在<varname>reflist</varname>中的第一个对象是<literal>'bit'</literal> &refnode;元素。</para>
721 </callout>
722 <callout arearefs="kgp.search.2.2">
723 <para>你可以在这个 &element_classname; 上使用相同的 &getelementsbytagname_functionname; 方法来寻找所有在<literal>'bit'</literal> &refnode; 元素中的<sgmltag><p></sgmltag>元素。</para>
724 </callout>
725 <callout arearefs="kgp.search.2.3">
726 <para>和前面一样,&getelementsbytagname_functionname; 方法返回一个找到元素的列表。在本例中,你有两个,每“位”使用一个。</para>
727 </callout>
728 </calloutlist>
729 </example>
730 <example>
731 <title>搜索实际上是递归的</title>
732 <screen>
733 &prompt;<userinput>plist = xmldoc.getElementsByTagName("p")</userinput> <co id="kgp.search.3.1"/>
734 &prompt;<userinput>plist</userinput>
735 <computeroutput>[<DOM Element: p at 136140116>, <DOM Element: p at 136142172>, <DOM Element: p at 136146124>]</computeroutput>
736 &prompt;<userinput>plist[0].toxml()</userinput> <co id="kgp.search.3.2"/>
737 <computeroutput>'<p>0</p>'</computeroutput>
738 &prompt;<userinput>plist[1].toxml()</userinput>
739 <computeroutput>'<p>1</p>'</computeroutput>
740 &prompt;<userinput>plist[2].toxml()</userinput> <co id="kgp.search.3.3"/>
741 <computeroutput>'<p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/>\
742 <xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p>'</computeroutput></screen>
743 <calloutlist>
744 <callout arearefs="kgp.search.3.1">
745 <para>仔细注意这个例子和前面例子之间的不同。前面,你是在<varname>firstref</varname>中搜索 &pnode; 元素,但是这里你是在<varname>xmldoc</varname>中搜索 &pnode; 元素,<varname>xmldoc</varname>是代表了整个 &xml; 文档的根层对象。这样<emphasis>就会</emphasis>找到嵌套在 &refnode; 元素(它嵌套在根 &grammarnode; 元素中)中的 &pnode; 元素。</para>
746 </callout>
747 <callout arearefs="kgp.search.3.2">
748 <para>前两个 &pnode; 元素在第一个 &refnode; 内(<literal>'bit'</literal> &refnode;)。</para>
749 </callout>
750 <callout arearefs="kgp.search.3.3">
751 <para>后一个 &pnode; 元素在第二个 &refnode; 中(<literal>'byte'</literal> &refnode;)。</para>
752 </callout>
753 </calloutlist>
754 </example>
755 </section>
756 <section id="kgp.attributes">
757 <?dbhtml filename="xml_processing/attributes.html"?>
758 <title>访问元素属性</title>
759 <abstract>
760 <title/>
761 <para>&xml; 元素可以有一个或者多个属性,一旦你已经解析了一个 &xml; 文档,访问它们就太简单了。</para>
762 </abstract>
763 <para>在这部分中,将使用 &binaryxml_filename; 语法文件,你在<link linkend="kgp.search">上一节</link>中已经看到过了。</para>
764 <note>
765 <title>&xml; 属性和&python; 属性</title>
766 <para>这部分由于某个涵义重叠的术语可能让人有点糊涂。在一个 &xml; 文档中,元素可以有属性,而 &python; 对象也有属性。当你解析一个 &xml; 文档时,你得到了一组 &python; 对象,它们代表 &xml; 文档中的所有片段,同时有些 &python; 对象代表 &xml; 元素的属性。但是表示(&xml;)属性的(&python;)对象也有(&python;)属性,它们用于访问对象表示的(&xml;)属性。我告诉过你它让人糊涂。我会公开提出关于如何更明显地区分这些不同的建议。
767 </para>
768 </note>
769 <example>
770 <title>访问元素属性</title>
771 <screen>
772 &prompt;<userinput>xmldoc = minidom.parse('binary.xml')</userinput>
773 &prompt;<userinput>reflist = xmldoc.getElementsByTagName('ref')</userinput>
774 &prompt;<userinput>bitref = reflist[0]</userinput>
775 &prompt;<userinput>print bitref.toxml()</userinput>
776 <computeroutput><ref id="bit">
777 <p>0</p>
778 <p>1</p>
779 </ref></computeroutput>
780 &prompt;<userinput>bitref.attributes</userinput> <co id="kgp.attributes.1.1"/>
781 <computeroutput><xml.dom.minidom.NamedNodeMap instance at 0x81e0c9c></computeroutput>
782 &prompt;<userinput>bitref.attributes.keys()</userinput> <co id="kgp.attributes.1.2"/> <co id="kgp.attributes.1.3"/>
783 <computeroutput>[u'id']</computeroutput>
784 &prompt;<userinput>bitref.attributes.values()</userinput> <co id="kgp.attributes.1.4"/>
785 <computeroutput>[<xml.dom.minidom.Attr instance at 0x81d5044>]</computeroutput>
786 &prompt;<userinput>bitref.attributes["id"]</userinput> <co id="kgp.attributes.1.5"/>
787 <computeroutput><xml.dom.minidom.Attr instance at 0x81d5044></computeroutput></screen>
788 <calloutlist>
789 <callout arearefs="kgp.attributes.1.1">
790 <para>每个 &element_classname; 对象都有一个称为<literal>attributes</literal>的属性,它是一个 &namednodemap_classname; 对象。听上去挺吓人的,其实不然,因为 &namednodemap_classname; 是一个<link linkend="fileinfo.userdict">行为像字典</link>的对象,所以你已经知道怎么使用它了。</para>
791 </callout>
792 <callout arearefs="kgp.attributes.1.2">
793 <para>将 &namednodemap_classname; 视为一个字典,你可以通过<function>attributes.keys()</function>获得属性名称的一个列表。这个元素只有一个属性,<literal>'id'</literal>。</para>
794 </callout>
795 <callout arearefs="kgp.attributes.1.3">
796 <para>属性名称,像其它 &xml; 文档中的文本一样,都是以<link linkend="kgp.unicode">unicode</link>保存的。</para>
797 </callout>
798 <callout arearefs="kgp.attributes.1.4">
799 <para>再次将 &namednodemap_classname; 视为一个字典,你可以通过<function>attributes.values()</function>获取属性值的一个列表。这些值本身是 &attr_classname; 类型的对象。你将在下一个例子中看到如何获取对象的有用信息。</para>
800 </callout>
801 <callout arearefs="kgp.attributes.1.5">
802 <para>仍然把 &namednodemap_classname; 视为一个字典,你可以通过常用的字典语法和名称访问单个的属性。(那些非常认真的读者将已经知道 &namednodemap_classname; 类是如何实现这一技巧的:通过定义一个<link linkend="fileinfo.specialmethods">&getitem; 特殊方法</link>。它的读者可能乐意接受这一事实:他们不需要理解它是如何工作的就可以有效地使用它。)</para>
803 </callout>
804 </calloutlist>
805 </example>
806 <example>
807 <title>访问单个属性</title>
808 <screen>
809 &prompt;<userinput>a = bitref.attributes["id"]</userinput>
810 &prompt;<userinput>a</userinput>
811 <computeroutput><xml.dom.minidom.Attr instance at 0x81d5044></computeroutput>
812 &prompt;<userinput>a.name</userinput> <co id="kgp.attributes.2.1"/>
813 <computeroutput>u'id'</computeroutput>
814 &prompt;<userinput>a.value</userinput> <co id="kgp.attributes.2.2"/>
815 <computeroutput>u'bit'</computeroutput></screen>
816 <calloutlist>
817 <callout arearefs="kgp.attributes.2.1">
818 <para>&attr_classname; 对象完整代表了单个 &xml; 元素的单个 &xml; 属性。属性的名称(与你在<literal>bitref.attributes</literal> &namednodemap_classname; 的伪目录中寻找的对象同名)保存在<literal>a.name</literal>中。</para>
819 </callout>
820 <callout arearefs="kgp.attributes.2.2">
821 <para>这个 &xml; 属性的真实文本值保存在<literal>a.value</literal>中。</para>
822 </callout>
823 </calloutlist>
824 </example>
825 <note>
826 <title>属性没有顺序</title>
827 <para>类似于字典,一个 &xml; 元素的属性没有顺序。属性可以以某种顺序<emphasis>偶然</emphasis>列在最初的 &xml; 文档中,而在 &xml; 文档解析为 &python; 对象时,&attr_classname; 对象以某种顺序<emphasis>偶然</emphasis>列出,这些顺序都是任意的,没有任何特别的含义。你应该总是使用名称来访问单个属性,就像字典的键一样。</para>
828 </note>
829 </section>
830 <section id="kgp.segue">
831 <?dbhtml filename="xml_processing/summary.html"?>
832 <title>Segue</title>
833 <abstract>
834 <title/>
835 <para>OK,that's it for the hard-core XML stuff. 下一章将继续使用相同的示例程序,但是焦点在于能使程序更加灵活的其它方面:使用输入流处理,使用 &getattr; 进行方法分发,并使用命令行标识允许用户重新配置程序而无需修改代码。</para>
836 </abstract>
837 <para>在进入下一章前,你应该没有困难的完成这些事情:</para>
838 <itemizedlist>
839 <listitem><para>使用 &minidom_modulename; <link linkend="kgp.parse">解析 &xml; 文档</link> ,<link linkend="kgp.search">搜索已解析文档</link>,并以任意顺序访问<link linkend="kgp.attributes">元素属性</link>和<link linkend="kgp.child">元素子元素</link></para></listitem>
840 <listitem><para>将复杂的库组织为<link linkend="kgp.packages">包</link></para></listitem>
841 <listitem><para>将<link linkend="kgp.unicode">unicode字符串转换</link>为不同的字符编码</para></listitem>
842 </itemizedlist>
843 </section>
844 </chapter>
845 <chapter id="streams">
846 <?dbhtml filename="scripts_and_streams/index.html"?>
847 <!-- You can only enter the same stream once. -->
848 <title>Scripts and Streams</title>
849 <titleabbrev id="streams.numberonly">第十章</titleabbrev>
850 <section id="kgp.openanything">
851 <?dbhtml filename="scripts_and_streams/input_sources.html"?>
852 <title>抽象输入源</title>
853 <abstract>
854 <title/>
855 <para>&python; 的最强大力量之一是它的动态绑定,并且动态绑定最强大的用法之一是<emphasis>类文件(file-like)对象</emphasis>。</para>
856 </abstract>
857 <para>
858 许多需要输入源的函数可以只接收一个文件名,并以读方式打开文件,读取文件,处理完成后关闭它。其实它们不是这样的,而是接收一个<emphasis>类文件对象</emphasis>。</para>
859 <para>在最简单的例子中,<emphasis>类文件对象</emphasis>是任意一个带有 &read; 方法的对象,这个方法带有一个可选的<varname>size</varname>参数,并返回一个字符串。调用时如果没有<varname>size</varname>参数,它从输入源中读取所有东西并将所有数据作为单个字符串返回。调用时如果指定了<varname>size</varname>参数,它将从输入源中读取<varname>size</varname>大小的数据并返回这些数据;再次调用的时候,它从余下的地方开始并返回下一块数据。</para>
860 <para>这就是<link linkend="fileinfo.files">从真实文件读取数据</link>的工作方式;区别在于你不用把自己局限于真实的文件。输入源可以是任何东西:磁盘上的文件,甚至是一个硬编码的字符串。只要你将一个类文件对象传递给函数,函数只是调用对象的 &read; 方法,函数可以处理任何类型的输入源,而不需要处理每种类型的特定代码。
861 </para>
862 <para>你可能纳闷过这和 &xml; 处理有什么关系,其实 &minidomparse_functionname; 就是一个可以接收类文件对象的函数。</para>
863 <example>
864 <title>从文件中解析 &xml; </title>
865 <screen>
866 &prompt;<userinput>from xml.dom import minidom</userinput>
867 &prompt;<userinput>fsock = open('binary.xml')</userinput> <co id="kgp.openanything.1.1"/>
868 &prompt;<userinput>xmldoc = minidom.parse(fsock)</userinput> <co id="kgp.openanything.1.2"/>
869 &prompt;<userinput>fsock.close()</userinput> <co id="kgp.openanything.1.3"/>
870 &prompt;<userinput>print xmldoc.toxml()</userinput> <co id="kgp.openanything.1.4"/>
871 <computeroutput><?xml version="1.0" ?>
872 <grammar>
873 <ref id="bit">
874 <p>0</p>
875 <p>1</p>
876 </ref>
877 <ref id="byte">
878 <p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/>\
879 <xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p>
880 </ref>
881 </grammar></computeroutput></screen>
882 <calloutlist>
883 <callout arearefs="kgp.openanything.1.1">
884 <para>首先,你要打开一个磁盘上的文件。这会提供给你一个<link linkend="fileinfo.files">文件对象</link>。</para>
885 </callout>
886 <callout arearefs="kgp.openanything.1.2">
887 <para>将文件对象传递给 &minidomparse_functionname; ,它调用<varname>fsock</varname>的 &read; 方法并从磁盘上的文件读取 &xml; 文档。</para>
888 </callout>
889 <callout arearefs="kgp.openanything.1.3">
890 <para>确保处理完成文件后调用 &close; 方法。&minidomparse_functionname;不会替你做这件事。</para>
891 </callout>
892 <callout arearefs="kgp.openanything.1.4">
893 <para>在返回的 &xml; 文档上调用<methodname>toxml()</methodname>方法,打印出整个文档的内容。</para>
894 </callout>
895 </calloutlist>
896 </example>
897 <para>哦,所有这些看上去象是在浪费大量的时间。毕竟,你已经看过 &minidomparse_functionname; 可以只接收文件名,并自动执行所有打开文件和关闭无用文件的行为。不错,如果你知道正要解析的是一个本地文件,你可以传递文件名而且 &minidomparse_functionname; 可以足够聪明的<trademark>做正确的事情</trademark>,这一切都不会有问题。但是请注意,使用类文件分析直接从Internet上来的 &xml; 文档是多么相似和容易的事情!</para>
898 <example id="kgp.openanything.urllib">
899 <title>解析来自 &url; 的 &xml; </title>
900 <screen>
901 &prompt;<userinput>import urllib</userinput>
902 &prompt;<userinput>usock = urllib.urlopen('http://slashdot.org/slashdot.rdf')</userinput> <co id="kgp.openanything.2.1"/>
903 &prompt;<userinput>xmldoc = minidom.parse(usock)</userinput> <co id="kgp.openanything.2.2"/>
904 &prompt;<userinput>usock.close()</userinput> <co id="kgp.openanything.2.3"/>
905 &prompt;<userinput>print xmldoc.toxml()</userinput> <co id="kgp.openanything.2.4"/>
906 <computeroutput><?xml version="1.0" ?>
907 <rdf:RDF xmlns="http://my.netscape.com/rdf/simple/0.9/"
908 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
909
910 <channel>
911 <title>Slashdot</title>
912 <link>http://slashdot.org/</link>
913 <description>News for nerds, stuff that matters</description>
914 </channel>
915
916 <image>
917 <title>Slashdot</title>
918 <url>http://images.slashdot.org/topics/topicslashdot.gif</url>
919 <link>http://slashdot.org/</link>
920 </image>
921
922 <item>
923 <title>To HDTV or Not to HDTV?</title>
924 <link>http://slashdot.org/article.pl?sid=01/12/28/0421241</link>
925 </item>
926
927 [...snip...]</computeroutput></screen>
928 <calloutlist>
929 <callout arearefs="kgp.openanything.2.1">
930 <para>如<link linkend="dialect.extract.urllib">前一章</link>,&urlopen; 接收一个web页面的 &url; 作为参数并返回一个类文件对象。最重要的是,这个对象有一个 &read; 方法可以返回web页面的 &html; 源代码。</para>
931 </callout>
932 <callout arearefs="kgp.openanything.2.2">
933 <para>现在把类文件对象传递给 &minidomparse_functionname; ,它顺从地调用对象的 &read; 方法并解析 &read; 方法返回的 &xml; 数据。这与 &xml; 数据现在直接来源于web页面的事实毫不相干。&minidomparse_functionname; 并不知道web页面,它也不关心web页面;它只知道类文件对象。</para>
934 </callout>
935 <callout arearefs="kgp.openanything.2.3">
936 <para>到这里已经处理完毕了,确保将 &urlopen; 提供给你的类文件对象关闭。</para>
937 </callout>
938 <callout arearefs="kgp.openanything.2.4">
939 <para>顺便提一句,这个 &url; 是真实的,它真的是一个 &xml;。它是<ulink url="http://slashdot.org/">Slashdot</ulink>站点(这是一个技术新闻和随笔站点)上当前标题的 &xml; 表示。</para>
940 </callout>
941 </calloutlist>
942 </example>
943 <example>
944 <title>解析字符串 &xml; (容易但不灵活的方式)</title>
945 <screen>
946 &prompt;<userinput>contents = "<grammar><ref id='bit'><p>0</p><p>1</p></ref></grammar>"</userinput>
947 &prompt;<userinput>xmldoc = minidom.parseString(contents)</userinput> <co id="kgp.openanything.3.1"/>
948 &prompt;<userinput>print xmldoc.toxml()</userinput>
949 <computeroutput><?xml version="1.0" ?>
950 <grammar><ref id="bit"><p>0</p><p>1</p></ref></grammar></computeroutput></screen>
951 <calloutlist>
952 <callout arearefs="kgp.openanything.3.1">
953 <para>&minidom_modulename; 有一个方法,&parsestring_functionname;,它接收一个字符串形式的完整 &xml; 文档作为参数并解析这个参数。如果你已经将整个 &xml; 文档放入一个字符串,你可以使用它代替&minidomparse_functionname;。</para>
954 </callout>
955 </calloutlist>
956 </example>
957 <para>OK,所以你可以使用 &minidomparse_functionname; 函数来解析本地文件和远端 &url;,但对于解析字符串,你使用...一个不同的函数。这就是说,你要从文件,&url; 或者字符串接收输入,你需要特别的逻辑来判断参数是否是字符串,然后调用 &parsestring_functionname;。多不让人满意。</para>
958 <para>如果有一个方法可以把字符串转换成类文件对象,那么你可以只把这个对象传递给 &minidomparse_functionname; 就可以了。事实上,有一个模块专门设计用来做这件事:&stringio_modulename;。</para>
959 <example id="kgp.openanything.stringio.example">
960 <title>&stringio_modulename; 介绍</title>
961 <screen>
962 &prompt;<userinput>contents = "<grammar><ref id='bit'><p>0</p><p>1</p></ref></grammar>"</userinput>
963 &prompt;<userinput>import StringIO</userinput>
964 &prompt;<userinput>ssock = StringIO.StringIO(contents)</userinput> <co id="kgp.openanything.4.1"/>
965 &prompt;<userinput>ssock.read()</userinput> <co id="kgp.openanything.4.2"/>
966 <computeroutput>"<grammar><ref id='bit'><p>0</p><p>1</p></ref></grammar>"</computeroutput>
967 &prompt;<userinput>ssock.read()</userinput> <co id="kgp.openanything.4.3"/>
968 <computeroutput>''</computeroutput>
969 &prompt;<userinput>ssock.seek(0)</userinput> <co id="kgp.openanything.4.4"/>
970 &prompt;<userinput>ssock.read(15)</userinput> <co id="kgp.openanything.4.5"/>
971 <computeroutput>'<grammar><ref i'</computeroutput>
972 &prompt;<userinput>ssock.read(15)</userinput>
973 <computeroutput>"d='bit'><p>0</p"</computeroutput>
974 &prompt;<userinput>ssock.read()</userinput>
975 <computeroutput>'><p>1</p></ref></grammar>'</computeroutput>
976 &prompt;<userinput>ssock.close()</userinput> <co id="kgp.openanything.4.6"/></screen>
977 <calloutlist>
978 <callout arearefs="kgp.openanything.4.1">
979 <para>&stringio_modulename; 模块只包含了单个类,也叫 &stringio_modulename;,它允许你将一个字符串转换为一个类文件对象。这个 &stringio_modulename; 类在创建实例的时候接收字符串作为参数。</para>
980 </callout>
981 <callout arearefs="kgp.openanything.4.2">
982 <para> 现在你有了一个类文件对象,你可用它做类文件的所有事情。比如 &read; 可以返回原始字符串。</para>
983 </callout>
984 <callout arearefs="kgp.openanything.4.3">
985 <para>再次调用 &read; 返回空字符串。真实文件对象的工作方式也是这样的;一旦你读取了整个文件,如果不显式定位到文件的开始位置,就不可能读取到任何其他数据。&stringio_classname; 对象以相同的方式进行工作。</para>
986 </callout>
987 <callout arearefs="kgp.openanything.4.4">
988 <para>使用 &stringio_classname; 对象的 &seek; 方法,你可以显式的定位到字符串的开始位置,就像在文件中定位一样。</para>
989 </callout>
990 <callout arearefs="kgp.openanything.4.5">
991 <para>将一个<varname>size</varname>参数传递给 &read; 方法,你还可以以块的形式读取字符串。</para>
992 </callout>
993 <callout arearefs="kgp.openanything.4.6">
994 <para>任何时候,&read; 都将返回字符串的未读剩余部分。所有这些严格地按文件对象的方式工作;,这就是术语<emphasis>类文件对象</emphasis>的来历。</para>
995 </callout>
996 </calloutlist>
997 </example>
998 <example>
999 <title>解析字符串 &xml; (类文件对象方式)</title>
1000 <screen>
1001 &prompt;<userinput>contents = "<grammar><ref id='bit'><p>0</p><p>1</p></ref></grammar>"</userinput>
1002 &prompt;<userinput>ssock = StringIO.StringIO(contents)</userinput>
1003 &prompt;<userinput>xmldoc = minidom.parse(ssock)</userinput> <co id="kgp.openanything.5.1"/>
1004 &prompt;<userinput>ssock.close()</userinput>
1005 &prompt;<userinput>print xmldoc.toxml()</userinput>
1006 <computeroutput><?xml version="1.0" ?>
1007 <grammar><ref id="bit"><p>0</p><p>1</p></ref></grammar></computeroutput></screen>
1008 <calloutlist>
1009 <callout arearefs="kgp.openanything.5.1">
1010 <para>现在你可以把类文件对象(实际是一个 &stringio_classname;)传递给 &minidomparse_functionname;,它将调用对象的 &read; 方法并高兴的开始解析,绝不会知道它的输入源源自一个硬编码的字符串。</para>
1011 </callout>
1012 </calloutlist>
1013 </example>
1014 <para>那么现在你知道了如何使用单个函数,&minidomparse_functionname;,来解析一个保存在web页面上,本地文件中或硬编码字符串中的 &xml; 文档。对于一个web页面,使用 &urlopen; 得到类文件对象;对于本地文件,使用 &open; ;对于字符串,使用 &stringio_classname;。现在让我们进一步并总结一下<emphasis>这些</emphasis> 不同。</para>
1015 <example id="kgp.openanything.example">
1016 <title>&openanything_functionname;</title>
1017 <programlisting>
1018 &tb_openanythingdef; <co id="kgp.openanything.6.1"/>
1019 &tb_urllibcomment;
1020 &tb_importurllib;
1021 &tb_tryurllib;
1022 &tb_urlopen; <co id="kgp.openanything.6.2"/>
1023 &tb_excepturllib;
1024 &tb_urllibpass;
1025
1026 &tb_nativecomment;
1027 &tb_trynative;
1028 &tb_native; <co id="kgp.openanything.6.3"/>
1029 &tb_exceptnative;
1030 &tb_nativepass;
1031
1032 &tb_stringiocomment;
1033 &tb_importstringio;
1034 &tb_stringio; <co id="kgp.openanything.6.4"/></programlisting>
1035 <calloutlist>
1036 <callout arearefs="kgp.openanything.6.1">
1037 <para>&openanything_functionname; 函数接收单个参数,<varname>source</varname>,并返回类文件对象。<varname>source</varname>是某种类型的字符串;它可能是一个 &url; (例如<literal>'http://slashdot.org/slashdot.rdf'</literal>),一个本地文件的完整或者部分路径名(例如<literal>'binary.xml'</literal>),或者是一个包含了需要解析 &xml; 数据的字符串。</para>
1038 </callout>
1039 <callout arearefs="kgp.openanything.6.2">
1040 <para>首先,查看<varname>source</varname>是否是一个 &url; 。这里通过强制方式进行:尝试把它当作一个 &url; 打开并静静地忽略打开非 &url; 引起的错误。感觉上这样做非常好,如果 &urllib; 将来支持更多的 &url; 类型,不用重新编码就可以支持它们。如果 &urllib; 能够打开<varname>source</varname>,那么 &return; 可以立刻把你踢出函数,下面的<literal>try</literal>语句将不会执行。</para>
1041 </callout>
1042 <callout arearefs="kgp.openanything.6.3">
1043 <para>另一方面,如果 &urllib; 向你呼喊并告诉你<varname>source</varname>不是一个有效的 &url;,你假设它是一个磁盘文件的路径并尝试打开它。再一次,你不用做任何特别的事来检查<varname>source</varname>是否是一个有效的文件名(总之在不同的平台上,判断文件名有效性的规则变化很大,那么不管怎样做都可能会判断错)。反而,只要盲目地打开文件并静静地捕获任何错误就可以了。</para>
1044 </callout>
1045 <callout arearefs="kgp.openanything.6.4">
1046 <para>到这里,你需要假设<varname>source</varname>是一个其中有硬编码数据的字符串(因为没有什么可以判断的了),所以你可以使用 &stringio_classname; 从中创建一个类文件对象并将它返回。(实际上,由于使用了 &str; 函数,所以<varname>source</varname>没有必要一定是字符串;它可以是任何对象,你可以使用它的字符串表示形式,通过它的 &strspecial; 定义的<link linkend="fileinfo.morespecial">特殊方法</link>。)</para>
1047 </callout>
1048 </calloutlist>
1049 </example>
1050 <para>现在你可以使用这个 &openanything_functionname; 函数联合 &minidomparse_functionname; 构造一个函数,接收一个指向 &xml; 文档的<varname>source</varname>,而且无需知道这个<varname>source</varname>的含义(可以是一个 &url; 或是一个本地文件名,或是一个硬编码 &xml; 文档的字符串形式),并解析它。</para>
1051 <example>
1052 <title>在 &kgp_filename; 中使用 &openanything_functionname;</title>
1053 <programlisting>
1054 &kgp_classdef;
1055 &kgp_privateload;
1056 &kgp_loadopen;
1057 &kgp_loadparse;
1058 &kgp_loadclose;
1059 &kgp_loadreturn;</programlisting>
1060 </example>
1061 </section>
1062 <section id="kgp.stdio">
1063 <?dbhtml filename="scripts_and_streams/stdin_stdout_stderr.html"?>
1064 <title>标准输入,输出和错误</title>
1065 <abstract>
1066 <title/>
1067 <para>&unix; 用户已经对标准输入,标准输出和标准错误的概念非常熟悉了。这一节是为其他不熟悉的人准备的。</para>
1068 </abstract>
1069 <para>标准输入和标准错误(通常缩写为 &stdout; 和 &stderr;)是內建在每一个 &unix; 系统中的管道。当你 &print; 某些东西时,结果前往 &stdout; 管道;当你的程序崩溃并打印出调试信息(类似于 &python; 中的错误跟踪)的时候,信息前往 &stderr; 管道。通常这两个管道只与你正在工作的终端窗口相联,所以当一个程序打印时,你可以看到输出,而当一个程序崩溃时,你可以看到调式信息。(如果你正在一个基于窗口的 &python; &ide; 上工作时,&stdout; 和 &stderr; 缺省为你的<quote>交互窗口</quote>。)</para>
1070 <example>
1071 <title>&stdout; 和 &stderr; 介绍</title>
1072 <screen>
1073 &prompt;<userinput>for i in range(3):</userinput>
1074 &continuationprompt;<userinput>print 'Dive in'</userinput> <co id="kgp.stdio.1.1"/>
1075 <computeroutput>Dive in
1076 Dive in
1077 Dive in</computeroutput>
1078 &prompt;<userinput>import sys</userinput>
1079 &prompt;<userinput>for i in range(3):</userinput>
1080 &continuationprompt;<userinput>sys.stdout.write('Dive in')</userinput> <co id="kgp.stdio.1.2"/>
1081 <computeroutput>Dive inDive inDive in</computeroutput>
1082 &prompt;<userinput>for i in range(3):</userinput>
1083 &continuationprompt;<userinput>sys.stderr.write('Dive in')</userinput> <co id="kgp.stdio.1.3"/>
1084 <computeroutput>Dive inDive inDive in</computeroutput></screen>
1085 <calloutlist>
1086 <callout arearefs="kgp.stdio.1.1">
1087 <para>正如<xref linkend="fileinfo.for.counter"/>中看到的,你可以使用 &python; 内置的 ⦥ 函数来构造简单的计数循环,即重复某物一定的次数。</para>
1088 </callout>
1089 <callout arearefs="kgp.stdio.1.2">
1090 <para>&stdout; 是一个类文件对象;调用它的 &write; 函数可以打印出你给定的任何字符串。实际上,这就是 &print; 函数真正做的事情;它在你打印的字符串后面加上一个硬回车,然后调用<function>sys.stdout.write</function>函数。</para>
1091 </callout>
1092 <callout arearefs="kgp.stdio.1.3">
1093 <para>在最简单的例子中,&stdout; 和 &stderr; 把它们的输出发送到相同的地方:&python; &ide; (如果你在一个 &ide; 中的话),或者终端(如果你从命令行运行 &python; 的话)。像 &stdout;,&stderr; 并不为你添加硬回车;如果需要,要自己加上。</para>
1094 </callout>
1095 </calloutlist>
1096 </example>
1097 <para>&stdout; 和 &stderr; 都是类文件对象,就像在<xref linkend="kgp.openanything"/>中讨论的一样,但是它们都是只写的。它们都没有 &read; 方法,只有 &write; 方法。然而,它们仍然是类文件对象,并且你可以将其它任何文件或者类文件对象赋值给它们来重定向它们的输出。</para>
1098 <example>
1099 <title>重定向输出</title>
1100 <screen>
1101 <prompt>[you@localhost kgp]$ </prompt><userinput>python stdout.py</userinput>
1102 <computeroutput>Dive in</computeroutput>
1103 <prompt>[you@localhost kgp]$ </prompt><userinput>cat out.log</userinput>
1104 <computeroutput>This message will be logged instead of displayed</computeroutput></screen>
1105 <para>(在Windows上,你可以使用<literal>type</literal>来代替<literal>cat</literal>显式文件的内容。)</para>
1106 ¶_download;
1107 <programlisting>
1108 #stdout.py
1109 import sys
1110
1111 print 'Dive in' <co id="kgp.stdio.2.1"/>
1112 saveout = sys.stdout <co id="kgp.stdio.2.2"/>
1113 fsock = open('out.log', 'w') <co id="kgp.stdio.2.3"/>
1114 sys.stdout = fsock <co id="kgp.stdio.2.4"/>
1115 print 'This message will be logged instead of displayed' <co id="kgp.stdio.2.5"/>
1116 sys.stdout = saveout <co id="kgp.stdio.2.6"/>
1117 fsock.close() <co id="kgp.stdio.2.7"/>
1118 </programlisting>
1119 <calloutlist>
1120 <callout arearefs="kgp.stdio.2.1">
1121 <para>打印输出到 &ide; <quote>交互窗口</quote>(或终端,如果从命令行运行脚本的话)。</para>
1122 </callout>
1123 <callout arearefs="kgp.stdio.2.2">
1124 <para>始终在重定向前保存 &stdout; ,这样的话之后你还可以将其设回正常。</para>
1125 </callout>
1126 <callout arearefs="kgp.stdio.2.3">
1127 <para>打开一个新文件用于写入。如果文件不存在,将会被创建。如果文件存在,将被覆盖。</para>
1128 </callout>
1129 <callout arearefs="kgp.stdio.2.4">
1130 <para>将所有后续的输出重定向到刚才打开的新文件上。</para>
1131 </callout>
1132 <callout arearefs="kgp.stdio.2.5">
1133 <para>这样只会将输出结果<quote>printed</quote>到日志文件中;在 &ide; 窗口中或在屏幕上不会看到输出结果。</para>
1134 </callout>
1135 <callout arearefs="kgp.stdio.2.6">
1136 <para>在我们将 &stdout; 搞乱之前,让我们把它设回原来的方式。</para>
1137 </callout>
1138 <callout arearefs="kgp.stdio.2.7">
1139 <para>关闭日志文件。</para>
1140 </callout>
1141 </calloutlist>
1142 </example>
1143 <para>重定向 &stderr; 完全以相同的方式进行,用 <function>sys.stderr</function> 代替 <function>sys.stdout</function>。</para>
1144 <example>
1145 <title>重定向错误信息</title>
1146 <screen>
1147 <prompt>[you@localhost kgp]$ </prompt><userinput>python stderr.py</userinput>
1148 <prompt>[you@localhost kgp]$ </prompt><userinput>cat error.log</userinput>
1149 <computeroutput>Traceback (most recent line last):
1150 File "stderr.py", line 5, in ?
1151 raise Exception, 'this error will be logged'
1152 Exception: this error will be logged</computeroutput></screen>
1153 ¶_download;
1154 <programlisting>
1155 #stderr.py
1156 import sys
1157
1158 fsock = open('error.log', 'w') <co id="kgp.stdio.3.1"/>
1159 sys.stderr = fsock <co id="kgp.stdio.3.2"/>
1160 raise Exception, 'this error will be logged' <co id="kgp.stdio.3.3"/> <co id="kgp.stdio.3.4"/>
1161 </programlisting>
1162 <calloutlist>
1163 <callout arearefs="kgp.stdio.3.1">
1164 <para>打开你要存储调试信息的日志文件。</para>
1165 </callout>
1166 <callout arearefs="kgp.stdio.3.2">
1167 <para>将新打开的日志文件的文件对象赋值给 &stderr; 以重定向标准错误。</para>
1168 </callout>
1169 <callout arearefs="kgp.stdio.3.3">
1170 <para>引发一个异常。从屏幕输出上可以注意到这个行为<emphasis>没有</emphasis>在屏幕上打印出任何东西。所有正常的跟踪信息已经写进 <filename>error.log</filename>。</para>
1171 </callout>
1172 <callout arearefs="kgp.stdio.3.4">
1173 <para>还要注意你既没有显式关闭日志文件,也没有将 &stderr; 设回最初的值。这样挺好,因为一旦程序崩溃(由于引发的异常),&python; 将替我们清理并关闭文件,这和永远不恢复 &stderr; 不会造成什么不同,因为,我提到过,一旦程序崩溃,则 &python; 结束。如果你希望在同一个脚本的后面去做其它的事情,恢复初始值对 &stdout; 更为重要。</para>
1174 </callout>
1175 </calloutlist>
1176 </example>
1177 <para>向标准错误写入错误信息是很常见的,所以有一种较快的语法可以立刻信息导出。</para>
1178 <example id="kgp.stdio.print.example">
1179 <title>打印到 &stderr;</title>
1180 <screen>
1181 &prompt;<userinput>print 'entering function'</userinput>
1182 <computeroutput>entering function</computeroutput>
1183 &prompt;<userinput>import sys</userinput>
1184 &prompt;<userinput>print >> sys.stderr, 'entering function'</userinput> <co id="kgp.stdio.6.1"/>
1185 <computeroutput>entering function</computeroutput>
1186 </screen>
1187 <calloutlist>
1188 <callout arearefs="kgp.stdio.6.1">
1189 <para>&print; 语句的快捷语法可以用于向任何打开的文件写入,或者是类文件对象。在这种情况下,你可以将单个&print; 语句重定向到&stderr; 而且不用影响后面的&print; 语句。</para>
1190 </callout>
1191 </calloutlist>
1192 </example>
1193 <para>标准输出,另一方面,只是一个只读文件对象,它表示从前一个程序到这个程序的数据流。这个对于老的&macos;用户和&windows;用户可能不太容易理解,除非你受到过 &dos; 命令行的影响。它工作的方式是你可以在单个命令行中构造一个命令的链,这样的话一个程序的输出就可以成为下一个程序的输入。第一个程序只是简单的输出到标准输出上(其本身没有做任何特别的重定向,只是执行了普通的 &print; 语句),然后,下一个程序从标准输入中读取,操作系统只是关注将一个程序的输出连接到一个程序的输入。</para>
1194 <example>
1195 <title>链接命令</title>
1196 <screen>
1197 <prompt>[you@localhost kgp]$ </prompt><userinput>python kgp.py -g binary.xml</userinput> <co id="kgp.stdio.4.1"/>
1198 <computeroutput>01100111</computeroutput>
1199 <prompt>[you@localhost kgp]$ </prompt><userinput>cat binary.xml</userinput> <co id="kgp.stdio.4.2"/>
1200 <computeroutput><?xml version="1.0"?>
1201 <!DOCTYPE grammar PUBLIC "-//diveintopython.org//DTD Kant Generator Pro v1.0//EN" "kgp.dtd">
1202 <grammar>
1203 <ref id="bit">
1204 <p>0</p>
1205 <p>1</p>
1206 </ref>
1207 <ref id="byte">
1208 <p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/>\
1209 <xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p>
1210 </ref>
1211 </grammar></computeroutput>
1212 <prompt>[you@localhost kgp]$ </prompt><userinput>cat binary.xml | python kgp.py -g -</userinput> <co id="kgp.stdio.4.3"/> <co id="kgp.stdio.4.4"/>
1213 <computeroutput>10110001</computeroutput></screen>
1214 <calloutlist>
1215 <callout arearefs="kgp.stdio.4.1">
1216 <para>正如你在<xref linkend="kgp.divein"/>中看到的,该命令将只打印一个随机的八位字符串,其中只有&zero; 或者 &one;。</para>
1217 </callout>
1218 <callout arearefs="kgp.stdio.4.2">
1219 <para>该处只是简单的打印出整个&binaryxml_filename;文档的内容。(&windows;用户应该用<literal>type</literal>代替<literal>cat</literal>。)</para>
1220 </callout>
1221 <callout arearefs="kgp.stdio.4.3">
1222 <para>该处打印&binaryxml_filename;的内容,但是<quote><literal>|</literal></quote>字符,称为<quote>管道</quote>符,说明内容不会打印到屏幕上。而且,它们会成为下一个命令的标准输入,在这个例子中是你调用的&python; 脚本。</para>
1223 </callout>
1224 <callout arearefs="kgp.stdio.4.4">
1225 <para>为了不用指定一个模块(例如&binaryxml_filename;),你需要指定<quote><literal>-</literal></quote>,它会使得你的脚本从标准输入载入脚本而不是从磁盘上的特定文件。(下一个例子更多地说明了这是如何实现的)。所以效果和第一种语法是一样的,在那里你要直接指定语法文件,但是想想这里的扩展性。代替<literal>cat binary.xml</literal>,你可以通过运行一个脚本动态生成语法,然后你可以通过管道将它导入你的脚本。它可以来源于任何地方:数据库,或者是生成语法的元脚本,或者其他。你根本不需要修改你的&kgp_filename; 脚本就可以混合使用这个功能。所有你要作的仅仅是从标准输入取得一个语法文件,然后你就可以将其他的逻辑分离出来放到另一程序中去了。</para>
1226 </callout>
1227 </calloutlist>
1228 </example>
1229 <para>那么脚本是如何<quote>知道</quote>在语法文件是<quote><literal>-</literal></quote>时从标准输入读取? 其实不神奇;它只是代码。</para>
1230 <example>
1231 <title>在&kgp_filename;中从标准输入读取</title>
1232 <programlisting>
1233 def openAnything(source):
1234 if source == "-": <co id="kgp.stdio.5.1"/>
1235 import sys
1236 return sys.stdin
1237
1238 # try to open with urllib (if source is http, ftp, or file URL)
1239 import urllib
1240 try:
1241
1242 [... snip ...]</programlisting>
1243 <calloutlist>
1244 <callout arearefs="kgp.stdio.5.1">
1245 <para>这是&toolbox_filename; 中的<function>openAnything</function>函数,以前在<xref linkend="kgp.openanything"/>中你已经检视过了。所有你要做的就是在函数的开始加入3行代码来检测源是否是<quote><literal>-</literal></quote>; 如果是,返回<literal>sys.stdin</literal>。实际上,that's it! 记住,&stdin; 是一个拥有&read;方法的类文件对象,所以剩下的代码(在&kgp_filename;中,在那里你调用了<function>openAnything</function>) 一点都不需要改动。</para>
1246 </callout>
1247 </calloutlist>
1248 </example>
1249 </section>
1250 <section id="kgp.cache">
1251 <?dbhtml filename="scripts_and_streams/caching.html"?>
1252 <title>缓冲节点查询</title>
1253 <abstract>
1254 <title/>
1255 <para>&kgp_filename; 使用了多种技巧,对你进行 &xml; 处理而言它们或许有用。第一个就是,使用输入文档的结构稳定特征来构建节点缓冲。</para>
1256 </abstract>
1257 <para>一个语法文件定义了一系列的 &refnode; 元素。每个 &refnode; 包含了一个或者多个 &pnode; 元素,&pnode; 元素可以包含很多不同的东西,包括 &xrefnode;。无论何时你遇到一个 &xrefnode; ,都可以通过相同的 &idattr; 属性找到相对应的 &refnode; 元素,并选择 &refnode; 元素的子元素之一进行解析。(在下一部分中你将看到是如何进行这种随机选择的。)</para>
1258 <para>如何构建语法:为最小的片段定义 &refnode; 元素,然后通过 &xrefnode; 定义“包含”第一个 &refnode; 元素的 &refnode; 元素,等等。然后,解析“最大的”引用并跟在每个 &xrefnode; 后面,最后输出真实的文本。输出的文本依赖于你每次填充 &xrefnode; 所做的(随机)决策,所以每次的输出都是不同的。</para>
1259 <para>这种方式非常灵活,但是有一个不好的地方:性能。当你找到一个 &xrefnode; 并需要找到相应的 &refnode; 元素时,会遇到一个问题。 &xrefnode; 有 &idattr; 属性,而你要找拥有相同 &idattr; 属性的 &refnode; 元素,但是没有简单的方式做到这件事。较慢的方式是每次获取所有 &refnode; 元素的完整列表,然后手动遍历并检视每一个 &idattr; 属性。较快的方式是只做一次然后以字典形式构建一个缓冲。</para>
1260 <example>
1261 <title><function>loadGrammar</function></title>
1262 <programlisting>
1263 &kgp_loadgrammardef;
1264 &kgp_loadgrammar;
1265 &kgp_initrefs; <co id="kgp.cache.1.1"/>
1266 &kgp_forref; <co id="kgp.cache.1.2"/>
1267 &kgp_refid; <co id="kgp.cache.1.3"/> <co id="kgp.cache.1.4"/></programlisting>
1268 <calloutlist>
1269 <callout arearefs="kgp.cache.1.1">
1270 <para>从创建一个空字典 &kgprefs; 开始。</para>
1271 </callout>
1272 <callout arearefs="kgp.cache.1.2">
1273 <para>正如你在<xref linkend="kgp.search"/>中看到的,&getelementsbytagname_functionname; 返回所有特定名称元素的一个列表。你可以很容易的得到所有 &refnode; 元素的一个列表,然后仅仅是遍历这个列表</para>
1274 </callout>
1275 <callout arearefs="kgp.cache.1.3">
1276 <para>正如你在<xref linkend="kgp.attributes"/>中看到的,使用标准的字典语法,你可以通过名称来访问个别元素。所以,&kgprefs; 字典的键将是每个 &refnode; 元素的 &idattr; 属性值。</para>
1277 </callout>
1278 <callout arearefs="kgp.cache.1.4">
1279 <para>&kgprefs; 字典的值将是 &refnode; 元素本身。如你在<xref linkend="kgp.parse"/>中看到的,已解析 &xml; 文档中的每个元素,每个节点,每个注释,每个文本片段都是一个对象。</para>
1280 </callout>
1281 </calloutlist>
1282 </example>
1283 <para>一旦你构建了这个缓冲,无论何时你遇到一个 &xrefnode; 并且需要找到具有相同 &idattr; 属性的 &refnode; 元素,你只要在 &kgprefs; 中查找它。</para>
1284 <example>
1285 <title>使用 &refnode; 元素缓冲</title>
1286 <programlisting>
1287 &kgp_doxrefdef;
1288 &kgp_id;
1289 &kgp_parsexref;</programlisting>
1290 </example>
1291 <para>你将在下一部分探究 &randomchildelement_functionname; 函数。</para>
1292 </section>
1293 <section id="kgp.child">
1294 <?dbhtml filename="scripts_and_streams/child_nodes.html"?>
1295 <title>查找节点的直接子节点</title>
1296 <abstract>
1297 <title/>
1298 <para>解析 &xml; 文档时,另一个有用的己技巧是查找某个特定元素的所有直接子元素。例如,在语法文件中,一个 &refnode; 元素可以有数个 &pnode; 元素,其中每一个都可以包含很多东西,包括其他的 &pnode; 元素。你只要查找作为 &refnode; 孩子的 &pnode; 元素,不用查找其他 &pnode; 元素的孩子 &pnode; 元素。</para>
1299 </abstract>
1300 <para>你可能认为你只要简单的使用 &getelementsbytagname_functionname; 来实现这点就可以了,但是你不可以这么做。 &getelementsbytagname_functionname; 递归搜索并返回所有找到的元素的单个列表。由于 &pnode; 元素可以包含其他的 &pnode; 元素,你不能使用 &getelementsbytagname_functionname; ,因为它会返回你不要的嵌套 &pnode; 元素。为了只找到直接子元素,你要自己进行处理。</para>
1301 <example>
1302 <title>查找直接子元素</title>
1303 <programlisting>
1304 &kgp_randomchildelementdef;
1305 &kgp_choices; <co id="kgp.child.1.1"/> <co id="kgp.child.1.2"/> <co id="kgp.child.1.3"/>
1306 &kgp_chosen; <co id="kgp.child.1.4"/>
1307 &kgp_returnchosen;</programlisting>
1308 <calloutlist>
1309 <callout arearefs="kgp.child.1.1">
1310 <para>正如你在<xref linkend="kgp.parse.gettingchildnodes.example"/>中看到的, &childnodes_attr; 属性返回元素所有子节点的一个列表。</para>
1311 </callout>
1312 <callout arearefs="kgp.child.1.2">
1313 <para>However, 正如你在<xref linkend="kgp.parse.childnodescanbetext.example"/>中看到的,&childnodes_attr; 返回的列表包含了所有不同类型的节点,包括文本节点。这并不是你在这里要查找的。你只要元素形式的孩子。</para>
1314 </callout>
1315 <callout arearefs="kgp.child.1.3">
1316 <para>每个节点都有一个<varname>nodeType</varname>属性,它可以是<literal>元素节点</literal>, <literal>文本节点</literal>, <literal>注释节点</literal>,或者任意数量的其它值。可能值的完整列表在<classname>xml.dom</classname>包的<filename>__init__.py</filename>文件中。(关于包更多的,参见<xref linkend="kgp.packages"/>。)但你只是对元素节点有兴趣,所以你可以过滤出一个列表,其中只包含<varname>nodeType</varname>是<literal>元素节点</literal>的节点。</para>
1317 </callout>
1318 <callout arearefs="kgp.child.1.4">
1319 <para>一旦你拥有了一个真实元素的列表,选择任意一个都很容易。&python; 有一个叫 &random_modulename; 的模块,它包含了好几个有用的函数。<function>random.choice</function>函数接收一个任意数量条目的列表并随机返回其中的一个条目。比如,如果 &refnode; 元素包含了多个 &pnode; 元素,那么<varname>choices</varname>将会是 &pnode; 元素的一个列表,并且<varname>chosen</varname>将以被赋予其中的一个确切值而结束,而这个值是随即选择的。</para>
1320 </callout>
1321 </calloutlist>
1322 </example>
1323 </section>
1324 <section id="kgp.handler">
1325 <?dbhtml filename="scripts_and_streams/handlers_by_node_type.html"?>
1326 <title>通过节点类型创建独立的处理句柄 Creating separate handlers by node type</title>
1327 <abstract>
1328 <title/>
1329 <para>第三个有用的 &xml; 处理技巧是将你的代码基于节点类型和元素名称分散到逻辑函数中。解析后的 &xml; 文档是由各种类型的节点组成的,每一个都是通过 &python; 对象表示的。文档本身的根层次通过一个<classname>Document</classname>对象表示。<classname>Document</classname>还包含了一个或者多个<classname>Element</classname>对象(for actual &xml; tags),其中的每一个可以包含其它的<classname>Element</classname>对象,<classname>Text</classname>对象(for bits of text),或者<classname>Comment</classname>对象(for embedded comments)。 &python; 使编写分离每个节点类型逻辑的分发器非常容易。</para>
1330 </abstract>
1331 <example>
1332 <title>已解析 &xml; 对象的类名</title>
1333 <screen>
1334 &prompt;<userinput>from xml.dom import minidom</userinput>
1335 &prompt;<userinput>xmldoc = minidom.parse('kant.xml')</userinput> <co id="kgp.handler.1.1"/>
1336 &prompt;<userinput>xmldoc</userinput>
1337 <computeroutput><xml.dom.minidom.Document instance at 0x01359DE8></computeroutput>
1338 &prompt;<userinput>xmldoc.__class__</userinput> <co id="kgp.handler.1.2"/>
1339 <computeroutput><class xml.dom.minidom.Document at 0x01105D40></computeroutput>
1340 &prompt;<userinput>xmldoc.__class__.__name__</userinput> <co id="kgp.handler.1.3"/>
1341 <computeroutput>'Document'</computeroutput></screen>
1342 <calloutlist>
1343 <callout arearefs="kgp.handler.1.1">
1344 <para>暂时假设<filename>kant.xml</filename>在当前目录中。</para>
1345 </callout>
1346 <callout arearefs="kgp.handler.1.2">
1347 <para>正如你在<xref linkend="kgp.packages"/>中看到的,解析 &xml; 文档返回的对象是一个<classname>Document</classname>对象,就像在<filename>xml.dom</filename>包的<filename>minidom.py</filename>中定义的一样。又如你在<xref linkend="fileinfo.create"/>中看到的,<literal>__class__</literal>是每个 &python; 对象的一个内置属性。</para>
1348 </callout>
1349 <callout arearefs="kgp.handler.1.3">
1350 <para>此外,<literal>__name__</literal>是每个 &python; 类的内置属性,是一个字符串。这个字符串并不神秘;它和你在定义类时输入的类名相同。(参见<xref linkend="fileinfo.class"/>。)</para>
1351 </callout>
1352 </calloutlist>
1353 </example>
1354 <para>好,现在你能够得到任何特定 &xml; 节点的类名了(因为每个 &xml; 节点都是以一个 &python; 对象表示的)。你怎样才能利用这点来分离解析每个节点类型的逻辑呢?答案就是 &getattr;,你第一次见它是在<xref linkend="apihelper.getattr"/>中。</para>
1355 <example>
1356 <title><function>parse</function>, 一个通用的 &xml; 节点分发器</title>
1357 <programlisting>
1358 &kgp_parsedef;
1359 &kgp_getparsemethod; <co id="kgp.handler.2.1"/> <co id="kgp.handler.2.2"/>
1360 &kgp_parsemethod; <co id="kgp.handler.2.3"/></programlisting>
1361 <calloutlist>
1362 <callout arearefs="kgp.handler.2.1">
1363 <para>First off, 注意你正在基于传入节点(在<varname>node</varname>参数中)的类名构造一个较大的字符串。所以如果你传入一个<classname>Document</classname>节点,你就构造了字符串<literal>'parse_Document'</literal>,其它类同于此。</para>
1364 </callout>
1365 <callout arearefs="kgp.handler.2.2">
1366 <para>现在你可以把这个字符串当作一个函数名称,然后通过 &getattr; 得到函数自身的引用。</para>
1367 </callout>
1368 <callout arearefs="kgp.handler.2.3">
1369 <para>最后,你可以调用函数并将节点自身作为参数传入。下一个例子将展示每个函数的定义。</para>
1370 </callout>
1371 </calloutlist>
1372 </example>
1373 <example>
1374 <title><function>parse</function>分发者调用的函数</title>
1375 <programlisting>
1376 &kgp_parsedocumentdef; <co id="kgp.handler.3.1"/>
1377 &kgp_parsedocument;
1378
1379 &kgp_parsetextdef;<co id="kgp.handler.3.2"/>
1380 &kgp_text;
1381 &kgp_ifcapitalizenextword;
1382 &kgp_appendcapital;
1383 &kgp_appendrest;
1384 &kgp_resetcapital;
1385 &kgp_elsecapitalizenextword;
1386 &kgp_appendnormal;
1387
1388 &kgp_parsecommentdef; <co id="kgp.handler.3.3"/>
1389 &kgp_pass;
1390
1391 &kgp_parseelementdef;<co id="kgp.handler.3.4"/>
1392 &kgp_gethandlermethod;
1393 &kgp_handlermethod;</programlisting>
1394 <calloutlist>
1395 <callout arearefs="kgp.handler.3.1">
1396 <para><function>parse_Document</function>只会被调用一次,因为在一个 &xml; 文档中只有一个<classname>Document</classname>节点,并且在已解析 &xml; 的表示中只有一个<classname>Document</classname>对象。它只是turn around并解析语法文件的根元素。</para>
1397 </callout>
1398 <callout arearefs="kgp.handler.3.2">
1399 <para><function>parse_Text</function> 在节点表示文本时被调用。这个函数本身做某种特殊处理,自动将句子的第一个单词进行大写处理,而不是简单的将表示的文本追加到一个列表中。</para>
1400 </callout>
1401 <callout arearefs="kgp.handler.3.3">
1402 <para><function>parse_Comment</function> 只有一个&pass;,因为你并不关心语法文件中嵌入的注释。但是注意,你还是要定义这个函数并显式的让它不做任何事情。如果这个函数不存在,通用<function>parse</function>函数在遇到一个注释的时候,会执行失败,因为它试图找到并不存在的<function>parse_Comment</function>函数。为每个节点类型定义独立的函数,甚至你不要使用的,将会使通用<function>parse</function>函数保持简单和沉默。</para>
1403 </callout>
1404 <callout arearefs="kgp.handler.3.4">
1405 <para><function>parse_Element</function>方法其实本身就是一个分发器,它基于元素的标记名称。这个基本概念是相同的:使用元素的区别(它们的标记名称)然后针对每一个分发到一个独立的函数。你构建了一个类似于<literal>'do_xref'</literal>的字符串(对<sgmltag><xref></sgmltag>标记而言),找到这个名称的函数,并调用它。对其它的标记名称在解析语法文件的时候都可以找到类似的函数(<sgmltag><p></sgmltag>标记,<sgmltag><choice></sgmltag>标记)。</para>
1406 </callout>
1407 </calloutlist>
1408 </example>
1409 <para>在这个例子中,分发函数<function>parse</function>和<function>parse_Element</function>只是找到相同类中的其它方法。如果你进行的处理过程很复杂(或者你有很多不同的标记名称),你可以将代码分散到独立的模块中,然后使用动态导入的方式导入每个模块并调用你需要的任何函数。动态导入将在<xref linkend="regression"/>中介绍。</para>
1410 </section>
1411 <section id="kgp.commandline">
1412 <?dbhtml filename="scripts_and_streams/command_line_arguments.html"?>
1413 <title>处理命令行参数</title>
1414 <abstract>
1415 <title/>
1416 <para>&python; 完备支持创建在命令行运行的程序,并且连同命令行参数和短长样式来指定各种选项。这些并非是 &xml; 特定的,但是这样的脚本可以充分使用命令行处理,看来是时候提一下它了。</para>
1417 </abstract>
1418 <para>如果不理解命令行参数如何暴露给你的 &python; 程序,讨论命令行处理是很困难的,所以让我们先写个简单那的程序来看一下。</para>
1419 <example>
1420 <title><varname>sys.argv</varname> 介绍</title>
1421 ¶_download;
1422 <programlisting>
1423 #argecho.py
1424 import sys
1425
1426 for arg in sys.argv: <co id="kgp.commandline.0.1"/>
1427 print arg</programlisting>
1428 <calloutlist>
1429 <callout arearefs="kgp.commandline.0.1">
1430 <para>每个传递给程序的命令行参数都在<varname>sys.argv</varname>,它仅仅是一个列表。这里是在独立行中打印出每个参数。</para>
1431 </callout>
1432 </calloutlist>
1433 </example>
1434 <example>
1435 <title><varname>sys.argv</varname>的内容</title>
1436 <screen>
1437 <prompt>[you@localhost py]$ </prompt><userinput>python argecho.py</userinput> <co id="kgp.commandline.1.1"/>
1438 <computeroutput>argecho.py</computeroutput>
1439 <prompt>[you@localhost py]$ </prompt><userinput>python argecho.py abc def</userinput> <co id="kgp.commandline.1.2"/>
1440 <computeroutput>argecho.py
1441 abc
1442 def</computeroutput>
1443 <prompt>[you@localhost py]$ </prompt><userinput>python argecho.py --help</userinput> <co id="kgp.commandline.1.3"/>
1444 <computeroutput>argecho.py
1445 --help</computeroutput>
1446 <prompt>[you@localhost py]$ </prompt><userinput>python argecho.py -m kant.xml</userinput> <co id="kgp.commandline.1.4"/>
1447 <computeroutput>argecho.py
1448 -m
1449 kant.xml</computeroutput></screen>
1450 <calloutlist>
1451 <callout arearefs="kgp.commandline.1.1">
1452 <para>关于<varname>sys.argv</varname>需要了解的第一件事情是它包含了你正在调用的脚本的名称。你后面会实际使用这个知识,在<xref linkend="regression"/>中。现在不用担心</para>
1453 </callout>
1454 <callout arearefs="kgp.commandline.1.2">
1455 <para>命令行参数通过空格进行分隔,在<varname>sys.argv</varname>类表中,每个参数都是一个独立的元素。</para>
1456 </callout>
1457 <callout arearefs="kgp.commandline.1.3">
1458 <para>命令行标志,就像<literal>--help</literal>,在<varname>sys.argv</varname>列表中还保存了它们自己的元素。</para>
1459 </callout>
1460 <callout arearefs="kgp.commandline.1.4">
1461 <para>为了让事情更有趣,有些命令行标志本身就接收参数。比如,这里有一个标记(<literal>-m</literal>)接收一个参数(<literal>kant.xml</literal>)。标记自身和标记参数只是<varname>sys.argv</varname>列表中的序列元素。并没有试图将元素与其它元素进行关联;所有你得到的是一个列表。</para>
1462 </callout>
1463 </calloutlist>
1464 </example>
1465 <para>所以正如你所看到的,你确实拥有了命令行传入的所有信息,但是, but then again, it doesn't look like it's going to be all that easy to actually use it. 对于只是接收单个参数或者没有标记的简单程序,你可以简单的使用<literal>sys.argv[1]</literal>来访问参数。这没有什么羞耻的;我一直都是这样做的。对更复杂的程序,你需要 &getopt_modulename; 模块。</para>
1466 <example>
1467 <title>&getopt_modulename; 介绍</title>
1468 <programlisting>
1469 &kgp_maindef;
1470 &kgp_initmaingrammar; <co id="kgp.commandline.2.1"/>
1471 &kgp_trygetopt;
1472 &kgp_getopt; <co id="kgp.commandline.2.2"/>
1473 &kgp_exceptgetopt; <co id="kgp.commandline.2.3"/>
1474 &kgp_errorusage; <co id="kgp.commandline.2.4"/>
1475 &kgp_errorexit;
1476
1477 ...
1478
1479 &kgp_ifmain;
1480 &kgp_main;</programlisting>
1481 <calloutlist>
1482 <callout arearefs="kgp.commandline.2.1">
1483 <para>First off,看一下例子最后并注意你正在调用<function>main</function>函数,参数是<literal>sys.argv[1:]</literal>。记住,<literal>sys.argv[0]</literal>是你正在运行脚本的名称;对命令行而言,你不用关心它,所以你可以砍掉它并传入列表的剩余部分。</para>
1484 </callout>
1485 <callout arearefs="kgp.commandline.2.2">
1486 <para>这里就是所有有趣处理发生的地方。&getopt_modulename; 模块的&getopt_functionname; 函数接收三个参数:参数列表(你从<literal>sys.argv[1:]</literal>得到的),一个包含了程序所有可能接收到的单字符命令行标志,和一个等价于单字符的长命令行标志的列表。第一次看的时候,这有点混乱,下面有更多的细节解释。</para>
1487 </callout>
1488 <callout arearefs="kgp.commandline.2.3">
1489 <para>在解析这些命令行标志时,如果有任何事情错了,&getopt_modulename; 会抛出异常,你可以捕获它。你可以告诉 &getopt_modulename; 你明白的所有标志,那么这也意味着终端用户可以传入一些你不理解的命令行标志。</para>
1490 </callout>
1491 <callout arearefs="kgp.commandline.2.4">
1492 <para>和 &unix; 世界中的标准实践一样,如果脚本被传入了不能理解的标志,你要打印出正确用法的一个概要并友好的退出。注意,在这里我没有写出<function>usage</function>函数。你还是要在某个地方写一个,使它打印出合适的概要;它不是自动的。</para>
1493 </callout>
1494 </calloutlist>
1495 </example>
1496 <para>那么你传给 &getopt_functionname; 函数的参数是什么呢?好的,第一个单单只是一个命令行标志和参数的原始列表(不包括第一个元素,脚本名称,你在调用<function>main</function>函数之前就已经将它砍掉了)。第二个是脚本接收的短命令行标志的一个列表。</para>
1497 <variablelist>
1498 <title><literal>"hg:d"</literal></title>
1499 <varlistentry>
1500 <term><literal>-h</literal></term>
1501 <listitem><para>print usage summary</para></listitem>
1502 </varlistentry>
1503 <varlistentry>
1504 <term><literal>-g ...</literal></term>
1505 <listitem><para>use specified grammar file or URL</para></listitem>
1506 </varlistentry>
1507 <varlistentry>
1508 <term><literal>-d</literal></term>
1509 <listitem><para>show debugging information while parsing</para></listitem>
1510 </varlistentry>
1511 </variablelist>
1512 <para>第一个标志和第三个标志是简单的独立标志;你选择是否指定它们,它们做某些事情(打印帮助)或者改变状态(关闭调试)。但是,第二个标志(<literal>-g</literal>)<emphasis>必须</emphasis>跟随一个参数,进行读取的语法文件的名称。实际上,它可以是一个文件名或者一个web地址,你可能还不知道(后面你会明白的),但是你要知道必须要<emphasis>有些东西</emphasis>。所以,你可以通过在 &getopt_functionname; 函数的第二个参数的<literal>g</literal>后面放一个冒号,来向 &getopt_modulename; 说明这一点。</para>
1513 <para>To further complicate things,这个脚本接收短标志(像<literal>-h</literal>)或者长标记(像<literal>--help</literal>),并且你要它们做相同的事。这就是 &getopt_functionname; 第三个参数存在的原因,为了指定长标志的一个列表,其中的长标志是和第二个参数中指定的短标志相对应的。</para>
1514 <variablelist>
1515 <title><literal>["help", "grammar="]</literal></title>
1516 <varlistentry>
1517 <term><literal>--help</literal></term>
1518 <listitem><para>print usage summary</para></listitem>
1519 </varlistentry>
1520 <varlistentry>
1521 <term><literal>--grammar ...</literal></term>
1522 <listitem><para>use specified grammar file or URL</para></listitem>
1523 </varlistentry>
1524 </variablelist>
1525 <para>这里要注意的三件事:</para>
1526 <orderedlist>
1527 <listitem><para>所有命令行中的长标志以两个短划线开始,但是在调用 &getopt_functionname; 时,你不用包含这两个短划线。它们是能够被理解的。</para></listitem>
1528 <listitem><para><literal>--grammar</literal>标志的后面必须跟着另一个参数,就像<literal>-g</literal>标志一样。通过等于号标识出来 <literal>"grammar="</literal>。</para></listitem>
1529 <listitem><para>长标志列表比短标志列表更短一些,因为<literal>-d</literal>标志没有相应的长标志。这也好;只有<literal>-d</literal>才会打开调试。但是短标志和长标志的顺序必须是相同的,你应该先指定有长标志的短标志,然后才是剩下的短标志。</para></listitem>
1530 </orderedlist>
1531 <para>被搞昏没?让我们看一下真实的代码,看看它在上下文中是否起作用。</para>
1532 <example>
1533 <title>在 &kgp_filename; 中处理命令行参数</title>
1534 <programlisting>
1535 &kgp_maindef; <co id="kgp.commandline.3.0"/>
1536 &kgp_initmaingrammar;
1537 &kgp_trygetopt;
1538 &kgp_getopt;
1539 &kgp_exceptgetopt;
1540 &kgp_errorusage;
1541 &kgp_errorexit;
1542 &kgp_foropt; <co id="kgp.commandline.3.1"/>
1543 &kgp_ifh; <co id="kgp.commandline.3.2"/>
1544 &kgp_helpusage;
1545 &kgp_helpexit;
1546 &kgp_ifd; <co id="kgp.commandline.3.3"/>
1547 &kgp_globaldebug;
1548 &kgp_setdebug;
1549 &kgp_ifg; <co id="kgp.commandline.3.4"/>
1550 &kgp_setmaingrammar;
1551
1552 &kgp_setdefaultsource; <co id="kgp.commandline.3.5"/>
1553
1554 &kgp_createkantgenerator;
1555 &kgp_nowrap;</programlisting>
1556 <calloutlist>
1557 <callout arearefs="kgp.commandline.3.0">
1558 <para><varname>grammar</varname>变量会跟踪你正在使用的语法文件。如果你没有在命令行指定它(使用<literal>-g</literal>或者<literal>--grammar</literal>标志定义它),在这里你将初始化它。</para>
1559 </callout>
1560 <callout arearefs="kgp.commandline.3.1">
1561 <para>你从 &getopt_functionname; 取回的<varname>opts</varname>变量包含了元组(<varname>flag</varname> 和 <varname>argument</varname>)的一个列表。如果标志没有带任何参数,那么<varname>arg</varname>只是 &none; 。这使得遍历标志更容易了。</para>
1562 </callout>
1563 <callout arearefs="kgp.commandline.3.2">
1564 <para>&getopt_functionname; 验证命令行标志是否可接受,但是它不会在短标志和长标志之间做任何转换。如果你指定<literal>-h</literal>标志,<varname>opt</varname>将会包含<literal>"-h"</literal>;如果你指定<literal>--help</literal>标志,<varname>opt</varname>将会包含<literal>"--help"</literal>标志。所以你需要检查它们两个。</para>
1565 </callout>
1566 <callout arearefs="kgp.commandline.3.3">
1567 <para>记得,<literal>-d</literal>标记没有相应的长标志,所以你只需要检查短形式。如果你找到了它,你就可以设置一个全局变量来指示后面要打印出调试信息。(我习惯在脚本的开发过程中使用它。What, you thought all these examples worked on the first try?)</para>
1568 </callout>
1569 <callout arearefs="kgp.commandline.3.4">
1570 <para>如果你找到了一个语法文件,<literal>-g</literal>标志或者<literal>--grammar</literal>标志带着的,那你要保存跟在它(保存在<varname>arg</varname>)后面的参数到变量<varname>grammar</varname>中,覆盖掉在<function>main</function>函数你初始化的默认值。</para>
1571 </callout>
1572 <callout arearefs="kgp.commandline.3.5">
1573 <para>That’s it。你已经遍历并处理了所有的命令行标志。这意味着所有剩下的东西都必须是命令行参数。这些从 &getopt_functionname; 函数的<varname>args</varname>变量回来。在这个例子中,你把它们当作了解析器源材料。如果没有指定命令行参数,<varname>args</varname>将是一个空列表,并且<varname>source</varname>将以空字符串结束。</para>
1574 </callout>
1575 </calloutlist>
1576 </example>
1577 </section>
1578 <section id="kgp.alltogether">
1579 <?dbhtml filename="scripts_and_streams/all_together.html"?>
1580 <title>放到一起</title>
1581 <abstract>
1582 <title/>
1583 <para>你已经了解很多基础的东西。让我们回来看看所有片段是如何整合到一起的。</para>
1584 </abstract>
1585 <para>作为开始,这里是一个<link linkend="kgp.commandline">接收命令行参数</link>的脚本,它使用 &getopt_modulename; 模块。</para>
1586 <informalexample>
1587 <programlisting>
1588 &kgp_maindef;
1589 ...
1590 &kgp_trygetopt;
1591 &kgp_getopt;
1592 &kgp_exceptgetopt;
1593 ...
1594 &kgp_foropt;
1595 ...</programlisting>
1596 </informalexample>
1597 <para>创建<classname>KantGenerator</classname>类的一个实例,然后将语法文件文件和源传给它,可能在命令行没有指定。</para>
1598 <informalexample>
1599 <programlisting>
1600 &kgp_createkantgenerator;</programlisting>
1601 </informalexample>
1602 <para><classname>KantGenerator</classname>实例自动加载语法,它是一个 &xml; 文件。你使用自定义的 &openanything_functionname; 函数打开这个文件(<link linkend="kgp.openanything">可能保存在一个本地文件中或者一个远程服务器上</link>),然后使用内置的&minidom_modulename; 解析函数<link linkend="kgp.parse">将 &xml; 解析为一棵 &python; 对象树</link>。</para>
1603 <informalexample>
1604 <programlisting>
1605 &kgp_privateload;
1606 &kgp_loadopen;
1607 &kgp_loadparse;
1608 &kgp_loadclose;</programlisting>
1609 </informalexample>
1610 <para>哦,根据这种方式,你将使用到 &xml; 文档结构的知识<link linkend="kgp.cache">建立一个引用的小缓冲</link>,这些引用只是 &xml; 文档中的元素。</para>
1611 <informalexample>
1612 <programlisting>
1613 &kgp_loadgrammardef;
1614 &kgp_forref;
1615 &kgp_refid;</programlisting>
1616 </informalexample>
1617 <para>如果你在命令行中指定了某些源材料,你可以使用它;否则你将打开语法查找“顶层”引用(没有被其它的东西引用)并把它作为开始点。</para>
1618 <informalexample>
1619 <programlisting>
1620 &kgp_defaultsourcedef;
1621 &kgp_initxrefs;
1622 &kgp_forxref;
1623 &kgp_xrefid;
1624 &kgp_xrefkeys;
1625 &kgp_standalonexrefs;
1626 &kgp_returndefaultsource;</programlisting>
1627 </informalexample>
1628 <para>现在你打开了了源材料。它是一个 &xml; 你每次解析一个节点。为了让代码分离并具备更高的可维护性,你可以使用<link linkend="kgp.handler">针对每个节点类型的独立处理方法</link>。</para>
1629 <informalexample>
1630 <programlisting>
1631 &kgp_parseelementdef;
1632 &kgp_gethandlermethod;
1633 &kgp_handlermethod;</programlisting>
1634 </informalexample>
1635 <para>通过语法的反弹,<link linkend="kgp.child">解析所有 &pnode; 元素的孩子</link>,</para>
1636 <informalexample>
1637 <programlisting>
1638 &kgp_dopdef;
1639 ...
1640 &kgp_ifdoit;
1641 &kgp_parsep;</programlisting>
1642 </informalexample>
1643 <para>用任意一个孩子替换 &choicenode; 元素,</para>
1644 <informalexample>
1645 <programlisting>
1646 &kgp_dochoicedef;
1647 &kgp_parsechoice;</programlisting>
1648 </informalexample>
1649 <para>并用对应 &refnode; 元素的任意孩子替换 &xrefnode; ,前面你已经进行了缓冲。</para>
1650 <informalexample>
1651 <programlisting>
1652 &kgp_doxrefdef;
1653 &kgp_id;
1654 &kgp_parsexref;</programlisting>
1655 </informalexample>
1656 <para>最后,你以你的方式进行解析直到普通文本。</para>
1657 <informalexample>
1658 <programlisting>
1659 &kgp_parsetextdef;
1660 &kgp_text;
1661 ...
1662 &kgp_appendnormal;</programlisting>
1663 </informalexample>
1664 <para>你打印出来的。</para>
1665 <informalexample>
1666 <programlisting>
1667 &kgp_maindef;
1668 ...
1669 &kgp_createkantgenerator;
1670 &kgp_nowrap;</programlisting>
1671 </informalexample>
1672 </section>
1673 <section id="kgp.summary">
1674 <?dbhtml filename="scripts_and_streams/summary.html"?>
1675 <title>综述</title>
1676 <abstract>
1677 <title/>
1678 <para>&python; 带有解析和操作 &xml; 文档非常强大的库。这个 &minidom_modulename; 接收一个 &xml; 文件并将其解析为 &python; 对象,提供了对任意元素的随即访问。进一步,本章展示了如何利用 &python; 创建一个“真实”独立的命令行脚本,连同命令行标志,命令行参数,错误处理,甚至从前一个程序的管道接收输入的能力。</para>
1679 </abstract>
1680 <para>在继续下一章前,你应该无困难的完成所有这些事情:</para>
1681 <itemizedlist>
1682 <listitem><para>通过标准输入输出<link linkend="kgp.stdio">链接程序</link></para></listitem>
1683 <listitem><para>使用 &getattr; <link linkend="kgp.handler">定义动态分发器</link>。</para></listitem>
1684 <listitem><para>通过 &getopt_modulename; <link linkend="kgp.commandline">使用命令行标志</link>并进行验证</para></listitem>
1685 </itemizedlist>
1686 </section>
1687 </chapter>
1688 <!--
1689 * unicode
1690 * getElementsByTagName
1691 * accessing element attributes
1692 * sys.stderr, sys.stdout, sys.stdin (brief explanation for Windows weenies)
1693 * toolbox.openAnything (abstracting input sources)
1694 * tips and tricks
1695 * caching ref nodes by name in a dictionary (name from id attribute)
1696 * finding child elements (e.ELEMENT_NODE)
1697 * separating handlers
1698 * by node type (Document, Element, Comment, Text)
1699 * by element name
1700 * handling command-line arguments
1701 * printing usage help
1702 * specifying a grammar
1703 * specifying a source
1704 X setting debug flag
1705 <prompt>[you@localhost kgp]$ </prompt><userinput>python kgp.py -d -g binary.xml</userinput>
1706 <computeroutput>1 available choices: [u'<p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p>']
1707 Chosen: <p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p>
1708 2 available choices: [u'<p>0</p>', u'<p>1</p>']
1709 Chosen: <p>0</p>
1710 2 available choices: [u'<p>0</p>', u'<p>1</p>']
1711 Chosen: <p>1</p>
1712 2 available choices: [u'<p>0</p>', u'<p>1</p>']
1713 Chosen: <p>1</p>
1714 2 available choices: [u'<p>0</p>', u'<p>1</p>']
1715 Chosen: <p>0</p>
1716 2 available choices: [u'<p>0</p>', u'<p>1</p>']
1717 Chosen: <p>0</p>
1718 2 available choices: [u'<p>0</p>', u'<p>1</p>']
1719 Chosen: <p>0</p>
1720 2 available choices: [u'<p>0</p>', u'<p>1</p>']
1721 Chosen: <p>1</p>
1722 2 available choices: [u'<p>0</p>', u'<p>1</p>']
1723 Chosen: <p>1</p>
1724 01100011</computeroutput>
1725
1726 * putting it all together
1727 * summary
1728 -->
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.