This file is indexed.

/usr/share/pyshared/doconce/DocWriter.py is in doconce 0.7.3-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

   1
   2
   3
   4
   5
   6
   7
   8
   9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  21
  22
  23
  24
  25
  26
  27
  28
  29
  30
  31
  32
  33
  34
  35
  36
  37
  38
  39
  40
  41
  42
  43
  44
  45
  46
  47
  48
  49
  50
  51
  52
  53
  54
  55
  56
  57
  58
  59
  60
  61
  62
  63
  64
  65
  66
  67
  68
  69
  70
  71
  72
  73
  74
  75
  76
  77
  78
  79
  80
  81
  82
  83
  84
  85
  86
  87
  88
  89
  90
  91
  92
  93
  94
  95
  96
  97
  98
  99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
"""
DocWriter is a tool for writing documents in ASCII, HTML,
LaTeX, Doconce, and other formats based on input from Python
datastructures.

The base class _BaseWriter defines common functions and data
structures, while subclasses HTML, Doconce, etc.  implement (i.e.,
write to) various formats.

This module works, but is unifinished and needs documentation!
"""

from StringIO import StringIO
import re, os, glob, commands

class _BaseWriter:
    """
    Base class for document writing classes.
    Each subclass implements a specific format (html, latex,
    rst, etc.).
    """
    def __init__(self, format, filename_extension):
        # use StringIO as a string "file" for writing the document:
        self.file = StringIO()
        self.filename_extension = filename_extension
        self.format = format
        self._footer_called = False

    document = property(fget=lambda self: self.file.getvalue(),
                        doc='Formatted document as a string')

    def write_to_file(self, filename):
        """
        Write formatted document to a file.
        Just give the stem of the file name;
        the extension will be automatically added (depending on the
        document format).
        """
        # footer?
        if not self._footer_called:
            self.footer()
            self._footer_called = True

        f = open(filename + self.filename_extension, 'w')
        f.write(self.document)
        f.close()

    def __str__(self):
        """Return formatted document."""
        return self.document

    def header(self):
        """Header as required by format. Called in constructor."""
        pass

    def footer(self):
        """Footer as required by format. Called in write_to_file."""
        pass

    def not_impl(self, method):
        raise NotImplementedError, \
              'method "%s" in class "%s" is not implemented' % \
              (method, self.__class__.__name__)

    def title(self, title, authors_and_institutions=[], date='today'):
        """
        Provide title and authors.

        @param title: document title (string).
        @param authors_and_institutions: list of authors and their
        associated institutions, where each list item is a tuple/list
        with author as first element followed by the name of all
        institutions this author is associated with.
        @param date: None implies no date, while 'today' generates
        the current date, otherwise a string is supplied.
        """
        self.not_impl('title')

    def today_date(self):
        """Return a string with today's date suitably formatted."""
        import time
        return time.strftime('%a, %d %b %Y (%H:%M)')

    def section(self, title, label=None):
        """
        Write a section heading with the given title and an optional
        label (for navigation).
        """
        self.not_impl('section')

    def subsection(self, title, label=None):
        """
        Write a subsection heading with the given title and an optional
        label (for navigation).
        """
        self.not_impl('subsection')

    def subsubsection(self, title, label=None):
        """
        Write a subsubsection heading with the given title and an optional
        label (for navigation).
        """
        self.not_impl('subsubsection')

    def paragraph(self, title, ending='.', label=None):
        """
        Write a paragraph heading with the given title and an ending
        (period, question mark, colon) and an optional label (for navigation).
        """
        self.not_impl('paragraph')

    def paragraph_separator(self):
        """
        Add a (space) separator between running paragraphs.
        """
        self.not_impl('paragraph_separator')

    def text(self, text, indent=0):
        """
        Write plain text. Each line can be idented by a given number
        of spaces.
        """
        # do the indentation here, subclasses should call this method first
        text = '\n'.join([' '*indent + line for line in text.split('\n')])
        # subclasses must substitute Doconce simple formatting
        # using the expandtext method
        return text

    def expandtext(self, text, tags, tags_replacements):
        """
        In a string text, replace all occurences of strings defined in tags
        by the corresponding strings defined in tags_replacements.
        Both tags and tags_replacements are dictionaries with keys such
        as 'bold', 'emphasize', 'verbatim', 'math', and values consisting of
        regular expression patterns.

        This method allows application code to use some generic ways of
        writing emphasized, boldface, and verbatim text, typically in the
        Doconce format with *emphasized text*, _boldface text_, and
        `verbatim fixed font width text`.
        """
        for tag in tags:
            tag_pattern = tags[tag]
            c = re.compile(tag_pattern, re.MULTILINE)
            try:
                tag_replacement = tags_replacements[tag]
            except KeyError:
                continue
            if tag_replacement is not None:
                text = c.sub(tag_replacement, text)
        return text

    def list(self, items, listtype='itemize'):
        """
        Write list or nested lists.

        @param items: list of items.
        @param listtype: 'itemize', 'enumerate', or 'description'.
        """
        # call _BaseWriter.unfold_list to traverse the list
        # and use self.item_handler to typeset each item
        self.not_impl('list')

    def unfold_list(self, items, item_handler, listtype, level=0):
        """
        Traverse a possibly nested list and call item_handler for
        each item. To be used in subclasses for easy list handling.

        @param items: list to be processed.
        @param item_handler: callable, see that method for doc of arguments.
        @param listtype: 'itemize', 'enumerate', or 'description'.
        @param level: the level of a sublist (0 is main list, increased by 1
        for each sublevel).
        """
        # check for common error (a trailing comma...):
        if isinstance(items, tuple) and len(items) == 1:
            raise ValueError, 'list is a 1-tuple, error? If there is '\
                  'only one item in the list, make a real Python list '\
                  'object instead - current list is\n(%s,)' % items
        item_handler('_begin', listtype, level)
        for i, item in enumerate(items):
            if isinstance(item, (list,tuple)):
                self.unfold_list(item, item_handler, listtype, level+1)
            elif isinstance(item, basestring):
                if listtype == 'description':
                    # split out keyword in a description list:
                    parts = item.split(':')
                    keyword = parts[0]
                    item = ':'.join(parts[1:])
                    item_handler(item, listtype, level, keyword)
                else:
                    item_handler(item, listtype, level)
            else:
                raise TypeError, 'wrong %s for item' % type(item)
        item_handler('_end', listtype, level)

    def item_handler(self, item, listtype, level, keyword=None):
        """
        Write out the syntax for an item in a list.

        @param item: text assoicated with the current list item. If item
        equals '_begin' or '_end', appropriate begin/end formatting of
        the list is written instead of an ordinary item.
        @param listtype: 'itemize, 'enumerate', or 'description'.
        @param level: list level number, 0 is the mainlist, increased by 1
        for each sublist (the level number implies the amount of indentation).
        @param keyword: the keyword of the item in a 'description' list.
        """
        self.not_impl('item_handler')

    def verbatim(self, code):
        """
        Write verbatim text in fixed-width form
        (typically for computer code).
        """
        self.not_impl('verbatim')

    def math(self, text):
        """Write block of mathematical text (equations)."""
        # default: dump raw
        self.raw(text)

    def raw(self, text):
        """Write text directly 'as is' to output."""
        self.file.write(text)

    def figure_conversion(self, filename, extensions):
        """
        Convert filename to an image with type according to
        extension(s).

        The first existing file with an extension encountered in the extensions
        list is returned. If no files with the right extensions are found,
        the convert utility from the ImageMagick suite is used to
        convert filename.ps or filename.eps to filename + extensions[0].
        """
        if not isinstance(extensions, (list,tuple)):
            extensions = [extensions]
        for ext in extensions:
            final = filename + ext
            if os.path.isfile(final):
                return final

        final = filename + extensions[0]  # convert to first mentioned type
        files = glob.glob(filename + '*')
        # first convert from ps or eps to other things:
        for file in files:
            stem, ext = os.path.splitext(file)
            if ext == '.ps' or ext == '.eps':
                cmd = 'convert %s %s' % (file, final)
                print cmd
                failure = os.system(cmd)
                if failure:
                    print 'Could not convert;\n  %s' % cmd
                return final
        # try to convert from the first file to the disired format:
        file = files[0]
        cmd = 'convert %s %s' % (file, final)
        print cmd
        failure, outtext = commands.getstatusoutput(cmd)
        if failure:
            print 'Could not convert;\n  %s' % cmd
        return final

    def figure(self, filename, caption, width=None, height=None, label=None):
        """
        Insert a figure into the document.
        filename should be without extension; a proper extension is added,
        and if the figure is not available in that image format, the
        convert utility from ImageMagick is called to convert the format.
        """
        self.not_impl('figure')

    def table(self, table, column_headline_pos='c', column_pos='c'):
        """
        Translates a two-dimensional list of data, containing strings or
        numbers, to a suitable "tabular" environment in the output.

        @param table: list of list with rows/columns in table, including
        (optional) column-headline 1st row and row-headline 1st column.
        @param column_pos: specify the l/c/r position of data
        entries in columns, give either (e.g.) 'llrrc' or one char
        (if all are equal).
        @param column_headline_pos : position l/c/r for the headline row
        """
        self.not_impl('table')

    def url(self, url_address, link_text=None):
        """Typeset an URL (with an optional link)."""
        self.not_impl('url')

    def link(self, link_text, link_target):
        """Typeset a hyperlink."""
        self.not_impl('link')

    # what about LaTeX references to labels in equations, pages, labels?

