#!/usr/bin/env sage 
# -*- coding: utf-8 -*-
#
# Parser for embedded Sage commands in HTML pages. 
# 
# Author: Harald Schilly <harald.schilly@gmail.com>
# Copyright 2008, ALL RIGHTS RESERVED
# License: GNU GPL v2+

r"""\
This function parses Sage Code embedded into HTML.
Input is valid XHTML, commands are inside the "sage" namespace.
Document must be parseable by the xml.dom.minidom parser.

It is designed to be called from inside Sage's own Python instance.
Therefore, add "sage" to your $PATH variabe, for example:
export PATH=$PATH:/path/to/sage
Then, call the script with all HTML pages in the intended order.

Special namespace commands:

 * sage:expr="function(arguments)" - any tag, as attribute
   the child nodes are removed (!) and replaced by the output
   of the command. $$ $$ are placed around for jsMath.

 * sage:eval="function(args)" - any tag, attribute
   like sage:expr, but also prints the input
   
 * sage:code - any tag (e.g. div, pre)
   code gets executed, but nothing else happens.

 * sage:plot="show(plotobject)" - only img tag
   the plotobject's ".save()" method is called to plot the
   graphic to a png file in a subdirectory and the img's
   src attribute is modified to point to it.

example document: (notice the xmlns namespace for "sage" in the body tag)

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>HTML Embedding Test</title>
  </head>
  <body xmlns:sage="http://www.sagemath.org/">
    <h1>HTML Embedding Test</h1>
    <div>This is a HTML div with included calculations: 2+2 = <span sage:expr="2+2"></span></div>
    <div>The same as above, but no automtically inserted dollar signs for jsMath: 
    $$sqrt(3^2+4^2) =$$ <span sage:plainexpr="sqrt(3^2+4^2)"></span></div>
    <h2>Sage code:</h2>
    <pre sage:code="true">
    a = 1+2
    b = 2*a
    print a,b
    </pre>
    <h2>A Plot</h2>
    <img src="" sage:plot="plot(sin(x))"></img>
    <img src="" sage:plot="plot(x+sin(1/x), -2, 4)" sage:plotoptions="dpi=50"></img><br/>
    <div>Eval code and show input expression: <span sage:eval="var('x,k,n')"></span></div>
  </body>
</html>

Note: This is similar to SageTeX. That package works on Tex documents and parses, executes
      and plots in a similar way. http://tug.ctan.org/tex-archive/macros/latex/contrib/sagetex/
"""

import xml.dom.minidom
import sys, os
from sage.misc.preparser import preparse
#from sage.misc.latex import latex

#the 'sage:' namespace prefix URI
NS = 'http://www.sagemath.org/'
TOKEN = 'SAGE_TOKEN_'
OUTPUT_DIR = 'output'
LB = os.linesep
plotfilenames = None

def remove_childs(node):
    """
    helper function to remove children in the xml dom
    """
    for n in node.childNodes:
      if node.hasChildNodes:
        remove_childs(n)
      else:
        node.removeChild(n)   

