if not modules then modules = { } end modules ['node-ser'] = {
    version   = 1.001,
    comment   = "companion to node-ini.mkiv",
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
    copyright = "PRAGMA ADE / ConTeXt Development Team",
    license   = "see context related readme files"
}

-- beware, some field names will change in a next releases
-- of luatex; this is pretty old code that needs an overhaul

local type = type
local concat, tohash, sortedkeys, printtable = table.concat, table.tohash, table.sortedkeys, table.print
local formatters, format, rep = string.formatters, string.format, string.rep

local allocate = utilities.storage.allocate

local context     = context
local nodes       = nodes
local node        = node

local traverse    = nodes.traverse
local is_node     = nodes.is_node

local nodecodes   = nodes.nodecodes
local subtcodes   = nodes.codes
local noadcodes   = nodes.noadcodes
local getfields   = nodes.fields

local tonode      = nodes.tonode

local hlist_code  = nodecodes.hlist
local vlist_code  = nodecodes.vlist

----- utfchar     = utf.char
local f_char      = formatters["%U"]
----- fontchars   = { } table.setmetatableindex(fontchars,function(t,k) fontchars = fonts.hashes.characters return fontchars[k] end)

----- f_char      = utilities.strings.chkuni -- formatters["%!chkuni!"]

local expand = allocate ( tohash {
    -- text:
    "list",         -- list_ptr & ins_ptr & adjust_ptr
    "pre",          --
    "post",         --
    "spec",         -- glue_ptr
    "top_skip",     --
    "attr",         --
    "replace",      -- nobreak
    "components",   -- lig_ptr
    "box_left",     --
    "box_right",    --
    "glyph",        -- margin_char
    "leader",       -- leader_ptr
    "action",       -- action_ptr
    "value",        -- user_defined nodes with subtype 'a' en 'n'
    "head",
    -- math:
    "nucleus",
    "sup",
    "sub",
    "list",
    "num",
    "denom",
    "left",
    "right",
    "display",
    "text",
    "script",
    "scriptscript",
    "delim",
    "degree",
    "accent",
    "bot_accent",
} )

-- page_insert: "height", "last_ins_ptr", "best_ins_ptr"
-- split_insert:  "height", "last_ins_ptr", "best_ins_ptr", "broken_ptr", "broken_ins"

local ignore = allocate ( tohash {
    "page_insert",
    "split_insert",
    "ref_count",
} )

local dimension = allocate ( tohash {
    "width", "height", "depth", "shift",
    "stretch", "shrink",
    "xoffset", "yoffset",
    "surround",
    "kern",
    "box_left_width", "box_right_width"
} )

-- flat: don't use next, but indexes
-- verbose: also add type
-- can be sped up

nodes.dimensionfields = dimension
nodes.listablefields  = expand
nodes.ignorablefields = ignore

-- not ok yet:

local function astable(n,sparse) -- not yet ok, might get obsolete anyway
    n = tonode(n)
    local f, t = getfields(n), { }
    for i=1,#f do
        local v = f[i]
        local d = n[v]
        if d then
            if ignore[v] or v == "id" then
                -- skip
            elseif expand[v] then -- or: type(n[v]) ~= "string" or type(n[v]) ~= "number" or type(n[v]) ~= "table"
                t[v] = "pointer to list"
            elseif sparse then
                if (type(d) == "number" and d ~= 0) or (type(d) == "string" and d ~= "") then
                    t[v] = d
                end
            else
                t[v] = d
            end
        end
    end
    t.type = nodecodes[n.id]
    return t
end

nodes.astable = astable

setinspector(function(v) if is_node(v) then printtable(astable(v),tostring(v)) return true end end)

-- under construction:

