#@+leo-ver=4-thin
#@+node:ekr.20040331071319:@thin rst2.py
#@<< docstring >>
#@+node:ekr.20050420105800:<< docstring >>
"""A plugin to generate HTML or LaTeX files from reStructured Text
embedded in Leo outlines.

If a headline starts with @rst <filename>, double-clicking on it will 
write a file in outline order, with the headlines converted to reStructuredText 
section headings.

If the name of the <filename> has the extension .html, .htm or .tex, and if you have
docutils installed, it will generate HTML or LaTeX, respectively.

The following settings are presently available: They can be set in the rst2 code
itself, or (maybe?) using @settings trees:
    
rst2_bodyfilter = False
# True: call body_filter to massage text.
# Removes @ignore, @nocolor, @wrap directives.

rst2file = False
# Controls whether to use external file or string.

rst2_pure_document = False
# Controls the following code in writeTreeAsRst
if config.rst2_pure_document or g.match_word(h,0,"@rst"):
    < < handle an rst node > >
    # Removes @ignore, @nocolor, @wrap directives.
    # Removes code blocks if rst2_replace_code_blocks is True.
    # Formats headlines only if rst2_format_headlines is True.
else:
    < < handle a plain node > >
    # Deletes @others from output.
    # Formats headlines only if rst2_format_headlines is False. (opposite of meaning in rst nodes!)
        
rst2_format_headlines = False
# Used differently.  See rst2_pure_document.
    
rst2_http_server_support = True
# True:
# 1. add_node_marker writes a string using generate_node_marker.
# Generates 'http-node-marker-'+str(number), where number is config.node_counter (incremented each time add_node_marker is called.
# 2. Enables the following code in writeTreeAsRst
    if config.tag == 'open2':
        http_map = config.http_map
    else:
        http_map = {}
        config.anchormap = {}
       # maps v nodes to markers.
        config.node_counter = 0
    # [snip] code to write the tree
    if config.rst2_http_server_support:
        config.http_map = http_map
# 3. http_support_main does nothing unless this option is True.
# False: add_node_marker does nothing.

rst2_clear_attributes = False
    # Deletes p.v.rst2_http_attributename from all nodes after writing.
    # Deletes p.v.unknownAttributes if it then becomes empty.
    
rst2_run_on_window_open = False
# Runs an alternative 'main line' in onFileOpen when a new window is opened.

rst2_replace_code_blocks = True
# See notes for rst2_pure_document. (Uses do_replace_code_blocks)
< < define alternate code block implementation> >
# Don't know what to do here: Can someone make a suggestion?
#import docutils.parsers.rst.directives.admonitions
#import docutils.parsers.rst.directives.body
# docutils.parsers.rst.directives._directives['code-block'] = docutils.parsers.rst.directives.body.block 
# docutils.parsers.rst.directives.register_directive('code-block', docutils.parsers.rst.directives.admonitions.admonition)
#docutils.parsers.rst.directives._directives['code-block'] = docutils.parsers.rst.directives.body.pull_quote 
# g.es("Registered some alternate implementation for code-block directive")
config.do_replace_code_blocks = config.rst2_replace_code_blocks
        
rst2_install_menu_item_in_edit_menu = True
# True: install 'Transform rst text in subtree'command to Edit menu.

rst2_node_begin_marker = 'http-node-marker-'
rst2_http_attributename = 'rst_http_attribute'

"""
#@nonl
#@-node:ekr.20050420105800:<< docstring >>
#@nl

# By Josef Dalcolmo: contributed under the same licensed as Leo.py itself.

#@<< imports >>
#@+node:ekr.20050420105800.1:<< imports >>

import leoGlobals as g
import leoPlugins

import os
import ConfigParser
from HTMLParser import HTMLParser
from pprint import pprint
try:
    import sys
    dir = os.path.split(__file__)[0]
    if dir not in sys.path:
        sys.path.append(dir)
    import mod_http
    from mod_http import set_http_attribute, get_http_attribute, reconstruct_html_from_attrs
except:
    mod_http = None
