imip-agent

Annotated markup.py

1253:333740ca50b6
2017-09-12 Paul Boddie Consider period replacement status when comparing form periods.
paul@69 1
# This code is in the public domain, it comes
paul@69 2
# with absolutely no warranty and you can do
paul@69 3
# absolutely whatever you want with it.
paul@69 4
paul@1116 5
__date__ = '16 March 2015'
paul@1116 6
__version__ = '1.10'
paul@69 7
__doc__= """
paul@69 8
This is markup.py - a Python module that attempts to
paul@69 9
make it easier to generate HTML/XML from a Python program
paul@69 10
in an intuitive, lightweight, customizable and pythonic way.
paul@1116 11
It works with both python 2 and 3.
paul@69 12
paul@69 13
The code is in the public domain.
paul@69 14
paul@69 15
Version: %s as of %s.
paul@69 16
paul@69 17
Documentation and further info is at http://markup.sourceforge.net/
paul@69 18
paul@69 19
Please send bug reports, feature requests, enhancement
paul@69 20
ideas or questions to nogradi at gmail dot com.
paul@69 21
paul@69 22
Installation: drop markup.py somewhere into your Python path.
paul@69 23
""" % ( __version__, __date__ )
paul@69 24
paul@69 25
try:
paul@69 26
    basestring
paul@69 27
    import string
paul@69 28
except:
paul@69 29
    # python 3
paul@69 30
    basestring = str
paul@69 31
    string = str
paul@1116 32
    long = int
paul@69 33
paul@69 34
# tags which are reserved python keywords will be referred 
paul@69 35
# to by a leading underscore otherwise we end up with a syntax error
paul@69 36
import keyword
paul@69 37
paul@69 38
class element:
paul@69 39
    """This class handles the addition of a new element."""
paul@69 40
paul@69 41
    def __init__( self, tag, case='lower', parent=None ):
paul@69 42
        self.parent = parent
paul@69 43
paul@69 44
        if case == 'upper':
paul@69 45
            self.tag = tag.upper( )
paul@69 46
        elif case == 'lower':
paul@69 47
            self.tag = tag.lower( )
paul@69 48
        elif case =='given':
paul@69 49
            self.tag = tag
paul@69 50
        else:
paul@69 51
            self.tag = tag
paul@69 52
    
paul@69 53
    def __call__( self, *args, **kwargs ):
paul@69 54
        if len( args ) > 1:
paul@69 55
            raise ArgumentError( self.tag )
paul@69 56
paul@69 57
        # if class_ was defined in parent it should be added to every element
paul@69 58
        if self.parent is not None and self.parent.class_ is not None:
paul@69 59
            if 'class_' not in kwargs:
paul@69 60
                kwargs['class_'] = self.parent.class_
paul@69 61
            
paul@69 62
        if self.parent is None and len( args ) == 1:
paul@69 63
            x = [ self.render( self.tag, False, myarg, mydict ) for myarg, mydict in _argsdicts( args, kwargs ) ]
paul@69 64
            return '\n'.join( x )
paul@69 65
        elif self.parent is None and len( args ) == 0:
paul@69 66
            x = [ self.render( self.tag, True, myarg, mydict ) for myarg, mydict in _argsdicts( args, kwargs ) ]
paul@69 67
            return '\n'.join( x )
paul@69 68
            
paul@69 69
        if self.tag in self.parent.twotags:
paul@69 70
            for myarg, mydict in _argsdicts( args, kwargs ):
paul@69 71
                self.render( self.tag, False, myarg, mydict )
paul@69 72
        elif self.tag in self.parent.onetags:
paul@69 73
            if len( args ) == 0:
paul@69 74
                for myarg, mydict in _argsdicts( args, kwargs ):
paul@69 75
                    self.render( self.tag, True, myarg, mydict )    # here myarg is always None, because len( args ) = 0
paul@69 76
            else:
paul@69 77
                raise ClosingError( self.tag )