def parse_childs(child):
  """
  takes actions according to the found child nodes
  """
  global nbToken, plotfilenames
  for c in child.childNodes:
    if c.nodeType == c.ELEMENT_NODE:

       #sage:expr - execute expression and wrap $$ $$ around
       if len(c.getAttributeNS(NS, 'expr')) != 0:
          print "sage:expr =", c.tagName, preparse(c.getAttributeNS(NS, 'expr'))
          sagecode.write('%s%s = latex(%s)'%(TOKEN, nbToken, preparse(c.getAttributeNS(NS, 'expr'))))
          sagecode.write(LB)
          nbToken += 1

       #sage:plainexpr - execute expression w/o $$ $$
       elif len(c.getAttributeNS(NS, 'plainexpr')) != 0:
          print "sage:plainexpr =", c.tagName, preparse(c.getAttributeNS(NS, 'plainexpr'))
          sagecode.write('%s%s = latex(%s)'%(TOKEN, nbToken, preparse(c.getAttributeNS(NS, 'plainexpr'))))
          sagecode.write(LB)
          nbToken += 1

       #sage:eval - like expr, but also writes input
       elif len(c.getAttributeNS(NS, 'eval')) != 0:
          print "sage:eval = ", c.tagName, preparse(c.getAttributeNS(NS, 'eval'))
          sagecode.write('%s%s = latex(%s)'%(TOKEN, nbToken, preparse(c.getAttributeNS(NS, 'eval'))))
          sagecode.write(LB)
          nbToken += 1

       #sage:code - execute code and do nothing else (it's still in the html, probably hidden by html style tags)
       elif len(c.getAttributeNS(NS, 'code')) != 0:
          sagecode.write("if 1:") #executes code with indents
          sagecode.write(LB)
          print "sage:code:", c.firstChild.wholeText
          for l in c.firstChild.wholeText.split(LB): #TODO improve line break detection
              sagecode.write(preparse(l))
              sagecode.write(LB)
          sagecode.write(LB)

       #sage:plot - assumes a graphics object and saves it to a png file, inserts relative path into src=""
       elif len(c.getAttributeNS(NS, 'plot')) != 0:
          plotfile = os.path.abspath(os.path.join(imgDir, ''.join(['plot_', str(nbToken), '.png'])))
          plotfilenames.append(plotfile)
          options = c.getAttributeNS(NS, 'plotoptions')
          plotcmd = ''.join(['(', c.getAttributeNS(NS, 'plot'), ').save(filename=\'', plotfile, '\',',options,')'])
          print "sage:plot:", plotcmd
          print "plotfile: ", plotfile
          sagecode.write(plotcmd)
          sagecode.write(LB)
          nbToken += 1
     
       #sage:cell - ...
       elif len(c.getAttributeNS(NS, 'cell')) != 0:
          print "sage:cell:", c.firstChild.wholeText
          sagecode.write('%s%s = %s'%(TOKEN, nbToken, preparse(c.firstChild.wholeText)))
          sagecode.write(LB)
          nbToken += 1

       else:
          #walk through recursively
          parse_childs(c)

def insert_token(child, doc):
    global nbToken, plotfilenames, tkCnt, imgDir, vars
    for c in child.childNodes:
      if c.nodeType == c.ELEMENT_NODE:

        #sage:expr
        if len(c.getAttributeNS(NS, 'expr')) != 0:
            print "sage:expr token %s: %s"%(tkCnt, TOKEN+str(tkCnt))
            remove_childs(c)
            c.appendChild(doc.createTextNode('$$%s$$' % vars[TOKEN+str(tkCnt)]))
            tkCnt += 1

        #sage:plainexpr
        elif len(c.getAttributeNS(NS, 'plainexpr')) != 0:
            print "sage:plainexpr token %s: %s"%(tkCnt, TOKEN+str(tkCnt))
            remove_childs(c)
            c.appendChild(doc.createTextNode('%s' % vars[TOKEN+str(tkCnt)]))
            tkCnt += 1

        #sage:eval
        elif len(c.getAttributeNS(NS, 'eval')) != 0:
            print "sage:eval token %s: %s" % (tkCnt, TOKEN+str(tkCnt))
            remove_childs(c)
            s = doc.createElement('code')
            s.setAttribute('class', 'sage.code')
            s.appendChild(doc.createTextNode('%s' % c.getAttributeNS(NS, 'eval')))
            c.appendChild(s)
            c.appendChild(doc.createTextNode(' => $$%s$$' % vars[TOKEN+str(tkCnt)]))
            tkCnt += 1
        
        #sage:plot    
        elif len(c.getAttributeNS(NS, 'plot')) != 0:
            plotfile = os.path.join(imgDirHTML, os.path.basename(plotfilenames.pop()))
            print plotfile
            c.setAttribute('src', plotfile)
	    c.appendChild(doc.createTextNode(' ')) #to avoid empty <img .../> tags (FF issue)
            tkCnt += 1

        #sage:cell
        elif len(c.getAttributeNS(NS, 'cell')) != 0:
            print "sage:cell token %s: %s" % (tkCnt, TOKEN+str(tkCnt))
            # copy node, replace node by construct, insert copied node and append copy as output node 
            c_in = c.cloneNode(1)
            c_out = c.cloneNode(1)
            remove_childs(c)
            c.appendChild(c_in)
            remove_childs(c_out)
            c_out.appendChild(doc.createTextNode('%s' % vars[TOKEN+str(tkCnt)]))
            c.appendChild(c_out)
            tkCnt += 1
        
        else:
            #walk recursively
            insert_token(c, doc)