def makedocstr(parent_class, subclass_method):
    """
    Compose a string (to be used as doc string) from a method's
    doc string in a parent class and an additional doc string
    in a subclass version of the method.

    @param parent_class: class object for parent class.
    @param subclass_method: method object for subclass.
    @return: parent_class.method.__doc__ + subclass_method.__doc__
    """
    parent_method = getattr(parent_class, subclass_method.__name__)
    docstr = parent_method.__doc__
    if subclass_method.__doc__ is not None and \
           subclass_method is not parent_method:
        docstr += subclass_func.__doc__
    return docstr


# regular expressions for inline tags:
# (these are taken from doconce.common.INLINE_TAGS)
inline_tag_begin = r'(?P<begin>(^|[(\s]))'
inline_tag_end = r'(?P<end>($|[.,?!;:)\s]))'
INLINE_TAGS = {
    # math: text inside $ signs, as in $a = b$, with space before the
    # first $ and space, comma, period, colon, semicolon, or question
    # mark after the enclosing $.
    'math':
    r'%s\$(?P<subst>[^ `][^$`]*)\$%s' % \
    (inline_tag_begin, inline_tag_end),

    # $latex text$|$pure text alternative$
    'math2':
    r'%s\$(?P<latexmath>[^ `][^$`]*)\$\|\$(?P<puretext>[^ `][^$`]*)\$%s' % \
    (inline_tag_begin, inline_tag_end),

    # *emphasized words*
    'emphasize':
    r'%s\*(?P<subst>[^ `][^*`]*)\*%s' % \
    (inline_tag_begin, inline_tag_end),

    # `verbatim inline text is enclosed in back quotes`
    'verbatim':
    r'%s`(?P<subst>[^ ][^`]*)`%s' % \
    (inline_tag_begin, inline_tag_end),

    # _underscore before and after signifies bold_
    'bold':
    r'%s_(?P<subst>[^ `][^_`]*)_%s' % \
    (inline_tag_begin, inline_tag_end),
    }