paul@69 78
        elif self.parent.mode == 'strict_html' and self.tag in self.parent.deptags:
paul@69 79
            raise DeprecationError( self.tag )
paul@69 80
        else:
paul@69 81
            raise InvalidElementError( self.tag, self.parent.mode )
paul@69 82
    
paul@69 83
    def render( self, tag, single, between, kwargs ):
paul@69 84
        """Append the actual tags to content."""
paul@69 85
paul@69 86
        out = "<%s" % tag
paul@69 87
        for key, value in list( kwargs.items( ) ):
paul@69 88
            if value is not None:               # when value is None that means stuff like <... checked>
paul@69 89
                key = key.strip('_')            # strip this so class_ will mean class, etc.
paul@69 90
                if key == 'http_equiv':         # special cases, maybe change _ to - overall?
paul@69 91
                    key = 'http-equiv'
paul@69 92
                elif key == 'accept_charset':
paul@69 93
                    key = 'accept-charset'
paul@69 94
                out = "%s %s=\"%s\"" % ( out, key, escape( value ) )
paul@69 95
            else:
paul@69 96
                out = "%s %s" % ( out, key )
paul@69 97
        if between is not None:
paul@789 98
            out = "%s>%s</%s>" % ( out, escape( between ), tag )
paul@69 99
        else:
paul@69 100
            if single:
paul@69 101
                out = "%s />" % out
paul@69 102
            else:
paul@69 103
                out = "%s>" % out
paul@69 104
        if self.parent is not None:
paul@69 105
            self.parent.content.append( out )
paul@69 106
        else:
paul@69 107
            return out
paul@69 108
    
paul@69 109
    def close( self ):
paul@69 110
        """Append a closing tag unless element has only opening tag."""
paul@69 111
paul@69 112
        if self.tag in self.parent.twotags:
paul@69 113
            self.parent.content.append( "</%s>" % self.tag )
paul@69 114
        elif self.tag in self.parent.onetags:
paul@69 115
            raise ClosingError( self.tag )
paul@69 116
        elif self.parent.mode == 'strict_html' and self.tag in self.parent.deptags:
paul@69 117
            raise DeprecationError( self.tag )
paul@69 118
paul@69 119
    def open( self, **kwargs ):
paul@69 120
        """Append an opening tag."""
paul@69 121
paul@69 122
        if self.tag in self.parent.twotags or self.tag in self.parent.onetags:
paul@69 123
            self.render( self.tag, False, None, kwargs )
paul@69 124
        elif self.mode == 'strict_html' and self.tag in self.parent.deptags:
paul@69 125
            raise DeprecationError( self.tag )
paul@69 126
paul@69 127
class page:
paul@69 128
    """This is our main class representing a document. Elements are added
paul@69 129
    as attributes of an instance of this class."""
paul@69 130
paul@69 131
    def __init__( self, mode='strict_html', case='lower', onetags=None, twotags=None, separator='\n', class_=None ):
paul@69 132
        """Stuff that effects the whole document.
paul@69 133
paul@69 134
        mode -- 'strict_html'   for HTML 4.01 (default)
paul@69 135
                'html'          alias for 'strict_html'
paul@69 136
                'loose_html'    to allow some deprecated elements
paul@69 137
                'xml'           to allow arbitrary elements
paul@69 138
paul@69 139
        case -- 'lower'         element names will be printed in lower case (default)
paul@69 140
                'upper'         they will be printed in upper case
paul@69 141
                'given'         element names will be printed as they are given
paul@69 142
paul@69 143
        onetags --              list or tuple of valid elements with opening tags only
paul@69 144
        twotags --              list or tuple of valid elements with both opening and closing tags
paul@69 145
                                these two keyword arguments may be used to select
paul@69 146
                                the set of valid elements in 'xml' mode
paul@69 147
                                invalid elements will raise appropriate exceptions
paul@69 148
        
paul@69 149
        separator --            string to place between added elements, defaults to newline
paul@69 150
        
paul@69 151
        class_ --               a class that will be added to every element if defined"""
