~openerp-groupes/openobject-server/6.0-fix-setup-windows

« back to all changes in this revision

Viewing changes to bin/reportlab/lib/rparsexml.py

  • Committer: pinky
  • Date: 2006-12-07 13:41:40 UTC
  • Revision ID: pinky-3f10ee12cea3c4c75cef44ab04ad33ef47432907
New trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""Radically simple xml parsing
 
2
 
 
3
Example parse
 
4
 
 
5
<this type="xml">text <b>in</b> xml</this>
 
6
 
 
7
( "this",
 
8
  {"type": "xml"},
 
9
  [ "text ",
 
10
    ("b", None, ["in"], None),
 
11
    " xml"
 
12
    ]
 
13
   None )
 
14
 
 
15
{ 0: "this"
 
16
  "type": "xml"
 
17
  1: ["text ",
 
18
      {0: "b", 1:["in"]},
 
19
      " xml"]
 
20
}
 
21
 
 
22
Ie, xml tag translates to a tuple:
 
23
 (name, dictofattributes, contentlist, miscellaneousinfo)
 
24
 
 
25
where miscellaneousinfo can be anything, (but defaults to None)
 
26
(with the intention of adding, eg, line number information)
 
27
 
 
28
special cases: name of "" means "top level, no containing tag".
 
29
Top level parse always looks like this
 
30
 
 
31
   ("", list, None, None)
 
32
 
 
33
 contained text of None means <simple_tag\>
 
34
 
 
35
In order to support stuff like
 
36
 
 
37
   <this></this><one></one>
 
38
 
 
39
AT THE MOMENT &amp; ETCETERA ARE IGNORED. THEY MUST BE PROCESSED
 
40
IN A POST-PROCESSING STEP.
 
41
 
 
42
PROLOGUES ARE NOT UNDERSTOOD.  OTHER STUFF IS PROBABLY MISSING.
 
43
"""
 
44
 
 
45
RequirePyRXP = 0        # set this to 1 to disable the nonvalidating fallback parser.
 
46
 
 
47
import string
 
48
try:
 
49
    #raise ImportError, "dummy error"
 
50
    simpleparse = 0
 
51
    import pyRXP
 
52
    if pyRXP.version>='0.5':
 
53
        def warnCB(s):
 
54
            print s
 
55
        pyRXP_parser = pyRXP.Parser(
 
56
                            ErrorOnValidityErrors=1,
 
57
                            NoNoDTDWarning=1,
 
58
                            ExpandCharacterEntities=0,
 
59
                            ExpandGeneralEntities=0,
 
60
                            warnCB = warnCB,
 
61
                            srcName='string input')
 
62
        def parsexml(xmlText, oneOutermostTag=0,eoCB=None,entityReplacer=None):
 
63
            pyRXP_parser.eoCB = eoCB
 
64
            p = pyRXP_parser.parse(xmlText)
 
65
            return oneOutermostTag and p or ('',None,[p],None)
 
66
    else:
 
67
        def parsexml(xmlText, oneOutermostTag=0,eoCB=None,entityReplacer=None):
 
68
            '''eoCB is the entity open callback'''
 
69
            def warnCB(s):
 
70
                print s
 
71
            flags = 0x0157e1ff | pyRXP.PARSER_FLAGS['ErrorOnValidityErrors']
 
72
            for k in ('ExpandCharacterEntities','ExpandGeneralEntities'):
 
73
                flags = flags & (~pyRXP.PARSER_FLAGS[k])
 
74
            p = pyRXP.parse(xmlText,srcName='string input',flags=flags,warnCB=warnCB,eoCB=eoCB)
 
75
            return oneOutermostTag and p or ('',None,[p],None)
 
76
except ImportError:
 
77
    simpleparse = 1
 
78
 
 
79
NONAME = ""
 
80
NAMEKEY = 0
 
81
CONTENTSKEY = 1
 
82
CDATAMARKER = "<![CDATA["
 
83
LENCDATAMARKER = len(CDATAMARKER)
 
84
CDATAENDMARKER = "]]>"
 
85
replacelist = [("&lt;", "<"), ("&gt;", ">"), ("&amp;", "&")] # amp must be last
 
86
#replacelist = []
 
87
def unEscapeContentList(contentList):
 
88
    result = []
 
89
    from string import replace
 
90
    for e in contentList:
 
91
        if "&" in e:
 
92
            for (old, new) in replacelist:
 
93
                e = replace(e, old, new)
 
94
        result.append(e)
 
95
    return result
 
96
 
 
97
def parsexmlSimple(xmltext, oneOutermostTag=0,eoCB=None,entityReplacer=unEscapeContentList):
 
98
    """official interface: discard unused cursor info"""
 
99
    if RequirePyRXP:
 
100
        raise ImportError, "pyRXP not found, fallback parser disabled"
 
101
    (result, cursor) = parsexml0(xmltext,entityReplacer=entityReplacer)
 
102
    if oneOutermostTag:
 
103
        return result[2][0]
 
104
    else:
 
105
        return result
 
106
 
 
107
if simpleparse:
 
108
    parsexml = parsexmlSimple
 
109
 
 
110
def parseFile(filename):
 
111
    raw = open(filename, 'r').read()
 
112
    return parsexml(raw)
 
113
 
 
114
verbose = 0
 
115
 
 
116
def skip_prologue(text, cursor):
 
117
    """skip any prologue found after cursor, return index of rest of text"""
 
118
    ### NOT AT ALL COMPLETE!!! definitely can be confused!!!
 
119
    from string import find
 
120
    prologue_elements = ("!DOCTYPE", "?xml", "!--")
 
121
    done = None
 
122
    while done is None:
 
123
        #print "trying to skip:", repr(text[cursor:cursor+20])
 
124
        openbracket = find(text, "<", cursor)
 
125
        if openbracket<0: break
 
126
        past = openbracket+1
 
127
        found = None
 
128
        for e in prologue_elements:
 
129
            le = len(e)
 
130
            if text[past:past+le]==e:
 
131
                found = 1
 
132
                cursor = find(text, ">", past)
 
133
                if cursor<0:
 
134
                    raise ValueError, "can't close prologue %s" % `e`
 
135
                cursor = cursor+1
 
136
        if found is None:
 
137
            done=1
 
138
    #print "done skipping"
 
139
    return cursor
 
140
 
 
141
def parsexml0(xmltext, startingat=0, toplevel=1,
 
142
        # snarf in some globals
 
143
        strip=string.strip, split=string.split, find=string.find, entityReplacer=unEscapeContentList,
 
144
        #len=len, None=None
 
145
        #LENCDATAMARKER=LENCDATAMARKER, CDATAMARKER=CDATAMARKER
 
146
        ):
 
147
    """simple recursive descent xml parser...
 
