fabbee6500c2de762ecad06918d81f72f2d526a8
[tx] / nodes.py
1 # -*- coding:utf-8 -*-
2
3 # XML Nodes
4 # Copyright (C) 2005  Frédéric Jolliton <frederic@jolliton.com>
5
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA      02111-1307      USA
19
20 __all__ = [
21           'Node'
22         , 'Document'
23         , 'Element'
24         , 'Attribute'
25         , 'ProcessingInstruction'
26         , 'Comment'
27         , 'Text'
28 ]
29
30 import cStringIO as StringIO
31 import codecs
32 import time
33 import re
34
35 from error import Error, NodeError
36 from misc import shortenText, typeOf, stringValuesCompare
37
38 # IMPORTANT: Additionnal import at end of module
39
40 def someDuplicates( items , getValue = id ) :
41
42         '''Predicate to test if there is duplicate in 'items'.'''
43
44         values = set()
45         for item in items :
46                 value = getValue( item )
47                 if value in values :
48                         return True
49                 values.add( value )
50         return False
51
52 def _combineAdjacentText( nodes ) :
53
54         '''Combine adjacent text nodes.'''
55
56         lastTextNode = None
57         result = []
58         for node in nodes :
59                 if node.parent is not None :
60                         node = node.clone()
61                 if isinstance( node , Text ) :
62                         if node.content != '' :
63                                 if lastTextNode is None :
64                                         result.append( node )
65                                         lastTextNode = node
66                                 else :
67                                         lastTextNode.content += node.content
68                 else :
69                         result.append( node )
70                         lastTextNode = None
71         return tuple( result )
72
73 def _chainNodes( nodes , parent ) :
74
75         '''Update parent, prev and next reference for nodes in 'nodes'.  The
76         parent reference will point to 'parent'.'''
77
78         last = len( nodes ) - 1
79         for i , node in enumerate( nodes ) :
80                 node._setParent( parent )
81                 if i < last :
82                         node._setNext( nodes[ i + 1 ] )
83                 if i > 0 :
84                         node._setPrev( nodes[ i - 1 ] )
85
86 def xmlQuoteText( s ) :
87
88         '''Return string 's' with & and < characters translated to XML entity
89         references.'''
90
91         return s \
92                 .replace( '&' , '&amp;' ) \
93                 .replace( '<' , '&lt;' )
94
95 reCharacterToQuoteForText = re.compile( '[&<]' )
96 def streamXmlQuoteText( s , prt ) :
97
98         '''Feed string 's' to 'prt' with & and < characters translated to XML
99         entity references.'''
100
101         p = 0
102         while 1 :
103                 r = reCharacterToQuoteForText.search( s , p )
104                 if r is None :
105                         break
106                 e = r.start( 0 )
107                 prt( s[ p : e ] )
108                 p = r.end( 0 )
109                 if s[ e ] == '&' :
110                         prt( '&amp;' )
111                 elif s[ e ] == '<' :
112                         prt( '&lt;' )
113         prt( s[ p : ] )
114
115 def xmlQuoteAttribute( s ) :
116
117         '''Return string 's' with &, <, " and ' characters translated to XML
118         entity references.'''
119
120         return s \
121                 .replace( '&' , '&amp;' ) \
122                 .replace( '<' , '&lt;' ) \
123                 .replace( '"' , '&quot;' ) \
124                 .replace( "'" , '&apos;' )
125
126 reCharacterToQuoteForText = re.compile( '''[&<"']''' )
127 def streamXmlQuoteAttribute( s , prt ) :
128
129         '''Feed string 's' to 'prt' with &, <, " and ' characters translated
130         to XML entity references.'''
131
132         p = 0
133         while 1 :
134                 r = reCharacterToQuoteForText.search( s , p )
135                 if r is None :
136                         break
137                 e = r.start( 0 )
138                 prt( s[ p : e ] )
139                 p = r.end( 0 )
140                 if s[ e ] == '&' :
141                         prt( '&amp;' )
142                 elif s[ e ] == '<' :
143                         prt( '&lt;' )
144                 elif s[ e ] == '"' :
145                         prt( '&quot;' )
146                 elif s[ e ] == "'" :
147                         prt( '&apos;' )
148         prt( s[ p : ] )
149
150 #----------------------------------------------------------------------------
151
152 class DebugOutputContext( object ) :
153
154         '''Object to hold attributes when generating debug output of a XML
155         tree.'''
156
157         __slots__ = [
158                 'prt' , 'depth' , 'treeSpace' , 'prefix' ,
159                 'depthLimit' , 'nameLimit' , 'attributeLimit' ,
160                 'childrenLimit' , 'textLimit' , 'commentLimit' ]
161
162         def __init__( self ) :
163
164                 self.prt            = None
165                 self.depth          = 0
166                 self.treeSpace      = '  '
167                 self.prefix         = ''
168                 self.depthLimit     = None
169                 self.nameLimit      = None
170                 self.attributeLimit = None
171                 self.childrenLimit  = None
172                 self.textLimit      = None
173                 self.commentLimit   = None
174
175         def checkAttributeCount( self , count ) :
176
177                 '''Predicate to test if we can output 'count' attributes.'''
178
179                 return self.attributeLimit is None or count <= self.attributeLimit
180
181         def checkChildrenCount( self , count ) :
182
183                 '''Predicate to test if we can output 'count' children.'''
184
185                 return self.childrenLimit is None or count <= self.childrenLimit
186
187         def checkDepth( self , level = 1 ) :
188
189                 '''Predicate to test if at least 'level' level are still allowed
190                 before reaching depth limit.'''
191
192                 return self.depthLimit is None or self.depth + level <= self.depthLimit
193
194 #----------------------------------------------------------------------------
195
196 class Node( object ) :
197
198         __slots__ = [ 'root' , 'parent' , 'next' , 'prev' , 'position' , 'meta' ]
199
200         def __init__( self ) :
201
202                 self.root     = None  # Root node (document)
203                 self.parent   = None  # Parent node
204                 self.next     = None  # Next sibling node
205                 self.prev     = None  # Previous sibling node
206                 self.position = None  # Relative position in document tree
207                 self.meta     = None  # Place holder for misc data
208
209         def clone( self ) :
210
211                 '''Return a clone of the document (or subtree) 'self'.'''
212
213                 raise NotImplementedError
214
215         def iterStringValue( self ) :
216
217                 '''Iterator that list all the strings inside the node 'self'.'''
218
219                 raise NotImplementedError
220
221         def dmAttributes( self ) :
222
223                 raise NotImplementedError
224
225         def dmChildren( self ) :
226
227                 raise NotImplementedError
228
229         def dmDocumentUri( self ) :
230
231                 raise NotImplementedError
232
233         def dmNamespaceNodes( self ) :
234
235                 raise NotImplementedError
236
237         def dmNodeKind( self ) :
238
239                 raise NotImplementedError
240
241         def dmNodeName( self ) :
242
243                 raise NotImplementedError
244
245         def dmParent( self ) :
246
247                 raise NotImplementedError
248
249         def dmStringValue( self ) :
250
251                 raise NotImplementedError
252
253         def _setParent( self , node ) :
254
255                 if self.parent is not None and self.parent is not node :
256                         raise NodeError( 'parent reference is already set' )
257                 self.parent = node
258
259         def _setNext( self , node ) :
260
261                 if self.next is not None and self.next is not node :
262                         raise NodeError( 'next reference is already set' )
263                 self.next = node
264
265         def _setPrev( self , node ) :
266
267                 if self.prev is not None and self.prev is not node :
268                         raise NodeError( 'prev reference is already set' )
269                 self.prev = node
270
271         def __getitem__( self , path ) :
272
273                 if not isinstance( path , basestring ) :
274                         return super( Node , self ).__getitem__( path )
275                 else :
276                         import xpath
277                         xpathObject = xpath.XPath( path )
278                         return xpathObject.eval( self )
279
280         # Abusing operator overloading
281         def __div__( self , path ) :
282
283                 return self.__getitem__( path )
284
285         def __idiv__( self , other ) :
286
287                 raise NotImplementedError
288
289         def __cmp__( self , other ) :
290
291                 if other is self :
292                         return 0
293                 elif isinstance( other , Node ) :
294                         return stringValuesCompare( self.iterStringValue() ,
295                                                                                 other.iterStringValue() )
296                 else :
297                         return cmp( type( self ) , type( other ) )
298
299         def serialize( self , charset = 'utf8' , errors = 'strict' , file = None ) :
300
301                 if file is None :
302                         f = StringIO.StringIO()
303                 else :
304                         f = file
305                 encoder = codecs.getencoder( charset )
306                 prt = lambda s : f.write( encoder( s , errors )[ 0 ] )
307                 self._serialize( prt )
308                 if file is None :
309                         f.seek( 0 )
310                         return f.read()
311
312         def _serialize( self , prt ) :
313
314                 raise NotImplementedError
315
316         def asRxp( self ) :
317
318                 raise NotImplementedError
319
320         def asDebug( self , charset = 'utf8' , errors = 'strict' , file = None , prefix = '' ,
321                                  maxDepth = None , maxAttributes = None , maxChildren = None ) :
322
323                 if file is None :
324                         f = StringIO.StringIO()
325                 else :
326                         f = file
327                 encoder = codecs.getencoder( charset )
328                 prt = lambda s : f.write( encoder( s , errors )[ 0 ] )
329
330                 context = DebugOutputContext()
331                 context.prt            = prt
332                 context.prefix         = prefix
333                 context.depthLimit     = maxDepth
334                 context.attributeLimit = maxAttributes
335                 context.childrenLimit  = maxChildren
336                 context.nameLimit      = 24
337                 context.commentLimit   = 64
338                 context.textLimit      = 64
339
340                 if context.checkDepth( 1 ) :
341                         self._asDebug( context )
342
343                 if file is None :
344                         f.seek( 0 )
345                         return f.read()
346
347         def _asDebug( self , context ) :
348
349                 raise NotImplementedError
350
351         def __hash__( self ) :
352
353                 return id( self )
354
355 class Document( Node ) :
356
357         __slots__ = [ 'children' , 'documentUri' , 'numberOfNodes' , 'ids' , 'attributesByName' , 'elementsByName' ]
358
359         def __init__( self , children = () , finalize = True ) :
360
361                 super( Document , self ).__init__()
362
363                 for child in children :
364                         if not isinstance( child , ( Element , ProcessingInstruction , Comment , Text ) ) :
365                                 raise NodeError( '%s not allowed as child of Document' % typeOf( child ) )
366
367                 if someDuplicates( children , id ) :
368                         raise NodeError( 'Duplicate node is not allowed in an element' )
369                 children = _combineAdjacentText( children )
370
371                 self.children      = children
372                 self.documentUri   = None
373
374                 self.numberOfNodes = None
375
376                 self.ids                      = {}
377                 self.attributesByName = {}
378                 self.elementsByName   = {}
379
380                 _chainNodes( self.children , self )
381
382                 if finalize :
383                         self.finalize()
384
385         def clone( self ) :
386
387                 return Document( map( lambda child : child.clone() , self.children ) )
388
389         def iterStringValue( self ) :
390
391                 for node in iterDescendant( self ) :
392                         if isinstance( node , Text ) :
393                                 if node.content :
394                                         yield node.content
395
396         def dmAttributes( self ) :
397
398                 return ()
399
400         def dmChildren( self ) :
401
402                 return self.children
403
404         def dmDocumentUri( self ) :
405
406                 return self.documentUri or ()
407
408         def dmNamespaceNodes( self ) :
409
410                 return ()
411
412         def dmNodeKind( self ) :
413
414                 return 'document'
415
416         def dmNodeName( self ) :
417
418                 return ()
419
420         def dmParent( self ) :
421
422                 return ()
423
424         def dmStringValue( self ) :
425
426                 return ''.join( self.iterStringValue() )
427
428         def finalize( self , start = 0 ) :
429
430                 '''For every descendant of the document, update position, set root
431                 node, keep reference to elements with an 'id' attribute, keep
432                 references to all attributes and elements by name.'''
433
434                 idNames = ( 'id' , )
435                 self.ids = {} # for 'id' attribute
436                 self.attributesByName = {}
437                 self.elementsByName = {}
438                 pos = start
439                 t = time.time()
440                 for node in iterDescendantOrSelfFull( self ) :
441                         node.position = pos
442                         node.root = self
443                         pos += 1
444                         if isinstance( node , Attribute ) :
445                                 if node.name in idNames :
446                                         if node.name in self.ids :
447                                                 raise NodeError( 'Duplicate ID %r' % nade.name )
448                                         self.ids[ node.value ] = node.parent
449                                 self.attributesByName.setdefault( node.name , [] ).append( node )
450                         elif isinstance( node , Element ) :
451                                 self.elementsByName.setdefault( node.name , [] ).append( node )
452                 self.numberOfNodes = pos - start
453                 return pos
454
455         def _check( self ) :
456
457                 pass
458
459         def _serialize( self , prt ) :
460
461                 for children in self.children :
462                         children._serialize( prt )
463
464         def asRxp( self ) :
465
466                 return ( None , None , [ item.asRxp() for item in self.children ] , None )
467
468         def _asDebug( self , context ) :
469
470                 prefix = context.prefix + context.depth * context.treeSpace
471                 assert context.checkDepth( 1 ) , 'reached depth limit'
472                 context.prt( prefix )
473                 context.prt( 'DOCUMENT[%s] with %d nodes'
474                                          % ( self.position , self.numberOfNodes or -1 ) )
475                 context.prt( '\n' )
476                 #
477                 # Children
478                 #
479                 if self.children :
480                         if not context.checkDepth( 2 ) or context.childrenLimit == 0 :
481                                 context.prt( prefix )
482                                 context.prt( context.treeSpace )
483                                 context.prt( '[.. %d children ..]' % len( self.children ) )
484                                 context.prt( '\n' )
485                         elif context.checkChildrenCount( len( self.children ) ) :
486                                 context.depth += 1
487                                 for child in self.children :
488                                         child._asDebug( context )
489                                 context.depth -= 1
490                         else :
491                                 context.depth += 1
492                                 for child in self.children[ : context.childrenLimit ] :
493                                         child._asDebug( context )
494                                 context.depth -= 1
495                                 context.prt( prefix )
496                                 context.prt( context.treeSpace )
497                                 context.prt( '[.. and %d more children ..]'
498                                                          % ( len( self.children ) - context.childrenLimit ) )
499                                 context.prt( '\n' )
500
501         def __repr__( self ) :
502
503                 return '<Document with %d children>' % len( self.children )
504
505 class Element( Node ) :
506
507         __slots__ = [ 'name' , 'children' , 'attributes' ]
508
509         def __init__( self , name , attributes = () , children = () , finalize = False ) :
510
511                 super( Element , self ).__init__()
512
513                 for child in children :
514                         if not isinstance( child , ( Element , ProcessingInstruction , Comment , Text ) ) :
515                                 raise NodeError( '%s not allowed as child of Element' % typeOf( child ) )
516
517                 if someDuplicates( children , id ) :
518                         raise NodeError( 'Duplicate node is not allowed in an element' )
519                 children = _combineAdjacentText( children )
520
521                 names = set()
522                 for attribute in attributes :
523                         if not isinstance( attribute , Attribute ) :
524                                 raise NodeError( '%s not allowed as attribute of Element' % typeOf( attribute ) )
525                         if attribute.name in names :
526                                 raise NodeError( 'Duplicate attribute with name %r' % attribute.name )
527                         names.add( attribute.name )
528
529                 self.name = name
530                 self.children = children
531                 self.attributes = tuple( attributes )
532
533                 _chainNodes( self.children , self )
534                 _chainNodes( self.attributes , self )
535
536                 if finalize :
537                         self.finalize()
538
539         def clone( self ) :
540
541                 return Element( self.name ,
542                                                 map( lambda attr : attr.clone() , self.attributes ) ,
543                                                 map( lambda child : child.clone() , self.children ) )
544
545         def getAttribute( self , name ) :
546
547                 for attribute in self.attributes :
548                         if attribute.name == name :
549                                 return attribute
550
551         def iterStringValue( self ) :
552
553                 for node in iterDescendant( self ) :
554                         if isinstance( node , Text ) :
555                                 if node.content :
556                                         yield node.content
557
558         def dmAttributes( self ) :
559
560                 return self.attributes
561
562         def dmChildren( self ) :
563
564                 return self.children
565
566         def dmDocumentUri( self ) :
567
568                 return ()
569
570         def dmNamespaceNodes( self ) :
571
572                 return ()
573
574         def dmNodeKind( self ) :
575
576                 return 'element'
577
578         def dmNodeName( self ) :
579
580                 return self.name
581
582         def dmParent( self ) :
583
584                 return self.parent or ()
585
586         def dmStringValue( self ) :
587
588                 return ''.join( self.iterStringValue() )
589
590         def finalize( self , start = 0 ) :
591
592                 '''For every descendant of the element, update position and set root
593                 node'''
594
595                 pos = start
596                 for node in iterDescendantOrSelfFull( self ) :
597                         node.position = pos
598                         node.root = self
599                         pos += 1
600                 return pos
601
602         def _serialize( self , prt ) :
603
604                 prt( '<' )
605                 prt( self.name )
606                 for attribute in self.attributes :
607                         prt( ' ' )
608                         attribute._serialize( prt )
609                 if not self.children :
610                         prt( '/>' )
611                 else :
612                         prt( '>' )
613                         for child in self.children :
614                                 child._serialize( prt )
615                         prt( '</' )
616                         prt( self.name )
617                         prt( '>' )
618
619         def asRxp( self ) :
620
621                 return ( self.name ,
622                                  dict( item.asRxp() for item in self.attributes ) or None ,
623                                  list( item.asRxp() for item in self.children ) or None ,
624                                  self.meta )
625
626         def _asDebug( self , context ) :
627
628                 prefix = context.prefix + context.depth * context.treeSpace
629                 assert context.checkDepth( 1 ) , 'reached depth limit'
630                 context.prt( prefix )
631                 context.prt( 'ELEMENT[%s] %s' % ( self.position , shortenText( self.name , context.nameLimit ) ) )
632                 context.prt( '\n' )
633                 #
634                 # Attributes
635                 #
636                 if self.attributes :
637                         if not context.checkDepth( 2 ) or context.attributeLimit == 0 :
638                                 context.prt( context.treeSpace )
639                                 context.prt( prefix )
640                                 context.prt( '[.. %d attributes ..]' % len( self.attributes ) )
641                                 context.prt( '\n' )
642                         elif context.checkAttributeCount( len( self.attributes ) ) :
643                                 context.depth += 1
644                                 for attribute in self.attributes :
645                                         attribute._asDebug( context )
646                                 context.depth -= 1
647                         else :
648                                 context.depth += 1
649                                 for attribute in self.attributes[ : context.attributeLimit ] :
650                                         attribute._asDebug( context )
651                                 context.depth -= 1
652                                 context.prt( prefix )
653                                 context.prt( context.treeSpace )
654                                 context.prt( '[.. and %d more attributes ..]'
655                                                          % ( len( self.attributes ) - context.attributeLimit ) )
656                                 context.prt( '\n' )
657                 #
658                 # Children
659                 #
660                 if self.children :
661                         if not context.checkDepth( 2 ) or context.childrenLimit == 0 :
662                                 context.prt( context.treeSpace )
663                                 context.prt( prefix )
664                                 context.prt( '[.. %d children ..]' % len( self.children ) )
665                                 context.prt( '\n' )
666                         elif context.checkChildrenCount( len( self.children ) ) :
667                                 context.depth += 1
668                                 for child in self.children :
669                                         child._asDebug( context )
670                                 context.depth -= 1
671                         else :
672                                 context.depth += 1
673                                 for child in self.children[ : context.childrenLimit ] :
674                                         child._asDebug( context )
675                                 context.depth -= 1
676                                 context.prt( prefix )
677                                 context.prt( context.treeSpace )
678                                 context.prt( '[.. and %d more children ..]'
679                                                          % ( len( self.children ) - context.childrenLimit ) )
680                                 context.prt( '\n' )
681
682         def __repr__( self ) :
683
684                 return '<Element %s with %d attributes and %d children>' \
685                         % ( shortenText( self.name , 24 ) , len( self.attributes ) , len( self.children ) )
686
687 class Attribute( Node ) :
688
689         __slots__ = [ 'name' , 'value' ]
690
691         def __init__( self , name , value ) :
692
693                 Node.__init__( self )
694                 if not isinstance( name , basestring ) :
695                         raise NodeError( '%s not allowed as name to construct Attribute' % typeOf( name ) )
696                 if not isinstance( value , basestring ) :
697                         raise NodeError( '%s not allowed as value to construct Attribute' % typeOf( value ) )
698                 self.name = name
699                 self.value = value
700
701         def clone( self ) :
702
703                 return Attribute( self.name , self.value )
704
705         def iterStringValue( self ) :
706
707                 yield self.value
708
709         def dmAttributes( self ) :
710
711                 return ()
712
713         def dmChildren( self ) :
714
715                 return ()
716
717         def dmDocumentUri( self ) :
718
719                 return ()
720
721         def dmNamespaceNodes( self ) :
722
723                 return ()
724
725         def dmNodeKind( self ) :
726
727                 return 'attribute'
728
729         def dmNodeName( self ) :
730
731                 return self.name
732
733         def dmParent( self ) :
734
735                 return self.parent or ()
736
737         def dmStringValue( self ) :
738
739                 return self.value
740
741         def _serialize( self , prt ) :
742
743                 prt( self.name )
744                 prt( '="' )
745                 streamXmlQuoteAttribute( self.value , prt )
746                 prt( '"' )
747
748         def asRxp( self ) :
749
750                 return ( self.name , self.value )
751
752         def _asDebug( self , context ) :
753
754                 prefix = context.prefix + context.depth * context.treeSpace
755                 assert context.checkDepth( 1 ) , 'reached depth limit'
756                 context.prt( prefix )
757                 context.prt( 'ATTRIBUTE[%s] %s = `%s`' \
758                                          % ( self.position ,
759                                                  shortenText( self.name , context.nameLimit ) ,
760                                                  shortenText( self.value , context.textLimit ) ) )
761                 context.prt( '\n' )
762
763         def __repr__( self ) :
764
765                 return '<Attribute %s>' % shortenText( self.name , 24 ) 
766
767 class ProcessingInstruction( Node ) :
768
769         __slots__ = [ 'target' , 'content' ]
770
771         def __init__( self , target , content = '' ) :
772
773                 super( ProcessingInstruction , self ).__init__()
774
775                 if target.lower() == 'xml' :
776                         raise NodeError( 'A processing instruction target cannot be named \'%s\'.' % ( target , ) )
777                 if '?>' in content :
778                         raise NodeError( "'?>' must not occur inside content of processing instruction." )
779                 self.target = target
780                 self.content = content
781
782         def iterStringValue( self ) :
783
784                 yield self.value
785
786         def dmAttributes( self ) :
787
788                 return ()
789
790         def dmChildren( self ) :
791
792                 return ()
793
794         def dmDocumentUri( self ) :
795                 
796                 return ()
797
798         def dmNamespaceNodes( self ) :
799
800                 return ()
801
802         def dmNodeKind( self ) :
803
804                 return 'processing-instruction'
805
806         def dmNodeName( self ) :
807
808                 return self.target
809
810         def dmParent( self ) :
811
812                 return self.parent or ()
813
814         def dmStringValue( self ) :
815
816                 return self.content
817
818         def _serialize( self , prt ) :
819
820                 assert self.target.lower() != 'xml' , '"xml" not allowed as PI target name'
821                 assert '?>' not in self.content , '"?>" not allowed inside PI contents'
822
823                 prt( '<?' )
824                 prt( self.target )
825                 if self.content :
826                         prt( ' ' )
827                         prt( self.content )
828                 prt( '?>' )
829
830         def _asDebug( self , context ) :
831
832                 prefix = context.prefix + context.depth * context.treeSpace
833                 assert context.checkDepth( 1 ) , 'reached depth limit'
834                 context.prt( prefix )
835                 context.prt( 'PI[%s] %s = `%s`'
836                                          % ( self.position ,
837                                                  shortenText( self.target , context.nameLimit ) ,
838                                                  shortenText( self.content , context.commentLimit ) ) )
839                 context.prt( '\n' )
840
841 class Comment( Node ) :
842
843         __slots__ = [ 'content' ]
844
845         def __init__( self , content ) :
846
847                 super( Comment , self ).__init__()
848
849                 if not isinstance( content , basestring ) :
850                         raise NodeError( '%s not allowed to construct Comment' % typeOf( content ) )
851                 if '--' in content :
852                         raise NodeError( "'--' at position %d is not allowed in comment" % content.index( '--' ) )
853                 if content.endswith( '-' ) :
854                         raise NodeError( "'-' must not occur at end of comment" )
855
856                 self.content = content
857
858         def iterStringValue( self ) :
859
860                 yield self.content
861
862         def dmAttributes( self ) :
863
864                 return ()
865
866         def dmChildren( self ) :
867
868                 return ()
869
870         def dmDocumentUri( self ) :
871
872                 return ()
873
874         def dmNamespaceNodes( self ) :
875
876                 return ()
877
878         def dmNodeKind( self ) :
879
880                 return 'comment'
881
882         def dmNodeName( self ) :
883
884                 return ()
885
886         def dmParent( self ) :
887
888                 return self.parent or ()
889
890         def dmStringValue( self ) :
891
892                 return self.content
893
894         def _serialize( self , prt ) :
895
896                 assert '--' not in self.content \
897                         and not self.content.endswith( '-' ) , \
898                         'invalid comment content'
899
900                 prt( '<!--' )
901                 prt( self.content )
902                 prt( '-->' )
903
904         def asRxp( self ) :
905
906                 # Not supported, use a raw element instead.
907                 return ( None , None , '<!--' + self.content.replace( '--' , '- -' ).replace( '--' , '- -' ) + '-->' )
908
909         def _asDebug( self , context ) :
910
911                 prefix = context.prefix + context.depth * context.treeSpace
912                 assert context.checkDepth( 1 ) , 'reached depth limit'
913                 context.prt( prefix )
914                 context.prt( 'COMMENT[%s] %r' % ( self.position , shortenText( self.content , context.commentLimit ) ) )
915                 context.prt( '\n' )
916
917         def __repr__( self ) :
918
919                 return '<Comment with %d characters>' % len( self.content )
920
921 class Text( Node ) :
922
923         __slots__ = [ 'content' ]
924
925         def __init__( self , content ) :
926
927                 super( Text , self ).__init__()
928
929                 if not isinstance( content , basestring ) :
930                         raise NodeError( '%s not allowed to construct Text' % typeOf( content ) )
931
932                 self.content = content
933
934         def clone( self ) :
935
936                 return Text( self.content )
937
938         def setParent( self , parent ) :
939
940                 if not self.content :
941                         raise NodeError( 'Text node inside document must not be empty' )
942                 return super( Text , self ).setParent( self , parent )
943
944         def iterStringValue( self ) :
945
946                 yield self.content
947
948         def dmAttributes( self ) :
949
950                 return ()
951
952         def dmChildren( self ) :
953
954                 return ()
955
956         def dmDocumentUri( self ) :
957
958                 return ()
959
960         def dmNamespaceNodes( self ) :
961
962                 return ()
963
964         def dmNodeKind( self ) :
965
966                 return 'text'
967
968         def dmNodeName( self ) :
969
970                 return ()
971
972         def dmParent( self ) :
973
974                 return self.parent or ()
975
976         def dmStringValue( self ) :
977
978                 return self.content
979
980         def _serialize( self , prt ) :
981
982                 streamXmlQuoteText( self.content , prt )
983
984         def asRxp( self ) :
985
986                 return self.content
987
988         def _asDebug( self , context ) :
989
990                 prefix = context.prefix + context.depth * context.treeSpace
991                 assert context.checkDepth( 1 ) , 'reached depth limit'
992                 context.prt( prefix )
993                 context.prt( 'TEXT[%s] %r' % ( self.position , shortenText( self.content , context.textLimit ) ) )
994                 context.prt( '\n' )
995
996         def __repr__( self ) :
997
998                 return '<Text with %d characters>' % len( self.content )
999
1000 from iterators import iterDescendant, iterDescendantOrSelfFull
1001
1002 # Local Variables:
1003 # tab-width: 4
1004 # python-indent: 4
1005 # End: