#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# テキストをHTMLの表組みに変換するプログラム
# テキストの書式はpukiwikiをお手本にしている
# ほとんど同じはず．
# 
# このプログラムを外部から
# 使用する時は，text2table関数に入力ファイル
# と出力ファイルを渡せばよい

import sys, glob, string, re

LEFT_RE    = re.compile(r'\s*LEFT$')
CENTER_RE  = re.compile(r'\s*CENTER$')
RIGHT_RE   = re.compile(r'\s*RIGHT$')
BGCOLOR_RE = re.compile(r'\s*BGCOLOR\((.*)\)$')
COLOR_RE   = re.compile(r'\s*COLOR\((.*)\)$')
SIZE_RE    = re.compile(r'\s*SIZE\((.*)\)$')
CSV_CEL_RE = re.compile(r'("(?:[^"]|"")*"|[^,]*),')

#後で，出力結果をテキストで取得できるようにす
#ファイルオブジェクト
class Output:
    def __init__(self):
        self.text = ''

    def write(self,string):
        self.text = self.text+string

    def writelines(self,lines):
        for line in lines: self.write(line)

#テキストを入力ファイルとして扱うための
#オブジェクト
class Input:
    def __init__(self, input=''):
        self.text = input

    def read(self, *size):
        if not size:
            res, self.text = self.text, ''
        else:
            res, self.text = self.text[:size[0]], self.text[size[0]:]
        return res

    def readline(self):
        eoln = string.find(self.text, '\n')
        if eoln == -1:
            res, self.text = self.text, ''
        else:
            res, self.text = self.text[:eoln+1], self.text[eoln+1:]
        return res

    def readlines(self):
        res = []
        while True:
            line = self.readline()
            if line == '':
                return res
            else:
                res.append(line)

#表の中の行を表現するクラス
class Cel:
    def __init__(self,orgText):
        self.orgText = orgText
        self.text = None
        self.align = None
        self.bgcolor = None
        self.fgcolor = None
        self.fsize = None
        self.colspan = 1
        self.rowspan = 1

#表の中の行を表現するクラス
#行のタイプは以下の6つある
#
class Row:
    NOT_TABLE_ROW = 0 # テーブルの行ではない
    NORMAL_ROW    = 1 # 普通の行
    HEADER_ROW    = 2 # ヘッダ行
    FOOTER_ROW    = 3 # フッタ行
    STYLE_ROW     = 4 # 書式の行
    CSV_ROW       = 5 # CSV形式の行
    
    def __init__(self,line):
        if line[0] == '|':
            self.initPipeRow(line)
        elif line[0] == ',':
            self.initCommaRow(line)
        else:
            self.type = Row.NOT_TABLE_ROW

    def initPipeRow(self,line):
        line = string.strip(line)
        cols = string.split(line[1:],'|')
        lastCol = string.strip(cols[-1])
        if lastCol == 'h':
            self.type = Row.HEADER_ROW
            cols = cols[:-1]
        elif lastCol == 'f':
            self.type = Row.FOOTER_ROW
            cols = cols[:-1]
        elif lastCol == 'c':
            self.type = Row.STYLE_ROW
            cols = cols[:-1]
        elif lastCol == '':
            self.type = Row.NORMAL_ROW
            cols = cols[:-1]
        else:
            self.type = Row.NORMAL_ROW
        self.cels = [Cel(col) for col in cols]

    def initCommaRow(self,line):
        line = string.strip(line)
        cols = splitCSV(line[1:])
        self.type = Row.CSV_ROW
        self.cels = [Cel(col) for col in cols]

    def getType(self):
        return self.type

    def getSize(self):
        return len(self.cels)

#CSV形式の行をセルに分割する関数
#python2.3のcsvモジュールは日本語が使えないらしい
#http://www.din.or.jp/~ohzaki/perl.htm#CSV2Values
#を参考に作ったけどあまりスマートではない
def splitCSV(line):
    if line[-1] == '\n':
        line = line[:-1]
    line = line + ','
    values = []
    while True:
        if line == '':
            break
        m = CSV_CEL_RE.match(line)
        values.append(m.group(1))
        line = line[m.end():]
    for x in range(len(values)):
        tmp = values[x]
        if (tmp[0]=='"') and (tmp[-1]=='"'):
            tmp = tmp[1:-1]
        tmp = string.replace(tmp,'""','"')
        values[x] = tmp
    return values