#@nonl
#@-node:ekr.20050420105800.1:<< imports >>
#@nl
#@<< change log >>
#@+node:ekr.20040331071319.2:<< change log >>
#@+at 
#@nonl
# Change log:
# 
# - JD 2003-03-10 (rev 1.3): some more corrections to the unicode-> encoding 
# translation.
#   No only check for missing docutils (doesn't mask other errors any more).
# 
# - JD 2003-03-11 (rev 1.4): separated out the file launching code to a 
# different pluging.
# 
# - 2003-11-02 Added generation of LaTeX files, just make the extension of the 
# filename '.tex'. --Timo Honkasalo
# 
# - 2003-12-24 S Zatz modifications to introduce concept of plain @rst nodes 
# to improve program documentation
# 
# - 2004-04-08 EKR:
#     - Eliminated "comment" text at start of nodes.
#     - Rewrote code that strips @nocolor, @ignore and @wrap directives from 
# start of text.
#     - Changed code to show explicitly that it uses positions.
#     - Added comments to << define code-block>>
#     - Added div.code-block style to silver_city.css (see documentation)
#     - Rewrote documentation.
# 2.2 EKR 2005-03-07 changed publish() to publish(argv=[''])
# 
# 2.3 bwmulder
#     - added various options which can be set with @settings directive
#     - add support for mod_http plugin.
#     - added possible settings to the beginning of the Leo file
# 2.4 EKR:
#     - Call onFileOpen from both "new" and "open2" hooks.
#@-at
#@nonl
#@-node:ekr.20040331071319.2:<< change log >>
#@nl

#@+others
#@+node:bwmulder.20050319123911:http plugin interface
#@+doc
# if enabled, this plugin stores information for the http plugin.
# 
# Each node can have one additional attribute, with the name 
# config.rst_http_attributename, which is a list.
# 
# The first three elements are stack of tags, the rest is html code.
# 
# [<tag n start>, <tag n end>, <other stack elements>, <html line 1>, <html 
# line 2>, ...]
# 
# <other stack elements has the same structure:
#     [<tag n-1 start>, <tag n-1 end>, <other stack elements>]
#@-doc
#@nonl
#@-node:bwmulder.20050319123911:http plugin interface
#@+node:bwmulder.20050314132625:config
class config:
    # 
    rst2_bodyfilter = False
    rst2file = False
    rst2_pure_document = False
    rst2_http_server_support = True
    rst2_format_headlines = False
    # number_headlines = False
    # warnofdrags = False
    rst2_clear_attributes = False
    rst2_run_on_window_open = False
    rst2_replace_code_blocks = True
    
    # debug flags: ignore in normal use
    rst2_debug_handle_starttag = False
    rst2_debug_handle_endtag = False
    rst2_debug_store_lines = False
    rst2_debug_show_unknownattributes = False
    rst2_debug_node_html_1 = False
    rst2_debug_anchors = False
    rst2_debug_before_and_after_replacement = False
    rst2_install_menu_item_in_edit_menu = True
    
    # These are really configuration options.
    rst2_node_begin_marker = 'http-node-marker-'
    rst2_http_attributename = 'rst_http_attribute'
    
    firstCall = True
    # set to False after the optional menu is installed.
    
    # Some data is also stored in the config class
    http_map = None
    # Maps node anchors to node.
    # A node anchor is a marker beginning with
    # rst2_node_begin_marker.
    # It is currently assumed that such a marker
    # does not occur in the rst document.
    
    tag = None
    # either doubleclick or open2.
    
    current_file = None
    
    node_counter = 0
    # Used to mark the beginning of the html code for each new
    # node.
    
    do_replace_code_blocks = False
#@nonl
#@-node:bwmulder.20050314132625:config
#@+node:bwmulder.20050327204614:transform_rst2_text_in_subtree
def transform_rst2_text_in_subtree(c):
    
    class callOnFileOpen(object):
        """
        Call onFileOpen passing the commander c.
        """
        def __init__(self):
            self.c = c
        def __call__(self):
            onFileOpen("menuItem", {"new_c": self.c})
            
    editMenu = c.frame.menu.getMenu('Edit')
        
    newEntries = (
            ("-", None, None),
            ("Transform rst2 text in subtree", "", callOnFileOpen))
        
    c.frame.menu.createMenuEntries(editMenu, newEntries)
#@-node:bwmulder.20050327204614:transform_rst2_text_in_subtree
#@+node:bwmulder.20050320000243:onFileOpen
def onFileOpen(tag, keywords):
    # c,old_c,new_c,fileName):
    c = keywords["new_c"]
    applyConfiguration(c)
    if config.firstCall and config.rst2_install_menu_item_in_edit_menu:
        config.firstCall = False
        transform_rst2_text_in_subtree(c)
    ignoreset = {}
    if c.config.getBool("rst2_run_on_open_window"):
    # if config.rst2_run_on_window_open:
        http_map = {}
        anchormap = {}
        config.node_counter = 0
        found_rst_trees = False
        root = c.currentVnode()
        
        if tag == 'open2':
            iterator = c.allNodes_iter(root)
        else:
            iterator = root.self_and_subtree_iter()
        for p in iterator:
            if p.v not in ignoreset:
                s = p.v.headString()
                if s.startswith("@rst ") and len(s.split()) >= 2:
                    for p1 in p.subtree_iter():
                        ignoreset[p1.v] = True
                    try:
                        config.http_map = {}
                        config.anchormap = {}
                        onIconDoubleClick("open2", {"c": c, "p": p})
                        http_map.update(config.http_map)
                        anchormap.update(config.anchormap)
                        found_rst_trees = True
                    except SystemExit, s:
                        g.es("Formatting failed for %s" % p, color='red')
        if found_rst_trees:
            config.http_map = http_map
            config.anchormap = anchormap
            relocate_references() 
          
            config.http_map = None
            config.anchormap = None
            g.es('html updated for html plugin', color="blue")
        if config.rst2_clear_attributes:
            g.es("http attributes cleared")