class Doconce(_BaseWriter):
    def __init__(self):
        _BaseWriter.__init__(self, 'Doconce', '.do.txt')

    def title(self, title, authors_and_institutions=[], date='today'):
        s = '\nTITLE: %s\n' % title
        for ai in authors_and_institutions:
            authorinfo = '; '.join(ai)
            s += 'AUTHOR: %s\n' % authorinfo
        if date is not None:
            if date == 'today':
                date = self.today_date()
            s += 'DATE: %s\n' % date
        self.file.write(s)
        self.paragraph_separator()

    def heading(self, underscores, title, label=None):
        underscores = '_'*underscores
        self.file.write('\n%s%s%s\n\n' % (underscores, title, underscores))

    def section(self, title, label=None):
        self.heading(7, title, label)

    def subsection(self, title, label=None):
        self.heading(5, title, label)

    def subsubsection(self, title, label=None):
        self.heading(3, title, label)

    def paragraph(self, title, ending='.', label=None):
        s = '\n\n__%s%s__ ' % (title, ending)
        self.file.write(s)

    def paragraph_separator(self):
        self.file.write('\n\n')

    def text(self, text, indent=0):
        text = _BaseWriter.text(self, text, indent)
        # not necessary since Doconce is the format for text:
        #text = _BaseWriter.expandtext(self, text,
        #                              INLINE_TAGS, HTML.INLINE_TAGS_SUBST)
        self.file.write(text)

    def list(self, items, listtype='itemize'):
        self.unfold_list(items, self.item_handler, listtype)

    def item_handler(self, item, listtype, level, keyword=None):
        indent = '  '*level
        s = ''
        if item == '_begin':
            if level == 1:
                s += '\n'
        elif item == '_end':
            if level == 1:
                s += '\n'
        else:
            # ordinary item:
            if item is not None:
                if listtype == 'itemize':
                    s += '\n%s%s* %s' % (indent, indent, item)
                elif listtype == 'enumerate':
                    s += '\n%s%so %s' % (indent, indent, item)
                elif listtype == 'description':
                    s += '\n%s%s- %s: %s' % (indent, indent, keyword, item)
        self.file.write(s)

    def verbatim(self, code):
        self.file.write('\n!bc\n' + r'%s' % code + '\n!ec\n')

    def figure(self, filename, caption, width=None, height=None, label=None):
        filename = self.figure_conversion(filename, \
                            ('.jpg', '.gif', '.png', '.ps', '.eps'))
        s = '\nFIGURE:[%s,' % filename
        if width:
            s += '  width=%s ' % width
        if height:
            s += '  height=%s ' % width
        s += '] ' + caption + '\n'
        self.file.write(s)

    def table(self, table, column_headline_pos='c', column_pos='c'):
        # find max column width
        mcw = 0
        for row in table:
            mcw = max(mcw, max([len(str(c)) for c in row]))
        formatted_table = []  # table where all columns have equal width
        column_format = '%%-%ds' % mcw
        for row in table:
            formatted_table.append([column_format % c for c in row])
        width = len(' | '.join(formatted_table[0])) + 4
        s = '\n\n   |' + '-'*(width-2) + '|\n'
        for row in formatted_table:
            s += '   | ' + ' | '.join(row) + ' |\n'
        s += '   |' + '-'*(width-2) + '|\n\n'
        self.file.write(s)

    def url(self, url_address, link_text=None):
        if link_text is None:
            link_text = 'link'  # problems with Doconce and empty link text
        self.file.write(' %s<%s>' % (url_address, link_text))

    def link(self, link_text, link_target):
        self.file.write('%s (%s)' % (link_text, link_target))

    # autogenerate doc strings by combining parent class doc strings
    # with subclass doc strings:
    for method in [title, section, subsection, subsubsection,
                   paragraph, text,
                   verbatim, # not defined here: math, raw,
                   figure, table, url,
                   list, item_handler,]:
        method.__doc__ = makedocstr(_BaseWriter, method)



