~fluidity-core/fluidity/embedded_models

« back to all changes in this revision

Viewing changes to libspud/diamond/diamond/schema.py

  • Committer: Timothy Bond
  • Date: 2011-04-14 15:40:24 UTC
  • Revision ID: timothy.bond@imperial.ac.uk-20110414154024-116ci9gq6mwigmaw
Following the move from svn to bzr we change the nature of inclusion of these
four software libraries. Previously, they were included as svn externals and
pulled at latest version for trunk, pinned to specific versions for release
and stable trunk. Since bzr is less elegant at dealing with externals we have
made the decision to include the packages directly into the trunk instead.

At this import the versions are:

libadaptivity: r163
libvtkfortran: r67
libspud: r545
libmba2d: r28

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
 
 
3
#    This file is part of Diamond.
 
4
#
 
5
#    Diamond is free software: you can redistribute it and/or modify
 
6
#    it under the terms of the GNU General Public License as published by
 
7
#    the Free Software Foundation, either version 3 of the License, or
 
8
#    (at your option) any later version.
 
9
#
 
10
#    Diamond is distributed in the hope that it will be useful,
 
11
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
13
#    GNU General Public License for more details.
 
14
#
 
15
#    You should have received a copy of the GNU General Public License
 
16
#    along with Diamond.  If not, see <http://www.gnu.org/licenses/>.
 
17
 
 
18
import base64
 
19
import bz2
 
20
import copy
 
21
import sys
 
22
 
 
23
import cStringIO
 
24
 
 
25
import gtk.gdk
 
26
from lxml import etree
 
27
 
 
28
import debug
 
29
import choice
 
30
import plist
 
31
import preprocess
 
32
import tree
 
33
 
 
34
##########################
 
35
#     SCHEMA CLASS       #
 
36
##########################
 
37
 
 
38
class Schema(object):
 
39
  def __init__(self, schemafile):
 
40
    p = etree.XMLParser(remove_comments=True)
 
41
    self.tree = etree.parse(cStringIO.StringIO(preprocess.preprocess(schemafile)), p)
 
42
 
 
43
    self.callbacks = {'element': self.cb_element,
 
44
                      'documentation': self.cb_documentation,
 
45
                      'value': self.cb_value,
 
46
                      'attribute': self.cb_attribute,
 
47
                      'data': self.cb_data,
 
48
                      'optional': self.cb_optional,
 
49
                      'zeroOrMore': self.cb_zeroormore,
 
50
                      'oneOrMore': self.cb_oneormore,
 
51
                      'choice': self.cb_choice,
 
52
                      'empty': self.cb_empty,
 
53
                      'list': self.cb_list,
 
54
                      'group': self.cb_group,
 
55
                      'interleave': self.cb_group,
 
56
                      'name': self.cb_name,
 
57
                      'text': self.cb_text,
 
58
                      'anyName' : self.cb_anyname,
 
59
                      'nsName' : self.cb_nsname,
 
60
                      'except' : self.cb_except,
 
61
                      'ignore' : self.cb_ignore,
 
62
                      'notAllowed' : self.cb_notallowed}
 
63
                      
 
64
    self.lost_eles  = []
 
65
    self.added_eles = []
 
66
    self.lost_attrs  = []
 
67
    self.added_attrs = []
 
68
  
 
69
    return
 
70
 
 
71
  def element_children(self, element):
 
72
    """
 
73
    Return a list of the children of the supplied element, following references
 
74
    as required.
 
75
    """
 
76
 
 
77
    children = []
 
78
    for child1 in element.iterchildren(tag=etree.Element):
 
79
      if self.tag(child1) == "ref":
 
80
        if not "name" in child1.keys():
 
81
          debug.deprint("Warning: Encountered reference with no name")
 
82
          continue
 
83
 
 
84
        name = child1.get("name")
 
85
 
 
86
        xpath = self.tree.xpath('/t:grammar/t:define[@name="' + name + '"]',
 
87
               namespaces={'t': 'http://relaxng.org/ns/structure/1.0'})
 
88
 
 
89
        if len(xpath) == 0:
 
90
          debug.deprint("Warning: Schema reference %s not found" % name, 0)
 
91
          continue
 
92
 
 
93
        for child2 in self.element_children(xpath[0]):
 