#@-node:bwmulder.20050320000243:onFileOpen
#@+node:ekr.20040331071319.3:onIconDoubleClick
# by Josef Dalcolmo 2003-01-13
#
# this does not check for proper filename syntax.
# path is the current dir, or the place @folder points to
# this should probably be changed to @path or so.

def onIconDoubleClick(tag,keywords):

    c = keywords.get("c")
    p = keywords.get("p")
    # g.trace(c)

    if not c or not p:
        return
    
    applyConfiguration(c)
    config.tag = tag

    h = p.headString().strip()

    if g.match_word(h,0,"@rst"):
        if len(h) > 5:
            fname = h[5:]
            ext = os.path.splitext(fname)[1].lower()
            if ext in ('.htm','.html','.tex'):
                #@                << write rST as HTML/LaTeX >>
                #@+node:ekr.20040331071319.4:<< write rST as HTML/LaTeX >>
                try:
                    import docutils
                except:
                    g.es('HTML/LaTeX generation requires docutils')
                    return
                else:
                    import docutils.parsers.rst
                    from docutils.core import Publisher
                    from docutils.io import StringOutput, StringInput, FileOutput,FileInput
                    import StringIO
                    
                # Set the writer and encoding for the converted file
                if ext in ('.html','.htm'):
                    writer='html' ; enc="utf-8"
                else:
                    writer='latex' ; enc="iso-8859-1"
                
                syntax = False
                if writer == 'html':
                    try:
                        import SilverCity
                        #@        << define code-block >>
                        #@+node:ekr.20040331071319.5:<< define code-block >>
                        def code_block(name,arguments,options,content,lineno,content_offset,block_text,state,state_machine):
                            
                            """Create a code-block directive for docutils."""
                            
                            # See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/252170
                            language = arguments[0]
                            module = getattr(SilverCity,language)
                            generator = getattr(module,language+"HTMLGenerator")
                            io = StringIO.StringIO()
                            generator().generate_html(io, '\n'.join(content))
                            html = '<div class="code-block">\n%s\n</div>\n'%io.getvalue()
                            raw = docutils.nodes.raw('',html, format='html') #(self, rawsource='', text='', *children, **attributes):
                            return [raw]
                            
                        # These are documented at http://docutils.sourceforge.net/spec/howto/rst-directives.html.
                        code_block.arguments = (
                            1, # Number of required arguments.
                            0, # Number of optional arguments.
                            0) # True if final argument may contain whitespace.
                        
                        # A mapping from option name to conversion function.
                        code_block.options = {
                            'language' :
                            docutils.parsers.rst.directives.unchanged # Return the text argument, unchanged
                        }
                        
                        code_block.content = 1 # True if content is allowed.
                         
                        # Register the directive with docutils.
                        docutils.parsers.rst.directives.register_directive('code-block',code_block)
                        
                        config.do_replace_code_blocks = False
                        #@nonl
                        #@-node:ekr.20040331071319.5:<< define code-block >>
                        #@nl
                        syntax = True
                    except ImportError:
                        g.es('SilverCity not present so no syntax highlighting')
                        #@        << define alternate code block implementation>>
                        #@+node:bwmulder.20050326114320: << define alternate code block implementation>>
                        # Don't know what to do here: Can someone make a suggestion?
                        #import docutils.parsers.rst.directives.admonitions
                        #import docutils.parsers.rst.directives.body
                        # docutils.parsers.rst.directives._directives['code-block'] = docutils.parsers.rst.directives.body.block 
                        # docutils.parsers.rst.directives.register_directive('code-block', docutils.parsers.rst.directives.admonitions.admonition)
                        #docutils.parsers.rst.directives._directives['code-block'] = docutils.parsers.rst.directives.body.pull_quote 
                        # g.es("Registered some alternate implementation for code-block directive")
                        config.do_replace_code_blocks = config.rst2_replace_code_blocks
                        #@-node:bwmulder.20050326114320: << define alternate code block implementation>>
                        #@nl
                
                if config.rst2file:
                    rstFileName = os.path.splitext(fname)[0] + ".txt"
                    rstFile = file(rstFileName, "w")
                    g.es("Using %s as rst file" % rstFileName)
                else:
                    rstFile = StringIO.StringIO()
                config.current_file = fname
                writeTreeAsRst(rstFile,fname,p,c,syntax=syntax)
                if config.rst2file:
                    rstFile.close()
                else:
                    rstText = rstFile.getvalue()
                
                # This code snipped has been taken from code contributed by Paul Paterson 2002-12-05.
                pub = Publisher()
                if config.rst2file:
                    pub.source = FileInput(source_path=rstFileName)
                    pub.destination = FileOutput(destination_path=fname, encoding='unicode')
                else:
                    pub.source = StringInput(source=rstText)
                    pub.destination = StringOutput(pub.settings, encoding=enc)
                pub.set_reader('standalone', None, 'restructuredtext')
                pub.set_writer(writer)
                output = pub.publish(argv=[''])
                
                if config.rst2file:
                    pass
                else:
                    convertedFile = file(fname,'w')
                    convertedFile.write(output)
                    convertedFile.close()
                rstFile.close()
                writeFullFileName(fname)
                return http_support_main(tag, fname)
                
                #@-node:ekr.20040331071319.4:<< write rST as HTML/LaTeX >>
                #@nl
            else:
                #@                << write rST file >>
                #@+node:ekr.20040331071319.6:<< write rST file >>
                rstFile = file(fname,'w')
                writeTreeAsRst(rstFile,fname,p,c)
                rstFile.close()
                writeFullFileName(fname)
                #@nonl
                #@-node:ekr.20040331071319.6:<< write rST file >>
                #@nl
        else:
            # if the headline only contains @rst then open the node and its parent in text editor
            # this works for me but needs to be generalized and should probably be a component
            # of the open_with plugin.
            if 0:
                c.openWith(("os.startfile", None, ".txt"))
                c.selectVnode(p.parent())
                c.openWith(("os.startfile", None, ".tp"))