class HTML(_BaseWriter):
    # class variables:
    table_border = '2'
    table_cellpadding = '5'
    table_cellspacing = '2'

    INLINE_TAGS_SUBST = {  # from inline tags to HTML tags
        # keep math as is:
        'math': None,  # indicates no substitution
        'math2':         r'\g<begin>\g<puretext>\g<end>',
        'emphasize':     r'\g<begin><em>\g<subst></em>\g<end>',
        'bold':          r'\g<begin><b>\g<subst></b>\g<end>',
        'verbatim':      r'\g<begin><tt>\g<subst></tt>\g<end>',
        }

    def __init__(self):
        _BaseWriter.__init__(self, 'html', '.html')
        self.header()

    def header(self):
        s = """\
<!-- HTML document generated by %s.%s -->
<html>
<body bgcolor="white">
""" % (__name__, self.__class__.__name__)
        self.file.write(s)

    def footer(self):
        s = """
</body>
</html>
"""
        self.file.write(s)

    def title(self, title, authors_and_institutions=[], date='today'):
        s = """
<title>%s</title>
<center><h1>%s</h1></center>
""" % (title, title)
        for ai in authors_and_institutions:
            author = ai[0]
            s += """
<center>
<h4>%s</h4>""" % author
            for inst in ai[1:]:
                s += """
<h6>%s</h6>""" % inst
            s += """\n</center>\n\n"""
        if date is not None:
            if date == 'today':
                date = self.today_date()
            s += """<center>%s</center>\n\n\n""" % date
        self.file.write(s)
        self.paragraph_separator()

    def heading(self, level, title, label=None):
        if label is None:
            s = """\n<h%d>%s</h%d>\n""" % (level, title, level)
        else:
            s = """\n<h%d><a href="%s">%s</h%d>\n""" % \
                (level, label, title, level)
        self.file.write(s)

    def section(self, title, label=None):
        self.heading(1, title, label)

    def subsection(self, title, label=None):
        self.heading(3, title, label)

    def subsubsection(self, title, label=None):
        self.heading(4, title, label)

    def paragraph(self, title, ending='.', label=None):
        s = '\n\n<p><!-- paragraph with heading -->\n<b>%s%s</b>\n' \
            % (title, ending)
        if label is not None:
            s += '<a name="%s">\n' % label
        self.file.write(s)

    def paragraph_separator(self):
        self.file.write('\n<p>\n')

    def text(self, text, indent=0):
        text = _BaseWriter.text(self, text, indent)
        text = _BaseWriter.expandtext(self, text,
                                      INLINE_TAGS, HTML.INLINE_TAGS_SUBST)
        self.file.write(text)

    def list(self, items, listtype='itemize'):
        self.unfold_list(items, self.item_handler, listtype)

    def item_handler(self, item, listtype, level, keyword=None):
        indent = '  '*level
        s = ''
        if item == '_begin':
            if listtype == 'itemize':
                s += '\n%s<ul>' % indent
            elif listtype == 'enumerate':
                s += '\n%s<ol>' % indent
            elif listtype == 'description':
                s += '\n%s<dl>' % indent
            s += ' <!-- start of "%s" list -->\n' % listtype
        elif item == '_end':
            if listtype == 'itemize':
                s += '%s</ul>' % indent
            elif listtype == 'enumerate':
                s += '%s</ol>' % indent
            elif listtype == 'description':
                s += '%s</dl>' % indent
            s += ' <!-- end of "%s" list -->\n' % listtype
        else:
            # ordinary item:
            if item is not None:
                if listtype in ('itemize', 'enumerate'):
                    s += '%s%s<p><li> %s\n' % (indent, indent, item)
                else:
                    s += '%s%s<p><dt>%s</dt><dd>%s</dd>\n' % \
                         (indent, indent, keyword, item)
        self.file.write(s)

    def verbatim(self, code):
        self.file.write('\n<pre>' + r'%s' % code + '\n</pre>\n')

    def figure(self, filename, caption, width=None, height=None, label=None):
        filename = self.figure_conversion(filename, ('.jpg', '.gif', '.png'))
        if width:
            width = ' width=%s ' % width
        else:
            width = ''
        if height:
            height = ' width=%s ' % width
        else:
            height = ''
        s = '\n<hr><img src="%s"%s%s>\n<p><em>%s</em>\n<hr><p>\n' % \
            (filename, width, height, caption)
        self.file.write(s)

    def table(self, table, column_headline_pos='c', column_pos='c'):
        s = '\n<p>\n<table border="%s" cellpadding="%s" cellspacing="%s">\n' %\
            (HTML.table_border, HTML.table_cellpadding, HTML.table_cellspacing)
        for line in table:
            s += '<tr>'
            for column in line:
                s += '<td>%s</td>' % column
            s += '</tr>\n'
        s += '</table>\n\n'
        self.file.write(s)

    def url(self, url_address, link_text=None):
        if link_text is None:
            link_text = url_address
        self.file.write('\n<a href="%s">%s</a>\n' % (url_address, link_text))

    def link(self, link_text, link_target):
        self.file.write('\n<a href="%s">%s</a>\n' % (link_text, link_target))

    # autogenerate doc strings by combining parent class doc strings
    # with subclass doc strings:
    for method in [title, section, subsection, subsubsection,
                   paragraph, text,
                   verbatim, # not defined here: math, raw,
                   figure, table, url,
                   list, item_handler,]:
        method.__doc__ = makedocstr(_BaseWriter, method)