# これがメインの変換関数
# 使う方法は簡単で，入出力ファイルを指定して呼出すだけ．
# なんか複雑になってしまったけど，このメイン関数で
# やっていることは，表の行を抽出してtableリストに保存してゆき，
# 表ブロックが終了したら表の処理・出力を行なうprocessTable関数に
# tableリストをわたす．表と関係ない行はそのまま出力する．
# それだけ．
def text2table(inFile,outFile):
    lines = inFile.readlines()
    table = []
    nowCols = 0
    inTable = False
    for line in lines:
        row = Row(line)
        if not inTable:
            if row.getType() == Row.NOT_TABLE_ROW:
                outFile.write(line)
            else:
                inTable = True
                nowCols = row.getSize()
                table.append(row)
        else:
            if row.getType() == Row.NOT_TABLE_ROW:
                processTable(table,outFile)
                table = []
                inTable = False
                outFile.write(line)
            else:
                if row.getSize()==nowCols:
                    table.append(row)
                else:
                    processTable(table,outFile)
                    table = []
                    table.append(row)
                    nowCols = row.getSize()
                    table.append(row)
    if len(table)!=0:
        processTable(table,outFile)

# テーブルを実際に出力ファイルに出力する．
# ただ出力する前にいろいろ前処理する必要があるので，
# それぞれサブ関数にしてある．
# この関数では，その3つのサブ関数を呼出しているだけ．
def processTable(table,outFile):
    calSpan(table)
    calStyle(table)
    writeTable(table,outFile)

# colspanとrowspanを計算する
# 下から上，左から右の方向にスキャンして
# '>'や'~'や'=='を見付けたら，対象のセルの右や上の
# colspan,rowspanを計算する．そしてそのセルは
# HTMLのタグに含まれてはいけないので，colspan
# やrowspanの値を-1にしておいて後で出力する時に
# わかるようにしておく
def calSpan(table):
    rowNo = len(table)
    colNo = len(table[0].cels)
    for y in range(rowNo-1,-1,-1):
        row = table[y]
        if row.type == Row.STYLE_ROW:
            continue
        for x in range(0,colNo-1):
            cel = row.cels[x]
            text = string.strip(cel.orgText)
            if (row.type!=Row.CSV_ROW) and (text == '>'):
                colPropagation(x,y,cel.colspan+1,table)
                cel.colspan = -1
            elif (row.type!=Row.CSV_ROW) and (text == '~'):
                rowPropagation(x,y,cel.rowspan+1,table)
                cel.rowspan = -1
            elif (row.type==Row.CSV_ROW) and (text == '=='):
                colPropagation(x,y,cel.colspan+1,table)
                cel.colspan = -1

# calSpanの中で使用するサブ関数で，
# 与えられた座標の右のセルのcolspanに
# nの値を設定する．
# ただ，nの値を代入する場所が表の外に
# なってしまう場合は文法エラーだけど，
# 何もしないこととする．
def colPropagation(x,y,n,table):
    if x+1 != len(table[y].cels):
        table[y].cels[x+1].colspan = n

# calSpanの中で使用するサブ関数で，
# 与えられた座標の上のセルのrowspanに
# nの値を設定する．
# もし上の場所がスタイル行ならばスキップ
# して，さらにその上に代入する．
# ただ，nの値を代入する場所が表の外に
# なってしまう場合は文法エラーだけど，
# 何もしないこととする．
def rowPropagation(x,y,n,table):
    for yy in range(y-1,-1,-1):
        if yy == -1:
            return
        if table[yy].type == Row.STYLE_ROW:
            continue
        table[yy].cels[x].rowspan = n
        return

# スタイルを計算するためのサブ関数
# 具体的にはCel.align,bgcolor,fgcolor,fsize,
# にスタイル情報を追加してゆき，Cel.textに
# スタイル情報をとっぱらった実際に表示される
# 文字列を生成する．
# 当然，Rowのスタイルによって処理内容が異なる．
# その処理内容を行なう部分は別関数にしてあり，
# calNonCSVRowStyleとcalCSVRowStyleである．
def calStyle(table):
    colNo = len(table[0].cels)
    nowStyleRow = None
    for row in table:
        if row.type == Row.STYLE_ROW:
            calNonCSVRowStyle(row)
            nowStyleRow = row
        else:
            if nowStyleRow:
                for x in range(colNo):
                    cA = row.cels[x]
                    cB = nowStyleRow.cels[x]
                    cA.align   = cB.align
                    cA.bgcolor = cB.bgcolor
                    cA.fgcolor = cB.fgcolor
                    cA.fsize   = cB.fsize
            if row.type == Row.CSV_ROW:
                calCSVRowStyle(row)
            else:
                calNonCSVRowStyle(row)