#@nonl
#@-node:ekr.20040331071319.3:onIconDoubleClick
#@+node:ekr.20040811064922:writeFullFileName
def writeFullFileName (fname):
    
    path = g.os_path_join(os.getcwd(),fname)
    path = g.os_path_abspath(path)
    
    g.es('rst written: ' + path,color="blue")
#@nonl
#@-node:ekr.20040811064922:writeFullFileName
#@+node:bwmulder.20050326125712:replace_code_block_directive
def replace_code_block_directive(line):
    if u"code-block::" in line:
        parts = line.split()
        if len(parts) == 3 and (parts[0] == '..') and (parts[1]=='code-block::'):
            line = '%s code::\n' % parts[2]
    return line
#@nonl
#@-node:bwmulder.20050326125712:replace_code_block_directive
#@+node:ekr.20040331071319.7:writeTreeAsRst & add_node_marker
def writeTreeAsRst(rstFile,fname,p,c,syntax=False):
    
    'Write the tree under position p to the file rstFile (fname is the filename)'
    
    def add_node_marker():
        if config.rst2_http_server_support:
            config.node_counter += 1
            marker = rst_htmlparser.generate_node_marker(config.node_counter)
            config.last_marker = marker
            http_map[marker] = p.copy()
            # the p.copy is necessary, since otherwise the
            # position is modified and unusable later.
            rstFile.write("\n\n.. _%s:\n\n" % marker)
            
    # we don't write a title, so the titlepage can be customized
    # use '#' for title under/overline
    directives = g.scanDirectives(c,p=p) # changed name because don't want to use keyword dict
    #@    << set encoding >>
    #@+node:ekr.20040408160625.1:<< set encoding >>
    encoding = directives.get("encoding",None)
    if encoding == None:
        encoding = c.config.default_derived_file_encoding
    #@nonl
    #@-node:ekr.20040408160625.1:<< set encoding >>
    #@nl
    #@    << set code_dir >>
    #@+node:ekr.20040403202850:<< set code_dir >>
    if syntax:
        lang_dict = {'python':'Python', 'ruby':'Ruby', 'perl':'Perl', 'c':'CPP'}
        language = directives['language']
        # SilverCity modules have first letter in caps
        if language in lang_dict:
            code_dir = '**code**:\n\n.. code-block:: %s\n\n' % lang_dict[language]
        else:
            code_dir = '**code**:\n\n.. class:: code\n..\n\n::\n\n'
    else:
        code_dir = '**code**:\n\n.. class:: code\n..\n\n::\n\n'
    #@nonl
    #@-node:ekr.20040403202850:<< set code_dir >>
    #@nl
    s = g.toEncodedString(fname,encoding,reportErrors=True)
    rstFile.write('.. filename: '+s+'\n')
    rstFile.write('\n')

    if config.rst2_bodyfilter:
        s = bodyfilter(p)
    else:
        s = p.bodyString()
    s = g.toEncodedString(s,encoding,reportErrors=True)
    rstFile.write(s+'\n')		# write body of titlepage.
    rstFile.write('\n')
    
    toplevel = p.level()+1 # Dec 20
    h = p.headString()
    
    if config.rst2_http_server_support:
        if config.tag == 'open2':
            http_map = config.http_map
        else:
            http_map = {}
            config.anchormap = {}
           # maps v nodes to markers.
            config.node_counter = 0
    for p in p.subtree_iter():
        h = p.headString().strip()
        if config.rst2_pure_document or g.match_word(h,0,"@rst"):
            #@            << handle an rst node >>
            #@+node:ekr.20040403202850.1:<< handle an rst node >>
            s = p.bodyString()
            s = g.toEncodedString(s,encoding,reportErrors=True)
            
            # Skip any leading @ignore, @nocolor, @wrap directives.
            while (
                g.match_word(s,0,"@ignore") or
                g.match_word(s,0,"@nocolor") or
                g.match_word(s,0,"@wrap")
            ):
                i = g.skip_line(s,0)
                s = s[i:]
            
            add_node_marker()    
            
            if config.rst2_format_headlines:
                rstFile.write(h+'\n')
                rstFile.write(underline(h,p.level()-toplevel))
                rstFile.write('\n')
            
            if config.do_replace_code_blocks and 'code-block::' in s:
                s = '\n'.join([replace_code_block_directive(line) for line in s.split("\n")])
            
            rstFile.write('%s\n\n'%s.strip())
            #@nonl
            #@-node:ekr.20040403202850.1:<< handle an rst node >>
            #@nl
        else:
            #@            << handle a plain node >>
            #@+node:ekr.20040403202850.2:<< handle a plain node >>
            if g.match_word(h,0,"@file-nosent"):
                h = h[13:]
            h = g.toEncodedString(h,encoding,reportErrors=True)
            if config.rst2_bodyfilter:
                s = bodyfilter(p)
            else:
                s = p.bodyString()
            s = g.toEncodedString(s,encoding,reportErrors=True)
            
            add_node_marker()    
            
            if config.rst2_format_headlines:
                pass
            else:
                rstFile.write(h+'\n')
                rstFile.write(underline(h,p.level()-toplevel))
                rstFile.write('\n')
            
            if s.strip():
                rstFile.write(code_dir)
                s = s.split('\n')
                for linenum,linetext in enumerate(s[:-1]):
                    if "@others" in linetext: #deleting lines with @other directive from output
                        continue
                    rstFile.write('\t%2d  %s\n'%(linenum+1,linetext))
            
            rstFile.write('\n')
            #@nonl
            #@-node:ekr.20040403202850.2:<< handle a plain node >>
            #@nl
        #@        << clear attributes >>
        #@+node:bwmulder.20050315150045:<< clear attributes >>
        if config.rst2_clear_attributes:
            if hasattr(p.v, 'unknownAttributes'):
                if p.v.unknownAttributes.has_key(config.rst2_http_attributename):
                    del p.v.unknownAttributes[config.rst2_http_attributename]
                    if p.v.unknownAttributes == {}:
                        del p.v.unknownAttributes
        #@nonl
        #@-node:bwmulder.20050315150045:<< clear attributes >>
        #@nl
    add_node_marker()
    if config.rst2_http_server_support:
        config.http_map = http_map
