#!/usr/bin/env ruby

#    ratexdb Version 0.14
#    Database Access in LaTeX
#    Copyright (C) 2007-2010 Robin H�ns, Integranova GmbH
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
#
#    For more information see the web pages
#
#    Robin H�ns: http://www.hoens.net/robin
#    Integranova and the Programming Machine:
#                http://www.programmiermaschine.de
#                http://www.care-t.com

require 'dbi'
require 'FileUtils'


$filedebug = nil

# primitive debug output routine
def debugoutput line
 if $filedebug
   $filedebug.puts "#{line}"
 end
end



def replace_latex dbtext
# Replace Latex special characters in DB result
#Unterstrich    _       \_
#R�ckstrich\Backslash   \       \setminus
#Dollarzeichen  $       \$
#Kaufmanns-Und  &       \&
#Raute  #       \#
#Geschweifte Klammern   { }     \{ \}
#Prozentzeichen          %      \%
 debugoutput "replatex rein: " + dbtext

 specialhash = {
   ?\\ => "\\\\ensuremath{\\\\backslash}",
   ?_ => "\\\\_",
   ?$ => "\\\\$",
   ?& => "\\\\&",
   ?# => "\\\\#",
   ?{ => "\\\\{",
   ?} => "\\\\}",
   ?~ => "\\\\~{}",
   ?% => "\\\\%"
 }

 result = ""

 for i in (0..dbtext.length)
   maski = specialhash[dbtext[i]]
   if maski
     result << maski
   else
     result << dbtext[i,1]
   end
 end

 debugoutput "replatex raus: " + result

 return result
end

def replace_sql dbtext
# Replace SQL special characters in DB result
# (important against SQL injection!)
 return dbtext.gsub("'", "''")
end

def check_number dbtext
# This text must contain a single numeric value
# (important against SQL injection!)
 unless dbtext =~ /\A(\+|-)?[\da-fA-F]+\Z/
   raise "Numeric variable contains non-numeric value: #{dbtext}"
 end
 return dbtext
end


def execute_if texblock
 debugoutput "Pruefe if #{$select_to_handle}"
 debugoutput texblock

 selectkram = $select_hash[$select_to_handle]
 $select_to_handle = nil
 $modus_collect = false

 selectbefehl = selectkram[0]
 debugoutput "Select: #{selectbefehl}"
 row = $dbconn.select_one( selectbefehl )

 if row
   # Ja, es gibt Daten zu diesem Select
   for nowline in texblock.split("\n")
     debugoutput "nowline " + nowline
     handle_line(nowline)
   end
 end
end


def execute_for_loop texblock

 debugoutput "Mache #{$select_to_handle} mit:"
 debugoutput texblock

 selectkram = $select_hash[$select_to_handle]
 $select_to_handle = nil
 $modus_collect = false

 selectbefehl = selectkram[0]
 variablen = selectkram[1]

 debugoutput "Select: #{selectbefehl}"
 for myvar in variablen
   debugoutput "Var: #{myvar}"
 end

 rows = $dbconn.select_all( selectbefehl )

 for row in rows
   ergebnis = texblock
   vari = 0
   while vari < variablen.length
     variable_regexs = variablen[vari].split("/")
     varname = variable_regexs[0]

     debugoutput "ok: " + varname
     if varname[0,2] != "##"
       raise "Variable #{varname} does not begin with ##"
     end
     varnamesql = "$$" + varname[2..-1]
     varnamenum = "&&" + varname[2..-1]

     original_db_result = row[vari].to_s
     current_db_result_latex = replace_latex(original_db_result)
     current_db_result_sql = replace_sql(original_db_result)
     if ergebnis.index(varnamenum)
           current_db_result_num = check_number(original_db_result)
     else
           current_db_result_num = original_db_result
     end

     debugoutput "latex: #{current_db_result_latex}"
     debugoutput "sql: #{current_db_result_sql}"
     debugoutput "num: #{current_db_result_num}"

     # Perform all Regexp replaces
     rei = 2
     while rei < variable_regexs.length
     debugoutput variable_regexs[rei-1]
       rei_regex = Regexp.new( variable_regexs[rei-1] )
     debugoutput rei_regex
       rei_replace = variable_regexs[rei]
     debugoutput rei_replace
       current_db_result_latex = current_db_result_latex.gsub(rei_regex, rei_replace)
       current_db_result_sql = current_db_result_sql.gsub(rei_regex, rei_replace)
       current_db_result_num = current_db_result_num.gsub(rei_regex, rei_replace)

     debugoutput "latex: #{current_db_result_latex}"
     debugoutput "sql: #{current_db_result_sql}"
     debugoutput "num: #{current_db_result_num}"

       rei += 2
     end