paul@69 152
        
paul@69 153
        valid_onetags = [ "AREA", "BASE", "BR", "COL", "FRAME", "HR", "IMG", "INPUT", "LINK", "META", "PARAM" ]
paul@69 154
        valid_twotags = [ "A", "ABBR", "ACRONYM", "ADDRESS", "B", "BDO", "BIG", "BLOCKQUOTE", "BODY", "BUTTON",
paul@69 155
                "CAPTION", "CITE", "CODE", "COLGROUP", "DD", "DEL", "DFN", "DIV", "DL", "DT", "EM", "FIELDSET",
paul@69 156
                "FORM", "FRAMESET", "H1", "H2", "H3", "H4", "H5", "H6", "HEAD", "HTML", "I", "IFRAME", "INS",
paul@69 157
                "KBD", "LABEL", "LEGEND", "LI", "MAP", "NOFRAMES", "NOSCRIPT", "OBJECT", "OL", "OPTGROUP",
paul@69 158
                "OPTION", "P", "PRE", "Q", "SAMP", "SCRIPT", "SELECT", "SMALL", "SPAN", "STRONG", "STYLE",
paul@69 159
                "SUB", "SUP", "TABLE", "TBODY", "TD", "TEXTAREA", "TFOOT", "TH", "THEAD", "TITLE", "TR",
paul@69 160
                "TT", "UL", "VAR" ]
paul@69 161
        deprecated_onetags = [ "BASEFONT", "ISINDEX" ]
paul@69 162
        deprecated_twotags = [ "APPLET", "CENTER", "DIR", "FONT", "MENU", "S", "STRIKE", "U" ]
paul@69 163
paul@69 164
        self.header = [ ]
paul@69 165
        self.content = [ ]
paul@69 166
        self.footer = [ ]
paul@69 167
        self.case = case
paul@69 168
        self.separator = separator
paul@69 169
paul@69 170
        # init( ) sets it to True so we know that </body></html> has to be printed at the end
paul@69 171
        self._full = False
paul@69 172
        self.class_= class_
paul@69 173
paul@69 174
        if mode == 'strict_html' or mode == 'html':
paul@69 175
            self.onetags = valid_onetags
paul@69 176
            self.onetags += list( map( string.lower, self.onetags ) )
paul@69 177
            self.twotags = valid_twotags
paul@69 178
            self.twotags += list( map( string.lower, self.twotags ) )
paul@69 179
            self.deptags = deprecated_onetags + deprecated_twotags
paul@69 180
            self.deptags += list( map( string.lower, self.deptags ) )
paul@69 181
            self.mode = 'strict_html'
paul@69 182
        elif mode == 'loose_html':
paul@69 183
            self.onetags = valid_onetags + deprecated_onetags 
paul@69 184
            self.onetags += list( map( string.lower, self.onetags ) )
paul@69 185
            self.twotags = valid_twotags + deprecated_twotags
paul@69 186
            self.twotags += list( map( string.lower, self.twotags ) )
paul@69 187
            self.mode = mode
paul@69 188
        elif mode == 'xml':
paul@69 189
            if onetags and twotags:
paul@69 190
                self.onetags = onetags
paul@69 191
                self.twotags = twotags
paul@69 192
            elif ( onetags and not twotags ) or ( twotags and not onetags ):
paul@69 193
                raise CustomizationError( )
paul@69 194
            else:
paul@69 195
                self.onetags = russell( )
paul@69 196
                self.twotags = russell( )
paul@69 197
            self.mode = mode
paul@69 198
        else:
paul@69 199
            raise ModeError( mode )
paul@69 200
paul@69 201
    def __getattr__( self, attr ):
paul@69 202
paul@69 203
        # tags should start with double underscore
paul@69 204
        if attr.startswith("__") and attr.endswith("__"):
paul@69 205
            raise AttributeError( attr )
paul@69 206
        # tag with single underscore should be a reserved keyword