148
       return (dictionary, endcharacter)
 
149
       special case: comment returns (None, endcharacter)"""
 
150
    #from string import strip, split, find
 
151
    #print "parsexml0", `xmltext[startingat: startingat+10]`
 
152
    # DEFAULTS
 
153
    NameString = NONAME
 
154
    ContentList = AttDict = ExtraStuff = None
 
155
    if toplevel is not None:
 
156
        #if verbose: print "at top level"
 
157
        #if startingat!=0:
 
158
        #    raise ValueError, "have to start at 0 for top level!"
 
159
        xmltext = strip(xmltext)
 
160
    cursor = startingat
 
161
    #look for interesting starting points
 
162
    firstbracket = find(xmltext, "<", cursor)
 
163
    afterbracket2char = xmltext[firstbracket+1:firstbracket+3]
 
164
    #print "a", `afterbracket2char`
 
165
    #firstampersand = find(xmltext, "&", cursor)
 
166
    #if firstampersand>0 and firstampersand<firstbracket:
 
167
    #    raise ValueError, "I don't handle ampersands yet!!!"
 
168
    docontents = 1
 
169
    if firstbracket<0:
 
170
            # no tags
 
171
            #if verbose: print "no tags"
 
172
            if toplevel is not None:
 
173
                #D = {NAMEKEY: NONAME, CONTENTSKEY: [xmltext[cursor:]]}
 
174
                ContentList = [xmltext[cursor:]]
 
175
                if entityReplacer: ContentList = entityReplacer(ContentList)
 
176
                return (NameString, AttDict, ContentList, ExtraStuff), len(xmltext)
 
177
            else:
 
178
                raise ValueError, "no tags at non-toplevel %s" % `xmltext[cursor:cursor+20]`
 
179
    #D = {}
 
180
    L = []
 
181
    # look for start tag
 
182
    # NEED to force always outer level is unnamed!!!
 
183
    #if toplevel and firstbracket>0:
 
184
    #afterbracket2char = xmltext[firstbracket:firstbracket+2]
 
185
    if toplevel is not None:
 
186
            #print "toplevel with no outer tag"
 
187
            NameString = name = NONAME
 
188
            cursor = skip_prologue(xmltext, cursor)
 
189
            #break
 
190
    elif firstbracket<0:
 
191
            raise ValueError, "non top level entry should be at start tag: %s" % repr(xmltext[:10])
 
192
    # special case: CDATA
 
193
    elif afterbracket2char=="![" and xmltext[firstbracket:firstbracket+9]=="<![CDATA[":
 
194
            #print "in CDATA", cursor
 
195
            # skip straight to the close marker
 
196
            startcdata = firstbracket+9
 
197
            endcdata = find(xmltext, CDATAENDMARKER, startcdata)
 
198
            if endcdata<0:
 
199
                raise ValueError, "unclosed CDATA %s" % repr(xmltext[cursor:cursor+20])
 
200
            NameString = CDATAMARKER
 
201
            ContentList = [xmltext[startcdata: endcdata]]
 
202
            cursor = endcdata+len(CDATAENDMARKER)
 
203
            docontents = None
 
204
    # special case COMMENT
 
205
    elif afterbracket2char=="!-" and xmltext[firstbracket:firstbracket+4]=="<!--":
 
206
            #print "in COMMENT"
 
207
            endcommentdashes = find(xmltext, "--", firstbracket+4)
 
208
            if endcommentdashes<firstbracket:
 
209
                raise ValueError, "unterminated comment %s" % repr(xmltext[cursor:cursor+20])
 
210
            endcomment = endcommentdashes+2
 
211
            if xmltext[endcomment]!=">":
 
212
                raise ValueError, "invalid comment: contains double dashes %s" % repr(xmltext[cursor:cursor+20])
 
213
            return (None, endcomment+1) # shortcut exit
 
214
    else:
 
215
            # get the rest of the tag
 
216
            #if verbose: print "parsing start tag"
 
217
            # make sure the tag isn't in doublequote pairs
 
218
            closebracket = find(xmltext, ">", firstbracket)
 
219
            noclose = closebracket<0
 
220
            startsearch = closebracket+1
 
221
            pastfirstbracket = firstbracket+1
 
222
            tagcontent = xmltext[pastfirstbracket:closebracket]
 
223
            # shortcut, no equal means nothing but name in the tag content
 
224
            if '=' not in tagcontent:
 
225
                if tagcontent[-1]=="/":
 
226
                    # simple case
 
227
                    #print "simple case", tagcontent
 
228
                    tagcontent = tagcontent[:-1]
 
229
                    docontents = None
 
230
                name = strip(tagcontent)
 
231
                NameString = name
 
232
                cursor = startsearch
 
233
            else:
 
234
                if '"' in tagcontent:
 
235
                    # check double quotes
 
236
                    stop = None
 
237
                    # not inside double quotes! (the split should have odd length)
 
238
                    if noclose or len(split(tagcontent+".", '"'))% 2:
 
239
                        stop=1
 
240
                    while stop is None:
 
241
                        closebracket = find(xmltext, ">", startsearch)
 
242
                        startsearch = closebracket+1
 
243
                        noclose = closebracket<0
 
244
                        tagcontent = xmltext[pastfirstbracket:closebracket]
 
245
                        # not inside double quotes! (the split should have odd length)
 
246
                        if noclose or len(split(tagcontent+".", '"'))% 2:
 
247
                            stop=1
 
248
                if noclose:
 
249
                    raise ValueError, "unclosed start tag %s" % repr(xmltext[firstbracket:firstbracket+20])
 
250
                cursor = startsearch
 
251
                #cursor = closebracket+1
 
252
                # handle simple tag /> syntax
 
253
                if xmltext[closebracket-1]=="/":
 
254
                    #if verbose: print "it's a simple tag"
 
255
                    closebracket = closebracket-1
 
256
                    tagcontent = tagcontent[:-1]
 
257
                    docontents = None
 
258
                #tagcontent = xmltext[firstbracket+1:closebracket]
 
259
                tagcontent = strip(tagcontent)
 
260
                taglist = split(tagcontent, "=")
 
261
                #if not taglist:
 
262
                #    raise ValueError, "tag with no name %s" % repr(xmltext[firstbracket:firstbracket+20])
 
263
                taglist0 = taglist[0]
 
264
                taglist0list = split(taglist0)
 
265
                #if len(taglist0list)>2:
 
266
                #    raise ValueError, "bad tag head %s" % repr(taglist0)
 
267
                name = taglist0list[0]
 
268
                #print "tag name is", name
 
269
                NameString = name
 
270
                # now parse the attributes
 
271
                attributename = taglist0list[-1]
 
272
                # put a fake att name at end of last taglist entry for consistent parsing
 
273
                taglist[-1] = taglist[-1]+" f"
 
274
                AttDict = D = {}
 
275
                taglistindex = 1
 
276
                lasttaglistindex = len(taglist)
 
277
                #for attentry in taglist[1:]:
 
278
                while taglistindex<lasttaglistindex:
 
279
                    #print "looking for attribute named", attributename
 
280
                    attentry = taglist[taglistindex]
 
281
                    taglistindex = taglistindex+1
 
282
                    attentry = strip(attentry)
 
283
                    if attentry[0]!='"':
 
284
                        raise ValueError, "attribute value must start with double quotes" + repr(attentry)
 
285
                    while '"' not in attentry[1:]:
 
286
                        # must have an = inside the attribute value...
 
287
                        if taglistindex>lasttaglistindex:
 
288
                            raise ValueError, "unclosed value " + repr(attentry)
 
289
                        nextattentry = taglist[taglistindex]
 
290
                        taglistindex = taglistindex+1
 
291
                        attentry = "%s=%s" % (attentry, nextattentry)
 
292
                    attentry = strip(attentry) # only needed for while loop...
 
293
                    attlist = split(attentry)
 
294
                    nextattname = attlist[-1]
 
295
                    attvalue = attentry[:-len(nextattname)]
 
296
                    attvalue = strip(attvalue)
 
297
                    try:
 
298
                        first = attvalue[0]; last=attvalue[-1]
 
299
                    except:
 
300
                        raise ValueError, "attvalue,attentry,attlist="+repr((attvalue, attentry,attlist))
 
301
                    if first==last=='"' or first==last=="'":
 
302
                        attvalue = attvalue[1:-1]
 
303
                    #print attributename, "=", attvalue
 
304
                    D[attributename] = attvalue
 
305
                    attributename = nextattname
 
306
    # pass over other tags and content looking for end tag
 
307
    if docontents is not None:
 
308
        #print "now looking for end tag"
 
309
        ContentList = L
 
310
    while docontents is not None:
 
311
            nextopenbracket = find(xmltext, "<", cursor)
 
312
            if nextopenbracket<cursor:
 
313
                #if verbose: print "no next open bracket found"
 
314
                if name==NONAME:
 
315
                    #print "no more tags for noname", repr(xmltext[cursor:cursor+10])
 
316
                    docontents=None # done
 
317
                    remainder = xmltext[cursor:]
 
318
                    cursor = len(xmltext)
 
319
                    if remainder:
 
320
                        L.append(remainder)
 
321
                else:
 
322
                    raise ValueError, "no close bracket for %s found after %s" % (name,repr(xmltext[cursor: cursor+20]))
 
323
            # is it a close bracket?
 
324
            elif xmltext[nextopenbracket+1]=="/":
 
325
                #print "found close bracket", repr(xmltext[nextopenbracket:nextopenbracket+20])
 
326
                nextclosebracket = find(xmltext, ">", nextopenbracket)
 
327
                if nextclosebracket<nextopenbracket:
 
328
                    raise ValueError, "unclosed close tag %s" % repr(xmltext[nextopenbracket: nextopenbracket+20])
 
329
                closetagcontents = xmltext[nextopenbracket+2: nextclosebracket]
 
330
                closetaglist = split(closetagcontents)
 
331
                #if len(closetaglist)!=1:
 
332
                    #print closetagcontents
 
333
                    #raise ValueError, "bad close tag format %s" % repr(xmltext[nextopenbracket: nextopenbracket+20])
 
334
                # name should match
 
335
                closename = closetaglist[0]
 
336
                #if verbose: print "closetag name is", closename
 
337
                if name!=closename:
 
338
                    prefix = xmltext[:cursor]
 
339
                    endlinenum = len(split(prefix, "\n"))
 
340
                    prefix = xmltext[:startingat]
 
341
                    linenum = len(split(prefix, "\n"))
 
342
                    raise ValueError, \
 
343
                       "at lines %s...%s close tag name doesn't match %s...%s %s" %(
 
344
                       linenum, endlinenum, `name`, `closename`, repr(xmltext[cursor: cursor+100]))
 
345
                remainder = xmltext[cursor:nextopenbracket]
 
346
                if remainder:
 
347
                    #if verbose: print "remainder", repr(remainder)
 
348
                    L.append(remainder)
 
349
                cursor = nextclosebracket+1
 
350
                #print "for", name, "found close tag"
 
351
                docontents = None # done
 
352
            # otherwise we are looking at a new tag, recursively parse it...
 
353
            # first record any intervening content
 
354
            else:
 
355
                remainder = xmltext[cursor:nextopenbracket]
 
356
                if remainder:
 
357
                    L.append(remainder)
 
358
                #if verbose:
 
359
                #    #print "skipping", repr(remainder)
 
360
                #    #print "--- recursively parsing starting at", xmltext[nextopenbracket:nextopenbracket+20]
 
361
                (parsetree, cursor) = parsexml0(xmltext, startingat=nextopenbracket, toplevel=None, entityReplacer=entityReplacer)
 
362
                if parsetree:
 
363
                    L.append(parsetree)
 
364
        # maybe should check for trailing garbage?
 
365
        # toplevel:
 
366
        #    remainder = strip(xmltext[cursor:])
 
367
        #    if remainder:
 
368
        #        raise ValueError, "trailing garbage at top level %s" % repr(remainder[:20])
 
369
    if ContentList:
 
370
        if entityReplacer: ContentList = entityReplacer(ContentList)
 
371
    t = (NameString, AttDict, ContentList, ExtraStuff)
 
372
    return (t, cursor)
 
373
 
 
374
import types
 
375
def pprettyprint(parsedxml):
 
376
    """pretty printer mainly for testing"""
 
377
    st = types.StringType
 
378
    if type(parsedxml) is st:
 
379
        return parsedxml
 
380
    (name, attdict, textlist, extra) = parsedxml
 
381
    if not attdict: attdict={}
 
382
    join = string.join
 
383
    attlist = []
 
384
    for k in attdict.keys():
 
385
        v = attdict[k]
 
386
        attlist.append("%s=%s" % (k, `v`))
 
387
    attributes = join(attlist, " ")
 
388
    if not name and attributes:
 
389
        raise ValueError, "name missing with attributes???"
 
390
    if textlist is not None:
 
391
        # with content
 
392
        textlistpprint = map(pprettyprint, textlist)
 
393
        textpprint = join(textlistpprint, "\n")
 
394
        if not name:
 
395
            return textpprint # no outer tag
 
396
        # indent it
 
397
        nllist = string.split(textpprint, "\n")
 
398
        textpprint = "   "+join(nllist, "\n   ")
 
399
        return "<%s %s>\n%s\n</%s>" % (name, attributes, textpprint, name)
 
400
    # otherwise must be a simple tag
 
401
    return "<%s %s/>" % (name, attributes)
 
402
 
 
403
dump = 0
 
404
def testparse(s):
 
405
    from time import time
 
406
    from pprint import pprint
 
407
    now = time()
 
408
    D = parsexmlSimple(s)
 
409
    print "DONE", time()-now
 
410
    if dump&4:
 
411
        pprint(D)
 
412
    #pprint(D)
 
413
    if dump&1:
 
414
        print "============== reformatting"
 
415
        p = pprettyprint(D)
 
416
        print p
 
417
 
 
418
def test():
 
419
    testparse("""<this type="xml">text &lt;&gt;<b>in</b> <funnytag foo="bar"/> xml</this>
 
420
                 <!-- comment -->
 
421
                 <![CDATA[
 
422
                 <this type="xml">text <b>in</b> xml</this> ]]>
 
423
                 <tag with="<brackets in values>">just testing brackets feature</tag>
 
424
                 """)
 
425
 
 
426
filenames = [ #"../../reportlab/demos/pythonpoint/pythonpoint.xml",
 
427
              "samples/hamlet.xml"]
 
428
 
 
429
#filenames = ["moa.xml"]
 
430
 
 
431
dump=1
 
432
if __name__=="__main__":
 
433
    test()
 
434
    from time import time
 
435
    now = time()
 
436
    for f in filenames:
 
437
        t = open(f).read()
 
438
        print "parsing", f
 
439
        testparse(t)
 
440
    print "elapsed", time()-now