#@-node:ekr.20040331071319.7:writeTreeAsRst & add_node_marker
#@+node:bwmulder.20050314131619:applyConfiguration
def applyConfiguration (c):

    """Called when the user presses the "Apply" button on the Properties form.
 
    Default: behave like the previous plugin.
    """

    def getboolean(name):
        value = getattr(config, name)
        newvalue = c.config.getBool(name)
        if newvalue is not None:
            setattr(config, name, newvalue)
        
    getboolean("rst2file")
    getboolean("rst2_bodyfilter")
    getboolean("rst2_clear_attributes")
    getboolean("rst2_http_server_support")
    if config.rst2_http_server_support and not mod_http:
        g.es("Resetting rst2_http_server_support because mod_http plugin was not imported successfully", color='red')
        config.rst2_http_server_support = False
    getboolean("rst2_pure_document")
    getboolean("rst2_format_headlines")
    # getboolean("rst2_warnofdrags")
    getboolean("rst2_run_on_window_open")
    
    getboolean("rst2_debug_handle_endtag")
    getboolean("rst2_debug_store_lines")
    getboolean("rst2_debug_handle_starttag")
    getboolean("rst2_debug_show_unknownattributes")
    getboolean("rst2_debug_node_html_1")
    getboolean("rst2_debug_anchors")
    getboolean("rst2_debug_before_and_after_replacement")
    getboolean("rst2_install_menu_item_in_edit_menu")