paul@69 207
        if attr.startswith( '_' ):
paul@69 208
            attr = attr.lstrip( '_' ) 
paul@69 209
            if attr not in keyword.kwlist:
paul@69 210
                raise AttributeError( attr )
paul@69 211
paul@69 212
        return element( attr, case=self.case, parent=self )
paul@69 213
paul@69 214
    def __str__( self ):
paul@69 215
        
paul@69 216
        if self._full and ( self.mode == 'strict_html' or self.mode == 'loose_html' ):
paul@69 217
            end = [ '</body>', '</html>' ]
paul@69 218
        else:
paul@69 219
            end = [ ]
paul@69 220
paul@69 221
        return self.separator.join( self.header + self.content + self.footer + end )
paul@69 222
paul@69 223
    def __call__( self, escape=False ):
paul@69 224
        """Return the document as a string.
paul@69 225
paul@69 226
        escape --   False   print normally
paul@69 227
                    True    replace < and > by &lt; and &gt;
paul@69 228
                            the default escape sequences in most browsers"""
paul@69 229
paul@69 230
        if escape:
paul@69 231
            return _escape( self.__str__( ) )
paul@69 232
        else:
paul@69 233
            return self.__str__( )
paul@69 234
paul@69 235
    def add( self, text ):
paul@69 236
        """This is an alias to addcontent."""
paul@69 237
        self.addcontent( text )
paul@69 238
paul@69 239
    def addfooter( self, text ):
paul@69 240
        """Add some text to the bottom of the document"""
paul@793 241
        self.footer.append( escape( text ) )
paul@69 242
paul@69 243
    def addheader( self, text ):
paul@69 244
        """Add some text to the top of the document"""
paul@793 245
        self.header.append( escape( text ) )
paul@69 246
paul@69 247
    def addcontent( self, text ):
paul@69 248
        """Add some text to the main part of the document"""
paul@793 249
        self.content.append( escape( text ) )
paul@69 250
paul@69 251
paul@69 252
    def init( self, lang='en', css=None, metainfo=None, title=None, header=None,
paul@69 253
              footer=None, charset=None, encoding=None, doctype=None, bodyattrs=None, script=None, base=None ):
paul@69 254
        """This method is used for complete documents with appropriate
paul@69 255
        doctype, encoding, title, etc information. For an HTML/XML snippet
paul@69 256
        omit this method.
paul@69 257
paul@69 258
        lang --     language, usually a two character string, will appear
paul@69 259
                    as <html lang='en'> in html mode (ignored in xml mode)
paul@69 260
        
paul@69 261
        css --      Cascading Style Sheet filename as a string or a list of
paul@69 262
                    strings for multiple css files (ignored in xml mode)
paul@69 263
paul@69 264
        metainfo -- a dictionary in the form { 'name':'content' } to be inserted
paul@69 265
                    into meta element(s) as <meta name='name' content='content'>
paul@69 266
                    (ignored in xml mode)
paul@69 267
        
paul@69 268
        base     -- set the <base href="..."> tag in <head>
paul@69 269
        
paul@69 270
        bodyattrs --a dictionary in the form { 'key':'value', ... } which will be added
paul@69 271
                    as attributes of the <body> element as <body key='value' ... >
paul@69 272
                    (ignored in xml mode)
paul@69 273
paul@69 274
        script --   dictionary containing src:type pairs, <script type='text/type' src=src></script>
paul@69 275
                    or a list of [ 'src1', 'src2', ... ] in which case 'javascript' is assumed for all
paul@69 276
paul@69 277
        title --    the title of the document as a string to be inserted into
paul@69 278
                    a title element as <title>my title</title> (ignored in xml mode)
paul@69 279
paul@69 280
        header --   some text to be inserted right after the <body> element
paul@69 281
                    (ignored in xml mode)
paul@69 282
paul@69 283
        footer --   some text to be inserted right before the </body> element
paul@69 284
                    (ignored in xml mode)
paul@69 285
paul@69 286
        charset --  a string defining the character set, will be inserted into a
paul@69 287
                    <meta http-equiv='Content-Type' content='text/html; charset=myset'>
paul@69 288
                    element (ignored in xml mode)
paul@69 289
paul@69 290
        encoding -- a string defining the encoding, will be put into to first line of
paul@69 291
                    the document as <?xml version='1.0' encoding='myencoding' ?> in
paul@69 292
                    xml mode (ignored in html mode)
paul@69 293
paul@69 294
        doctype --  the document type string, defaults to
paul@69 295
                    <!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>
paul@69 296
                    in html mode (ignored in xml mode)"""