94
          children.append(child2)
 
95
      else:
 
96
        children.append(child1)
 
97
 
 
98
    return children
 
99
    
 
100
  def choice_children(self, children):
 
101
    """
 
102
    Collapse all choices within a choice into a single list of (non-choice) children
 
103
    """
 
104
  
 
105
    out_children = []
 
106
    for child in children:
 
107
      if self.tag(child) == "choice":
 
108
        out_children = out_children + self.choice_children(self.element_children(child))
 
109
      else:
 
110
        out_children.append(child)
 
111
        
 
112
    return out_children
 
113
 
 
114
  def valid_children(self, eid):
 
115
    if isinstance(eid, tree.Tree):
 
116
      eid = eid.schemaname
 
117
 
 
118
    if eid == ":start":
 
119
      try:
 
120
        node = self.tree.xpath('/t:grammar/t:start', namespaces={'t': 'http://relaxng.org/ns/structure/1.0'})[0]
 
121
      except:
 
122
        debug.deprint("No valid start node found. Are you using a library Relax-NG file like spud_base.rng?", 0)
 
123
        sys.exit(0)
 
124
    else:
 
125
      xpath = self.tree.xpath(eid)
 
126
      if len(xpath) == 0:
 
127
        debug.deprint("Warning: no element with XPath %s" % eid)
 
128
        return []
 
129
      node = xpath[0]
 
130
 
 
131
    results = []
 
132
 
 
133
    for child in self.element_children(node):
 
134
      self.append(results, self.to_tree(child))
 
135
 
 
136
    if eid == ":start" and len(results) != 1:
 
137
      debug.deprint("Error: there must be exactly one root element in an XML document, but found:", 0)
 
138
      for result in results:
 
139
        debug.deprint("  %s" % result.name, 0)
 
140
      sys.exit(1)
 
141
      
 
142
    return results
 
143
 
 
144
  def to_tree(self, element):
 
145
    tag = self.tag(element)
 
146
    f = self.callbacks[tag]
 
147
    facts = {}
 
148
    x = f(element, facts)
 
149
    return x
 
150
 
 
151
  #############################################
 
152
  # Beginning of schema processing functions. #
 
153
  #############################################
 
154
 
 
155
  def cb_name(self, element, facts):
 
156
    name = element.text
 
157
    facts["name"] = name
 
158
 
 
159
  def cb_element(self, element, facts):
 
160
    newfacts = {}
 
161
    if "cardinality" in facts:
 
162
      newfacts["cardinality"] = facts["cardinality"]
 
163
 
 
164
    if "name" in element.keys():
 
165
      newfacts["name"] = element.get("name")
 
166
    else:
 
167
      debug.deprint("Warning: Encountered element with no name")
 
168
 
 
169
    newfacts['schemaname'] = self.tree.getpath(element)
 
170
 
 
171
    for child in self.element_children(element):
 
172
      tag = self.tag(child)
 
173
 
 
174
      if tag not in ['element', 'optional', 'zeroOrMore', 'oneOrMore', 'ignore']:
 
175
        f = self.callbacks[tag]
 
176
        x = f(child, newfacts)
 
177
 
 
178
    try:
 
179
      d = newfacts["datatype"]
 
180
      if isinstance(d, tuple):
 
181
        new_d = []
 
182
 
 
183
        for x in d:
 
184
          if x is not None:
 
185
            new_d.append(x)
 
186
 
 
187
        d = tuple(new_d)
 
188
        newfacts["datatype"] = d
 
189
        if len(d) == 0:
 
190
          newfacts["datatype"] = None
 
191
        elif len(d) == 1 and isinstance(d[0], plist.List):
 
192
          newfacts["datatype"] = d[0]
 
193
        else:
 
194
          l_values = []
 
195
          l_data   = []
 
196
          for x in d:
 
197
            if isinstance(x, str):
 
198
              l_values.append(x)
 
199
            else:
 
200
              l_data.append(x)
 
201
 
 
202
          if len(l_data) > 1:
 
203
            if "name" in element.keys():
 
204
              debug.deprint("Warning: Element %s has multiple datatypes - using first one" % newfacts["name"])
 
205
            else:
 
206
              debug.deprint("Warning: Unnamed element has multiple datatypes - using first one")
 