#@-node:bwmulder.20050314131619:applyConfiguration
#@+node:bwmulder.20041011145032:bodyfilter
def bodyfilter(p):
    s = p.bodyString()
    while (s.startswith("@ignore") or
           s.startswith("@nocolor") or
           s.startswith("@wrap")):
       i = g.skip_line(s,0)
       s = s[i:]
    return s
#@-node:bwmulder.20041011145032:bodyfilter
#@+node:ekr.20040331071319.8:underline
# note the first character is intentionally unused, to serve as the underline
# character in a title (in the body of the @rst node)

def underline(h,level):
    
    """Return the underlining string to be used at the given level for headline h."""

    str = """#=+*^~"'`-:><_"""[level]

    return str*max(len(h),4)+'\n'
#@-node:ekr.20040331071319.8:underline
#@+node:bwmulder.20050319191929:http related stuff
#@+node:bwmulder.20050320092000:http_support_main
def http_support_main(tag, fname):
    if config.rst2_http_server_support:
        set_initial_http_attributes(fname)
        find_anchors()
        if tag == 'open2':
            return True
        
        # We relocate references here if we are only running
        # for one file, otherwise we must postpone the
        # relocation until we have processed all files.
        relocate_references() 
      
        config.http_map = None
        config.anchormap = None
        g.es('html updated for html plugin', color="blue")
        if config.rst2_clear_attributes:
            g.es("http attributes cleared")
#@nonl
#@-node:bwmulder.20050320092000:http_support_main
#@+node:bwmulder.20050319191929.1:general routines
#@+node:bwmulder.20050319181934:link_anchor_parser
class link_anchor_parser(HTMLParser):
    #@    @+others
    #@+node:bwmulder.20050319181934.1:docstring
    """
    This subclass of HTMLParser contains functions to recognise anchors and links.
    """
    #@nonl
    #@-node:bwmulder.20050319181934.1:docstring
    #@+node:bwmulder.20050319181934.2:is_anchor
    def is_anchor(self, tag, attrs):
        if tag != 'a':
            return False
        for name, value in attrs:
            if name == 'name':
                return True
        return False
      
    #@-node:bwmulder.20050319181934.2:is_anchor
    #@+node:bwmulder.20050323091905:is_link
    def is_link(self, tag, attrs):
        if tag != 'a':
            return False
        for name, value in attrs:
            if name == 'href':
                return True
        return False
      
    #@-node:bwmulder.20050323091905:is_link
    #@-others
#@nonl
#@-node:bwmulder.20050319181934:link_anchor_parser
#@+node:bwmulder.20050319152820.1:http_attribute_iter
def http_attribute_iter():
    for p in config.http_map.values():
        attr = get_http_attribute(p)
        if attr:
            yield (p, attr)