paul@69 297
paul@69 298
        self._full = True
paul@69 299
paul@69 300
        if self.mode == 'strict_html' or self.mode == 'loose_html':
paul@69 301
            if doctype is None:
paul@69 302
                doctype = "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>"
paul@69 303
            self.header.append( doctype )
paul@69 304
            self.html( lang=lang )
paul@69 305
            self.head( )
paul@69 306
            if charset is not None:
paul@69 307
                self.meta( http_equiv='Content-Type', content="text/html; charset=%s" % charset )
paul@69 308
            if metainfo is not None:
paul@69 309
                self.metainfo( metainfo )
paul@69 310
            if css is not None:
paul@69 311
                self.css( css )
paul@69 312
            if title is not None:
paul@69 313
                self.title( title )
paul@69 314
            if script is not None:
paul@69 315
                self.scripts( script )
paul@69 316
            if base is not None:
paul@69 317
                self.base( href='%s' % base )
paul@69 318
            self.head.close()
paul@69 319
            if bodyattrs is not None:
paul@69 320
                self.body( **bodyattrs )
paul@69 321
            else:
paul@69 322
                self.body( )
paul@69 323
            if header is not None:
paul@69 324
                self.content.append( header )
paul@69 325
            if footer is not None:
paul@69 326
                self.footer.append( footer )
paul@69 327
paul@69 328
        elif self.mode == 'xml':
paul@69 329
            if doctype is None:
paul@69 330
                if encoding is not None:
paul@69 331
                    doctype = "<?xml version='1.0' encoding='%s' ?>" % encoding
paul@69 332
                else:
paul@69 333
                    doctype = "<?xml version='1.0' ?>"
paul@69 334
            self.header.append( doctype )
paul@69 335
paul@69 336
    def css( self, filelist ):
paul@69 337
        """This convenience function is only useful for html.
paul@69 338
        It adds css stylesheet(s) to the document via the <link> element."""
paul@69 339
      
paul@69 340
        if isinstance( filelist, basestring ):
paul@69 341
            self.link( href=filelist, rel='stylesheet', type='text/css', media='all' )
paul@69 342
        else:
paul@69 343
            for file in filelist:
paul@69 344
                self.link( href=file, rel='stylesheet', type='text/css', media='all' )
paul@69 345
paul@69 346
    def metainfo( self, mydict ):
paul@69 347
        """This convenience function is only useful for html.
paul@69 348
        It adds meta information via the <meta> element, the argument is
paul@69 349
        a dictionary of the form { 'name':'content' }."""
paul@69 350
paul@69 351
        if isinstance( mydict, dict ):
paul@69 352
            for name, content in list( mydict.items( ) ):
paul@69 353
                self.meta( name=name, content=content )
paul@69 354
        else:
paul@69 355
            raise TypeError( "Metainfo should be called with a dictionary argument of name:content pairs." )
paul@69 356
paul@69 357
    def scripts( self, mydict ):
paul@69 358
        """Only useful in html, mydict is dictionary of src:type pairs or a list
paul@69 359
        of script sources [ 'src1', 'src2', ... ] in which case 'javascript' is assumed for type.
paul@69 360
        Will be rendered as <script type='text/type' src=src></script>"""
paul@69 361
paul@69 362
        if isinstance( mydict, dict ):