# Handle the three variable flavours
     ergebnis = ergebnis.gsub(varname, current_db_result_latex)
     ergebnis = ergebnis.gsub(varnamesql, current_db_result_sql)
     if ergebnis.index(varnamenum)
       ergebnis = ergebnis.gsub(varnamenum, current_db_result_num)
     end

     vari += 1
   end
   debugoutput "Ergebnis: #{ergebnis}"
   for nowline in ergebnis.split("\n")
     handle_line(nowline)
   end
 end
end

def execute_defselect texblock
 debugoutput "defselect #{$select_to_define}"
 debugoutput texblock

 $defining_select_statement = texblock

end


def execute_defvariablen texblock
 debugoutput "defvariablen #{$select_to_define}"
 debugoutput texblock

 # remove all white space from variable list

 varliste = Array.new

 for var in texblock.split(",")
   varliste << var.strip
 end

 varliste.each {|var| debugoutput "Got Var: " + var}

 $select_hash[$select_to_define] = [$defining_select_statement, varliste]

 $select_to_define = nil
 $modus_collect = false
end


def handle_line_unless_empty(line)
 debugoutput "handle unless empty"
 unless line == nil
   unless line.length == 0
     debugoutput "Handle: " + line
     handle_line line
   else
#      debugoutput "line is empty"
   end
 else
#    debugoutput "line is nil"
 end
end

def handle_line(line)
#  debugoutput "Handle: " + line