207
 
 
208
          if len(l_data) > 0:
 
209
            if len(l_values) == 0:
 
210
              newfacts["datatype"] = l_data[0]
 
211
            else:
 
212
              newfacts["datatype"] = tuple([tuple(l_values)] + l_data[0])
 
213
    except KeyError:
 
214
      pass
 
215
 
 
216
    return tree.Tree(**newfacts)
 
217
 
 
218
  def cb_documentation(self, element, facts):
 
219
    facts['doc'] = element.text
 
220
 
 
221
  def cb_value(self, element, facts):
 
222
    if "datatype" in facts:
 
223
      l = list(facts["datatype"])
 
224
    else:
 
225
      l = []
 
226
 
 
227
    l.append(element.text)
 
228
    facts["datatype"] = tuple(l)
 
229
 
 
230
  def cb_attribute(self, element, facts):
 
231
    if not "name" in element.keys():
 
232
      debug.deprint("Warning: Encountered attribute with no name")
 
233
      return
 
234
 
 
235
    newfacts = {}
 
236
    name = element.get("name")
 
237
 
 
238
    for child in self.element_children(element):
 
239
      tag = self.tag(child)
 
240
      f = self.callbacks[tag]
 
241
      x = f(child, newfacts)
 
242
 
 
243
    if "attrs" not in facts:
 
244
      facts["attrs"] = {}
 
245
 
 
246
    try:
 
247
      datatype = newfacts["datatype"]
 
248
    except:
 
249
      debug.deprint("Warning: Encountered attribute with no datatype")
 
250
      return
 
251
    curval = None
 
252
 
 
253
    if isinstance(datatype, tuple):
 
254
      new_datatype = []
 
255
      for x in datatype:
 
256
        if not x is None:
 
257
          new_datatype.append(x)
 
258
      datatype = new_datatype
 
259
      if len(datatype) == 0:
 
260
        datatype = None
 
261
      elif len(datatype) == 1:
 
262
        datatype = datatype[0]
 
263
        if isinstance(datatype, str):
 
264
          curval = datatype
 
265
          datatype = 'fixed'
 
266
        else:
 
267
          curval = None
 
268
      else:
 
269
        l_values = []
 
270
        l_data   = []
 
271
        for x in datatype:
 
272
          if isinstance(x, str):
 
273
            l_values.append(x)
 
274
          else:
 
275
            l_data.append(x)
 
276
 
 
277
        if len(l_data) > 0:
 
278
          debug.deprint("Warning: Attribute %s has multiple datatypes - using first one" % name)
 
279
          if len(l_values) == 0:
 
280
            datatype = l_data[0]
 
281
          else:
 
282
            datatype = tuple([tuple(l_values)] + l_data[0])
 
283
        else:
 
284
          datatype = tuple(l_values)
 
285
 
 
286
    facts["attrs"][name] = (datatype, curval)
 
287
 
 
288
  def cb_data(self, element, facts):
 
289
    if "datatype" in facts:
 
290
      if isinstance(facts["datatype"], tuple):
 
291
        l = list(facts["datatype"])
 
292
      else:
 
293
        l = [facts["datatype"]]
 
294
    else:
 
295
      l = []
 
296
 
 
297
    mapping = {'integer': int,
 
298
               'float': float,
 
299
               'double': float,
 
300
               'decimal': float,
 
301
               'string': str,
 
302
               'ID' : str,
 
303
               'anyURI' : str,
 
304
               'IDREF' : int,
 
305
               'NMTOKEN' : str,
 
306
               'boolean': bool}
 
307
 
 
308
    datatype_name = element.get("type")
 
309
    l.append(mapping[datatype_name])
 
310
    if len(l) == 1:
 
311
      facts["datatype"] = l[0]
 
312
    else:
 
313
      facts["datatype"] = tuple(l)
 
314
 
 
315
  def cb_optional(self, element, facts):
 
316
    facts["cardinality"] = '?'
 
317
    r = []
 
318
    for child in self.element_children(element):
 
319
      tag = self.tag(child)
 
320
      f = self.callbacks[tag]
 
321
      self.append(r, f(child, facts))
 
322
 
 
323
    return r
 
324
 
 
325
  def cb_zeroormore(self, element, facts):
 
326
    facts["cardinality"] = '*'
 
