#!/usr/bin/env python
# Author : Hanne Moa
# Date   : 2004-12-10
# LICENSE: public domain

import string, sys
from optparse import OptionParser

class Lisp2XML_FST(object):

    def __init__(self, root='ROOT', empty='EMPTY', readfrom=''):
        self.emptytag = empty
        self.roottag = root
        if readfrom:
            self.readfrom = file(readfrom)
        else:
            self.readfrom = sys.stdin
        self._tags = []
        self._next = None
        self._prev = None

    def is_startparens(self):
        self.is_tag()
        self._tags.append([])

    def is_tag(self):
        prevtag = ''.join(self._tags[-1]) or self.emptytag
        self._tags[-1] = prevtag
        self.write("<%s>" % prevtag)

    def is_endparens(self):
        tag = ''.join(self._tags.pop())
        self.write("</%s>" % tag)

    def write(self, something):
        sys.stdout.write(something)

    def comment(self, char):
        if char == '\n':
            return self._prev
        else:
            return 'comment'
    
    def starttag(self, char):
        self._prev = 'starttag'
        if char in string.whitespace:
            return 'starttag'
        elif char == '(':
            self._tags.append([])
            return 'intag'
        elif char == '%':
            return 'comment'
        else:
            return 'error'

    def intag(self, char):
        self._prev = 'intag'
        if char == '(':
            self.is_startparens()
            return 'intag'
        elif char in string.whitespace:
            if not self._tags[-1]:
                return 'intag'
            else:
                self.is_tag() 
                return 'indata'
        elif char == '%':
            return 'comment'
        elif char == ')':
            self.is_endparens()
            return 'indata'
        else:
            self._tags[-1].append(char)
            return 'intag'

    def indata(self, char):
        self._prev = 'indata'
        default = 'indata'
        if char == '(':
            self._tags.append([])
            return 'intag'
        elif char == ')':
            self.is_endparens()
            return default
        elif char in string.whitespace:
            self.write(char)
            return default
        elif char == '%':
            return 'comment'
        else:
            self.write(char)
            return default
            
    def error(self, char):
        return ''

    engine = {
        'comment': comment,
        'starttag': starttag,
        'intag': intag,
        'indata': indata,
        'error': error,
        }

def convert(root='ROOT', empty='EMPTY', readfrom=''):
    fst = Lisp2XML_FST(root, empty, readfrom)
    engine = fst.engine
    fst.write("<%s>" % fst.roottag)
    for line in fst.readfrom:
        for char in line:
            if not fst._tags:
                next = 'starttag'
                fst.write('\n')
            prev = next
            next = engine[next](fst, char)
            if not next: 
                if fst._tags:
                    fst.is_endparens()
                break
    if fst._tags:
        fst.is_endparens()
    fst.write("</%s>\n" % fst.roottag)
    fst.readfrom.close()

if __name__ == '__main__':
    usage = """Usage: %prog [options] < FILE
Translate s-epressions into XML."""
    parser = OptionParser(usage=usage)
    parser.add_option("-f", "--file", metavar='FILE',
        help="convert s-expressions in FILE")
    parser.add_option("-e", "--empty", default='EMPTY',
        help="fallback tag in case of '((' in source")
    parser.add_option("-r", "--root", default='ROOT',
        help="tag to use as root, if none")

    (opts, args) = parser.parse_args()

    convert(root=opts.root, empty=opts.empty, readfrom=opts.file)