##  matchDef=/\A([^%]*)\\texdbdef\{##([^\}]*)\}\{(.*)\}\{(.*)\}(.*)/
 matchDef=/\A([^%]*)\\texdbdef\{##([^\}]*)\}\{(.*)/
##  matchFor=/\A(.*)\\texdbfor\{##([^\}]*)\}\{(.*)\}(.*)/
 matchFor=/\A([^%]*)\\texdbfor\{##([^\}]*)\}\{(.*)/
 matchIf=/\A([^%]*)\\texdbif\{##([^\}]*)\}\{(.*)/
 matchDbDef=/\A([^%]*)\\texdbconnection\{([^\}]*)\}(.*)/
 matchDbCmd=/\A([^%]*)\\texdbcommand\{([^\}]*)\}(.*)/

 if $modus_collect == false
   if line =~ matchDef
     debugoutput "Found Def!"
     handle_line_unless_empty($1)
     $select_to_define = $2
     $bracedepth = 1
     $modus_collect = 1
     $collected_text = ""
     $kommando="defselect"
     handle_line_unless_empty($3)
   elsif line =~ matchFor
     debugoutput "Found For!"
     handle_line_unless_empty($1)
     $select_to_handle = $2
     $bracedepth = 1
     $modus_collect = 1
     $collected_text = ""
     $kommando="for"
     handle_line_unless_empty($3)
   elsif line =~ matchIf
     debugoutput "Found If!"
     handle_line_unless_empty($1)
     $select_to_handle = $2
     $bracedepth = 1
     $modus_collect = 1
     $collected_text = ""
     $kommando="if"
     handle_line_unless_empty($3)
   elsif line =~ matchDbDef
     debugoutput "Found DbDef!"
     handle_line_unless_empty($1)
     debugoutput "Datenbank: #{$2}"
     dbpar = $2.split(",")
     $dbconn = DBI.connect(dbpar[0], dbpar[1], dbpar[2], dbpar[3])
     handle_line_unless_empty($3)
   elsif line =~ matchDbCmd
     debugoutput "Found DbCmd!"
     handle_line_unless_empty($1)
     debugoutput "Command: #{$2}"
     unless $dbconn
       raise "Please put texdbconnection before texdbcommand!"
     end
     sth = $dbconn.execute($2)
     sth.finish
     handle_line_unless_empty($3)
   else
     $fileout.puts "#{line}"
   end
 else
   # wir sammeln Zeilen

   if line == nil
     linelength = 0
   else
     linelength = line.length
   end
#    debugoutput linelength
   i = 0
   backslashgelesen = 0
   while i < linelength && $bracedepth > 0
     currchar = line[i]
#      debugoutput "curr #{currchar} backsl #{backslashgelesen}"
     if currchar == ?\\
       if backslashgelesen == 0
         backslashgelesen = 1
       else
         backslashgelesen = 0
       end
     elsif currchar == ?{
       if backslashgelesen == 0
         $bracedepth += 1
         debugoutput "Tiefe: #{$bracedepth}"
       else
         debugoutput "kein ++, Backslash!"
       end
       backslashgelesen = 0
     elsif currchar == ?}
       if backslashgelesen == 0
         $bracedepth -= 1
         debugoutput "Tiefe: #{$bracedepth}"
       else
         debugoutput "kein --, Backslash!"
       end
       backslashgelesen = 0
     else
       backslashgelesen = 0
     end
     i += 1
   end # while
   debugoutput "Zeile Tiefe: #{$bracedepth}"
   $collected_text << line[0, i]
   i -= 1

   if $bracedepth == 0
     # chop, um } wegzuschmei�en
     if $kommando == "for"
       execute_for_loop $collected_text.chop
       $modus_collect = false
     elsif $kommando == "if"
       execute_if $collected_text.chop
       $modus_collect = false
     elsif $kommando == "defselect"
       execute_defselect $collected_text.chop
       i += 1
       if i >= linelength || line[i] != ?{
         raise "In texdbdef of #{$select_to_define} there is nothing allowed between the select and variable block, but I did not find the \}\{."
       end
       $modus_collect = 1
       $kommando = "defvariablen"
       $bracedepth = 1
       $collected_text = ""
     elsif $kommando == "defvariablen"
       execute_defvariablen $collected_text.chop
       $modus_collect = false
     end

     handle_line_unless_empty line[i+1 .. -1]
   end

   $collected_text << "\n"

 end
end

def output_help

 puts "Ratexdb 0.14 Copyright (C) 2007-2010 Robin Hoens, Integranova GmbH"
 puts "This program comes with ABSOLUTELY NO WARRANTY."
 puts "This is free software, and you are welcome to redistribute it"
 puts "under certain conditions."
 puts "See file COPYING for details."
 puts ""
 puts "Usage:"
 puts "#{$0} [-p|-l] <texfile.tex> [Parameters...]"
 puts "-p = pdflatex"
 puts "-l = latex"
 puts "none of the two: just produce texfile1.tex"
 exit
end


if ARGV.length == 0
 output_help
end

arg_index = 0

before_texfile = 1

while before_texfile
 if ARGV[arg_index] == "-p"
   texbefehl = "pdflatex"
   resultextension = ".pdf"
 elsif ARGV[arg_index] == "-l"
   texbefehl = "latex"
   resultextension = ".dvi"
 elsif ARGV[arg_index] == "-d"
   $filedebug = File.new("ratexdb.txt", "w")
 else
   before_texfile = false
 end
 arg_index += 1
 if ARGV.length < arg_index
   output_help
 end
end

arg_index -= 1

datei_rein = ARGV[arg_index]
datei_raus = datei_rein.sub(/\.tex\Z/, "1.tex")

if datei_raus == datei_rein
 raise "Sorry: File name must end on .tex. I got #{datei_rein}"
end

debugoutput datei_rein
debugoutput datei_raus

$select_to_handle = nil
$select_hash = {}
$variable_hash = {}
$modus_collect = false

cmdlinehash = {}
i=1
while arg_index + i < ARGV.length
 cmdlinehash[i.to_s] = ARGV[arg_index + i]
 i+=1
end

filein = File.new(datei_rein, "r")
$fileout = File.new(datei_raus, "w")
while (line = filein.gets)
 cmdlinehash.each {|key, value|
     line = line.gsub("##" + key, replace_latex(value))
     line = line.gsub("$$" + key, replace_sql(value))
     if line.index("&&" + key)
       line = line.gsub("&&" + key, check_number(value))
     end
 }
 handle_line line.chomp
end
filein.close
$fileout.close

$dbconn.disconnect if $dbconn

if texbefehl
 system(texbefehl, datei_raus)

 rauspdf = datei_raus.sub(/\.tex\Z/, resultextension)
 ergebnispdf = datei_rein.sub(/\.tex\Z/, resultextension)

 File.mv(rauspdf, ergebnispdf)
end