327
    r = []
 
328
    for child in self.element_children(element):
 
329
      tag = self.tag(child)
 
330
      f = self.callbacks[tag]
 
331
      self.append(r, f(child, facts))
 
332
 
 
333
    return r
 
334
 
 
335
  def cb_oneormore(self, element, facts):
 
336
    facts["cardinality"] = '+'
 
337
    r = []
 
338
    for child in self.element_children(element):
 
339
      tag = self.tag(child)
 
340
      f = self.callbacks[tag]
 
341
      self.append(r, f(child, facts))
 
342
 
 
343
    return r
 
344
 
 
345
  def cb_choice(self, element, facts):
 
346
    # there are really two cases here.
 
347
    # choice between values of elements,
 
348
    # and choice between elements
 
349
 
 
350
    tagnames = [self.tag(child) for child in element]
 
351
 
 
352
    if "value" in tagnames:
 
353
      for child in self.element_children(element):
 
354
        tag = self.tag(child)
 
355
        f = self.callbacks[tag]
 
356
        x = f(child, facts)
 
357
 
 
358
    else:
 
359
      if "schemaname" in facts:
 
360
        return
 
361
 
 
362
      r = []
 
363
      children = self.choice_children(self.element_children(element))
 
364
      
 
365
      # bloody simplified RNG
 
366
      if len(children) == 2:
 
367
        empty = [x for x in children if self.tag(x) == "empty"]
 
368
        nonempty = [x for x in children if self.tag(x) != "empty"]
 
369
        if len(empty) > 0:
 
370
          tag = self.tag(nonempty[0])
 
371
          if tag == "oneOrMore":
 
372
            return self.cb_oneormore(element, facts)
 
373
          else:
 
374
            f = self.callbacks[tag]
 
375
            return f(element, facts)
 
376
 
 
377
      for child in children:
 
378
        newfacts = {}
 
379
        tag = self.tag(child)
 
380
        f = self.callbacks[tag]
 
381
        self.append(r, f(child, newfacts))
 
382
 
 
383
      return choice.Choice(r, **facts)
 
384
 
 
385
  def cb_empty(self, element, facts):
 
386
    pass
 
387
 
 
388
  def cb_list(self, element, facts):
 
389
    newfacts = {}
 
390
    for child in self.element_children(element):
 
391
      tag = self.tag(child)
 
392
      f = self.callbacks[tag]
 
393
      f(child, newfacts)
 
394
 
 
395
    d = newfacts["datatype"]
 
396
    try:
 
397
      c = newfacts["cardinality"]
 
398
    except KeyError:
 
399
      c = ''
 
400
      if isinstance(d, tuple):
 
401
        c = str(len(d))
 
402
        d = d[0]
 
403
 
 
404
    l = plist.List(d, c)
 
405
    if "datatype" in facts:
 
406
      e = list(facts["datatype"])
 
407
    else:
 
408
      e = []
 
409
 
 
410
    e.append(l)
 
411
    facts["datatype"] = tuple(e)
 
412
 
 
413
  def cb_group(self, element, facts):
 
414
    results = []
 
415
    for child in self.element_children(element):
 
416
      newfacts = {}
 
417
      tag = self.tag(child)
 
418
      f = self.callbacks[tag]
 
419
      self.append(results, f(child, newfacts))
 
420
 
 
421
    return results
 
422
 
 
423
  def cb_text(self, element, facts):
 
424
    if "datatype" in facts:
 
425
      if isinstance(facts["datatype"], tuple):
 
426
        l = list(facts["datatype"])
 
427
      else:
 
428
        l = [facts["datatype"]]
 
429
    else:
 
430
      l = []
 
431
 
 
432
    l.append(str)
 
433
    if len(l) == 1:
 
434
      facts["datatype"] = l[0]
 
435
    else:
 
436
      facts["datatype"] = tuple(l)
 
437
 
 
438
  def cb_anyname(self, element, facts):
 
439
    debug.deprint("anyName element found. Yet to handle.", 0)
 
440
#    sys.exit(1)
 
441
 
 
442
  def cb_nsname(self, element, facts):
 
443
    debug.deprint("nsName element found. Yet to handle.", 0)
 
444
#    sys.exit(1)
 
445
 
 
446
  def cb_except(self, element, facts):
 
