%D \module
%D   [       file=x-cals,
%D        version=2007.09.05,
%D          title=\CONTEXT\ XML Modules,
%D       subtitle=Cals table renderer,
%D         author=Hans Hagen,
%D           date=\currentdate,
%D      copyright={PRAGMA ADE}]
%C
%C This module is part of the \CONTEXT\ macro||package and is
%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
%C details.

\writestatus{loading}{ConTeXt XML Macros / Cals Tables}

\startluacode
do

    lxml.cals = { }
    lxml.cals.ignore_widths  = false

    -- the following flags only apply to columns that have a specified width
    --
    -- proportional : shrink or stretch proportionally to the width
    -- equal        : shrink or stretch equaly distributed
    -- n < 1        : shrink or stretch proportionally to the width but multiplied by n
    --
    -- more clever things, e.g. the same but applied to unspecified widths
    -- has to happen at the core-ntb level (todo)

    lxml.cals.shrink_widths  = false
    lxml.cals.stretch_widths = false

    local format    = string.format
    local texsprint = tex.sprint
    local xmlsprint = xml.sprint
    local xmlcprint = xml.cprint

    local halignments = {
        left = "flushleft",
        right = "flushright",
        center = "middle",
        centre = "middle",
        justify = "normal",
    }

    local valignments = {
        top = "high",
        bottom = "low",
        middle = "lohi",
    }

    local function getspecs(root, pattern, names, widths)
        -- here, but actually we need this in core-ntb.tex
        -- but ideally we need an mkiv enhanced core-ntb.tex
        local ignore_widths = lxml.cals.ignore_widths
        local shrink_widths = lxml.cals.shrink_widths
        local stretch_widths = lxml.cals.stretch_widths
        for r, d, k in xml.elements(root,pattern) do
            local dk = d[k]
            local at = dk.at
            local column = at.colnum
            if column then
                if not ignore_widths then
                    local width = at.colwidth
                    if width then
                        widths[tonumber(column)] = width:lower()
                    end
                end
                local name = at.colname
                if name then
                    names[name] = tonumber(column)
                end
            end
        end
        local function adapt(b,w,delta,sum,n,what)
            if b == "equal" then
                delta = delta/n
                for k, v in pairs(w) do
                    widths[k] = number.todimen(v - delta)
                end
            elseif b == "proportional" then
                delta = delta/sum
                for k, v in pairs(w) do
                    widths[k] = number.todimen(v - v*delta)
                end
            elseif type(b) == "number" and b < 1 then
                delta = b*delta/sum
                for k, v in pairs(w) do
                    widths[k] = number.todimen(v - v*delta)
                end
            end
        end
        if ignore_width then
            -- forget about it
        elseif shrink_widths or stretch_widths then
            local sum, n, w = 0, 0, { }
            for _, v in pairs(widths) do
                n = n + 1
                v = (type(v) == "string" and v:todimen()) or v
                if v then
                    w[n] = v
                    sum = sum + v
                end
            end
            local hsize = tex.hsize
            if type(hsize) == "string" then
                hsize = hsize:todimen()
            end
            local delta = sum - hsize
            if shrink_widths and delta > 0 then
                adapt(shrink_widths,w,delta,sum,n,"shrink")
            elseif stretch_widths and delta < 0 then
                adapt(stretch_widths,w,delta,sum,n,"stretch")
            end
        end
    end

    local function getspans(root, pattern, names, spans)
        for r, d, k in xml.elements(root,pattern) do
            local dk = d[k]
            local at = dk.at
            local name, namest, nameend = at.colname, names[at.namest or "?"], names[at.nameend or "?"]
            if name and namest and nameend then
                spans[name] = tonumber(nameend) - tonumber(namest) + 1
            end
        end
    end

    --local function texsprint(a,b) print(b) end
    --local function xmlsprint(a) print(a) end

    function lxml.cals.table(root,namespace)

        local prefix = (namespace or "cals") .. ":"
        local p = "/" .. prefix

        local tgroupspec = p .. "tgroup"
        local colspec    = p .. "colspec"
        local spanspec   = p .. "spanspec"
        local hcolspec   = p .. "thead" .. p .. "colspec"
        local bcolspec   = p .. "tbody" .. p .. "colspec"
        local fcolspec   = p .. "tfoot" .. p .. "colspec"
        local entryspec  = p .. "entry" .. "|" ..prefix .. "entrytbl"
        local hrowspec   = p .. "thead" .. p .. "row"
        local browspec   = p .. "tbody" .. p .. "row"
        local frowspec   = p .. "tfoot" .. p .. "row"

        local function tablepart(root, xcolspec, xrowspec, before, after)
            texsprint(tex.ctxcatcodes,before)
            local at = root.at
            local pphalign, ppvalign = at.align, at.valign
            local names, widths, spans = { }, { }, { }
            getspecs(root, colspec , names, widths)
            getspecs(root, xcolspec, names, widths)
            getspans(root, spanspec, names, spans)
            for r, d, k in xml.elements(root,xrowspec) do
                texsprint(tex.ctxcatcodes,"\\bTR")
                local dk = d[k]
                local at = dk.at
                local phalign, pvalign = at.align or pphalign, at.valign or ppvalign -- todo: __p__ test
                local col = 1
                for rr, dd, kk in xml.elements(dk,entryspec) do
                    local dk = dd[kk]
                    if dk.tg == "entrytbl" then
                        texsprint(tex.ctxcatcodes,"\\bTD{")
                        lxml.cals.table(dk)
                        texsprint(tex.ctxcatcodes,"}\\eTD")
                        col = col + 1
                    else
                        local at = dk.at
                        local b, e, s, m = names[at.namest or "?"], names[at.nameend or "?"], spans[at.spanname or "?"], at.morerows
                        local halign, valign = at.align or phalign, at.valign or pvalign
                        if b and e then
                            s = e - b + 1
                        end
                        if halign then
                            halign = halignments[halign]
                        end
                        if valign then
                            valign = valignments[valign]
                        end
                        local width = widths[col]
                        if s or m or halign or valign or width then -- only english interface !
                            texsprint(tex.ctxcatcodes,format("\\bTD[nx=%s,ny=%s,align={%s,%s},width=%s]",
                                s or 1, (m or 0)+1, halign or "flushleft", valign or "high", width or "fit"))
                        else
                            texsprint(tex.ctxcatcodes,"\\bTD[align={flushleft,high},width=fit]") -- else problems with vertical material
                        end
                    --    xmlsprint(dk)
                        xmlcprint(dk)
                        texsprint(tex.ctxcatcodes,"\\eTD")
                        col = col + (s or 1)
                    end
                end
                texsprint(tex.ctxcatcodes,"\\eTR")
            end
            texsprint(tex.ctxcatcodes,after)
        end

        for r, d, k in xml.elements(lxml.id(root),tgroupspec) do
            local tgroup = d[k]
            texsprint(tex.ctxcatcodes, "\\directsetup{cals:table:before}")
            lxml.directives.before(root,"cdx") -- "cals:table"
            texsprint(tex.ctxcatcodes, "\\bgroup")
            lxml.directives.setup(root,"cdx") -- "cals:table"
            texsprint(tex.ctxcatcodes, "\\bTABLE")
            tablepart(tgroup, hcolspec, hrowspec, "\\bTABLEhead", "\\eTABLEhead")
            tablepart(tgroup, bcolspec, browspec, "\\bTABLEbody", "\\eTABLEbody")
            tablepart(tgroup, fcolspec, frowspec, "\\bTABLEfoot", "\\eTABLEfoot")
            texsprint(tex.ctxcatcodes, "\\eTABLE")
            texsprint(tex.ctxcatcodes, "\\egroup")
            lxml.directives.after(root,"cdx") -- "cals:table"
            texsprint(tex.ctxcatcodes, "\\directsetup{cals:table:after}")
        end
    end

end
\stopluacode

% \startxmlsetups xml:cals:process
%     \xmlsetsetup {\xmldocument} {cals:table} {*}
% \stopxmlsetups
% \startxmlsetups cals:table
%     \ctxlua{lxml.cals.table("#1")}
% \stopxmlsetups
% \xmlregistersetup{xml:cals:process}

\startxmlsetups xml:cals:process
    \xmlsetfunction {\xmldocument} {cals:table} {lxml.cals.table}
\stopxmlsetups

\xmlregistersetup{xml:cals:process}

\xmlregisterns{cals}{cals}

\endinput