# CSV形式ではないRowのスタイルを計算する．
#
# スタイルの括弧の中で指定されるカラーは
# 文法違反かどうかはチェックしていない
# pukiwikiもそうみたいなのでいいかな．
def calNonCSVRowStyle(row):
    for cel in row.cels:
        s = string.split(cel.orgText,':')
        endStyle = -1
        for x in range(len(s)):
            if LEFT_RE.match(s[x]):
                cel.align = 'text-align:left;'
            elif CENTER_RE.match(s[x]):
                cel.align = 'text-align:center;'
            elif RIGHT_RE.match(s[x]):
                cel.align = 'text-align:right;'
            elif BGCOLOR_RE.match(s[x]):
                color = BGCOLOR_RE.match(s[x]).group(1)
                cel.bgcolor = 'background-color:%s;' % color
            elif COLOR_RE.match(s[x]):
                color = COLOR_RE.match(s[x]).group(1)
                cel.bgcolor = 'color:%s;' % color
            elif SIZE_RE.match(s[x]):
                size = SIZE_RE.match(s[x]).group(1)
                try:
                    size = int(size)
                    cel.bgcolor = 'font-size:%dpx;' % size
                except:
                    endStyle = x
                    break
            else:
                endStyle = x
                break
        if endStyle == -1:
            cel.text = ''
            return
        cel.text = s[endStyle]
        for x in range(endStyle+1,len(s)):
            cel.text = cel.text + ':' + s[x]

# CSV形式のRowのスタイルを計算する．
#
def calCSVRowStyle(row):
    for cel in row.cels:
        if len(cel.orgText)==0:
            cel.text=''
            continue
        if cel.orgText[0]==' ':
            if cel.orgText[-1]==' ':
                cel.align = 'text-align:center;'
            else:
                cel.align = 'text-align:right;'
        cel.text = string.strip(cel.orgText)

# 実際に表のデータを出力するための関数
# ヘッダ，フッタ，それ以外の順に表示する
# pukiwikiにならって<th>ってのは使わない．
# それが普通なんですかね．
# セル一つ一つの表示はサブ関数にしてある
def writeTable(table,outFile):
    #まず，ヘッダ，フッタ，それ以外に分ける
    #スタイルは不必要なので省く
    thead = []
    tfoot = []
    tbody = []
    for row in table:
        if row.type == Row.NORMAL_ROW:
            tbody.append(row)
        elif row.type == Row.HEADER_ROW:
            thead.append(row)
        elif row.type == Row.FOOTER_ROW:
            tfoot.append(row)
        elif row.type == Row.CSV_ROW:
            tbody.append(row)
    # 表のはじまり
    outFile.write('<table cellspacing="1" border="1">\n')

    #<thead>の出力
    if not len(thead)==0:
        outFile.write(' <thead>\n')
        for row in thead:
            outFile.write('  <tr>\n    ')
            for cel in row.cels:
                writeCel(cel,outFile)
            outFile.write('\n  </tr>\n')
        outFile.write(' </thead>\n')
    
    #<tfoot>の出力
    if not len(tfoot)==0:
        outFile.write(' <tfoot>\n')
        for row in tfoot:
            outFile.write('  <tr>\n    ')
            for cel in row.cels:
                writeCel(cel,outFile)
            outFile.write('\n  </tr>\n')
        outFile.write(' </tfoot>\n')
    
    #<tbody>の出力
    if not len(tbody)==0:
        outFile.write(' <tbody>\n')
        for row in tbody:
            outFile.write('  <tr>\n    ')
            for cel in row.cels:
                writeCel(cel,outFile)
            outFile.write('\n  </tr>\n')
        outFile.write(' </tbody>\n')

    # 表の終り
    outFile.write('</table>\n')

# スタイルの表記も含めてセルを出力する
# (colspan==-1)or(rowspan==-1)のときは
# 出力しない．
def writeCel(cel,outFile):
    if (cel.colspan==-1) or (cel.rowspan==-1):
        return

    styAtr = ''
    if cel.align:
        styAtr = styAtr + cel.align
    if cel.bgcolor:
        styAtr = styAtr + cel.bgcolor
    if cel.fgcolor:
        styAtr = styAtr + cel.fgcolor
    if cel.fsize:
        styAtr = styAtr + cel.fsize
    if not styAtr == '':
        styAtr = ' style="%s"' % styAtr

    if cel.colspan > 1:
        colAtr = ' colspan="%d"' % cel.colspan
    else:
        colAtr = ''

    if cel.rowspan > 1:
        rowAtr = ' rowspan="%d"' % cel.rowspan
    else:
        rowAtr = ''

    outFile.write('<td%s%s%s>' % (styAtr,colAtr,rowAtr))
    outFile.write(cel.text)
    outFile.write('</td>')

#入出力がファイルでなくてテキストの
#関数
def text_to_table(inputText):
    i = Input(inputText)
    o = Output()
    text2table(i,o)
    return o.text

# 以下このスクリプトが直接呼ばれた時の処理
# 
if __name__ == '__main__':
    if len(sys.argv) == 1:
        inFile = sys.stdin
        outFile = sys.stdout
        text2table(inFile,outFile)
    else:
        for f in sys.argv[1:]:
            g = glob.glob(f);
            if len(g) == 0:
                print '???file %s does not exist???' % f
                continue
            inFile = open(f)
            co = string.rfind(f,'.mm')
            if co == -1:
                ff = f + '.xhtml'
            else:
                ff = f[:co] + '.xhtml'
            outFile = open(ff,'w')
            text2table(inFile,outFile)
            inFile.close()
            outFile.close()