447
    debug.deprint("except element found. Yet to handle.", 0)
 
448
#    sys.exit(1)
 
449
 
 
450
  def cb_ignore(self, element, facts):
 
451
    pass
 
452
 
 
453
  def cb_notallowed(self, element, facts):
 
454
    debug.dprint("notallowed element found. Yet to handle.", 0)
 
455
 
 
456
  #######################################
 
457
  # End of schema processing functions. #
 
458
  #######################################
 
459
 
 
460
  def tag(self, element):
 
461
    # Ignore non-RelaxNG elements. Is this the best way to handle it? 
 
462
    namespace = element.tag.split('}')[0]
 
463
    if namespace.find("relaxng") != -1:
 
464
      return element.tag.split('}')[-1]
 
465
    else:
 
466
      return "ignore"
 
467
 
 
468
  # append - append either a list or single element 'x' to 'r'.
 
469
  def append(self, r, x):
 
470
    if x is None:
 
471
      return
 
472
 
 
473
    if isinstance(x, list):
 
474
      for y in x:
 
475
        r.append(y)
 
476
      return
 
477
 
 
478
    r.append(x)
 
479
 
 
480
  ##########################################
 
481
  # Beginning of XML processing functions. #
 
482
  ##########################################
 
483
 
 
484
  # read takes a file handle, constructs a generic in-memory representation using the
 
485
  # the etree API, and then converts it to a tree of Tree and Choice elements.
 
486
  def read(self, xmlfile):
 
487
    doc = etree.parse(xmlfile)
 
488
 
 
489
    self.lost_eles = []
 
490
    self.added_eles = []
 
491
    self.lost_attrs  = []
 
492
    self.added_attrs = []
 
493
 
 
494
    datatree = self.valid_children(":start")[0]
 
495
    xmlnode  = doc.getroot()
 
496
    self.xml_read_merge(datatree, xmlnode)
 
497
    self.xml_read_core(datatree, xmlnode, doc)
 
498
 
 
499
    if len(self.lost_eles) != 0:
 
500
      debug.deprint("WARNING: Lost XML elements:\n" + str(self.lost_eles))
 
501
    if len(self.added_eles) != 0:
 
502
      debug.deprint("WARNING: Added XML elements:\n" + str(self.added_eles))
 
503
    if len(self.lost_attrs) != 0:
 
504
      debug.deprint("WARNING: Lost XML attributes:\n" + str(self.lost_attrs))
 
505
    if len(self.added_eles) != 0:
 
506
      debug.deprint("WARNING: Added XML attributes:\n" + str(self.added_attrs))
 
507
      
 
508
    return datatree
 
509
 
 
510
  def xml_read_merge(self, datatree, xmlnode):
 
511
    # The datatree has the following set:
 
512
    # name, schemaname, doc, cardinality, datatype, parent,
 
513
    # attribute datatypes.
 
514
    # the xmlnode contains the following information:
 
515
    # attribute values, data
 
516
    # merge the two.
 
517
    
 
518
    datatree.xmlnode = xmlnode
 
519
    xmlkeys = xmlnode.keys()
 
520
 
 
521
    if datatree.__class__ is tree.Tree:
 
522
      to_set = datatree
 
523
    elif datatree.__class__ is choice.Choice:
 
524
      if "name" in xmlkeys:
 
525
        xmlname = xmlnode.get("name")
 
526
        have_found = False
 
527
 
 
528
        possibles = [tree_choice for tree_choice in datatree.choices() if tree_choice.name == xmlnode.tag]
 
529
        # first loop over the fixed-value names
 
530
        for tree_choice in possibles:
 
531
          if "name" not in tree_choice.attrs:
 
532
            continue
 
533
 
 
534
          datatype = tree_choice.attrs["name"][0]
 
535
          if datatype == 'fixed':
 
536
            treename = tree_choice.attrs["name"][1]
 
537
            if treename == xmlname:
 
538
              have_found = True
 
539
              datatree.set_active_choice_by_ref(tree_choice)
 
540
              break
 
541
 
 
542
        # if we haven't found it, look for a generic name
 
543
        if have_found is False:
 
544
          for tree_choice in possibles:
 
545
            if "name" not in tree_choice.attrs:
 
546
              continue
 