class LaTeX(_BaseWriter):
    def __init__(self):
        raise NotImplementedError, \
              'Use Doconce class instead and filter to LaTeX'

# Efficient way of generating class DocWriter.
# A better way (for pydoc and other API references) is to
# explicitly list all methods and their arguments and then add
# the body for writer in self.writers: writer.method(arg1, arg2, ...)

class DocWriter:
    """
    DocWriter can write documents in several formats at once.
    """
    methods = 'title', 'section', 'subsection', 'subsubsection', \
              'paragraph', 'paragraph_separator', 'text', 'list', \
              'verbatim', 'math', 'raw', 'url', 'link', \
              'write_to_file', 'figure', 'table',


    def __init__(self, *formats):
        """
        @param formats: sequence of strings specifying the desired formats.
        """
        self.writers = [eval(format)() for format in formats]

    def documents(self):
        return [writer.document for writer in self.writers]

    def __str__(self):
        s = ''
        for writer in self.writers:
            s += '*'*60 + \
                  '\nDocWriter: format=%s (without footer)\n' % \
                  writer.__class__.__name__ + '*'*60
            s += str(writer)
        return s

    def dispatcher(self, *args, **kwargs):
        #print 'in dispatcher for', self.method_name, 'with args', args, kwargs
        #self.history = (self.method_name, args, kwargs)
        for writer in self.writers:
            s = getattr(writer, self.method_name)(*args, **kwargs)

    '''
    Alternative to attaching separate global functions:
    def __getattribute__(self, name):
        print 'calling __getattribute__ with', name
        if name in DocWriter.methods:
            self.method_name = name
            return self.dispatcher
        else:
            return object.__getattribute__(self, name)

    # can use inspect module to extract doc of all methods and
    # put this doc in __doc__
    '''

