pywbem.tupleparse

Package pywbem :: Module tupleparse |
[frames] | no frames]

# Source Code for Module pywbem.tupleparse

       1  #
       2  # (C) Copyright 2003,2004 Hewlett-Packard Development Company, L.P.
       3  # (C) Copyright 2006-2007 Novell, Inc.
       4  #
       5  # This library is free software; you can redistribute it and/or
       6  # modify it under the terms of the GNU Lesser General Public
       7  # License as published by the Free Software Foundation; either
       8  # version 2.1 of the License, or (at your option) any later version.
       9  #
      10  # This program is distributed in the hope that it will be useful, but
      11  # WITHOUT ANY WARRANTY; without even the implied warranty of
      12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      13  # Lesser General Public License for more details.
      14  #
      15  # You should have received a copy of the GNU Lesser General Public
      16  # License along with this program; if not, write to the Free Software
      17  # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
      18  #
      19  # Author: Martin Pool <mbp@hp.com>
      20  # Author: Tim Potter <tpot@hp.com>
      21  # Author: Bart Whiteley <bwhiteley@suse.de>
      22  #
      23
      24  '''Tuple parser for the XML schema representing CIM messages.
      25
      26  This framework is meant to add some value to the tuple-tree
      27  representation of CIM in XML by having the following properties:
      28
      29    - Silently ignoring whitespace text elements
      30
      31    - Conversion from tuple-tree representation into a python dictionary
      32      which can then be accessed in a readable fashion.
      33
      34    - Validation of the XML elements and attributes without having to
      35      use the DTD file or any external tools.
      36
      37  '''
      38
      39  # Implementation: this works by a recursive descent down the CIM XML
      40  # tupletree.  As we walk down, we produce cim_obj and cim_type
      41  # objects representing the CIM message in digested form.
      42
      43  # For each XML node type FOO there is one function parse_foo, which
      44  # returns the digested form by examining a tuple tree rooted at FOO.
      45
      46  # The resulting objects are constrained to the shape of the CIM XML
      47  # tree: if one node in XML contains another, then the corresponding
      48  # CIM object will contain the second.  However, there can be local
      49  # transformations at each node: some levels are ommitted, some are
      50  # transformed into lists or hashes.
      51
      52  # We try to validate that the tree is well-formed too.  The validation
      53  # is more strict than the DTD, but it is forgiving of implementation
      54  # quirks and bugs in Pegasus.
      55
      56  # Bear in mind in the parse functions that each tupletree tuple is
      57  # structured as
      58
      59  #   tt[0]: name string             == name(tt)
      60  #   tt[1]: hash of attributes      == attrs(tt)
      61  #   tt[2]: sequence of children    == kids(tt)
      62
      63  # At the moment this layer is a little inconsistent: in some places it
      64  # returns tupletrees, and in others Python objects.  It may be better
      65  # to hide the tupletree/XML representation from higher level code.
      66
      67
      68  # TODO: Maybe take a DTD fragment like "(DECLGROUP |
      69  # DECLGROUP.WITHNAME | DECLGROUP.WITHPATH)*", parse that and check it
      70  # directly.
      71
      72  # TODO: Syntax-check some attributes with defined formats, such as NAME
      73
      74  # TODO: Implement qualifiers by making subclasses of CIM types with a
      75  # .qualifiers property.
      76
      77  # This module is meant to be safe for 'import *'.
      78
      79  import string
      80  from types import StringTypes
      81
      82  from pywbem import cim_obj, tupletree
      83  from pywbem.cim_obj import CIMInstance, CIMInstanceName, CIMClass, \
      84                             CIMClassName, CIMProperty, CIMMethod, \
      85                             CIMParameter, CIMQualifier, CIMQualifierDeclaration
      86
      87  __all__ = ['ParseError', 'parse_cim', 'parse_any']
      88
      89 -class ParseError(Exception):
      90      """This exception is raised when there is a validation error detected
      91      by the parser."""
      92      pass
      93
      94
      95 -def filter_tuples(l):
      96      """Return only the tuples in a list.
      97
      98      In a tupletree, tuples correspond to XML elements.  Useful for
      99      stripping out whitespace data in a child list."""
     100
     101      if l is None:
     102          return []
     103      else:
     104          return [x for x in l if type(x) == tuple]
     105
     106
     107 -def pcdata(tt):
     108      """Return the concatenated character data within a tt.
     109
     110      The tt must not have non-character children."""
     111      for x in tt[2]:
     112          if not isinstance(x, StringTypes):
     113              raise ParseError, 'unexpected node %s under %s' % (`x`, `tt`)
     114      return ''.join(tt[2])
     115
     116
     117 -def name(tt):
     118      return tt[0]
     119
     120
     121 -def attrs(tt):
     122      return tt[1]
     123
     124
     125 -def kids(tt):
     126      return filter_tuples(tt[2])
     127
     128
     129 -def check_node(tt, nodename, required_attrs=[], optional_attrs=[],
     130                 allowed_children=None,
     131                 allow_pcdata=False):
     132      """Check static local constraints on a single node.
     133
     134      The node must have the given name.  The required attrs must be
     135      present, and the optional attrs may be.
     136
     137      If allowed_children is not None, the node may have children of the
     138      given types.  It can be [] for nodes that may not have any
     139      children.  If it's None, it is assumed the children are validated
     140      in some other way.
     141
     142      If allow_pcdata is true, then non-whitespace text children are allowed.
     143      (Whitespace text nodes are always allowed.)
     144      """
     145
     146      if name(tt) <> nodename:
     147          raise ParseError('expected node type %s, not %s' %
     148                           (nodename, name(tt)))
     149
     150      # Check we have all the required attributes, and no unexpected ones
     151      tt_attrs = {}
     152      if attrs(tt) is not None:
     153          tt_attrs = attrs(tt).copy()
     154
     155      for attr in required_attrs:
     156          if not tt_attrs.has_key(attr):
     157              raise ParseError('expected %s attribute on %s node, but only '
     158                               'have %s' % (attr, name(tt), attrs(tt).keys()))
     159          del tt_attrs[attr]
     160
     161      for attr in optional_attrs:
     162          if tt_attrs.has_key(attr):
     163              del tt_attrs[attr]
     164
     165      if len(tt_attrs.keys()) > 0:
     166          raise ParseError('invalid extra attributes %s' % tt_attrs.keys())
     167
     168      if allowed_children is not None:
     169          for c in kids(tt):
     170              if name(c) not in allowed_children:
     171                  raise ParseError('unexpected node %s under %s; wanted %s'
     172                                   % (name(c), name(tt), allowed_children))
     173
     174      if not allow_pcdata:
     175          for c in tt[2]:
     176              if isinstance(c, StringTypes):
     177                  if c.lstrip(' \t\n') <> '':
     178                      raise ParseError('unexpected non-blank pcdata node %s '
     179                                       'under %s' % (`c`, name(tt)))
     180
     181
     182 -def one_child(tt, acceptable):
     183      """Parse children of a node with exactly one child node.
     184
     185      PCData is ignored.
     186      """
     187
     188      k = kids(tt)
     189
     190      if len(k) <> 1:
     191          raise ParseError('In element %s with attributes %s, expected just '\
     192                  'one child element %s, but got child elements %s' %\
     193                  (name(tt), attrs(tt), acceptable, [t[0] for t in k]))
     194
     195      child = k[0]
     196
     197      if name(child) not in acceptable:
     198          raise ParseError('In element %s with attributes %s, expected one '\
     199                  'child element %s, but got child element %s' %\
     200                  (name(tt), attrs(tt), acceptable, name(child)))
     201
     202      return parse_any(child)
     203
     204
     205 -def optional_child(tt, allowed):
     206      """Parse exactly zero or one of a list of elements from the
     207      child nodes."""
     208
     209      k = kids(tt)
     210
     211      if len(k) > 1:
     212          raise ParseError('In element %s with attributes %s, expected zero or '\
     213                  'one child element %s, but got child elements %s' %\
     214                  (name(tt), attrs(tt), allowed, [t[0] for t in k]))
     215      elif len(k) == 1:
     216          return one_child(tt, allowed)
     217      else:
     218          return None
     219
     220
     221 -def list_of_various(tt, acceptable):
     222      """Parse zero or more of a list of elements from the child nodes.
     223
     224      Each element of the list can be any type from the list of acceptable
     225      nodes."""
     226
     227      r = []
     228
     229      for child in kids(tt):
     230          if name(child) not in acceptable:
     231              raise ParseError('In element %s with attributes %s, expected zero '\
     232                      'or more child elements %s, but got child element %s' %\
     233                      (name(tt), attrs(tt), acceptable, name(child)))
     234          r.append(parse_any(child))
     235
     236      return r
     237
     238
     239 -def list_of_matching(tt, matched):
     240      """Parse only the children of particular types under tt.
     241
     242      Other children are ignored rather than giving an error."""
     243
     244      r = []
     245
     246      for child in kids(tt):
     247          if name(child) not in matched:
     248              continue
     249          r.append(parse_any(child))
     250
     251      return r
     252
     253
     254 -def list_of_same(tt, acceptable):
     255      """Parse a list of elements from child nodes.
     256
     257      The children can be any of the listed acceptable types, but they
     258      must all be the same.
     259      """
     260
     261      k = kids(tt)
     262      if not k:                   # empty list, consistent with list_of_various
     263          return []
     264
     265      w = name(k[0])
     266      if w not in acceptable:
     267          raise ParseError('In element %s with attributes %s, expected child '\
     268                  'elements %s, but got child element %s' %\
     269                  (name(tt), attrs(tt), acceptable, w))
     270      r = []
     271      for child in k:
     272          if name(child) <> w:
     273              raise ParseError('In element %s with attributes %s, expected '\
     274                      'sequence of only child elements %s, but got child '\
     275                      'element %s' % (name(tt), attrs(tt), w, name(child)))
     276          r.append(parse_any(child))
     277
     278      return r
     279
     280
     281 -def notimplemented(tt):
     282      raise ParseError('parser for %s not implemented' % name(tt))
     283
     284  #
     285  # Root element
     286  #
     287
     288 -def parse_cim(tt):
     289      """
     290      <!ELEMENT CIM (MESSAGE | DECLARATION)>
     291      <!ATTLIST CIM
     292          CIMVERSION CDATA #REQUIRED
     293          DTDVERSION CDATA #REQUIRED>
     294      """
     295
     296      check_node(tt, 'CIM', ['CIMVERSION', 'DTDVERSION'])
     297
     298      if not attrs(tt)['CIMVERSION'].startswith('2.'):
     299          raise ParseError('CIMVERSION is %s, expected 2.x.y' %
     300                           attrs(tt)['CIMVERSION'])
     301
     302      child = one_child(tt, ['MESSAGE', 'DECLARATION'])
     303
     304      return name(tt), attrs(tt), child
     305
     306
     307  #
     308  # Declaration elements
     309  #
     310
     311 -def parse_declaration(tt):
     312      """
     313      <!ELEMENT DECLARATION ( DECLGROUP | DECLGROUP.WITHNAME |
     314                              DECLGROUP.WITHPATH )+>
     315
     316      Note: We only support the DECLGROUP child, at this point.
     317      """
     318
     319      check_node(tt, 'DECLARATION')
     320
     321      child = one_child(tt, ['DECLGROUP'])
     322
     323      return name(tt), attrs(tt), child
     324
     325
     326 -def parse_declgroup(tt):
     327      """
     328      <!ELEMENT DECLGROUP ( (LOCALNAMESPACEPATH|NAMESPACEPATH)?,
     329                            QUALIFIER.DECLARATION*, VALUE.OBJECT* )>
     330
     331      Note: We only support the QUALIFIER.DECLARATION and VALUE.OBJECT
     332            children, and with a multiplicity of 1, at this point.
     333      """
     334
     335      check_node(tt, 'DECLGROUP')
     336
     337      child = one_child(tt, ['QUALIFIER.DECLARATION', 'VALUE.OBJECT'])
     338
     339      return name(tt), attrs(tt), child
     340
     341
     342  #
     343  # Object value elements
     344  #
     345
     346 -def parse_value(tt):
     347      '''Return VALUE contents as a string'''
     348      ## <!ELEMENT VALUE (#PCDATA)>
     349      check_node(tt, 'VALUE', [], [], [], True)
     350
     351      return pcdata(tt)
     352
     353
     354 -def parse_value_array(tt):
     355      """Return list of strings."""
     356      ## <!ELEMENT VALUE.ARRAY (VALUE*)>
     357      check_node(tt, 'VALUE.ARRAY', [], [], ['VALUE'])
     358
     359      return list_of_same(tt, ['VALUE'])
     360
     361
     362 -def parse_value_reference(tt):
     363      """
     364      <!ELEMENT VALUE.REFERENCE (CLASSPATH | LOCALCLASSPATH | CLASSNAME |
     365                                 INSTANCEPATH | LOCALINSTANCEPATH |
     366                                 INSTANCENAME)>
     367      """
     368
     369      check_node(tt, 'VALUE.REFERENCE', [])
     370
     371      child = one_child(tt,
     372                        ['CLASSPATH', 'LOCALCLASSPATH', 'CLASSNAME',
     373                         'INSTANCEPATH', 'LOCALINSTANCEPATH',
     374                         'INSTANCENAME'])
     375
     376      # The VALUE.REFERENCE wrapper element is discarded
     377      return child
     378
     379
     380 -def parse_value_refarray(tt):
     381      """
     382      <!ELEMENT VALUE.REFARRAY (VALUE.REFERENCE*)>
     383      """
     384
     385      check_node(tt, 'VALUE.REFARRAY')
     386
     387      children = list_of_various(tt, ['VALUE.REFERENCE'])
     388
     389      # The VALUE.REFARRAY wrapper element is discarded
     390      return children
     391
     392
     393 -def parse_value_object(tt):
     394      """
     395      <!ELEMENT VALUE.OBJECT (CLASS | INSTANCE)>
     396      """
     397
     398      check_node(tt, 'VALUE.OBJECT')
     399
     400      child = one_child(tt, ['CLASS', 'INSTANCE'])
     401
     402      return (name(tt), attrs(tt), child)
     403
     404
     405 -def parse_value_namedinstance(tt):
     406      """
     407      <!ELEMENT VALUE.NAMEDINSTANCE (INSTANCENAME, INSTANCE)>
     408      """
     409
     410      check_node(tt, 'VALUE.NAMEDINSTANCE')
     411
     412      k = kids(tt)
     413      if len(k) <> 2:
     414          raise ParseError('expecting (INSTANCENAME, INSTANCE), got %s' % `k`)
     415
     416      instancename = parse_instancename(k[0])
     417      instance = parse_instance(k[1])
     418
     419      instance.path = instancename
     420
     421      return instance
     422
     423
     424 -def parse_value_namedobject(tt):
     425      """
     426      <!ELEMENT VALUE.NAMEDOBJECT (CLASS | (INSTANCENAME, INSTANCE))>
     427      """
     428
     429      check_node(tt, 'VALUE.NAMEDOBJECT')
     430
     431      k = kids(tt)
     432      if len(k) == 1:
     433          object = parse_class(k[0])
     434      elif len(k) == 2:
     435          path = parse_instancename(kids(tt)[0])
     436          object = parse_instance(kids(tt)[1])
     437
     438          object.path = path
     439      else:
     440          raise ParseError('Expecting one or two elements, got %s' %
     441                           `kids(tt)`)
     442
     443      return (name(tt), attrs(tt), object)
     444
     445
     446 -def parse_value_objectwithlocalpath(tt):
     447      """
     448      <!ELEMENT VALUE.OBJECTWITHLOCALPATH ((LOCALCLASSPATH, CLASS) |
     449                                           (LOCALINSTANCEPATH, INSTANCE))>
     450      """
     451
     452      check_node(tt, 'VALUE.OBJECTWITHLOCALPATH')
     453
     454      if len(kids(tt)) != 2:
     455          raise ParseError('Expecting two elements, got %s' %
     456                           len(kids(tt)));
     457
     458      if kids(tt)[0][0] == 'LOCALCLASSPATH':
     459          object = (parse_localclasspath(kids(tt)[0]),
     460                    parse_class(kids(tt)[1]))
     461      else:
     462          path = parse_localinstancepath(kids(tt)[0])
     463          object = parse_instance(kids(tt)[1])
     464
     465          object.path = path
     466
     467      return (name(tt), attrs(tt), object)
     468
     469 -def parse_value_objectwithpath(tt):
     470      """
     471      <!ELEMENT VALUE.OBJECTWITHPATH ((CLASSPATH, CLASS) |
     472                                      (INSTANCEPATH, INSTANCE))>
     473      """
     474
     475      check_node(tt, 'VALUE.OBJECTWITHPATH')
     476
     477      k = kids(tt)
     478
     479      if len(k) != 2:
     480          raise ParseError('Expecting two elements, got %s' % k)
     481
     482      if name(k[0]) == 'CLASSPATH':
     483          object = (parse_classpath(k[0]),
     484                    parse_class(k[1]))
     485      else:
     486          path = parse_instancepath(k[0])
     487          object = parse_instance(k[1])
     488
     489          object.path = path
     490
     491      return (name(tt), attrs(tt), object)
     492
     493  #
     494  # Object naming and locating elements
     495  #
     496
     497 -def parse_namespacepath(tt):
     498      """
     499      <!ELEMENT NAMESPACEPATH (HOST, LOCALNAMESPACEPATH)>
     500      """
     501
     502      check_node(tt, 'NAMESPACEPATH')
     503
     504      if len(kids(tt)) != 2:
     505          raise ParseError('Expecting (HOST, LOCALNAMESPACEPATH) '
     506                           'got %s' % kids(tt).keys())
     507
     508      host = parse_host(kids(tt)[0])
     509      localnspath = parse_localnamespacepath(kids(tt)[1])
     510
     511      return (host, localnspath)
     512
     513
     514 -def parse_localnamespacepath(tt):
     515      """
     516      <!ELEMENT LOCALNAMESPACEPATH (NAMESPACE+)>
     517      """
     518
     519      check_node(tt, 'LOCALNAMESPACEPATH', [], [], ['NAMESPACE'])
     520
     521      if len(kids(tt)) == 0:
     522          raise ParseError('Expecting one or more of NAMESPACE, got nothing')
     523
     524      ns_list = list_of_various(tt, ['NAMESPACE'])
     525
     526      return string.join(ns_list, '/')
     527
     528
     529 -def parse_host(tt):
     530      """
     531      <!ELEMENT HOST (#PCDATA)>
     532      """
     533
     534      check_node(tt, 'HOST', allow_pcdata=True)
     535
     536      return pcdata(tt)
     537
     538
     539 -def parse_namespace(tt):
     540      """
     541      <!ELEMENT NAMESPACE EMPTY>
     542      <!ATTLIST NAMESPACE
     543          %CIMName;>
     544      """
     545
     546      check_node(tt, 'NAMESPACE', ['NAME'], [], [])
     547
     548      return attrs(tt)['NAME']
     549
     550
     551 -def parse_classpath(tt):
     552      """
     553      <!ELEMENT CLASSPATH (NAMESPACEPATH, CLASSNAME)>
     554      """
     555
     556      check_node(tt, 'CLASSPATH')
     557
     558      if len(kids(tt)) != 2:
     559          raise ParseError('Expecting (NAMESPACEPATH, CLASSNAME) '
     560                           'got %s' % kids(tt).keys())
     561
     562      nspath = parse_namespacepath(kids(tt)[0])
     563      classname = parse_classname(kids(tt)[1])
     564
     565      return CIMClassName(classname.classname,
     566                                  host=nspath[0], namespace=nspath[1])
     567
     568
     569 -def parse_localclasspath(tt):
     570      """
     571      <!ELEMENT LOCALCLASSPATH (LOCALNAMESPACEPATH, CLASSNAME)>
     572      """
     573
     574      check_node(tt, 'LOCALCLASSPATH')
     575
     576      if len(kids(tt)) != 2:
     577          raise ParseError('Expecting (LOCALNAMESPACEPATH, CLASSNAME) '
     578                           'got %s' % kids(tt).keys())
     579
     580      localnspath = parse_localnamespacepath(kids(tt)[0])
     581      classname = parse_classname(kids(tt)[1])
     582
     583      return CIMClassName(classname.classname, namespace=localnspath)
     584
     585 -def parse_classname(tt):
     586      """
     587      <!ELEMENT CLASSNAME EMPTY>
     588      <!ATTLIST CLASSNAME
     589          %CIMName;>
     590      """
     591      check_node(tt, 'CLASSNAME', ['NAME'], [], [])
     592      return CIMClassName(attrs(tt)['NAME'])
     593
     594
     595 -def parse_instancepath(tt):
     596      """
     597      <!ELEMENT INSTANCEPATH (NAMESPACEPATH, INSTANCENAME)>
     598      """
     599
     600      check_node(tt, 'INSTANCEPATH')
     601
     602      if len(kids(tt)) != 2:
     603          raise ParseError('Expecting (NAMESPACEPATH, INSTANCENAME), got %s'
     604                           % `kids(tt)`)
     605
     606      nspath = parse_namespacepath(kids(tt)[0])
     607      instancename = parse_instancename(kids(tt)[1])
     608
     609      instancename.host = nspath[0]
     610      instancename.namespace = nspath[1]
     611
     612      return instancename
     613
     614 -def parse_localinstancepath(tt):
     615      """
     616      <!ELEMENT LOCALINSTANCEPATH (LOCALNAMESPACEPATH, INSTANCENAME)>
     617      """
     618
     619      check_node(tt, 'LOCALINSTANCEPATH')
     620
     621      if len(kids(tt)) != 2:
     622          raise ParseError('Expecting (LOCALNAMESPACEPATH, INSTANCENAME), '
     623                           'got %s' % kids(tt).keys())
     624
     625      localnspath = parse_localnamespacepath(kids(tt)[0])
     626      instancename = parse_instancename(kids(tt)[1])
     627
     628      instancename.namespace = localnspath
     629
     630      return instancename
     631
     632 -def parse_instancename(tt):
     633      """Parse XML INSTANCENAME into CIMInstanceName object."""
     634
     635      ## <!ELEMENT INSTANCENAME (KEYBINDING* | KEYVALUE? | VALUE.REFERENCE?)>
     636      ## <!ATTLIST INSTANCENAME %ClassName;>
     637
     638      check_node(tt, 'INSTANCENAME', ['CLASSNAME'])
     639
     640      if len(kids(tt)) == 0:
     641          # probably not ever going to see this, but it's valid
     642          # according to the grammar
     643          return CIMInstanceName(attrs(tt)['CLASSNAME'], {})
     644
     645      k0 = kids(tt)[0]
     646      w = name(k0)
     647
     648      classname = attrs(tt)['CLASSNAME']
     649
     650      if w == 'KEYVALUE' or w == 'VALUE.REFERENCE':
     651          if len(kids(tt)) != 1:
     652              raise ParseError('expected only one %s under %s' %
     653                               w, name(tt))
     654
     655          # FIXME: This is probably not the best representation of these forms...
     656          val = parse_any(k0)
     657          return CIMInstanceName(classname, {None: val})
     658      elif w == 'KEYBINDING':
     659          kbs = {}
     660          for kb in list_of_various(tt, ['KEYBINDING']):
     661              kbs.update(kb)
     662          return CIMInstanceName(classname, kbs)
     663      else:
     664          raise ParseError('unexpected node %s under %s' %
     665                           (name(kids(tt)[0]), name(tt)))
     666
     667
     668 -def parse_objectpath(tt):
     669      """
     670      <!ELEMENT OBJECTPATH (INSTANCEPATH | CLASSPATH)>
     671      """
     672
     673      check_node(tt, 'OBJECTPATH')
     674
     675      child = one_child(tt, ['INSTANCEPATH', 'CLASSPATH'])
     676
     677      return (name(tt), attrs(tt), child)
     678
     679
     680
     681 -def parse_keybinding(tt):
     682      ##<!ELEMENT KEYBINDING (KEYVALUE | VALUE.REFERENCE)>
     683      ##<!ATTLIST KEYBINDING
     684      ##  %CIMName;>
     685
     686      """Returns one-item dictionary from name to Python value."""
     687
     688      check_node(tt, 'KEYBINDING', ['NAME'])
     689
     690      child = one_child(tt, ['KEYVALUE', 'VALUE.REFERENCE'])
     691
     692      return {attrs(tt)['NAME']: child}
     693
     694
     695 -def parse_keyvalue(tt):
     696      ##<!ELEMENT KEYVALUE (#PCDATA)>
     697      ##<!ATTLIST KEYVALUE
     698      ##          VALUETYPE (string | boolean | numeric) "string"
     699      ##          %CIMType;              #IMPLIED>
     700
     701
     702      """Parse VALUETYPE into Python primitive value"""
     703
     704      check_node(tt, 'KEYVALUE', ['VALUETYPE'], ['TYPE'], [], True)
     705
     706      p = pcdata(tt)
     707
     708      if not attrs(tt).has_key('VALUETYPE'):
     709          return p;
     710
     711      vt = attrs(tt).get('VALUETYPE')
     712
     713      if vt == 'string':
     714          return p
     715      elif vt == 'boolean':
     716          return unpack_boolean(p)
     717      elif vt == 'numeric':
     718
     719          try:
     720              # XXX: Use TYPE attribute to create named CIM type.
     721              # if attrs(tt).has_key('TYPE'):
     722              #    return cim_obj.tocimobj(attrs(tt)['TYPE'], p.strip())
     723
     724              # XXX: Would like to use long() here, but that tends to cause
     725              # trouble when it's written back out as '2L'
     726              return int(p.strip())
     727          except ValueError, e:
     728              raise ParseError('invalid numeric %s under %s' %
     729                               (`p`, name(tt)))
     730      else:
     731          raise ParseError('invalid VALUETYPE %s in %s',
     732                           vt, name(tt))
     733
     734
     735  #
     736  # Object definition elements
     737  #
     738
     739 -def parse_class(tt):
     740      ## <!ELEMENT CLASS (QUALIFIER*, (PROPERTY | PROPERTY.ARRAY |
     741      ##                               PROPERTY.REFERENCE)*, METHOD*)>
     742      ## <!ATTLIST CLASS
     743      ##     %CIMName;
     744      ##     %SuperClass;>
     745
     746      # This doesn't check the ordering of elements, but it's not very important
     747      check_node(tt, 'CLASS', ['NAME'], ['SUPERCLASS'],
     748                 ['QUALIFIER', 'PROPERTY', 'PROPERTY.REFERENCE',
     749                  'PROPERTY.ARRAY', 'METHOD'])
     750
     751      superclass = attrs(tt).get('SUPERCLASS')
     752
     753      # TODO: Return these as maps, not lists
     754      properties = cim_obj.byname(list_of_matching(tt, ['PROPERTY',
     755                                                        'PROPERTY.REFERENCE',
     756                                                        'PROPERTY.ARRAY']))
     757
     758      qualifiers = cim_obj.byname(list_of_matching(tt, ['QUALIFIER']))
     759      methods = cim_obj.byname(list_of_matching(tt, ['METHOD']))
     760
     761      return CIMClass(attrs(tt)['NAME'],
     762                              superclass=superclass,
     763                              properties=properties,
     764                              qualifiers=qualifiers,
     765                              methods=methods)
     766
     767
     768 -def parse_instance(tt):
     769      """Return a CIMInstance.
     770
     771      The instance contains the properties, qualifiers and classname for
     772      the instance"""
     773
     774      ##<!ELEMENT INSTANCE (QUALIFIER*, (PROPERTY | PROPERTY.ARRAY |
     775      ##                                 PROPERTY.REFERENCE)*)>
     776      ##<!ATTLIST INSTANCE
     777      ##  %ClassName;>
     778
     779      check_node(tt, 'INSTANCE', ['CLASSNAME'],
     780                 ['QUALIFIER', 'PROPERTY', 'PROPERTY.ARRAY',
     781                  'PROPERTY.REFERENCE'])
     782
     783      ## XXX: This does not enforce ordering constraint
     784
     785      ## XXX: This does not enforce the constraint that there be only
     786      ## one PROPERTY or PROPERTY.ARRAY.
     787
     788      ## TODO: Parse instance qualifiers
     789      qualifiers = {}
     790      props = list_of_matching(tt, ['PROPERTY.REFERENCE', 'PROPERTY',
     791                                    'PROPERTY.ARRAY'])
     792
     793      obj = CIMInstance(attrs(tt)['CLASSNAME'],
     794                                qualifiers=qualifiers)
     795
     796      [obj.__setitem__(p.name, p) for p in props]
     797
     798      return obj
     799
     800 -def parse_scope(tt):
     801      # <!ELEMENT SCOPE EMPTY>
     802      # <!ATTLIST SCOPE
     803      #   CLASS (true | false) "false"
     804      #   ASSOCIATION (true | false) "false"
     805      #   REFERENCE (true | false) "false"
     806      #   PROPERTY (true | false) "false"
     807      #   METHOD (true | false) "false"
     808      #   PARAMETER (true | false) "false"
     809      #   INDICATION (true | false) "false"
     810      check_node(tt, 'SCOPE', [],
     811                 ['CLASS', 'ASSOCIATION', 'REFERENCE', 'PROPERTY', 'METHOD',
     812                  'PARAMETER', 'INDICATION'], [])
     813      return dict([(k, v.lower() == 'true') for k, v in attrs(tt).items()])
     814
     815 -def parse_qualifier_declaration(tt):
     816      ## <!ELEMENT QUALIFIER.DECLARATION (SCOPE?, (VALUE | VALUE.ARRAY)?)>
     817      ## <!ATTLIST QUALIFIER.DECLARATION
     818      ##     %CIMName;
     819      ##     %CIMType;               #REQUIRED
     820      ##     ISARRAY    (true|false) #IMPLIED
     821      ##     %ArraySize;
     822      ##     %QualifierFlavor;>
     823
     824      check_node(tt, 'QUALIFIER.DECLARATION',
     825                 ['NAME', 'TYPE'],
     826                 ['ISARRAY', 'ARRAYSIZE', 'OVERRIDABLE', 'TOSUBCLASS',
     827                  'TOINSTANCE', 'TRANSLATABLE'],
     828                 ['SCOPE', 'VALUE', 'VALUE.ARRAY'])
     829
     830      a = attrs(tt)
     831      qname = a['NAME']
     832      type = a['TYPE']
     833      try:
     834          is_array = a['ISARRAY'].lower() == 'true'
     835      except KeyError:
     836          is_array = False
     837      try:
     838          array_size = int(a['ARRAYSIZE'])
     839      except KeyError:
     840          array_size = None
     841
     842      flavors = {}
     843      for f in ['OVERRIDABLE', 'TOSUBCLASS', 'TOINSTANCE', 'TRANSLATABLE']:
     844          try:
     845              flavors[f.lower()] = a[f].lower() == 'true'
     846          except KeyError:
     847              pass
     848
     849      scopes = None
     850      value = None
     851      for child in kids(tt):
     852          if name(child) == 'SCOPE':
     853              if scopes is not None:
     854                  raise ParseError("Multiple SCOPE tags encountered")
     855              scopes = parse_any(child)
     856          else:
     857              if value is not None:
     858                  raise ParseError("Multiple VALUE/VALUE.ARRAY tags encountered")
     859              value = cim_obj.tocimobj(type, parse_any(child))
     860
     861      return CIMQualifierDeclaration(qname, type, value, is_array,
     862                                             array_size, scopes, **flavors)
     863
     864
     865 -def parse_qualifier(tt):
     866      ## <!ELEMENT QUALIFIER (VALUE | VALUE.ARRAY)>
     867      ## <!ATTLIST QUALIFIER %CIMName;
     868      ##      %CIMType;              #REQUIRED
     869      ##      %Propagated;
     870      ##      %QualifierFlavor;>
     871
     872      check_node(tt, 'QUALIFIER', ['NAME', 'TYPE'],
     873                 ['OVERRIDABLE', 'TOSUBCLASS', 'TOINSTANCE',
     874                  'TRANSLATABLE', 'PROPAGATED'],
     875                 ['VALUE', 'VALUE.ARRAY'])
     876
     877      a = attrs(tt)
     878
     879      q = CIMQualifier(a['NAME'], unpack_value(tt), type=a['TYPE'])
     880
     881      ## TODO: Lift this out?
     882      for i in ['OVERRIDABLE', 'TOSUBCLASS', 'TOINSTANCE',
     883                'TRANSLATABLE', 'PROPAGATED']:
     884          rv = a.get(i)
     885          if rv not in ['true', 'false', None]:
     886              raise ParseError("invalid value %s for %s on %s" %
     887                               (`rv`, i, name(tt)))
     888          if rv == 'true':
     889              rv = True
     890          elif rv == 'false':
     891              rv = False
     892
     893          setattr(q, i.lower(), rv)
     894
     895      return q
     896
     897
     898 -def parse_property(tt):
     899      """Parse PROPERTY into a CIMProperty object.
     900
     901      VAL is just the pcdata of the enclosed VALUE node."""
     902
     903      ## <!ELEMENT PROPERTY (QUALIFIER*, VALUE?)>
     904      ## <!ATTLIST PROPERTY %CIMName;
     905      ##      %ClassOrigin;
     906      ##      %Propagated;
     907      ##      %CIMType;              #REQUIRED>
     908
     909      ## TODO: Parse this into NAME, VALUE, where the value contains
     910      ## magic fields for the qualifiers and the propagated flag.
     911
     912      check_node(tt, 'PROPERTY', ['TYPE', 'NAME'],
     913                 ['NAME', 'CLASSORIGIN', 'PROPAGATED', 'EmbeddedObject',
     914                  'EMBEDDEDOBJECT'],
     915                 ['QUALIFIER', 'VALUE'])
     916
     917      quals = {}
     918      for q in list_of_matching(tt, ['QUALIFIER']):
     919          quals[q.name] = q
     920
     921      a = attrs(tt)
     922      try:
     923          val = unpack_value(tt)
     924      except ValueError as exc:
     925          msg = str(exc)
     926          raise ParseError('Cannot parse value for property "%s": %s' %\
     927                           (a['NAME'], msg))
     928
     929      embedded_object = None
     930      if 'EmbeddedObject' in a or 'EMBEDDEDOBJECT' in a:
     931          try:
     932              embedded_object = a['EmbeddedObject']
     933          except KeyError:
     934              embedded_object = a['EMBEDDEDOBJECT']
     935      if embedded_object is not None:
     936          val = parse_embeddedObject(val)
     937
     938      return CIMProperty(a['NAME'],
     939                                 val,
     940                                 a['TYPE'],
     941                                 class_origin=a.get('CLASSORIGIN'),
     942                                 propagated=unpack_boolean(a.get('PROPAGATED')),
     943                                 qualifiers=quals,
     944                                 embedded_object=embedded_object)
     945
     946
     947 -def parse_property_array(tt):
     948      """
     949      <!ELEMENT PROPERTY.ARRAY (QUALIFIER*, VALUE.ARRAY?)>
     950      <!ATTLIST PROPERTY.ARRAY %CIMName;
     951           %CIMType;              #REQUIRED
     952           %ArraySize;
     953           %ClassOrigin;
     954           %Propagated;>
     955      """
     956
     957      check_node(tt, 'PROPERTY.ARRAY', ['NAME', 'TYPE'],
     958                 ['REFERENCECLASS', 'CLASSORIGIN', 'PROPAGATED',
     959                  'ARRAYSIZE', 'EmbeddedObject', 'EMBEDDEDOBJECT'],
     960                 ['QUALIFIER', 'VALUE.ARRAY'])
     961
     962      quals = {}
     963      for q in list_of_matching(tt, ['QUALIFIER']):
     964          quals[q.name] = q
     965
     966      values = unpack_value(tt)
     967      a = attrs(tt)
     968      embedded_object = None
     969      if 'EmbeddedObject' in a or 'EMBEDDEDOBJECT' in a:
     970          try:
     971              embedded_object = a['EmbeddedObject']
     972          except KeyError:
     973              embedded_object = a['EMBEDDEDOBJECT']
     974
     975      if embedded_object is not None:
     976          values = parse_embeddedObject(values)
     977
     978      obj = CIMProperty(a['NAME'],
     979                                values,
     980                                a['TYPE'],
     981                                class_origin=a.get('CLASSORIGIN'),
     982                                qualifiers=quals,
     983                                is_array=True,
     984                                embedded_object=embedded_object)
     985
     986      ## TODO: qualifiers, other attributes
     987      return obj
     988
     989
     990 -def parse_property_reference(tt):
     991      """
     992      <!ELEMENT PROPERTY.REFERENCE (QUALIFIER*, (VALUE.REFERENCE)?)>
     993      <!ATTLIST PROPERTY.REFERENCE
     994          %CIMName;
     995          %ReferenceClass;
     996          %ClassOrigin;
     997          %Propagated;>
     998      """
     999
    1000      check_node(tt, 'PROPERTY.REFERENCE', ['NAME'],
    1001                 ['REFERENCECLASS', 'CLASSORIGIN', 'PROPAGATED'])
    1002
    1003      value = list_of_matching(tt, ['VALUE.REFERENCE'])
    1004
    1005      if value is None or len(value) == 0:
    1006          value = None
    1007      elif len(value) == 1:
    1008          value = value[0]
    1009      else:
    1010          raise ParseError('Too many VALUE.REFERENCE elements.')
    1011
    1012      attributes = attrs(tt)
    1013      pref = CIMProperty(attributes['NAME'], value, type='reference')
    1014
    1015      for q in list_of_matching(tt, ['QUALIFIER']):
    1016          pref.qualifiers[q.name] = q
    1017
    1018      if attributes.has_key('REFERENCECLASS'):
    1019          pref.reference_class = attributes['REFERENCECLASS']
    1020
    1021      if attributes.has_key('CLASSORIGIN'):
    1022          pref.class_origin = attributes['CLASSORIGIN']
    1023
    1024      if attributes.has_key('PROPAGATED'):
    1025          pref.propagated = attributes['PROPAGATED']
    1026
    1027      return pref
    1028
    1029
    1030 -def parse_method(tt):
    1031      """
    1032      <!ELEMENT METHOD (QUALIFIER*, (PARAMETER | PARAMETER.REFERENCE |
    1033                                     PARAMETER.ARRAY | PARAMETER.REFARRAY)*)>
    1034      <!ATTLIST METHOD %CIMName;
    1035           %CIMType;              #IMPLIED
    1036           %ClassOrigin;
    1037           %Propagated;>
    1038      """
    1039
    1040      check_node(tt, 'METHOD', ['NAME'],
    1041                 ['TYPE', 'CLASSORIGIN', 'PROPAGATED'],
    1042                 ['QUALIFIER', 'PARAMETER', 'PARAMETER.REFERENCE',
    1043                  'PARAMETER.ARRAY', 'PARAMETER.REFARRAY'])
    1044
    1045      qualifiers = cim_obj.byname(list_of_matching(tt, ['QUALIFIER']))
    1046
    1047      parameters = cim_obj.byname(list_of_matching(tt, ['PARAMETER',
    1048                                                        'PARAMETER.REFERENCE',
    1049                                                        'PARAMETER.ARRAY',
    1050                                                        'PARAMETER.REFARRAY',]))
    1051
    1052      a = attrs(tt)
    1053
    1054      return CIMMethod(a['NAME'],
    1055                               return_type=a.get('TYPE'),
    1056                               parameters=parameters,
    1057                               qualifiers=qualifiers,
    1058                               class_origin=a.get('CLASSORIGIN'),
    1059                               propagated=unpack_boolean(a.get('PROPAGATED')))
    1060
    1061
    1062 -def parse_parameter(tt):
    1063      """
    1064      <!ELEMENT PARAMETER (QUALIFIER*)>
    1065      <!ATTLIST PARAMETER
    1066           %CIMName;
    1067           %CIMType;              #REQUIRED>
    1068      """
    1069
    1070      check_node(tt, 'PARAMETER', ['NAME', 'TYPE'], [])
    1071
    1072      quals = {}
    1073      for q in list_of_matching(tt, ['QUALIFIER']):
    1074          quals[q.name] = q
    1075
    1076      a = attrs(tt)
    1077
    1078      return CIMParameter(a['NAME'], type=a['TYPE'], qualifiers=quals)
    1079
    1080 -def parse_parameter_reference(tt):
    1081      """
    1082      <!ELEMENT PARAMETER.REFERENCE (QUALIFIER*)>
    1083      <!ATTLIST PARAMETER.REFERENCE
    1084          %CIMName;
    1085          %ReferenceClass;>
    1086      """
    1087
    1088      check_node(tt, 'PARAMETER.REFERENCE', ['NAME'], ['REFERENCECLASS'])
    1089
    1090      quals = {}
    1091      for q in list_of_matching(tt, ['QUALIFIER']):
    1092          quals[q.name] = q
    1093
    1094      a = attrs(tt)
    1095
    1096      return CIMParameter(a['NAME'],
    1097                                  type='reference',
    1098                                  reference_class=a.get('REFERENCECLASS'),
    1099                                  qualifiers=quals)
    1100
    1101
    1102 -def parse_parameter_array(tt):
    1103      """
    1104      <!ELEMENT PARAMETER.ARRAY (QUALIFIER*)>
    1105      <!ATTLIST PARAMETER.ARRAY
    1106           %CIMName;
    1107           %CIMType;              #REQUIRED
    1108           %ArraySize;>
    1109      """
    1110
    1111      check_node(tt, 'PARAMETER.ARRAY', ['NAME', 'TYPE'],
    1112                 ['ARRAYSIZE'])
    1113
    1114      quals = {}
    1115      for q in list_of_matching(tt, ['QUALIFIER']):
    1116          quals[q.name] = q
    1117
    1118      a = attrs(tt)
    1119
    1120      array_size = a.get('ARRAYSIZE')
    1121      if array_size is not None:
    1122          array_size = int(array_size)
    1123
    1124      return CIMParameter(a['NAME'],
    1125                                  type=a['TYPE'],
    1126                                  is_array=True,
    1127                                  array_size=array_size,
    1128                                  qualifiers=quals)
    1129
    1130
    1131 -def parse_parameter_refarray(tt):
    1132      """
    1133      <!ELEMENT PARAMETER.REFARRAY (QUALIFIER*)>
    1134      <!ATTLIST PARAMETER.REFARRAY
    1135          %CIMName;
    1136          %ReferenceClass;
    1137          %ArraySize;>
    1138      """
    1139
    1140      check_node(tt, 'PARAMETER.REFARRAY', ['NAME'],
    1141                 ['REFERENCECLASS', 'ARRAYSIZE'])
    1142
    1143      quals = {}
    1144      for q in list_of_matching(tt, ['QUALIFIER']):
    1145          quals[q.name] = q
    1146
    1147      a = attrs(tt)
    1148
    1149      array_size = a.get('ARRAYSIZE')
    1150      if array_size is not None:
    1151          array_size = int(array_size)
    1152
    1153      return CIMParameter(a['NAME'], 'reference',
    1154                                  is_array=True,
    1155                                  reference_class=a.get('REFERENCECLASS'),
    1156                                  array_size=array_size,
    1157                                  qualifiers=quals)
    1158
    1159
    1160  #
    1161  # Message elements
    1162  #
    1163
    1164 -def parse_message(tt):
    1165      """
    1166      <!ELEMENT MESSAGE (SIMPLEREQ | MULTIREQ | SIMPLERSP | MULTIRSP)>
    1167      <!ATTLIST MESSAGE
    1168          ID CDATA #REQUIRED
    1169          PROTOCOLVERSION CDATA #REQUIRED>
    1170      """
    1171
    1172      check_node(tt, 'MESSAGE', ['ID', 'PROTOCOLVERSION'])
    1173
    1174      messages = one_child(
    1175          tt, ['SIMPLEREQ', 'MULTIREQ', 'SIMPLERSP', 'MULTIRSP', 'SIMPLEEXPREQ'])
    1176
    1177      if type(messages) is not list:
    1178          # make single and multi forms consistent
    1179          messages = [messages]
    1180
    1181      return name(tt), attrs(tt), messages
    1182
    1183
    1184 -def parse_multireq(tt):
    1185      raise ParseError('MULTIREQ parser not implemented')
    1186
    1187
    1188 -def parse_multiexpreq(tt):
    1189      raise ParseError('MULTIEXPREQ parser not implemented')
    1190
    1191 -def parse_simpleexpreq(tt):
    1192      """
    1193      <!ELEMENT SIMPLEEXPREQ (EXPMETHODCALL)>
    1194      """
    1195
    1196      child = one_child(tt, ['EXPMETHODCALL'])
    1197
    1198      return name(tt), attrs(tt), child
    1199
    1200 -def parse_simplereq(tt):
    1201      """
    1202      <!ELEMENT SIMPLEREQ (IMETHODCALL | METHODCALL)>
    1203      """
    1204
    1205      check_node(tt, 'SIMPLEREQ')
    1206
    1207      child = one_child(tt, ['IMETHODCALL', 'METHODCALL'])
    1208
    1209      return name(tt), attrs(tt), child
    1210
    1211
    1212 -def parse_imethodcall(tt):
    1213      """
    1214      <!ELEMENT IMETHODCALL (LOCALNAMESPACEPATH, IPARAMVALUE*)>
    1215      <!ATTLIST IMETHODCALL
    1216          %CIMName;>
    1217      """
    1218
    1219      check_node(tt, 'IMETHODCALL', ['NAME'])
    1220
    1221      if len(kids(tt)) < 1:
    1222          raise ParseError('Expecting LOCALNAMESPACEPATH, got nothing')
    1223
    1224      localnspath = parse_localnamespacepath(kids(tt)[0])
    1225
    1226      params = map(lambda x: parse_iparamvalue(x),
    1227                   kids(tt)[1:])
    1228
    1229      return (name(tt), attrs(tt), localnspath, params)
    1230
    1231
    1232 -def parse_methodcall(tt):
    1233      """
    1234      <!ELEMENT METHODCALL ((LOCALCLASSPATH|LOCALINSTANCEPATH),PARAMVALUE*)>
    1235      <!ATTLIST METHODCALL
    1236           %CIMName;>
    1237      """
    1238
    1239      check_node(tt, 'METHODCALL', ['NAME'], [],
    1240                 ['LOCALCLASSPATH', 'LOCALINSTANCEPATH', 'PARAMVALUE'])
    1241      path = list_of_matching(tt, ['LOCALCLASSPATH', 'LOCALINSTANCEPATH'])
    1242      if len(path) != 1:
    1243          raise ParseError('Expecting one of LOCALCLASSPATH or ' \
    1244                           'LOCALINSTANCEPATH, got %s' % `path`)
    1245      path = path[0]
    1246      params = list_of_matching(tt, ['PARAMVALUE'])
    1247      return (name(tt), attrs(tt), path, params)
    1248
    1249
    1250 -def parse_expmethodcall(tt):
    1251      """
    1252      <!ELEMENT EXPMETHODCALL (EXPPARAMVALUE*)>
    1253      <!ATTLIST EXPMETHODCALL
    1254          %CIMName;>
    1255      """
    1256
    1257      check_node(tt, 'EXPMETHODCALL', ['NAME'], [], ['EXPPARAMVALUE'])
    1258
    1259
    1260      params = list_of_matching(tt, ['EXPPARAMVALUE'])
    1261
    1262      return (name(tt), attrs(tt), params)
    1263
    1264
    1265 -def parse_paramvalue(tt):
    1266      ## <!ELEMENT PARAMVALUE (VALUE | VALUE.REFERENCE | VALUE.ARRAY |
    1267      ##                       VALUE.REFARRAY)?>
    1268      ## <!ATTLIST PARAMVALUE
    1269      ##   %CIMName;
    1270      ##   %ParamType;  #IMPLIED
    1271      ##   %EmbeddedObject;>
    1272
    1273      ## Version 2.1.1 of the DTD lacks the %ParamType attribute but it
    1274      ## is present in version 2.2.  Make it optional to be backwards
    1275      ## compatible.
    1276
    1277      check_node(tt, 'PARAMVALUE', ['NAME'],
    1278                 ['PARAMTYPE', 'EmbeddedObject', 'EMBEDDEDOBJECT'])
    1279
    1280      child = optional_child(tt,
    1281                             ['VALUE', 'VALUE.REFERENCE', 'VALUE.ARRAY',
    1282                              'VALUE.REFARRAY',])
    1283
    1284      if attrs(tt).has_key('PARAMTYPE'):
    1285          paramtype = attrs(tt)['PARAMTYPE']
    1286      else:
    1287          paramtype = None
    1288
    1289      if 'EmbeddedObject' in attrs(tt) or 'EMBEDDEDOBJECT' in attrs(tt):
    1290          child = parse_embeddedObject(child)
    1291
    1292      return attrs(tt)['NAME'], paramtype, child
    1293
    1294
    1295 -def parse_iparamvalue(tt):
    1296      ## <!ELEMENT IPARAMVALUE (VALUE | VALUE.ARRAY | VALUE.REFERENCE |
    1297      ##                       INSTANCENAME | CLASSNAME | QUALIFIER.DECLARATION |
    1298      ##                       CLASS | INSTANCE | VALUE.NAMEDINSTANCE)?>
    1299      ## <!ATTLIST IPARAMVALUE %CIMName;>
    1300
    1301      """Returns NAME, VALUE pair."""
    1302
    1303      check_node(tt, 'IPARAMVALUE', ['NAME'], [])
    1304
    1305      child = optional_child(tt,
    1306                             ['VALUE', 'VALUE.ARRAY', 'VALUE.REFERENCE',
    1307                              'INSTANCENAME', 'CLASSNAME',
    1308                              'QUALIFIER.DECLARATION', 'CLASS', 'INSTANCE',
    1309                              'VALUE.NAMEDINSTANCE'])
    1310
    1311      name = attrs(tt)['NAME']
    1312      if isinstance(child, basestring) and \
    1313              name.lower() in ['deepinheritance', 'localonly',
    1314                               'includequalifiers', 'includeclassorigin']:
    1315          if child.lower() in ['true', 'false']:
    1316              child = child.lower() == 'true'
    1317
    1318      return name, child
    1319
    1320
    1321 -def parse_expparamvalue(tt):
    1322      """
    1323      <!ELEMENT EXPPARAMVALUE (INSTANCE?)>
    1324      <!ATTLIST EXPPARAMVALUE
    1325          %CIMName;>
    1326      """
    1327
    1328      check_node(tt, 'EXPPARAMVALUE', ['NAME'], [], ['INSTANCE'])
    1329
    1330      child = optional_child(tt, ['INSTANCE'])
    1331
    1332      name = attrs(tt)['NAME']
    1333      return name, child
    1334
    1335
    1336 -def parse_multirsp(tt):
    1337      raise ParseError('MULTIRSP parser not implemented')
    1338
    1339
    1340 -def parse_multiexprsp(tt):
    1341      raise ParseError('MULTIEXPRSP parser not implemented')
    1342
    1343
    1344 -def parse_simplersp(tt):
    1345      ## <!ELEMENT SIMPLERSP (METHODRESPONSE | IMETHODRESPONSE)>
    1346      check_node(tt, 'SIMPLERSP', [], [])
    1347
    1348      child = one_child(tt, ['METHODRESPONSE', 'IMETHODRESPONSE'])
    1349
    1350      return name(tt), attrs(tt), child
    1351
    1352
    1353 -def parse_simpleexprsp(tt):
    1354      raise ParseError('SIMPLEEXPRSP parser not implemented')
    1355
    1356
    1357 -def parse_methodresponse(tt):
    1358      ## <!ELEMENT METHODRESPONSE (ERROR | (RETURNVALUE?, PARAMVALUE*))>
    1359      ## <!ATTLIST METHODRESPONSE
    1360      ##    %CIMName;>
    1361
    1362      check_node(tt, 'METHODRESPONSE', ['NAME'], [])
    1363
    1364      return name(tt), attrs(tt), list_of_various(tt, ['ERROR', 'RETURNVALUE',
    1365                                                       'PARAMVALUE'])
    1366
    1367
    1368 -def parse_expmethodresponse(tt):
    1369      raise ParseError('EXPMETHODRESPONSE parser not implemented')
    1370
    1371
    1372 -def parse_imethodresponse(tt):
    1373      ## <!ELEMENT IMETHODRESPONSE (ERROR | IRETURNVALUE?)>
    1374      ## <!ATTLIST IMETHODRESPONSE %CIMName;>
    1375      check_node(tt, 'IMETHODRESPONSE', ['NAME'], [])
    1376
    1377      return name(tt), attrs(tt), optional_child(tt, ['ERROR', 'IRETURNVALUE'])
    1378
    1379
    1380 -def parse_error(tt):
    1381      """
    1382      <!ELEMENT ERROR EMPTY>
    1383      <!ATTLIST ERROR
    1384          CODE CDATA #REQUIRED
    1385          DESCRIPTION CDATA #IMPLIED>
    1386      """
    1387
    1388      ## TODO: Return a CIMError object, not a tuple
    1389
    1390      check_node(tt, 'ERROR', ['CODE'], ['DESCRIPTION'])
    1391
    1392      return (name(tt), attrs(tt), None)
    1393
    1394
    1395 -def parse_returnvalue(tt):
    1396      ## <!ELEMENT RETURNVALUE (VALUE | VALUE.ARRAY | VALUE.REFERENCE |
    1397      ##                        VALUE.REFARRAY)>
    1398      ## <!ATTLIST RETURNVALUE %ParamType;       #IMPLIED>
    1399
    1400      ## Version 2.1.1 of the DTD lacks the %ParamType attribute but it
    1401      ## is present in version 2.2.  Make it optional to be backwards
    1402      ## compatible.
    1403
    1404      check_node(tt, 'RETURNVALUE', [], ['PARAMTYPE'])
    1405
    1406      return name(tt), attrs(tt), one_child(tt, ['VALUE', 'VALUE.ARRAY',
    1407                                                 'VALUE.REFERENCE',
    1408                                                 'VALUE.REFARRAY'])
    1409
    1410
    1411 -def parse_ireturnvalue(tt):
    1412      ## <!ELEMENT IRETURNVALUE (CLASSNAME* | INSTANCENAME* | VALUE* |
    1413      ##                         VALUE.OBJECTWITHPATH* |
    1414      ##                         VALUE.OBJECTWITHLOCALPATH* | VALUE.OBJECT* |
    1415      ##                         OBJECTPATH* | QUALIFIER.DECLARATION* |
    1416      ##                         VALUE.ARRAY? | VALUE.REFERENCE? | CLASS* |
    1417      ##                         INSTANCE* | VALUE.NAMEDINSTANCE*)>
    1418
    1419      check_node(tt, 'IRETURNVALUE', [], [])
    1420
    1421      # XXX: doesn't prohibit the case of only one VALUE.ARRAY or
    1422      # VALUE.REFERENCE.  But why is that required?  Why can it return
    1423      # multiple VALUEs but not multiple VALUE.REFERENCEs?
    1424
    1425      values = list_of_same(tt, ['CLASSNAME', 'INSTANCENAME',
    1426                                 'VALUE', 'VALUE.OBJECTWITHPATH', 'VALUE.OBJECT',
    1427                                 'OBJECTPATH', 'QUALIFIER.DECLARATION',
    1428                                 'VALUE.ARRAY', 'VALUE.REFERENCE',
    1429                                 'CLASS', 'INSTANCE',
    1430                                 'VALUE.NAMEDINSTANCE',])
    1431
    1432      ## TODO: Call unpack_value if appropriate
    1433
    1434      return name(tt), attrs(tt), values
    1435
    1436  #
    1437  # Object naming and locating elements
    1438  #
    1439
    1440 -def parse_any(tt):
    1441      """Parse any fragment of XML."""
    1442
    1443      nodename = name(tt).lower().replace('.', '_')
    1444      fn_name = 'parse_' + nodename
    1445      fn = globals().get(fn_name)
    1446      if fn is None:
    1447          raise ParseError('no parser for node type %s' % name(tt))
    1448      else:
    1449          return fn(tt)
    1450
    1451 -def parse_embeddedObject(val):
    1452      if isinstance(val, list):
    1453          return [parse_embeddedObject(obj) for obj in val]
    1454      if val is None:
    1455          return None
    1456      tt = tupletree.xml_to_tupletree(val)
    1457      if tt[0] == 'INSTANCE':
    1458          return parse_instance(tt)
    1459      elif tt[0] == 'CLASS':
    1460          return parse_class(tt)
    1461      else:
    1462          raise ParseError('Error parsing embedded object')
    1463
    1464
    1465 -def unpack_value(tt):
    1466      """Find VALUE or VALUE.ARRAY under TT and convert to a Python value.
    1467
    1468      Looks at the TYPE of the node to work out how to decode it.
    1469      Handles nodes with no value (e.g. in CLASS.)
    1470      """
    1471      ## TODO: Handle VALUE.REFERENCE, VALUE.REFARRAY
    1472
    1473      valtype = attrs(tt)['TYPE']
    1474
    1475      raw_val = list_of_matching(tt, ['VALUE', 'VALUE.ARRAY'])
    1476      if len(raw_val) == 0:
    1477          return None
    1478      elif len(raw_val) > 1:
    1479          raise ParseError('more than one VALUE or VALUE.ARRAY under %s' % \
    1480                           name(tt))
    1481
    1482      raw_val = raw_val[0]
    1483
    1484      if isinstance(raw_val, list):
    1485          return [cim_obj.tocimobj(valtype, x) for x in raw_val]
    1486      elif len(raw_val) == 0 and valtype != 'string':
    1487          return None
    1488      else:
    1489          return cim_obj.tocimobj(valtype, raw_val)
    1490
    1491
    1492 -def unpack_boolean(p):
    1493      """Unpack a boolean, represented as "TRUE" or "FALSE" in CIM."""
    1494      if p is None:
    1495          return None
    1496
    1497      ## CIM-XML says "These values MUST be treated as case-insensitive"
    1498      ## (even though the XML definition requires them to be lowercase.)
    1499
    1500      p = p.strip().lower()                   # ignore space
    1501      if p == 'true':
    1502          return True
    1503      elif p == 'false':
    1504          return False
    1505      elif p == '':
    1506          return None
    1507      else:
    1508          raise ParseError('invalid boolean %s' % `p`)
    1509