547
 
 
548
            datatype = tree_choice.attrs["name"][0]
 
549
            if datatype != 'fixed':
 
550
              have_found = True
 
551
              datatree.set_active_choice_by_ref(tree_choice)
 
552
              break
 
553
 
 
554
      else:
 
555
        datatree.set_active_choice_by_name(xmlnode.tag)
 
556
 
 
557
      to_set = datatree.get_current_tree()
 
558
 
 
559
    # catch any lost XML attributes
 
560
    for key in xmlkeys:
 
561
      if key not in to_set.attrs.keys():
 
562
        self.lost_attrs += [to_set.name + '/' + key]
 
563
 
 
564
    # attribute values.
 
565
    for key in to_set.attrs.keys():
 
566
      if key in xmlkeys:
 
567
        try:
 
568
          to_set.set_attr(key, xmlnode.get(key))
 
569
        except:
 
570
          pass
 
571
      else:
 
572
        self.added_attrs += [to_set.name + '/' + key]
 
573
 
 
574
    # Get the text value (the node's data)
 
575
    if xmlnode.text is not None:
 
576
     try:
 
577
       text=xmlnode.text.strip()
 
578
       if text != "":
 
579
         to_set.set_data(text)
 
580
     except:
 
581
       pass
 
582
      
 
583
 
 
584
    # data.
 
585
    for child in xmlnode.iterchildren(tag=etree.Element):
 
586
      if child.tail is not None:
 
587
         try:
 
588
           text = child.tail.strip()
 
589
           if text != "":
 
590
             to_set.set_data(text)
 
591
             break
 
592
         except:
 
593
           pass
 
594
 
 
595
    to_set.recompute_validity()
 
596
    datatree.recompute_validity()
 
597
 
 
598
  ###########################################################################################
 
599
  # construct the priority queue
 
600
  # we treat compulsory nodes first, then descend through the cardinalities
 
601
  ###########################################################################################  
 
602
  
 
603
  def construct_priority_queue(self, schemachildren):
 
604
    # priority_queue will store the schemachildren, in the order in which
 
605
    # they query data from the XML
 
606
    priority_queue = []
 
607
    
 
608
    # compulsory first.
 
609
    for schemachild in schemachildren:
 
610
      if schemachild.cardinality == '':
 
611
        priority_queue.append(schemachild)
 
612
 
 
613
    # then oneormore.
 
614
    for schemachild in schemachildren:
 
615
      if schemachild.cardinality == '+':
 
616
        priority_queue.append(schemachild)
 
617
 
 
618
    # then, optional
 
619
    for schemachild in schemachildren:
 
620
      if schemachild.cardinality == '?':
 
621
        priority_queue.append(schemachild)
 
622
 
 
623
    # then zeroormore.
 
624
    for schemachild in schemachildren:
 
625
      if schemachild.cardinality == '*':
 
626
        priority_queue.append(schemachild)
 
627
        
 
628
    return priority_queue
 
629
 
 
630
  ###########################################################################################
 
631
  # initialise the availability data
 
632
  # avail[name][xmlnode] records whether xmlnode is available or not
 
633
  ###########################################################################################
 
634
  def init_avail_data(self, xmlnode, schemachildren):
 
635
    used = {}
 
636
    avail = {}
 
637
    
 
638
    for xml in xmlnode.iterchildren(tag=etree.Element):
 
639
      used[xml] = False
 
640
    
 
641
    for schemachild in schemachildren:
 
642
      for name in schemachild.get_possible_names():
 
643
        avail[name] = {}
 
644
 
 
645
    for name in avail:
 
646
      for xmldata in xmlnode.iterchildren(tag=name):
 
647
        avail[name][xmldata] = True
 
648
        
 
649
    return (used, avail)
 
650
 
 
651
  ###########################################################################################
 
652
  # assign the available xml nodes to the children the schema says should be there
 
653
  # in order of priority.
 
654
  # xmls[schemachild.schemaname] is the list of xml nodes
 
655
  # that schemachild should take
 
656
  ###########################################################################################
 
657
  def assign_xml_nodes(self, priority_queue, xmlnode, avail):   
 
658
    xmls = {}
 
659
 
 
660
    for schemachild in priority_queue:
 
661
      if schemachild.cardinality in ['', '?']:
 