#@-node:bwmulder.20050319152820.1:http_attribute_iter
#@-node:bwmulder.20050319191929.1:general routines
#@+node:bwmulder.20050314184440:rst_htmlparser
class rst_htmlparser(link_anchor_parser):
    #@    @+others
    #@+node:bwmulder.20050319111254:docstring
    """
    The responsibility of the html parser is:
        1. to find out which html belongs to which node.
        2. to keep a stack of html markings which proceed each node.
        
    Later, we have to relocate inter-file links: if a reference to another location
    is in a file, we must change the link.
    
    """
    #@nonl
    #@-node:bwmulder.20050319111254:docstring
    #@+node:bwmulder.20050315115739:__init__
    def __init__(self, http_map):
        HTMLParser.__init__(self)
        self.stack = None
        # The stack contains lists of the form:
            # [text1, text2, previous].
            # text1 is the opening tag
            # text2 is the closing tag
            # previous points to the previous stack element
        
        self.http_map = http_map
        # see remark in config class
            
        self.node_marker_stack = []
        # self.node_marker_stack.pop() returns True for a closing
        # tag if the opening tag identified an anchor belonging to a vnode.
        
        self.node_code = []
        # Accumulated html code. Once the hmtl code is assigned a vnode,
        # it is deleted here.
        
        self.deleted_lines = 0
        # Number of lines deleted in self.node_code
        
        self.endpos_pending = False
        # self.node_code[0:self.endpos_pending] should not be included in
        # the html code stored in a vnode.
        
        self.last_position = None
        # Last vnode; we must attach html code to this node.
            
    
    #@-node:bwmulder.20050315115739:__init__
    #@+node:bwmulder.20050315115739.1:handle_starttag
    def handle_starttag(self, tag, attrs):
        """
        1. Find out if the current tag is an achor.
        2. If it is an anchor, we check if this anchor marks the beginning of a new 
           node
        3. If a new node begins, then we might have to store html code in the
           previous code.
        4. In any case, put the new tag on the stack.
        """
        is_node_marker = False
        if self.is_anchor(tag, attrs):
            for name, value in attrs:
                if name == 'name' and value.startswith(config.rst2_node_begin_marker):
                    is_node_marker = True
                    break
            if is_node_marker:
                is_node_marker = value
                line, column = self.getpos()
                if self.last_position:
                    lines = self.node_code[:]
                    lines[0] = lines[0][self.startpos:]
                    del lines[line - self.deleted_lines - 1:]
                    if config.rst2_debug_store_lines:
                        print "rst2: Storing in", self.last_position, ":"
                        print lines
                    get_http_attribute(self.last_position).extend(lines)
    
                    if config.rst2_debug_show_unknownattributes:
                        print "rst2: unknownAttributes[config.rst2_http_attributename]"
                        print "For:", self.last_position
                        pprint(get_http_attr(self.last_position))
                                
                if self.deleted_lines < line - 1:
                    del self.node_code[:line - 1 - self.deleted_lines]
                    self.deleted_lines = line - 1
                    self.endpos_pending = True
        if config.rst2_debug_handle_starttag:
            from pprint import pprint
            print "rst2: handle_starttag:", tag, attrs, is_node_marker
        starttag = self.get_starttag_text( ) 
        self.stack = [starttag, None, self.stack]
        self.node_marker_stack.append(is_node_marker)
                
    #@nonl
    #@-node:bwmulder.20050315115739.1:handle_starttag
    #@+node:bwmulder.20050315115739.2:handle_endtag
    def handle_endtag(self, tag):
        """
        1. Set the second element of the current top of stack.
        2. If this is the end tag for an anchor for a node,
           store the current stack for that node.
        """
        self.stack[1] = "</" + tag + ">"
        if config.rst2_debug_handle_endtag:
            from pprint import pprint
            print "rst2: handle_endtag:", tag
            pprint(self.stack)
        if self.endpos_pending:
            line, column = self.getpos()
            self.startpos = self.node_code[0].find(">", column) + 1
            self.endpos_pending = False
        is_node_marker = self.node_marker_stack.pop()
        if is_node_marker and not config.rst2_clear_attributes:
            self.last_position = self.http_map[is_node_marker]
            if is_node_marker != config.last_marker:
                set_http_attribute(self.http_map[is_node_marker], self.stack)
        self.stack = self.stack[2]
        
    #@nonl
    #@-node:bwmulder.20050315115739.2:handle_endtag
    #@+node:bwmulder.20050315115739.3:generate_node_marker
    def generate_node_marker(cls, number):
        
        """
        Generate a node marker for 
        """
        return config.rst2_node_begin_marker + ("%s" % number)
        
    generate_node_marker = classmethod(generate_node_marker)
        
    
    #@-node:bwmulder.20050315115739.3:generate_node_marker
    #@+node:bwmulder.20050315134705:feed
    def feed(self, line):
        self.node_code.append(line)
        HTMLParser.feed(self, line)
    #@-node:bwmulder.20050315134705:feed
    #@-others
#@nonl
#@-node:bwmulder.20050314184440:rst_htmlparser
#@+node:bwmulder.20050319180047:anchor_htmlparser
class anchor_htmlparser(link_anchor_parser):
    #@    @+others
    #@+node:bwmulder.20050319180047.1:docstring
    """
    This htmlparser does the first step of relocating: finding all the anchors within the html node.
    
    Each anchor is mapped to a tuple:
        (current_file, vnode).
        
    Filters out markers which mark the beginning of the html code for a node.
    """
    #@nonl
    #@-node:bwmulder.20050319180047.1:docstring
    #@+node:bwmulder.20050319235437:__init__
    def __init__(self, vnode, first_node):
        HTMLParser.__init__(self)
        self.vnode = vnode
        self.anchormap = config.anchormap
        self.first_node = first_node
    #@-node:bwmulder.20050319235437:__init__
    #@+node:bwmulder.20050319181934.3:handle_starttag
    def handle_starttag(self, tag, attrs):
        """
        1. Find out if the current tag is an achor.
        2. If the current tag is an anchor, update the mapping;
             anchor -> vnode
        """
        if not self.is_anchor(tag, attrs):
            return
        if self.first_node:
            self.anchormap[config.current_file] = (config.current_file, self.vnode)
            self.first_node = False
        for name, value in attrs:
            if name == 'name':
                if not value.startswith(config.rst2_node_begin_marker):
                    self.anchormap[value] = (config.current_file, self.vnode)
                  
    #@nonl
    #@-node:bwmulder.20050319181934.3:handle_starttag
    #@-others
