r5292: *** empty log message ***
[xmlutils.git] / pxml.txt
1 Description
2
3 The parse-xml function processes XML input, returning a list of XML tags,
4 attributes, and text. Here is a simple example:
5
6 (parse-xml "<item1><item2 att1='one'/>this is some text</item1>")
7
8 -->
9
10 ((item1 ((item2 att1 "one")) "this is some text"))
11
12 The output format is known as LXML format.
13
14 Here is a description of LXML:
15
16 LXML is a list representation of XML tags and content.
17
18 Each list member may be:
19
20 a. a string containing text content, such as "Here is some text with a "
21
22 b. a list representing a XML tag with associated attributes and/or content,
23    such as ('item1 "text") or (('item1 :att1 "help.html") "link"). If the XML tag
24    does not have associated attributes, then the first list member will be a
25    symbol representing the XML tag, and the other elements will 
26    represent the content, which can be a string (text content), a symbol (XML
27    tag with no attributes or content), or list (nested XML tag with
28    associated attributes and/or content). If there are associated attributes,
29    then the first list member will be a list containing a symbol
30    followed by two list members for each associated attribute; the first member is a
31    symbol representing the attribute, and the next member is a string corresponding
32    to the attribute value.
33
34 c. XML comments and or processing instructions - see the more detailed example below for
35    further information.
36
37 Parse-xml is a non-validating XML parser. It will detect non-well-formed XML input. When
38 processing valid XML input, parse-xml will optionally produce the same output as a validating 
39 parser would, including the processing of an external DTD subset and external entity declarations.
40
41 By default, parse-xml outputs a DTD parse along with the parsed XML contents. The DTD  parse may
42 be optionally suppressed. The following example shows DTD parsed output components:
43
44 (defvar *xml-example-external-url*
45     "<!ENTITY ext1 'this is some external entity %param1;'>")
46
47 (defun example-callback (var-name token &optional public)
48   (declare (ignorable token public))
49   (setf var-name (uri-path var-name))
50   (if* (equal var-name "null") then nil
51      else
52           (let ((string (eval (intern var-name (find-package :user)))))
53             (make-string-input-stream string))))
54
55 (defvar *xml-example-string*
56     "<?xml version='1.0' encoding='utf-8'?>
57 <!-- the following XML input is well-formed but its validity has not been checked ... -->
58 <?piexample this is an example processing instruction tag ?>
59 <!DOCTYPE example SYSTEM '*xml-example-external-url*' [
60    <!ELEMENT item1 (item2* | (item3+ , item4))>
61    <!ELEMENT item2 ANY>
62    <!ELEMENT item3 (#PCDATA)>
63    <!ELEMENT item4 (#PCDATA)>
64    <!ATTLIST item1
65         att1 CDATA #FIXED 'att1-default'
66         att2 ID #REQUIRED
67         att3 ( one | two | three ) 'one'
68         att4 NOTATION ( four | five ) 'four' >
69    <!ENTITY % param1 'text'>
70    <!ENTITY nentity SYSTEM 'null' NDATA somedata>
71    <!NOTATION notation SYSTEM 'notation-processor'>
72 ]>
73 <item1 att2='1'><item3>&ext1;</item3></item1>")
74
75 (pprint (parse-xml *xml-example-string* :external-callback 'example-callback))
76
77 -->
78
79 ((:xml :version "1.0" :encoding "utf-8")
80  (:comment " the following XML input is well-formed but may or may not be valid ")
81  (:pi :piexample "this is an example processing instruction tag ")
82  (:DOCTYPE :example
83   (:[ (:ELEMENT :item1 (:choice (:* :item2) (:seq (:+ :item3) :item4))) 
84    (:ELEMENT :item2 :ANY)
85    (:ELEMENT :item3 :PCDATA) (:ELEMENT :item4 :PCDATA)
86    (:ATTLIST item1 (att1 :CDATA :FIXED "att1-default") (att2 :ID :REQUIRED)
87     (att3 (:enumeration :one :two :three) "one") 
88     (att4 (:NOTATION :four :five) "four"))
89    (:ENTITY :param1 :param "text") 
90    (:ENTITY :nentity :SYSTEM "null" :NDATA :somedata)
91    (:NOTATION :notation :SYSTEM "notation-processor"))
92   (:external (:ENTITY :ext1 "this is some external entity text")))
93  ((item1 att1 "att1-default" att2 "1" att3 "one" att4 "four") 
94   (item3 "this is some external entity text")))
95
96
97 Usage Notes:
98
99 1. The parse-xml function has been compiled and tested only in a
100    modern ACL. Its successful operation depends on both the mixed
101    case support and wide character support found in modern ACL.
102
103 2. The parser uses the keyword package for DTD tokens and other
104    special XML tokens. Since element and attribute token symbols are usually interned
105    in the current package, it is not recommended to execute parse-xml
106    when the current package is the keyword package.
107
108 3. The XML parser supports the XML Namespaces specification. The parser
109    recognizes a "xmlns" attribute and attribute names starting with "xmlns:".
110    As per the specification, the parser expects that the associated value
111    is an URI string. The parser then associates XML Namespace prefixes with a
112    Lisp package provided via the parse-xml :uri-to-package option or, if 
113    necessary, a package created on the fly. The following example demonstrates
114    this behavior:
115
116    (setf *xml-example-string4*
117   "<bibliography
118       xmlns:bib='http://www.bibliography.org/XML/bib.ns'
119       xmlns='urn:royal-mail.gov.uk/XML/ns/postal.ns,1999'>
120     <bib:book owner='Smith'>
121        <bib:title>A Tale of Two Cities</bib:title>
122        <bib:bibliography
123          xmlns:bib='http://www.franz.com/XML/bib.ns'
124          xmlns='urn:royal-mail2.gov.uk/XML/ns/postal.ns,1999'>
125         <bib:library branch='Main'>UK Library</bib:library>
126         <bib:date calendar='Julian'>1999</bib:date>
127         </bib:bibliography>
128        <bib:date calendar='Julian'>1999</bib:date>
129        </bib:book>
130      </bibliography>")
131
132    (setf *uri-to-package* nil)
133    (setf *uri-to-package*
134               (acons (parse-uri "http://www.bibliography.org/XML/bib.ns")
135                      (make-package "bib") *uri-to-package*))
136    (setf *uri-to-package*
137               (acons (parse-uri "urn:royal-mail.gov.uk/XML/ns/postal.ns,1999")
138                      (make-package "royal") *uri-to-package*))
139    (setf *uri-to-package*
140               (acons (parse-uri "http://www.franz.com/XML/bib.ns")
141                      (make-package "franz-ns") *uri-to-package*))
142    (pprint (multiple-value-list 
143               (parse-xml *xml-example-string4*
144                   :uri-to-package *uri-to-package*)))
145
146 -->
147
148 ((((bibliography |xmlns:bib| "http://www.bibliography.org/XML/bib.ns" xmlns
149     "urn:royal-mail.gov.uk/XML/ns/postal.ns,1999")
150    "
151     "
152    ((bib::book royal::owner "Smith") "
153        " (bib::title "A Tale of Two Cities") "
154        "
155     ((bib::bibliography royal::|xmlns:bib| "http://www.franz.com/XML/bib.ns" royal::xmlns
156       "urn:royal-mail2.gov.uk/XML/ns/postal.ns,1999")
157      "
158         " ((franz-ns::library net.xml.namespace.0::branch "Main") "UK Library") "
159         " ((franz-ns::date net.xml.namespace.0::calendar "Julian") "1999") "
160         ")
161     "
162        " ((bib::date royal::calendar "Julian") "1999") "
163        ")
164    "
165      "))
166  ((#<uri urn:royal-mail2.gov.ukXML/ns/postal.ns,1999> . #<The net.xml.namespace.0 package>)
167   (#<uri http://www.franz.com/XML/bib.ns> . #<The franz-ns package>)
168   (#<uri urn:royal-mail.gov.ukXML/ns/postal.ns,1999> . #<The royal package>)
169   (#<uri http://www.bibliography.org/XML/bib.ns> . #<The bib package>)))
170
171  In the absence of XML Namespace attributes, element and attribute symbols are interned
172  in the current package. Note that this implies that attributes and elements referenced
173  in DTD content will be interned in the current package.
174
175 4. The ACL 6.0 beta does not contain a little-endian Unicode external format. To 
176    process XML input containing Unicode characters correctly:
177
178    a. Place the following in a file called ef-fat-little.cl in the ACL code
179       directory:
180
181 (provide :ef-fat-little)
182
183 (in-package :excl)
184
185 (def-external-format :fat-little-base
186                      :size 2)
187
188 (def-char-to-octets-macro :fat-little-base (char
189                                             state
190                                             &key put-next-octet external-format)
191   (declare (ignore external-format state))
192   `(let ((code (char-code ,char)))
193      (,put-next-octet (ldb (byte 8 0) code))
194      (,put-next-octet (ldb (byte 8 8) code))))
195
196 (def-octets-to-char-macro :fat-little-base (state-loc
197                                             &key get-next-octet external-format
198                                             octets-count-loc unget-octets)
199   (declare (ignore external-format state-loc unget-octets))
200   `(let ((lo ,get-next-octet)
201          (hi (progn (incf ,octets-count-loc)
202                     ,get-next-octet)))
203      (code-char (+ (ash hi 8) lo))))
204
205 (create-newline-ef :name :fat-little :base-name :fat-little-base
206                    :nicknames '(:unicode-little))
207
208    
209       b. Compile the file using a modern ACL.
210
211 5. The parse-xml function has been tested using the OASIS conformance test suite (see
212    details below). The test suite has wide coverage across possible XML and DTD syntax,
213    but there may be some syntax paths that have not yet been tested or completely
214    supported. Here is a list of currently known syntax parsing issues:
215
216    a. ACL does not support 4 byte Unicode scalar values, so input containing such data
217       will not be processed correctly. (Note, however, that parse-xml does correctly detect
218       and process wide Unicode input.)
219
220    b. The OASIS tests that contain wide Unicode all use a little-endian encoded Unicode.
221       Changes to the unicode-check function are required to also support big-endian encoded
222       Unicode. (Note also that this issue may be resolved by an ACL 6.0 final release change.)
223
224    c. An initial <?xml declaration in external entity files is skipped without a check
225       being made to see if the <?xml declaration is itself incorrect.
226
227 6. When investigating possible parser errors or examining more closely where the parser
228    determined that the input was non-well-formed, the net.xml.parser internal symbols 
229    *debug-xml* and *debug-dtd* are useful. When not bound to nil, these variables cause
230    lexical analysis and intermediate parsing results to be output to *standard-output*.
231
232 XML Conformance Test Suite
233
234 Using the OASIS test suite (http://www.oasis-open.org),
235 here are the current parse-xml results:
236
237 xmltest/invalid:        Not tested, since parse-xml is a non-validating parser
238
239   not-wf/
240
241         ext.sa: 3 tests; all pass
242         not-sa: 8 tests; all pass
243         sa: 186 tests; the following fail:
244
245                 170.xml: fails because ACL does not support 4 byte Unicode scalar values
246
247   valid/
248
249         ext-sa: 14 tests; all pass
250         not-sa: 31 tests; all pass
251         sa: 119 tests: the following fail:
252
253                 052.xml, 064.xml, 089.xml: fails because ACL does not support 4 byte 
254                                            Unicode scalar values
255
256 Compiling and Loading
257
258 Load build.cl into a modern ACL session will result in a pxml.fasl file that can subsequently be
259 loaded in a modern ACL to provide XML parsing functionality.
260
261 -------------------------------------------------------------------------------------------
262
263 parse-xml reference
264
265 parse-xml                       [Generic function]
266
267 Arguments: input-source &key external-callback content-only 
268                             general-entities parameter-entities
269                             uri-to-package
270
271 Returns multiple values:
272
273  1) LXML and parsed DTD output, as described above.
274  2) An association list containing the uri-to-package argument conses (if any)
275     and conses associated with any XML Namespace packages created during the
276     parse (see uri-to-package argument description, below).
277
278 The external-callback argument, if specified, is a function object or symbol
279 that parse-xml will execute when encountering an external DTD subset
280 or external entity DTD declaration. Here is an example which shows that
281 arguments the function should expect, and the value it should return:
282
283 (defun file-callback (uri-object token &optional public)
284   ;; the uri-object is an ACL URI object created from
285   ;; the XML input. In this example, this function
286   ;; assumes that all uri's will be file specifications.
287   ;;
288   ;; the token argument identifies what token is associated
289   ;; with the external parse (for example :DOCTYPE for external
290   ;; DTD subset
291   ;;
292   ;; the public argument contains the associated PUBLIC string,
293   ;; when present
294   ;;
295   (declare (ignorable token public))
296   ;; an open stream is returned on success
297   ;; a nil return value indicates that the external
298   ;; parse should not occur
299   ;; Note that parse-xml will close the open stream before
300   ;; exiting
301   (ignore-errors (open (uri-path uri-object))))
302
303 The general-entities argument is an association list containing general entity symbol 
304 and replacement text pairs. The entity symbols should be in the keyword package.
305 Note that this option may be useful in generating desirable parse results in 
306 situations where you do not wish to parse external entities or the external DTD subset.
307
308 The parameter-entities argument is an association list containing parameter entity symbol 
309 and replacement text pairs. The entity symbols should be in the keyword package.
310 Note that this option may be useful in generating desirable parse results in 
311 situations where you do not wish to parse external entities or the external DTD subset.
312
313 The uri-to-package argument is an association list containing uri objects and package
314 objects. Typically, the uri objects correspond to XML Namespace attribute values, and
315 the package objects correspond to the desired package for interning symbols associated
316 with the uri namespace. If the parser encounters an uri object not contained in this list,
317 it will generate a new package. The first generated package will be named net.xml.namespace.0,
318 the second will be named net.xml.namespace.1, and so on.
319
320 parse-xml Methods
321
322 (parse-xml (p stream) &key external-callback content-only 
323                             general-entities parameter-entities
324                             uri-to-package)
325
326 (parse-xml (str string) &key external-callback content-only 
327                             general-entities parameter-entities
328                             uri-to-package)
329
330 An easy way to parse a file containing XML input:
331
332 (with-open-file (p "example.xml")
333    (parse-xml p :content-only p))
334
335 net.xml.parser unexported special variables:
336
337 *debug-xml*
338
339 When not bound to nil, generates XML lexical state and intermediary
340 parse result debugging output.
341
342 *debug-dtd*
343
344 When not bound to nil, generates DTD lexical state and intermediary
345 parse result debugging output.