Ticket #10637: tools_sws2rst_3.patch

File tools_sws2rst_3.patch, 25.4 KB (added by pang, 10 years ago)

tools_sws2rst_3.patch (replaces earlier versions)

  • new file sagenb/misc/comments2rst.py

    # HG changeset patch
    # User Pablo Angulo <pablo.angulo@uam.es>
    # Date 1295452728 -3600
    # Node ID 9ab6908fa8f8db76313ed4b670d362a5a7b68a94
    # Parent  d12daf8c85a85a184c16dc52f1c9132ee1910f41
    improved documentation of worksheet2rst, replaced tabs by spaces
    
    diff -r d12daf8c85a8 -r 9ab6908fa8f8 sagenb/misc/comments2rst.py
    - +  
     1# -*- coding: utf-8 -*-
     2r"""
     3Convert html from text cells in the notebook into ReStructuredText
     4
     5This is called by sws2rst
     6
     7- Pablo Angulo Ardoy (2011-02-25): initial version
     8"""
     9#**************************************************
     10# Copyright (C) 2011 Pablo Angulo
     11#
     12# Distributed under the terms of the GPL License
     13#**************************************************
     14
     15
     16import re
     17import os
     18try:
     19    from BeautifulSoup import (ICantBelieveItsBeautifulSoup, Tag,
     20                               CData, Comment, Declaration, ProcessingInstruction)
     21except ImportError:
     22    raise ImportError, """BeautifulSoup must be installed.
     23
     24You might download a spkg from:
     25
     26http://trac.sagemath.org/sage_trac/raw-attachment/ticket/10637/beautifulsoup-3.2.0.p0.spkg
     27"""
     28
     29def preprocess_display_latex(text):
     30    r"""replace $$some display latex$$ with <display>some display latex</display>
     31    before the soup is built.
     32
     33    Deals with the situation where <p></p> tags are mixed
     34    with $$, like $$<p>display_latex$$</p>, unless the mess is huge
     35
     36    EXAMPLES::
     37
     38        sage: from sagenb.misc.comments2rst import preprocess_display_latex
     39        sage: s="$$a=2$$"
     40        sage: preprocess_display_latex(s)
     41        '<display>a=2</display>'
     42        sage: s="<p>$$a=2$$</p>"
     43        sage: preprocess_display_latex(s)
     44        '<p><display>a=2</display></p>'
     45        sage: s="<p>$$a=2</p>$$"
     46        sage: preprocess_display_latex(s)
     47        '<p><display>a=2</display></p>'
     48        sage: s="$$<p>a=2</p>$$"
     49        sage: preprocess_display_latex(s)
     50        '<display>a=2</display>'
     51    """
     52    ls = []
     53    start_tag = True
     54    partes = text.split('$$')
     55    for c in partes[:-1]:
     56        if start_tag:
     57            ls.append(c)
     58            ls.append('<display>')
     59        else:
     60            c0, count = prune_tags(c)
     61            ls.append(c0)
     62            ls.append('</display>')
     63            if count == 1:
     64                ls.append('<p>')
     65            elif count == -1:
     66                ls.append('</p>')
     67            elif abs(count)>1:
     68                raise Exception, 'display latex was messed up with html code'
     69        start_tag = not start_tag
     70    ls.append(partes[-1])
     71    return ''.join(ls)
     72
     73def prune_tags(text):
     74    count = text.count('<p>') - text.count('</p>')
     75    return text.replace('<br/>','').replace('<br />','').replace('<p>','').replace('</p>',''), count
     76
     77escapable_chars = { '+' :r'\+',
     78                    '*' :r'\*',
     79                    '|' :r'\|',
     80                    '-' :r'\-'}
     81def escape_chars(text):
     82    for c,r in escapable_chars.iteritems():
     83        text = text.replace(c,r)
     84    return text
     85
     86def replace_courier(soup):
     87    """Lacking a better option, I use courier font to mark <code>
     88    within tinyMCE. And I want to turn that into real code tags.
     89
     90    Most users won't be needing this(?)
     91    """
     92    for t in soup.findAll(lambda s:s.has_key('style') and 'courier' in s['style']):
     93        tag = Tag(soup, 'code')
     94        while t.contents:
     95            tag.append(t.contents[0])
     96        t.replaceWith(tag)
     97
     98#inline_latex is careful not to confuse escaped dollars
     99inline_latex = re.compile(r'([^\\])\$(.*?)([^\\])\$')
     100latex_beginning = re.compile(r'\$(.*?)([^\\])\$')
     101def replace_latex(soup):
     102    r"""Replaces inline latex by :math:`code` and escapes
     103    some rst special chars like +, -, * and | outside of inline latex
     104
     105    does not escape chars inside display or pre tags
     106
     107    EXAMPLES::
     108
     109        sage: from sagenb.misc.comments2rst import replace_latex
     110        sage: from BeautifulSoup import ICantBelieveItsBeautifulSoup
     111        sage: s = ICantBelieveItsBeautifulSoup("<p>Some <strong>latex: $e^\pi i=-1$</strong></p>")
     112        sage: replace_latex(s)
     113        sage: s
     114        <p>Some <strong>latex: :math:`e^\pi i=-1`</strong></p>
     115        sage: s = ICantBelieveItsBeautifulSoup("<p><strong>2+2 | 1+3</strong></p>")
     116        sage: replace_latex(s)
     117        sage: s
     118        <p><strong>2\+2 \| 1\+3</strong></p>
     119    """
     120    for t in soup.findAll(text=re.compile('.+')):
     121        if latex_beginning.match(t):
     122            t.replaceWith(inline_latex.sub('\\1:math:`\\2\\3`',
     123                                           latex_beginning.sub(':math:`\\1\\2`',
     124                                                               unicode(t),
     125                                                               1)))       
     126        elif inline_latex.search(t):
     127            t.replaceWith(inline_latex.sub('\\1:math:`\\2\\3`',
     128                                           unicode(t)))
     129        elif not (t.fetchParents(name = 'display')
     130                  or t.fetchParents(name = 'pre')):
     131            t.replaceWith(escape_chars(t))
     132
     133class Soup2Rst(object):
     134    """builds the rst text from the Soup Tree
     135    """
     136    tags = {'h1':'header',
     137            'h2':'header',
     138            'h3':'header',
     139            'h4':'header',
     140            'p': 'inline_no_tag',
     141            '[document]': 'document',
     142            'br': 'br',
     143            'b':'strong',
     144            'strong':'strong',
     145            'em':'em',
     146            'pre':'pre',
     147            'code':'code',
     148            'display':'display',
     149            'span':'inline_no_tag',
     150            'ul':'ul',
     151            'ol':'ol',
     152            'li':'li',
     153            'a':'a',
     154            'table':'table',
     155#            'tr':'tr',
     156            'td':'inline_no_tag',
     157            'th':'inline_no_tag',
     158            'tt':'inline_no_tag',
     159            'div':'block_no_tag',
     160            'img':'img',
     161#            '':'',
     162            }
     163
     164    headers = {'h1':u'=',
     165               'h2':u'-',
     166               'h3':u'~',
     167               'h4':u'"',
     168               }
     169   
     170    def __init__(self, images_dir):
     171        self.images_dir = images_dir
     172        self._nested_list = 0
     173        self._inside_ol   = False
     174        self._inside_code_tag = False
     175
     176    def visit(self, node):
     177        if isinstance(node, (CData, Comment, Declaration, ProcessingInstruction)):
     178            return ''
     179        elif hasattr(node, 'name'):
     180            try:
     181                visitor = getattr(self, 'visit_' + self.tags[node.name])
     182                return visitor(node)
     183            except (KeyError, AttributeError):
     184                print 'Warning: node not supported (or something else?) ' + node.name
     185                return unicode(node)
     186        else:
     187            #Assume plain string
     188            return unicode(node).replace('\n','')
     189
     190    def visit_document(self, node):
     191        return '\n'.join(self.visit(tag) for tag in node.contents)   
     192
     193    def get_plain_text(self, node):
     194        """Gets all text, removing all tags"""
     195        if hasattr(node, 'contents'):
     196            t = ' '.join(self.get_plain_text(tag) for tag in node.contents)
     197        else:
     198            t = unicode(node)
     199        return t.replace('\n','')
     200       
     201    def visit_header(self, node):
     202        s = ' '.join(self.visit(tag) for tag in node.contents)
     203        spacer = self.headers[node.name]*len(s)
     204        return s.replace( '\n', '') +  '\n' + spacer
     205
     206    def visit_pre(self, node):
     207        return '::\n\n\t'+unicode(node)[5:-6].replace('<br />','\n').replace('<br></br>','\n').replace('\n','\n\t')
     208
     209    def visit_ul(self, node):
     210        self._nested_list += 1
     211        result = '\n'.join(self.visit(tag) for tag in node.contents)
     212        self._nested_list -= 1
     213        return result
     214
     215    def visit_ol(self, node):
     216        self._nested_list += 1
     217        self._inside_ol = True
     218        result = '\n'.join(self.visit(tag) for tag in node.contents)
     219        self._nested_list -= 1
     220        self._inside_ol = False
     221        return result
     222
     223    def visit_li(self, node):
     224        return (' '*self._nested_list
     225                + ('#. ' if self._inside_ol else '- ')
     226                +' '.join(self.visit(tag) for tag in node.contents))
     227
     228    def visit_display(self, node):
     229        return ('\n.. MATH::\n\n\t' +
     230                unicode(node)[9:-10].replace('<br></br>','\n').replace('\n','\n\t') +
     231                '\n\n')
     232
     233    def visit_img(self, node):
     234        return '.. image:: ' + os.path.join(self.images_dir, node['src']) + '\n\t:align: center\n'
     235
     236    def visit_table(self,node):
     237        rows = []
     238        for elt in node.contents:
     239            if not hasattr(elt,'name'):
     240                pass
     241            elif elt.name == 'thead':
     242                rows.extend(self.prepare_tr(row)
     243                            for row in elt
     244                            if hasattr(row,'name') and
     245                            row.name=='tr')
     246                rows.append([]) #this row represents a separator
     247            elif elt.name == 'tbody':
     248                rows.extend(self.prepare_tr(row)
     249                            for row in elt
     250                            if hasattr(row,'name') and
     251                            row.name=='tr')
     252            elif elt.name == 'tr':
     253                rows.append(self.prepare_tr(elt))
     254
     255        ncols = max(len(row) for row in rows)
     256        for row in rows:
     257            if len(row) < ncols:
     258                row.extend( ['']*(ncols - len(row)))
     259        cols_sizes = [max(len(td) for td in tds_in_col)
     260                      for tds_in_col in zip(*rows)]
     261        result = [' '.join('='*c for c in cols_sizes)]
     262       
     263        for row in rows:
     264            if any(td for td in row):
     265                result.append(' '.join(td+' '*(l - len(td))
     266                                       for l,td in zip(cols_sizes,row)))
     267            else:
     268                result.append(' '.join('-'*c for c in cols_sizes))
     269        result.append(' '.join('='*c for c in cols_sizes))
     270        return '\n'.join(result)
     271
     272    def prepare_tr(self, node):
     273        return [self.visit(tag) for tag in node.contents if tag!='\n']
     274       
     275    def visit_br(self, node):
     276        return '\n'
     277
     278    def visit_strong(self, node):
     279        if node.contents:
     280            content = ' '.join(self.visit(tag) for tag in node.contents).strip()
     281            if '``' in content or self._inside_code_tag:
     282                return content
     283            else:
     284                return '**' + content + '**'
     285        else:
     286            return ''
     287
     288    def visit_em(self,node):
     289        if node.contents:
     290            return '*' + ' '.join(self.visit(tag) for tag in node.contents).strip() + '*'
     291        else:
     292            return ''
     293
     294    def visit_code(self, node):
     295        if node.contents:
     296            self._inside_code_tag = True
     297            content = self.get_plain_text(node).strip()
     298            self._inside_code_tag = False
     299            return '``' + content + '``'
     300        else:
     301            return ''
     302
     303    def visit_inline_no_tag(self, node):
     304        return (' '.join(self.visit(tag)
     305                         for tag in node.contents)).strip() + '\n'
     306
     307    def visit_block_no_tag(self, node):
     308        return '\n'.join(self.visit(tag) for tag in node.contents)
     309
     310    def visit_a(self, node):
     311        return ('`' + ' '.join(self.visit(tag) for tag in node.contents) +
     312                ' <' + node['href'] + '>`_'
     313                )
     314
     315def html2rst(text, images_dir):
     316    """Converts html, tipically generated by tinyMCE, into rst
     317    compatible with Sage documentation.
     318
     319    The main job is done by BeautifulSoup, which is much more
     320    robust than conventional parsers like HTMLParser, but also
     321    several details specific of this context are taken into
     322    account, so this code differs from generic approaches like
     323    those found on the web.
     324
     325    INPUT:
     326
     327    - ``text`` -- string -- a chunk of HTML text
     328
     329    - ``images_dir`` -- string -- folder where images are stored
     330
     331    OUTPUT:
     332
     333    - string -- rst text
     334
     335    EXAMPLES::
     336
     337        sage: from sagenb.misc.comments2rst import html2rst
     338        sage: html2rst('<p>Some text with <em>math</em>: $e^{\pi i}=-1$</p>', '')
     339        u'Some text with  *math* : :math:`e^{\\pi i}=-1`\n'
     340        sage: html2rst('<p>Text with <em>incorrect</p> nesting</em>.', '')       
     341        u'Text with  *incorrect*\n\n nesting\n.'
     342        sage: html2rst('<pre>Preformatted: \n    a+2\n</pre><p> Not preformatted: \n    a+2\n</p>', '')
     343        u'::\n\n\tPreformatted: \n\t    a+2\n\t\nNot preformatted:     a\\+2\n'
     344        sage: html2rst('&aacute;ñ&nbsp;&ntildeá','')
     345        u'\xe1\xf1 \xf1\xe1'
     346        sage: html2rst('<p>some text</p><p>$$</p><p>3.183098861 \cdot 10^{-1}</p><p>$$</p>','')
     347        u'some text\n\n.. MATH::\n\n\t3.183098861 \\cdot 10^{-1}\n'
     348    """
     349   
     350    #replace $$some display latex$$ with
     351    #<display>some display latex</display>
     352    text = preprocess_display_latex(text)
     353
     354    #eliminate nasty &nbsp;
     355    text = text.replace('&nbsp;',' ')
     356           
     357    #ICantBelieveItsBeautifulSoup is better than BeautifulSoup
     358    #for html that wasn't generated by humans (like tinyMCE)
     359    soup = ICantBelieveItsBeautifulSoup(text,
     360                       convertEntities=ICantBelieveItsBeautifulSoup.HTML_ENTITIES)   
     361
     362    #remove all comments
     363    comments = soup.findAll(text=lambda text:isinstance(text, Comment))
     364    for comment in comments:
     365        comment.extract()
     366
     367    replace_courier(soup)
     368    replace_latex(soup)
     369    v = Soup2Rst(images_dir)
     370    return v.visit(soup)
  • new file sagenb/misc/results2rst.py

    diff -r d12daf8c85a8 -r 9ab6908fa8f8 sagenb/misc/results2rst.py
    - +  
     1# -*- coding: utf-8 -*-
     2r"""
     3Convert output from code cells in the notebook into ReStructuredText
     4
     5This is called by sws2rst
     6
     7- Pablo Angulo Ardoy (2011-02-25): initial version
     8"""
     9#**************************************************
     10# Copyright (C) 2011 Pablo Angulo
     11#
     12# Distributed under the terms of the GPL License
     13#**************************************************
     14
     15
     16import re
     17IMAGES_DIR = 'images/'
     18
     19#We parse lines one by one but keep track of current scope
     20#similarly to worksheet2rst.py
     21#Results are split into different types. Some are discarded
     22class States(object):
     23    NORMAL = 0
     24    HTML = 1
     25    MATH = 2
     26    TRACEBACK = 3
     27
     28class LineTypes(object):
     29    PLAIN = 0
     30    IMAGE = 1
     31    LATEX = 2
     32    HTML  = 3
     33    TRACE = 4
     34
     35class ResultsParser(object):
     36    """Auxiliary class for results2rst
     37    """
     38    def __init__(self, images_dir):
     39        ##Order matters, place more restrictive regex's before more general ones
     40        ##If no regex matches, line will be discarded
     41        ##a self transition is needes to produce any output
     42        self.transitions = {
     43            States.NORMAL:[
     44                #IMAGE
     45                     (re.compile(r"^\<html\>\<font color='black'\>"
     46                                 r"\<img src='cell\://(.*?)'\>"
     47                                 r"\</font\>\</html\>"),
     48                      "\n.. image:: " + images_dir + "\\1\n\t:align: center\n",
     49                      LineTypes.IMAGE,
     50                      States.NORMAL),
     51                #SELF-CONTAINED MATH
     52                     (re.compile(r"^\<html\>\<div class=\"math\"\>"
     53                                 r"\\newcommand\{\\Bold\}\[1\]\{\\mathbf\{\#1\}\}"
     54                                 r"(.*?)\</div\>\</html\>$"),
     55                      "\n.. MATH::\n\n\t\\1\n",
     56                      LineTypes.LATEX,
     57                      States.NORMAL),
     58                #SELF-CONTAINED MATH - BIS
     59                     (re.compile(r"^\<html\>\<div class=\"math\"\>"
     60                                 r"(.*?)\</div\>\</html\>$"),
     61                      "\n.. MATH::\n\n\t\\1",
     62                      LineTypes.LATEX,
     63                      States.NORMAL),
     64                #START Traceback
     65                     (re.compile(r"^(Traceback.*)"),
     66                      "\tTraceback (most recent call last):",
     67                      LineTypes.TRACE,
     68                      States.TRACEBACK),
     69                #START MATH
     70                     (re.compile(r"^\<html\>\<div class=\"math\"\>"
     71                                 r"\\newcommand\{\\Bold\}\[1\]\{\\mathbf\{\#1\}\}(.*?)"),
     72                      "\n.. MATH::\n\n\t\\1",
     73                      LineTypes.LATEX,
     74                      States.MATH),
     75                #SELF-CONTAINED HTML
     76                     (re.compile(r"^\<html\>.*</html\>$"),
     77                      "\t<html>...</html>",
     78                      LineTypes.HTML,
     79                      States.NORMAL),       
     80                #START HTML
     81                     (re.compile(r"^\<html\>.*"),
     82                      "\t<html>...</html>",
     83                      LineTypes.HTML,
     84                      States.HTML),       
     85                #CONTINUE NORMAL
     86                     (re.compile("(.*)"),
     87                      "\t\\1",
     88                      LineTypes.PLAIN,
     89                      States.NORMAL),               
     90                ],
     91            States.MATH:[
     92                 #END MATH
     93                     (re.compile(r"(.*?)\</div\>\</html\>$"),
     94                      "\t\\1",
     95                      LineTypes.LATEX,
     96                      States.NORMAL),
     97                 #CONTINUE MATH
     98                     (re.compile("(.*)"),
     99                      "\t\\1",
     100                      LineTypes.LATEX,
     101                      States.MATH),       
     102                ],
     103            States.TRACEBACK:[
     104                 #END Traceback
     105                     (re.compile(r"^(\S.*)"),
     106                      "\t...\n\t\\1",
     107                      LineTypes.TRACE,
     108                      States.NORMAL),
     109                ],
     110            States.HTML:[
     111                 #END HTML
     112                     (re.compile(r".*</html\>$"),
     113                      "",
     114                      LineTypes.HTML,
     115                      States.NORMAL),
     116                ],
     117        }
     118   
     119    def parse(self, text):
     120        result_plain = []
     121        result_show = []
     122        state = States.NORMAL
     123        for line in text.splitlines():
     124            for regex, replacement, line_type, new_state in self.transitions[state]:
     125                if regex.match(line):
     126                    result = result_plain if line_type in (LineTypes.PLAIN, LineTypes.HTML)\
     127                             else result_show
     128                    result.append( regex.sub(replacement, line))
     129                    state = new_state
     130                    break
     131        result_plain.extend(result_show)
     132        return '\n'.join(result_plain)
     133
     134def results2rst(text, images_dir):
     135    r"""Converts the result of evaluation of notebook cells
     136    into rst compatible with Sage documentation.
     137
     138    Several common patterns are identified, and treated
     139    accordingly. Some patterns are dropped, while others
     140    are not recognized.
     141
     142    Currently, latex and images are recognized and converted.
     143
     144    INPUT:
     145
     146    - ``text`` -- string -- a chunk of HTML text
     147
     148    - ``images_dir`` -- string -- folder where images are stored
     149
     150    OUTPUT:
     151
     152    - string -- rst text
     153
     154    EXAMPLES::
     155
     156        sage: from sagenb.misc.results2rst import results2rst
     157        sage: s="<html><font color='black'><img src='cell://sage0.png'></font></html>"
     158        sage: results2rst(s,'')
     159        '\n.. image:: sage0.png\n\t:align: center\n'
     160        sage: results2rst("4",'')
     161        '\t4'
     162        sage: s=r'<html><div class="math">\newcommand{\Bold}[1]{\mathbf{#1}}\frac{3}{2}</div></html>'
     163        sage: results2rst(s,'')                                       
     164        '\n.. MATH::\n\n\t\\frac{3}{2}\n'
     165    """
     166    Parser = ResultsParser(images_dir)
     167    return Parser.parse(text)
     168
  • new file sagenb/misc/worksheet2rst.py

    diff -r d12daf8c85a8 -r 9ab6908fa8f8 sagenb/misc/worksheet2rst.py
    - +  
     1#!/usr/bin/python
     2# -*- coding: utf-8 -*-
     3r"""
     4Convert worksheet.html files into ReStructuredText documents
     5
     6This is called by 'sage -sws2rst'. Can also be used as a commandline script
     7(if BeautifulSoup is installed):
     8
     9``python worksheet2rst.py worksheet.html``
     10
     11or
     12
     13``cat worksheet.html | python worksheet2rst.py``
     14
     15AUTHOR:
     16
     17- Pablo Angulo Ardoy (2011-02-25): initial version
     18
     19
     20The content of worksheet.html is split into comments, code, and output
     21(the result of evaluating the code), as follows:
     22
     23comments
     24{{{id=..|
     25code
     26///
     27results
     28}}}
     29
     30Each kind of text is dealt with separately.
     31
     32"""
     33#**************************************************
     34# Copyright (C) 2011 Pablo Angulo
     35#
     36# Distributed under the terms of the GPL License
     37#**************************************************
     38
     39
     40import sys
     41import os
     42import re
     43from comments2rst import html2rst
     44from results2rst import results2rst
     45import codecs
     46
     47#We parse lines one by one but keep track of current scope
     48#comments
     49#{{{id=..|
     50#code
     51#///
     52#results
     53#}}}
     54#RESULT_TO_BE_DROPPED corresponds to a results section whose
     55#code was empty, and will be discarded, whether it's empty or not
     56class States(object):
     57    COMMENT = 0
     58    CODE = 1
     59    RESULT = 2
     60    RESULT_TO_BE_DROPPED = 3
     61
     62# REs for splitting comments, code and results
     63START_CELL_RE = re.compile('^\{\{\{id=(\d*)\|')
     64END_CODE_RE   = re.compile('^\/\/\/')
     65END_CELL_RE   = re.compile('^\}\}\}')
     66
     67#When to switch State, and which State to
     68transitions = {
     69    States.COMMENT:(
     70        START_CELL_RE,
     71        States.CODE
     72        ),
     73    States.CODE:(
     74        END_CODE_RE,
     75        States.RESULT),
     76    States.RESULT:(
     77        END_CELL_RE,
     78        States.COMMENT),
     79    States.RESULT_TO_BE_DROPPED:(
     80        END_CELL_RE,
     81        States.COMMENT)
     82    }
     83
     84def code_parser(text):
     85    """
     86   
     87    Arguments:
     88
     89    INPUT:
     90
     91    - ``s``:sage code, may or may not start with "sage:"
     92
     93    OUTPUT:
     94
     95    - string -- rst text
     96
     97    EXAMPLES (not used for unit test, see
     98    http://groups.google.com/group/sage-devel/browse_thread/thread/d82cb049ac102f3a)
     99
     100    : from sagenb.misc.worksheet2rst import code_parser
     101    : s="a=2"
     102    : code_parser(s)
     103    '::\n\n    sage: a=2'
     104    : s="def f(n):\n    return n+1\n"
     105    : code_parser(s)
     106    '::\n\n    sage: def f(n):\n    ...       return n+1'
     107    : s="sage: def f(n):\nsage:     return n+1\n"
     108    : code_parser(s)
     109    '::\n\n    sage: def f(n):\n    ...       return n+1'
     110    """
     111    lines = ['::', '']
     112    for s in text.splitlines():
     113        l = s[6:] if s.startswith('sage: ') else s
     114        if not l: continue
     115        prefix = '    ...   ' if l[0] == ' ' else '    sage: '
     116        lines.append(prefix + l)
     117    return '\n'.join(lines)
     118
     119def worksheet2rst(s, images_dir=''):
     120    """Parses a string, tipically the content of the file
     121    worksheet.html inside a sws file, and converts it into
     122    rst compatible with Sage documentation.
     123
     124    INPUT:
     125
     126    - ``s`` -- string -- text, tipically the content of
     127                               worksheet.html
     128
     129    - ``images_dir`` -- string -- folder where images are stored
     130
     131    OUTPUT:
     132
     133    - string -- rst text
     134
     135    EXAMPLES (not used for unit test, see
     136    http://groups.google.com/group/sage-devel/browse_thread/thread/d82cb049ac102f3a)
     137
     138    : from sagenb.misc.worksheet2rst import worksheet2rst
     139    : worksheet2rst('<p>some text</p>\n{{{id=1|\nprint 2+2\n///\n4\n}}}')     
     140    u'.. -*- coding: utf-8 -*-\n\nsome text\n\n::\n\n    sage: print 2+2\n\t4\n\n.. end of output\n'
     141    : s = '{{{id=2|\nshow(f)\n///\n<html><div class="math">\\sqrt{x}</div></html>\n}}}\n'
     142    : worksheet2rst(s)
     143    u'.. -*- coding: utf-8 -*-\n\n\n::\n\n    sage: show(f)\n\n.. MATH::\n\n\t\\sqrt{x}\n\n.. end of output\n'       
     144    """
     145    result_parser = results2rst
     146    state = States.COMMENT
     147    result = ['.. -*- coding: utf-8 -*-\n']
     148    ls = []
     149    last = 0
     150    for line in s.splitlines():
     151        regex, next_state= transitions[state]
     152        m = regex.match(line)
     153        if m:
     154            if state == States.COMMENT:
     155                last_cell_id = m.group(1)
     156                img_path = images_dir + os.path.sep
     157                result.append(html2rst(u'\n'.join(ls), img_path))
     158            elif state == States.RESULT:
     159                img_path = os.path.join(images_dir, 'cell_%s_'%last_cell_id)
     160                result.append(result_parser(u'\n'.join(ls),
     161                                             img_path))
     162                result.append('')
     163                result.append('.. end of output')
     164            elif state == States.CODE:
     165                if ls and any(ls):
     166                    result.append(code_parser(u'\n'.join(ls)))
     167                else:
     168                    next_state = States.RESULT_TO_BE_DROPPED
     169            ls = []
     170            state = next_state
     171        else:
     172            ls.append(line)
     173    if state == States.COMMENT:
     174        img_path = images_dir + os.path.sep
     175        result.append(html2rst(u'\n'.join(ls), img_path))
     176    elif state == States.RESULT:
     177        img_path = os.path.join(images_dir, 'cell_%s_'%last_cell_id)
     178        result.append(result_parser(u'\n'.join(ls),
     179                                     img_path))
     180        result.append('')
     181        result.append('.. end of output')
     182    elif state == States.CODE:
     183        result.append(code_parser(u'\n'.join(ls)))
     184
     185    return u'\n'.join(result)
     186
     187if __name__=='__main__':
     188    if len(sys.argv)>1:       
     189        fichero = codecs.open(sys.argv[1], mode='r', encoding='utf-8')
     190        text = fichero.read()
     191        fichero.close()
     192    else:
     193        text = sys.stdin.read()
     194
     195    print worksheet2rst(text).encode('utf-8')
     196