# Autogenerate methods in class DocWriter (with right
# method signature and doc strings stolen from class _BaseWriter (!)):

import inspect

def func_to_method(func, class_, method_name=None):
    setattr(class_, method_name or func.__name__, func)

for method in DocWriter.methods:
    docstring = eval('_BaseWriter.%s.__doc__' % method)
    # extract function signature:
    a = inspect.getargspec(eval('_BaseWriter.%s' % method))
    if a[3] is not None:  # keyword arguments?
        kwargs = ['%s=%r' % (arg, value) \
                  for arg, value in zip(a[0][-len(a[3]):], a[3])]
        args = a[0][:-len(a[3])]
        allargs = args + kwargs
    else:
        allargs = a[0]
    #print method, allargs, '\n', a
    signature_def = '%s(%s)' % (method, ', '.join(allargs))
    signature_call = '%s(%s)' % (method, ', '.join(a[0][1:]))  # exclude self
    code = """\
def _%s:
    '''\
%s
    '''
    for writer in self.writers:
        writer.%s

func_to_method(_%s, DocWriter, '%s')
""" % (signature_def, docstring, signature_call, method, method)
    #print 'Autogenerating\n', code
    exec code

def html_movie(plotfiles, interval_ms=300, width=800, height=600,
               casename='movie'):
    """
    Takes a list plotfiles which should be for example of the form::

        ['frame00.png', 'frame01.png', ... ]

    where each string should be the name of an image file and they should be
    in the proper order for viewing as an animation.

    The result is html text strings that incorporate javascript to
    loop through the plots one after another.  The html text also features
    buttons for controlling the movie.
    The parameter iterval_ms is the time interval between loading
    successive images and is in milliseconds.

    The width and height parameters do not seem to have any effect
    for reasons not understood.

    The following strings are returned: header, javascript code, form
    with movie and buttons, and footer. Concatenating these strings
    and dumping to an html file yields a kind of movie file to be
    viewed in a browser. The images variable in the javascript code
    is unique for each movie, because it is annotated by the casename
    string, so several such javascript sections can be used in the
    same html file.

    This function is based on code written by R.J. LeVeque, based on
    a template from Alan McIntyre.
    """
    import os
    if not isinstance(plotfiles, (tuple,list)):
        raise TypeError('html_movie: plotfiles=%s of wrong type %s' %
                        (str(plotfiles), type(plotfiles)))
    # Check that the plot files really exist
    missing_files = [fname for fname in plotfiles if not os.path.isfile(fname)]
    if missing_files:
        raise ValueError('Missing plot files: %s' % str(missing_files)[1:-1])

    ext = os.path.splitext(plotfiles[0])[-1]
    if ext == '.png' or ext == '.jpg' or ext == '.jpeg' or ext == 'gif':
        pass
    else:
        raise ValueError('Plotfiles (%s, ...) must be PNG files with '\
                         'extension .png' % plotfiles[0])
    header = """\
<html>
<head>
</head>
<body>
"""
    no_images = len(plotfiles)
    jscode = """
<script language="Javascript">
<!---
var num_images_%(casename)s = %(no_images)d;
var img_width = %(width)d;
var img_height = %(height)d;
var interval = %(interval_ms)d;
var images_%(casename)s = new Array();

function preload_images_%(casename)s()
{
   t = document.getElementById("progress");
""" % vars()

    i = 0
    for fname in plotfiles:
        jscode += """
   t.innerHTML = "Preloading image ";
   images_%(casename)s[%(i)s] = new Image(img_width, img_height);
   images_%(casename)s[%(i)s].src = "%(fname)s";
        """ % vars()
        i = i+1
    jscode += """
   t.innerHTML = "";
}

function tick_%(casename)s()
{
   if (frame_%(casename)s > num_images_%(casename)s - 1)
       frame_%(casename)s = 0;

   document.movie.src = images_%(casename)s[frame_%(casename)s].src;
   frame_%(casename)s += 1;
   tt = setTimeout("tick_%(casename)s()", interval);
}

function startup_%(casename)s()
{
   preload_images_%(casename)s();
   frame_%(casename)s = 0;
   setTimeout("tick_%(casename)s()", interval);
}

function stopit()
{ clearTimeout(tt); }

function restart_%(casename)s()
{ tt = setTimeout("tick_%(casename)s()", interval); }

function slower()
{ interval = interval/0.7; }

function faster()
{ interval = interval*0.7; }

// --->
</script>
""" % vars()
    plotfile0 = plotfiles[0]
    form = """
<form>
&nbsp;
<input type="button" value="Start movie" onClick="startup_%(casename)s()">
<input type="button" value="Pause movie" onClick="stopit()">
<input type="button" value="Restart movie" onClick="restart_%(casename)s()">
&nbsp;
<input type="button" value="Slower" onClick="slower()">
<input type="button" value="Faster" onClick="faster()">
</form>

<p><div ID="progress"></div></p>
<img src="%(plotfile0)s" name="movie" border=2/>
""" % vars()
    footer = '\n</body>\n</html>\n'
    return header, jscode, form, footer