662
        for curtree in schemachild.choices():
 
663
          name = curtree.name
 
664
 
 
665
          have_fixed_name = False
 
666
          if "name" in curtree.attrs:
 
667
            datatype = curtree.attrs["name"][0]
 
668
            if datatype == 'fixed':
 
669
              have_fixed_name = True
 
670
 
 
671
          if have_fixed_name is False:
 
672
            xml = xmlnode.xpath(name)
 
673
          else:
 
674
            xml = xmlnode.xpath(name + '[@name="%s"]' % curtree.get_attr("name"))
 
675
 
 
676
          for xmldata in xml:
 
677
            if avail[name][xmldata]:
 
678
              avail[name][xmldata] = False
 
679
              xmls[schemachild.schemaname] = [xmldata]
 
680
              break 
 
681
 
 
682
          if schemachild.schemaname not in xmls:
 
683
            if schemachild.cardinality == '':
 
684
              xmls[schemachild.schemaname] = copy.deepcopy([])
 
685
            else:
 
686
              xmls[schemachild.schemaname] = copy.deepcopy([])
 
687
      elif schemachild.cardinality in ['*', '+']:
 
688
        xmls[schemachild.schemaname] = copy.deepcopy([])
 
689
        for curtree in schemachild.choices():
 
690
          name = curtree.name
 
691
 
 
692
          have_fixed_name = False
 
693
          if "name" in curtree.attrs:
 
694
            datatype = curtree.attrs["name"][0]
 
695
            if datatype == 'fixed':
 
696
              have_fixed_name = True
 
697
 
 
698
          if have_fixed_name is False:
 
699
            xml = xmlnode.xpath(name)
 
700
          else:
 
701
            xml = xmlnode.xpath(name + '[@name="%s"]' % curtree.get_attr("name"))
 
702
 
 
703
          for xmldata in xml:
 
704
            if avail[name][xmldata]:
 
705
              avail[name][xmldata] = False
 
706
              xmls[schemachild.schemaname].append(xmldata)
 
707
                
 
708
    return xmls
 
709
 
 
710
  ###########################################################################################
 
711
  # now that we have assigned the xml nodes, loop through and grab them
 
712
  # stuff the tree data in bins[schemachild.schemaname]
 
713
  ###########################################################################################
 
714
  def assign_xml_children(self, priority_queue, xmlnode, xmls, schemachildren, used, rootdoc):
 
715
    # bins[schemachild.schemaname] will store the data associated with schemachild
 
716
    bins = {}
 
717
 
 
718
    for schemachild in schemachildren:
 
719
      bins[schemachild.schemaname] = []
 
720
      
 
721
    for schemachild in priority_queue:
 
722
      if schemachild.cardinality in ['', '?']:
 
723
        child = schemachild.copy()
 
724
        child.xmlnode = None
 
725
        child.active = True
 
726
        if len(xmls[schemachild.schemaname]) == 1:
 
727
          xmldata = xmls[schemachild.schemaname][0]
 
728
          used[xmldata] = True
 
729
          self.xml_read_merge(child, xmldata)
 
730
 
 
731
          # Was this part of the uncompressed XML file or part of a hidden comment?
 
732
          if xmldata.getroottree().getroot() != rootdoc.getroot():
 
733
            self.xml_read_core(child.get_current_tree(), xmldata, xmldata.getroottree())
 
734
            child.active = False
 
735
            child.recurse = False
 
736
        else:
 
737
          if schemachild.cardinality == '?':
 
738
            child.active = False
 
739
 
 
740
        bins[schemachild.schemaname] = [child]
 
741
 
 
742
      elif schemachild.cardinality in ['*', '+']:
 
743
        for xmldata in xmls[schemachild.schemaname]:
 
744
          child = schemachild.copy()
 
745
          child.active = True
 
746
          used[xmldata] = True
 
747
          self.xml_read_merge(child, xmldata)
 
748
          bins[schemachild.schemaname].append(child)
 
749
 
 
750
      if schemachild.cardinality == '+':
 
751
        # check that we have at least one.
 
752
        count = len(bins[schemachild.schemaname])
 
753
        if count == 0:
 
754
          child = schemachild.copy()
 
755
          child.active = True
 
756
          child.xmlnode = None
 
757
          bins[schemachild.schemaname] = [child]
 