local function totable(n,flat,verbose,noattributes) -- nicest: n,true,true,true
    local function to_table(n,flat,verbose,noattributes) -- no need to pass
        local f = getfields(n)
        local tt = { }
        for k=1,#f do
            local v = f[k]
            local nv = v and n[v]
            if nv then
                if ignore[v] then
                    -- skip
                elseif noattributes and v == "attr" then
                    -- skip
                elseif expand[v] then
                    if type(nv) == "number" or type(nv) == "string" then
                        tt[v] = nv
                    else
                        tt[v] = totable(nv,flat,verbose,noattributes)
                    end
                elseif type(nv) == "table" then
                    tt[v] = nv -- totable(nv,flat,verbose) -- data
                else
                    tt[v] = nv
                end
            end
        end
        if verbose then
            local subtype = tt.subtype
            local id = tt.id
            local nodename = nodecodes[id]
            tt.id = nodename
            local subtypes = subtcodes[nodename]
            if subtypes then
                tt.subtype = subtypes[subtype]
            elseif subtype == 0 then
                tt.subtype = nil
            else
                -- we need a table
            end
            if tt.char then
                tt.char = f_char(tt.char)
            end
            if tt.small_char then
                tt.small_char = f_char(tt.small_char)
            end
            if tt.large_char then
                tt.large_char = f_char(tt.large_char)
            end
        end
        return tt
    end
    if n then
        if flat then
            local t, tn = { }, 0
            while n do
                tn = tn + 1
                local nt = to_table(n,flat,verbose,noattributes)
                t[tn] = nt
                nt.next = nil
                nt.prev = nil
                n = n.next
            end
            return t
        else
            local t = to_table(n,flat,verbose,noattributes)
            local n = n.next
            if n then
                t.next = totable(n,flat,verbose,noattributes)
            end
            return t
        end
    else
        return { }
    end
end

nodes.totable = function(n,...) return totable(tonode(n),...) end
nodes.totree  = function(n)     return totable(tonode(n),true,true,true) end -- no attributes, todo: attributes in k,v list

local function key(k)
    return ((type(k) == "number") and "["..k.."]") or k
end

-- not ok yet; this will become a module

-- todo: adapt to nodecodes etc .. use formatters

local function serialize(root,name,handle,depth,m,noattributes)
    handle = handle or print
    if depth then
        depth = depth .. " "
        handle(format("%s%s={",depth,key(name)))
    else
        depth = ""
        local tname = type(name)
        if tname == "string" then
            if name == "return" then
                handle("return {")
            else
                handle(name .. "={")
            end
        elseif tname == "number" then
            handle("[" .. name .. "]={")
        else
            handle("t={")
        end
    end
    if root then
        local fld
        if root.id then
            fld = getfields(root) -- we can cache these (todo)
        else
            fld = sortedkeys(root)
        end
        if type(root) == 'table' and root['type'] then -- userdata or table
            handle(format("%s type=%q,",depth,root['type']))
        end
        for f=1,#fld do
            local k = fld[f]
            if k == "ref_count" then
                -- skip
            elseif noattributes and k == "attr" then
                -- skip
            elseif k == "id" then
                local v = root[k]
                handle(format("%s id=%s,",depth,nodecodes[v] or noadcodes[v] or v))
            elseif k then
                local v = root[k]
                local t = type(v)
                if t == "number" then
                    if v == 0 then
                        -- skip
                    else
                        handle(format("%s %s=%s,",depth,key(k),v))
                    end
                elseif t == "string" then
                    if v == "" then
                        -- skip
                    else
                        handle(format("%s %s=%q,",depth,key(k),v))
                    end
                elseif t == "boolean" then
                    handle(format("%s %s=%q,",depth,key(k),tostring(v)))
                elseif v then -- userdata or table
                    serialize(v,k,handle,depth,m+1,noattributes)
                end
            end
        end
        if root['next'] then -- userdata or table
            serialize(root['next'],'next',handle,depth,m+1,noattributes)
        end
    end
    if m and m > 0 then
        handle(format("%s},",depth))
    else
        handle(format("%s}",depth))
    end
end

function nodes.serialize(root,name,noattributes)
    local t, n = { }, 0
    local function flush(s)
        n = n + 1
        t[n] = s
    end
    serialize(tonode(root),name,flush,nil,0,noattributes)
    return concat(t,"\n")
end

function nodes.serializebox(n,flat,verbose,name)
    return nodes.serialize(nodes.totable(tex.box[n],flat,verbose),name)
end

function nodes.visualizebox(...) -- to be checked .. will move to module anyway
    context.starttyping()
    context.pushcatcodes("verbatim")
    context(nodes.serializebox(...))
    context.stoptyping()
    context.popcatcodes()
end

function nodes.list(head,n) -- name might change to nodes.type -- to be checked .. will move to module anyway
    head = tonode(head)
    if not n then
        context.starttyping(true)
    end
    while head do
        local id = head.id
        context(rep(" ",n or 0) .. tostring(head) .. "\n")
        if id == hlist_code or id == vlist_code then
            nodes.list(head.list,(n or 0)+1)
        end
        head = head.next
    end
    if not n then
        context.stoptyping(true)
    end
end

function nodes.print(head,n)
    head = tonode(head)
    while head do
        local id = head.id
        logs.writer(string.formatters["%w%S"],n or 0,head)
        if id == hlist_code or id == vlist_code then
            nodes.print(head.list,(n or 0)+1)
        end
        head = head.next
    end
end