def html_movie_embed(moviefile, width=400, height=400):
    """
    Return HTML for embedding a moviefile using the default
    handling of such files.
    """
    text = """
<embed src="%(moviefile)s"
width="%(width)s"
height="%(height)s"
autoplay="false"
loop="true">
</embed>
""" % vars()
    return text

def html_movie_embed_wmp(moviefile, width=400, height=400):
    """Return HTML text for embedding a movie file
    (Windows Media Player code)."""
    text = """
<object id="MediaPlayer1" width="180" height="200"
classid="CLSID:22D6F312-B0F6-11D0-94AB-0080C74C7E95"
codebase="http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=5,1,52,701"
standby="Loading Microsoft Windows Media Player components..."
type="application/x-oleobject" align="middle">
<param name="FileName" value="%(moviefile)s">
<param name="ShowStatusBar" value="True">
<param name="DefaultFrame" value="mainFrame">
<param name="autostart" value="false">
<embed type="application/x-mplayer2"
pluginspage = "http://www.microsoft.com/Windows/MediaPlayer/"
src="%(moviefile)s"
autostart="false"
align="middle"
width="%(width)s"
height="%(height)s"
loop="100"
defaultframe="rightFrame"
showstatusbar="true">
</embed>
</object>
<!--
<a href="%(moviefile)s"><font size="2">Download movie file</font></a>
<a href="http://www.microsoft.com/windows/windowsmedia/mp10/default.aspx">
<font size="1">Download Windows Media Player</font></a></p>
-->
<!--
Attributes of the <embed> tag are:
src - tells what file to use.
autostart="true" - tells the computer to start the Video playing upon loading the page.
autostart="false" - tells the computer not to start the Video playing upon loading the page. You must click the start button to make the Video play.
align=middle - tells the computer to put the start/stop buttons to the middle.
width= and height= - are the dimensions of a small button panel that will appear when the page loads and contains both a START & STOP button so the visitor can start/stop the Video.
loop=2 - will play the Video for two complete loops.
-->
""" % vars()
    return text