758
 
 
759
      if schemachild.cardinality in ['*', '+']:
 
760
        # add an inactive instance
 
761
        child = schemachild.copy()
 
762
        child.active = False
 
763
        child.xmlnode = None
 
764
        bins[schemachild.schemaname].append(child)
 
765
 
 
766
      # search for neglected choices
 
767
      if schemachild.__class__ is choice.Choice and schemachild.cardinality in ['', '?']:
 
768
        for child in bins[schemachild.schemaname]:
 
769
 
 
770
          # Does the child have a valid XML node attached?
 
771
          if not hasattr(child, "xmlnode"): continue
 
772
          if child.xmlnode is None: continue
 
773
 
 
774
          current_choice = child.get_current_tree()
 
775
          for tree_choice in child.l:
 
776
            if tree_choice is current_choice: continue
 
777
 
 
778
    return bins
 
779
 
 
780
  # Loop over lost nodes, and store their XML so the user can be notified later.
 
781
  def check_unused_nodes(self, used): 
 
782
    def xml2string(xml):
 
783
      buf = cStringIO.StringIO()
 
784
      buf.write(etree.tostring(xml, pretty_print = True))
 
785
      s = buf.getvalue()
 
786
      buf.close()
 
787
      
 
788
      return s
 
789
  
 
790
    for xml in used:
 
791
      if used[xml] is False:
 
792
        s = xml2string(xml)
 
793
        self.lost_eles += [s]
 
794
  
 
795
  # Append the children to the datatree in the order the schema presents them.
 
796
  # Order matters here.
 
797
  def append_children(self, schemachildren, datatree, bins):
 
798
    for schemachild in schemachildren:
 
799
      for child in bins[schemachild.schemaname]:
 
800
        child.set_parent(datatree)
 
801
        datatree.children.append(child)  
 
802
      
 
803
  # Recurse down the in-memory XML tree, reading elements and merging their
 
804
  # information into the in-memory Tree structure.
 
805
  def read_children(self, datatree, rootdoc):
 
806
    for schild in datatree.children:
 
807
 
 
808
      if hasattr(schild, "recurse"):
 
809
        if schild.recurse is False:
 
810
          continue
 
811
 
 
812
      if schild.__class__ is choice.Choice:
 
813
        child = schild.get_current_tree()
 
814
      else:
 
815
        child = schild
 
816
 
 
817
      if schild.active is False:
 
818
        continue
 
819
 
 
820
      child.children = copy.copy([])
 
821
      self.xml_read_core(child, schild.xmlnode, rootdoc)
 
822
 
 
823
  # xml_read_core recurses throughout the tree, calling xml_read_merge on the current node "xmlnode" and
 
824
  # and reading information about the node's children.
 
825
  def xml_read_core(self, datatree, xmlnode, rootdoc):
 
826
    """This is the part that recurses, you see."""
 
827
 
 
828
    assert len(datatree.children) == 0
 
829
 
 
830
    # no information from XML to be had :-/
 
831
    if xmlnode is None:
 
832
      self.added_eles.append(self.readable_name(datatree))
 
833
      if datatree.active: datatree.add_children(self)
 
834
      return
 
835
 
 
836
    schemachildren = self.valid_children(datatree)
 
837
    
 
838
    priority_queue = self.construct_priority_queue(schemachildren) 
 
839
    (used, avail) = self.init_avail_data(xmlnode, schemachildren)
 
840
    xmls = self.assign_xml_nodes(priority_queue, xmlnode, avail)
 
841
    bins = self.assign_xml_children(priority_queue, xmlnode, xmls, schemachildren, used, rootdoc)
 
842
    self.append_children(schemachildren, datatree, bins)              
 
843
    self.check_unused_nodes(used)
 
844
    self.read_children(datatree, rootdoc)
 
845
 
 
846
    datatree.recompute_validity()
 
847
    
 
848
  def read_errors(self):
 
849
    return self.lost_eles, self.added_eles, self.lost_attrs, self.added_attrs
 
850
 
 
851
  def readable_name(self, datatree):
 
852
    output = ""
 
853
    node = datatree
 
854
    while node is not None:
 
855
      output = node.name + '/' + output
 
856
      node = node.parent
 
857
    return '/' + output[:-1]