#@-node:bwmulder.20050319180047:anchor_htmlparser
#@+node:bwmulder.20050320102006:link_htmlparser
class link_htmlparser(link_anchor_parser):
    #@    @+others
    #@+node:bwmulder.20050320102006.1:docstring
    """
    This html parser does the second step of relocating links:
        1. It scans the html code for links.
        2. If there is a link which links to a previously processed file, then
           this link is changed so that it now refers to the node.
    """
    #@nonl
    #@-node:bwmulder.20050320102006.1:docstring
    #@+node:bwmulder.20050320102006.2:__init__
    def __init__(self, vnode):
        HTMLParser.__init__(self)
        self.vnode = vnode
        self.anchormap = config.anchormap
        self.replacements = []
    #@-node:bwmulder.20050320102006.2:__init__
    #@+node:bwmulder.20050320102006.3:handle_starttag
    def handle_starttag(self, tag, attrs):
        """
        1. Find out if the current tag is an achor.
        2. If the current tag is an anchor, update the mapping;
             anchor -> vnode
        """
        if not self.is_link(tag, attrs):
            return
        for name, value in attrs:
            if name == 'href':
                href = value
                href_parts = href.split("#")
                if len(href_parts) == 1:
                    href_a = href_parts[0]
                else:
                    href_a = href_parts[1]
                if not href_a.startswith(config.rst2_node_begin_marker):
                    if href_a in self.anchormap:
                        href_file, href_node = self.anchormap[href_a]
                        http_node_ref = mod_http.node_reference(href_node)
                        line, column = self.getpos()
                        self.replacements.append((line, column, href, href_file, http_node_ref))
                                
    #@nonl
    #@-node:bwmulder.20050320102006.3:handle_starttag
    #@+node:bwmulder.20050320155022:get_replacements
    def get_replacements(self):
        return self.replacements
    #@nonl
    #@-node:bwmulder.20050320155022:get_replacements
    #@-others
#@-node:bwmulder.20050320102006:link_htmlparser
#@+node:bwmulder.20050319131813:set_initial_http_attributes
def set_initial_http_attributes(filename):
    http_map = config.http_map
    f = file(filename)
    parser = rst_htmlparser(config.http_map)
    line = f.readline()
    while line:
        parser.feed(line)
        line = f.readline()

#@-node:bwmulder.20050319131813:set_initial_http_attributes
#@+node:bwmulder.20050319131813.1:relocate_references
def relocate_references():
    relocate_references_using_anchormap()
#@-node:bwmulder.20050319131813.1:relocate_references
#@+node:bwmulder.20050319152820:find_anchors
def find_anchors():
    """
    Find the anchors in all the nodes.
    """
    first_node = True
    for vnode, attrs in http_attribute_iter():
        html = reconstruct_html_from_attrs(attrs)
        if config.rst2_debug_node_html_1:
            pprint(html)
        parser = anchor_htmlparser(vnode, first_node)
        for line in html:
            parser.feed(line)
        first_node = parser.first_node
    if config.rst2_debug_anchors:
        print "Anchors found:"
        pprint(config.anchormap)
#@-node:bwmulder.20050319152820:find_anchors
#@+node:bwmulder.20050319153321:relocate_references_using_anchormap
def relocate_references_using_anchormap():
    for vnode, attr in http_attribute_iter():
        if config.rst2_debug_before_and_after_replacement:
            print "Before replacement:", vnode
            pprint (attr)
        http_lines = attr[3:]
        parser = link_htmlparser(vnode)
        for line in attr[3:]:
            parser.feed(line)
        replacements = parser.get_replacements()
        replacements.reverse()
        for line, column, href, href_file, http_node_ref in replacements:
            marker_parts = href.split("#")
            if len(marker_parts) == 2:
                marker = marker_parts[1]
                replacement = "%s#%s" % (http_node_ref, marker)
                attr[line+2] = attr[line+2].replace('href="%s"' % href, 'href="%s"' % replacement)
            else:
                filename = marker_parts[0]
                attr[line+2] = attr[line+2].replace('href="%s"' % href, 'href="%s"' % http_node_ref)
    if config.rst2_debug_before_and_after_replacement:
            print "After replacement"
            pprint (attr)
            for i in range(3): print
            

#@-node:bwmulder.20050319153321:relocate_references_using_anchormap
#@-node:bwmulder.20050319191929:http related stuff
#@-others

if 1: # Ok for unit testing.
    leoPlugins.registerHandler("icondclick1",onIconDoubleClick)
    leoPlugins.registerHandler(("new","open2"), onFileOpen)
    __version__ = "2.4"

    g.plugin_signon(__name__)
#@nonl
#@-node:ekr.20040331071319:@thin rst2.py
#@-leo