def html_movie_embed_qt(moviefile, width=400, height=400):
    """Return HTML for embedding a moviefile (QuickTime code)."""
    text = """
<object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B"
codebase="http://www.apple.com/qtactivex/qtplugin.cab"
width="%(width)s" height="%(height)s" >
<param name="src" value="%(moviefile)s" >
<param name="autoplay" value="false" >
<embed src="%(moviefile)s"
pluginspage="http://www.apple.com/quicktime/download"
width="%(width)s" height="%(height)s" autoplay="false">
</embed>
</object>
""" % vars()
    return text



def _test(d):
    # d is formatclass() or DocWriter(HTML, LaTeX, ...)
    print '\n\n', '*'*70, \
          '\n*** Testing class "%s"\n' % d.__class__.__name__, '*'*70

    d.title('My Test of Class %s' % d.__class__.__name__,
            [('Hans Petter Langtangen',
              'Simula Research Laboratory',
              'Dept. of Informatics, Univ. of Oslo'),
             ])
    d.section('First Section')
    d.text("""
Here is some
text for section 1.

This is a *first* example of using the _DocWriter
module_ for writing documents from *Python* scripts.
It could be a nice tool since we do not need to bother
with special typesetting, such as `fixed width fonts`
in plain text.
""")
    d.subsection('First Subsection')
    d.text('Some text for the subsection.')
    d.paragraph('Test of a Paragraph')
    d.text("""
Some paragraph text taken from "Documenting Python": The Python language
has a substantial body of documentation, much of it contributed by various
authors. The markup used for the Python documentation is based on
LaTeX and requires a significant set of macros written specifically
for documenting Python. This document describes the macros introduced
to support Python documentation and how they should be used to support
a wide range of output formats.

This document describes the document classes and special markup used
in the Python documentation. Authors may use this guide, in
conjunction with the template files provided with the distribution, to
create or maintain whole documents or sections.

If you're interested in contributing to Python's documentation,
there's no need to learn LaTeX if you're not so inclined; plain text
contributions are more than welcome as well.
""")
    d.text('Here is an enumerate list:')
    samplelist = ['item1', 'item2',
                  ['subitem1', 'subitem2'],
                  'item3',
                  ['subitem3', 'subitem4']]
    d.list(samplelist, listtype='enumerate')
    d.text('...with some trailing text.')
    d.subsubsection('First Subsubsection with an Itemize List')
    d.list(samplelist, listtype='itemize')
    d.text('Here is some Python code:')
    d.verbatim("""
class A:
    pass

class B(A):
    pass

b = B()
b.item = 0  # create a new attribute
""")
    d.section('Second Section')
    d.text('Here is a description list:')
    d.list(['keyword1: item1', 'keyword2: item2 goes here, with a colon : and some text after',
        ['key3: subitem1', 'key4: subitem2'],
        'key5: item3',
        ['key6: subitem3', 'key7: subitem4']],
           listtype='description')
    d.paragraph_separator()
    d.text('And here is a table:')
    d.table([['a', 'b'], ['c', 'd'], ['e', 'and a longer text']])
    print d
    d.write_to_file('tmp_%s' % d.__class__.__name__)

if __name__ == '__main__':
    formats = HTML, Doconce
    for format in formats:
        d = format()
        _test(d)
    formats_str = [format.__name__ for format in formats]
    d = DocWriter(*formats_str)
    _test(d)