def insert_token_file(file):
    global plotfilenames
    plotfilenames.reverse()
    print "inserting tokens into file", file
    doc = xml.dom.minidom.parse(file)
    body = doc.getElementsByTagName('body')[0]
    insert_token(body, doc)
    print 'processed DOM tree:'
    print doc.toxml()
    return doc
    #import xml.dom.ext
    #xml.dom.ext.PrettyPrint(doc)

def parse_html(file):
    print "parsing file", file
    doc = xml.dom.minidom.parse(file)
    body = doc.getElementsByTagName('body')[0]
    parse_childs(body)

def write_intro(file):
    file.write("""\
#!/usr/bin/env sage
#this file is automatically created. do not modify it!

from sage.all import *
""")

def write_outro(file):
    file.write('#outro')
    file.write(LB)

if __name__ == '__main__':
    file = None
    global sagecode, vars, nbToken, imgDir, workPath, tkCnt, imgDirHTML
    plotfilenames = [] # plotfilenames go here
    nbToken = 0        # output into html
    tkCnt = 0          # token counter when inserting
    vars = {}          # global variables for execution
    if len(sys.argv) < 2:
        print('first and only argument must be the filename.')
        sys.exit(1)

    #change to directory of html files, to work there
    workPath =  os.path.abspath(os.path.dirname(sys.argv[1]))
    print 'Changing working path to %s' % workPath
    os.chdir(workPath)

    #create a tempfile to collect all sage commands
    import shutil
    outputPath = os.path.join(workPath, OUTPUT_DIR)
    if os.path.exists(outputPath):
        #remove everything
        shutil.rmtree(outputPath)
    os.mkdir(outputPath)
    
    warnFile = open(os.path.join(workPath, OUTPUT_DIR, 'README.TXT'), 'w')
    warnFile.write("""This directory and all included files were automatically created by Sage's HTML paser.""")
    warnFile.close()

    sagecode = os.path.join(workPath, OUTPUT_DIR, os.path.splitext(os.path.basename(sys.argv[1]))[0] + '.sage')
    if os.path.exists(sagecode):
         os.remove(sagecode)
    sagecode = open(sagecode, 'w')
    print "Sage Code File:", sagecode.name
    
    #subdir for images
    imgDir = os.path.basename(sys.argv[1])
    imgDir = imgDir.replace('.', '-')
    if imgDir.find('-') != -1:
        imgDir = imgDir.rstrip(imgDir.split('-')[-1])
    imgDir += "img"
    imgDirHTML = imgDir
    imgDir = os.path.join(OUTPUT_DIR, imgDir)
    print "imgDir = ", imgDir
    if os.path.exists(imgDir):
        #delete everything
        shutil.rmtree(imgDir)
    os.mkdir(imgDir)
    #sagecode = open(".".join([sys.argv[1], 'code', 'py']), 'w')
    
    #write the sage file to execute below
    write_intro(sagecode)
    for f in [os.path.basename(f) for f in sys.argv[1:]]:
        parse_html(f)
    write_outro(sagecode)
    sagecode.close()
    sagecode = open(sagecode.name, 'r')
   
    if 1: #the actual deal 
        sagecode.seek(0)
        exec sagecode in vars
        for k in vars.keys():
            if k.startswith(TOKEN):
                print "%s: %s"%(k, vars[k])
        #now all the TOKENS should be inside vars
        #reparse dom and insert all the stuff
        for f in [os.path.basename(f) for f in sys.argv[1:]]:
            dom = insert_token_file(f)
            outfilename = os.path.join(OUTPUT_DIR, f)
            if os.path.exists(outfilename):
                os.remove(outfilename)
            outfile = open(outfilename, 'w')
            outfile.write(dom.toxml())
            outfile.close()

    else: #for debugging
        print
        print '-------- sage temporary file ----------'
        sagecode.seek(0)
        for l in sagecode:
            print l,
        print
        print '---------- plotfilenames --------------'
        for p in plotfilenames:
            print "plotfile:", p
        print '---------------- end ------------------'

