node-ser.lua /size: 8380 b    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['node-ser'] = {
2    version   = 1.001,
3    comment   = "companion to node-ini.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9-- beware, some field names will change in a next releases
10-- of luatex; this is pretty old code that needs an overhaul
11
12local type, tostring = type, tostring
13local concat, tohash, sortedkeys, printtable, serialize = table.concat, table.tohash, table.sortedkeys, table.print, table.serialize
14local formatters, format, rep = string.formatters, string.format, string.rep
15
16local allocate = utilities.storage.allocate
17
18local context     = context
19local nodes       = nodes
20
21local isnode      = nodes.isnode
22local nodecodes   = nodes.nodecodes
23local subtcodes   = nodes.codes
24local getfields   = nodes.fields
25local tonode      = nodes.tonode
26local tonut       = nodes.tonut
27
28local hlist_code  = nodecodes.hlist
29local vlist_code  = nodecodes.vlist
30
31----- utfchar     = utf.char
32local f_char      = formatters["%U"]
33local f_attr      = formatters["<%i>"]
34----- fontchars   = { } table.setmetatableindex(fontchars,function(t,k) fontchars = fonts.hashes.characters return fontchars[k] end)
35
36----- f_char      = utilities.strings.chkuni -- formatters["%!chkuni!"]
37
38-- this needs checking with the latest state of affairs:
39
40local expand = allocate ( tohash {
41    -- text:
42    "list",         -- list_ptr & ins_ptr & adjust_ptr
43    "pre",          --
44    "post",         --
45    "replace",      -- nobreak
46    "top_skip",     --
47    "attr",         --
48    "components",   -- lig_ptr
49    "box_left",     --
50    "box_right",    --
51    "glyph",        -- margin_char
52    "leader",       -- leader_ptr
53    "action",       -- action_ptr
54    "value",        -- user_defined nodes with subtype 'a' en 'n'
55    "head",
56    -- math:
57    "nucleus",
58    "sup",
59    "sub",
60    "list",
61    "num",
62    "denom",
63    "left",
64    "right",
65    "display",
66    "text",
67    "script",
68    "scriptscript",
69    "delim",
70    "degree",
71    "accent",
72    "bot_accent",
73} )
74
75-- page_insert: "height", "last_ins_ptr", "best_ins_ptr"
76-- split_insert:  "height", "last_ins_ptr", "best_ins_ptr", "broken_ptr", "broken_ins"
77
78local ignore = allocate ( tohash {
79    "page_insert",
80    "split_insert",
81    "ref_count",
82} )
83
84local dimension = allocate ( tohash {
85    "width", "height", "depth", "shift",
86    "stretch", "shrink",
87    "xoffset", "yoffset",
88    "surround",
89    "kern",
90    "box_left_width", "box_right_width"
91} )
92
93-- flat    : don't use next, but indexes
94-- verbose : also add type
95-- todo    : speed up
96
97nodes.dimensionfields = dimension
98nodes.listablefields  = expand
99nodes.ignorablefields = ignore
100
101-- not ok yet:
102
103local function astable(n,sparse) -- not yet ok, might get obsolete anyway
104    n = tonode(n)
105    local f = getfields(n)
106    local t = { }
107    for i=1,#f do
108        local v = f[i]
109        local d = n[v]
110        if d then
111            if ignore[v] or v == "id" then
112                -- skip
113            elseif expand[v] then -- or: type(n[v]) ~= "string" or type(n[v]) ~= "number" or type(n[v]) ~= "table"
114                t[v] = "<list>"
115            elseif sparse then
116                if (type(d) == "number" and d ~= 0) or (type(d) == "string" and d ~= "") then
117                    t[v] = d
118                end
119            else
120                t[v] = d
121            end
122        end
123    end
124    t.type = nodecodes[n.id]
125    return t
126end
127
128nodes.astable = astable
129
130setinspector("node",function(v) if isnode(v) then printtable(astable(v),tostring(v)) return true end end)
131
132-- under construction:
133
134local function totable(n,flat,verbose,noattributes) -- nicest: n,true,true,true
135    local function to_table(n,flat,verbose,noattributes) -- no need to pass
136        local f  = getfields(n.id)
137        local tt = { }
138        for k=1,#f do
139            local v = f[k]
140            local nv = v and n[v]
141            if nv then
142                if ignore[v] then
143                    -- skip
144                elseif noattributes and v == "attr" then
145                    tt[v] = f_attr(tonut(nv))
146                    -- skip
147                elseif v == "prev" then
148                    tt[v] = "<node>"
149                elseif expand[v] then
150                    if type(nv) == "number" or type(nv) == "string" then
151                        tt[v] = nv
152                    else
153                        tt[v] = totable(nv,flat,verbose,noattributes)
154                    end
155                elseif type(nv) == "table" then
156                    tt[v] = nv -- totable(nv,flat,verbose) -- data
157                else
158                    tt[v] = nv
159                end
160            end
161        end
162        if verbose then
163            local subtype = tt.subtype
164            local id = tt.id
165            local nodename = nodecodes[id]
166            tt.id = nodename
167            local subtypes = subtcodes[nodename]
168            if subtypes then
169                tt.subtype = subtypes[subtype]
170            elseif subtype == 0 then
171                tt.subtype = nil
172            else
173                -- we need a table
174            end
175            if tt.char then
176                tt.char = f_char(tt.char)
177            end
178            if tt.small_char then
179                tt.small_char = f_char(tt.small_char)
180            end
181            if tt.large_char then
182                tt.large_char = f_char(tt.large_char)
183            end
184        end
185        return tt
186    end
187    if n then
188        if flat then
189            local t, tn = { }, 0
190            while n do
191                tn = tn + 1
192                local nt = to_table(n,flat,verbose,noattributes)
193                t[tn] = nt
194                nt.next = nil
195                nt.prev = nil
196                n = n.next
197            end
198            return t
199        else
200            local t = to_table(n,flat,verbose,noattributes)
201            local n = n.next
202            if n then
203                t.next = totable(n,flat,verbose,noattributes)
204            end
205            return t
206        end
207    else
208        return { }
209    end
210end
211
212nodes.totable = function(n,...) return totable(tonode(n),...) end
213nodes.totree  = function(n)     return totable(tonode(n),true,true,true) end -- no attributes, todo: attributes in k,v list
214
215local function key(k)
216    return ((type(k) == "number") and "["..k.."]") or k
217end
218
219function nodes.serialize(root,flat,verbose,noattributes,name)
220    return serialize(totable(tonode(root),flat,verbose,noattributes),name)
221end
222
223function nodes.serializebox(n,flat,verbose,noattributes,name)
224    return serialize(totable(tex.box[n],flat,verbose,noattributes),name)
225end
226
227function nodes.visualizebox(n,flat,verbose,noattributes,name)
228    context.tocontext(totable(tex.box[n],flat,verbose,noattributes),name)
229end
230
231function nodes.list(head,n) -- name might change to nodes.type -- to be checked .. will move to module anyway
232    head = tonode(head)
233    if not n then
234        context.starttyping(true)
235    end
236    while head do
237        local id = head.id
238        context(rep(" ",n or 0) .. tostring(head) .. "\n")
239        if id == hlist_code or id == vlist_code then
240            nodes.list(head.list,(n or 0)+1)
241        end
242        head = head.next
243    end
244    if not n then
245        context.stoptyping(true)
246    end
247end
248
249function nodes.print(head,n)
250    head = tonode(head)
251    while head do
252        local id = head.id
253        logs.writer(string.formatters["%w%S"],n or 0,head)
254        if id == hlist_code or id == vlist_code then
255            nodes.print(head.list,(n or 0)+1)
256        end
257        head = head.next
258    end
259end
260
261-- quick hack, nicer is to have a proper expand per node type
262-- already prepared
263
264local function apply(n,action)
265    while n do
266        action(n)
267        local id = n.id
268        if id == hlist_code or id == vlist_code then
269            apply(n.list,action)
270        end
271        n = n.next
272    end
273end
274
275nodes.apply = apply
276
277local nuts    = nodes.nuts
278local getid   = nuts.getid
279local getlist = nuts.getlist
280local getnext = nuts.getnext
281
282local function apply(n,action)
283    while n do
284        action(n)
285        local id = getid(n)
286        if id == hlist_code or id == vlist_code then
287            local list = getlist(n,action)
288            if list then
289                apply(list,action)
290            end
291        end
292        n = getnext(n)
293    end
294end
295
296nuts.apply = apply
297