paul@69 363
            for src, type in list( mydict.items( ) ):
paul@69 364
                self.script( '', src=src, type='text/%s' % type )
paul@69 365
        else:
paul@69 366
            try:
paul@69 367
                for src in mydict:
paul@69 368
                    self.script( '', src=src, type='text/javascript' )
paul@69 369
            except:
paul@69 370
                raise TypeError( "Script should be given a dictionary of src:type pairs or a list of javascript src's." )
paul@69 371
paul@69 372
paul@69 373
class _oneliner:
paul@69 374
    """An instance of oneliner returns a string corresponding to one element.
paul@69 375
    This class can be used to write 'oneliners' that return a string
paul@69 376
    immediately so there is no need to instantiate the page class."""
paul@69 377
    
paul@69 378
    def __init__( self, case='lower' ):
paul@69 379
        self.case = case
paul@69 380
    
paul@69 381
    def __getattr__( self, attr ):
paul@69 382
        
paul@69 383
        # tags should start with double underscore
paul@69 384
        if attr.startswith("__") and attr.endswith("__"):
paul@69 385
            raise AttributeError( attr )
paul@69 386
        # tag with single underscore should be a reserved keyword
paul@69 387
        if attr.startswith( '_' ):
paul@69 388
            attr = attr.lstrip( '_' ) 
paul@69 389
            if attr not in keyword.kwlist:
paul@69 390
                raise AttributeError( attr )
paul@69 391
        
paul@69 392
        return element( attr, case=self.case, parent=None )
paul@69 393
paul@69 394
oneliner = _oneliner( case='lower' )
paul@69 395
upper_oneliner = _oneliner( case='upper' )
paul@69 396
given_oneliner = _oneliner( case='given' )
paul@69 397
paul@69 398
def _argsdicts( args, mydict ):
paul@69 399
    """A utility generator that pads argument list and dictionary values, will only be called with len( args ) = 0, 1."""
paul@69 400
    
paul@69 401
    if len( args ) == 0:
paul@69 402
        args = None, 
paul@69 403
    elif len( args ) == 1:
paul@69 404
        args = _totuple( args[0] )
paul@69 405
    else:
paul@69 406
        raise Exception( "We should have never gotten here." )
paul@69 407
paul@69 408
    mykeys = list( mydict.keys( ) )
paul@69 409
    myvalues = list( map( _totuple, list( mydict.values( ) ) ) )
paul@69 410
paul@69 411
    maxlength = max( list( map( len, [ args ] + myvalues ) ) )
paul@69 412
paul@69 413
    for i in range( maxlength ):
paul@69 414
        thisdict = { }
paul@69 415
        for key, value in zip( mykeys, myvalues ):
paul@69 416
            try:
paul@69 417
                thisdict[ key ] = value[i]
paul@69 418
            except IndexError:
paul@69 419
                thisdict[ key ] = value[-1]
paul@69 420
        try:
paul@69 421
            thisarg = args[i]
paul@69 422
        except IndexError:
paul@69 423
            thisarg = args[-1]
paul@69 424
paul@69 425
        yield thisarg, thisdict
paul@69 426
paul@69 427
def _totuple( x ):
paul@69 428
    """Utility stuff to convert string, int, long, float, None or anything to a usable tuple."""
paul@69 429
paul@69 430
    if isinstance( x, basestring ):
paul@69 431
        out = x,
paul@69 432
    elif isinstance( x, ( int, long, float ) ):
paul@69 433
        out = str( x ),
paul@69 434
    elif x is None:
paul@69 435
        out = None,
paul@69 436
    else:
paul@69 437
        out = tuple( x )
paul@69 438
paul@69 439
    return out
paul@69 440
paul@69 441
def escape( text, newline=False ):
paul@69 442
    """Escape special html characters."""
paul@69 443
paul@69 444
    if isinstance( text, basestring ):
paul@69 445
        if '&' in text:
paul@69 446
            text = text.replace( '&', '&amp;' )
paul@69 447
        if '>' in text:
paul@69 448
            text = text.replace( '>', '&gt;' )
paul@69 449
        if '<' in text:
paul@69 450
            text = text.replace( '<', '&lt;' )
paul@69 451
        if '\"' in text:
paul@69 452
            text = text.replace( '\"', '&quot;' )
paul@69 453
        if '\'' in text:
paul@270 454
            text = text.replace( '\'', '&apos;' )
paul@69 455
        if newline:
paul@69 456
            if '\n' in text:
paul@69 457
                text = text.replace( '\n', '<br>' )
paul@69 458
paul@69 459
    return text
paul@69 460
paul@69 461
_escape = escape
paul@69 462
paul@69 463
def unescape( text ):
paul@69 464
    """Inverse of escape."""
paul@69 465
    
paul@69 466
    if isinstance( text, basestring ):
paul@69 467
        if '&amp;' in text:
paul@69 468
            text = text.replace( '&amp;', '&' )
paul@69 469
        if '&gt;' in text:
paul@69 470
            text = text.replace( '&gt;', '>' )
paul@69 471
        if '&lt;' in text:
paul@69 472
            text = text.replace( '&lt;', '<' )
paul@69 473
        if '&quot;' in text:
paul@69 474
            text = text.replace( '&quot;', '\"' )
paul@69 475
paul@69 476
    return text
paul@69 477
paul@69 478
class dummy:
paul@69 479
    """A dummy class for attaching attributes."""
paul@69 480
    pass
paul@69 481
paul@69 482
doctype = dummy( )
paul@69 483
doctype.frameset = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">"""
paul@69 484
doctype.strict = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">"""
paul@69 485
doctype.loose = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">"""
paul@69 486
paul@69 487
class russell:
paul@69 488
    """A dummy class that contains anything."""
paul@69 489
paul@69 490
    def __contains__( self, item ):
paul@69 491
        return True
paul@69 492
paul@69 493
paul@69 494
class MarkupError( Exception ):
paul@69 495
    """All our exceptions subclass this."""
paul@69 496
    def __str__( self ):
paul@69 497
        return self.message
paul@69 498
paul@69 499
class ClosingError( MarkupError ):
paul@69 500
    def __init__( self, tag ):
paul@69 501
        self.message = "The element '%s' does not accept non-keyword arguments (has no closing tag)." % tag
paul@69 502
paul@69 503
class OpeningError( MarkupError ):
paul@69 504
    def __init__( self, tag ):
paul@69 505
        self.message = "The element '%s' can not be opened." % tag
paul@69 506
paul@69 507
class ArgumentError( MarkupError ):
paul@69 508
    def __init__( self, tag ):
paul@69 509
        self.message = "The element '%s' was called with more than one non-keyword argument." % tag
paul@69 510
paul@69 511
class InvalidElementError( MarkupError ):
paul@69 512
    def __init__( self, tag, mode ):
paul@69 513
        self.message = "The element '%s' is not valid for your mode '%s'." % ( tag, mode )
paul@69 514
paul@69 515
class DeprecationError( MarkupError ):
paul@69 516
    def __init__( self, tag ):
paul@69 517
        self.message = "The element '%s' is deprecated, instantiate markup.page with mode='loose_html' to allow it." % tag
paul@69 518
paul@69 519
class ModeError( MarkupError ):
paul@69 520
    def __init__( self, mode ):
paul@69 521
        self.message = "Mode '%s' is invalid, possible values: strict_html, html (alias for strict_html), loose_html, xml." % mode
paul@69 522
paul@69 523
class CustomizationError( MarkupError ):
paul@69 524
    def __init__( self ):
paul@69 525
        self.message = "If you customize the allowed elements, you must define both types 'onetags' and 'twotags'."
paul@69 526
paul@69 527
if __name__ == '__main__':
paul@69 528
    import sys
paul@69 529
    sys.stdout.write( __doc__ )