data-tex.lua /size: 8243 b    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['data-tex'] = {
2    version   = 1.001,
3    comment   = "companion to luat-lib.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
9local tostring, tonumber, type = tostring, tonumber, type
10local char, find = string.char, string.find
11
12local trace_locating = false trackers.register("resolvers.locating", function(v) trace_locating = v end)
13
14local report_tex = logs.reporter("resolvers","tex")
15
16
17local sequencers        = utilities.sequencers
18local utffiletype       = utf.filetype
19local setmetatableindex = table.setmetatableindex
20local loaddata          = io.loaddata
21----- readall           = io.readall
22
23local resolvers         = resolvers
24local methodhandler     = resolvers.methodhandler
25local loadbinfile       = resolvers.loadbinfile
26local pushinputname     = resolvers.pushinputname
27local popinputname      = resolvers.popinputname
28
29-- local fileprocessor = nil
30-- local lineprocessor = nil
31
32local textfileactions = sequencers.new {
33    arguments    = "str,filename,coding",
34    returnvalues = "str",
35    results      = "str",
36}
37
38local textlineactions = sequencers.new {
39    arguments    = "str,filename,linenumber,noflines,coding",
40    returnvalues = "str",
41    results      = "str",
42}
43
44local helpers      = resolvers.openers.helpers
45local appendgroup  = sequencers.appendgroup
46local appendaction = sequencers.appendaction
47
48helpers.textfileactions = textfileactions
49helpers.textlineactions = textlineactions
50
51appendgroup(textfileactions,"before") -- user
52appendgroup(textfileactions,"system") -- private
53appendgroup(textfileactions,"after" ) -- user
54
55appendgroup(textlineactions,"before") -- user
56appendgroup(textlineactions,"system") -- private
57appendgroup(textlineactions,"after" ) -- user
58
59local ctrl_d = char( 4) -- unix
60local ctrl_z = char(26) -- windows
61
62----------------------------------------
63
64local lpegmatch  = lpeg.match
65local newline    = lpeg.patterns.newline
66local tsplitat   = lpeg.tsplitat
67
68local linesplitters = {
69    tsplitat(newline),                       -- default since we started
70    tsplitat(lpeg.S(" ")^0 * newline),
71    tsplitat(lpeg.S(" \t")^0 * newline),
72    tsplitat(lpeg.S(" \f\t")^0 * newline),   -- saves a bit of space at the cost of runtime
73 -- tsplitat(lpeg.S(" \v\f\t")^0 * newline),
74 -- tsplitat(lpeg.R("\0\31")^0 * newline),
75}
76
77local linesplitter = linesplitters[1]
78
79directives.register("system.linesplitmethod",function(v)
80    linesplitter = linesplitters[tonumber(v) or 1] or linesplitters[1]
81end)
82
83local function splitlines(str)
84    return lpegmatch(linesplitter,str)
85end
86
87-- not really a bottleneck, but it might become:
88--
89-- local splitlines = string.splitlines or function(str)
90--     return lpegmatch(linesplitter,str)
91-- end
92--
93-- directives.register("system.linesplitmethod",function(v)
94--     linesplitter = linesplitters[tonumber(v) or 1] or linesplitters[1]
95--     splitlines = function(str)
96--         return lpegmatch(linesplitter,str)
97--     end
98-- end)
99
100-----------------------------------------
101
102local wideutfcoding = {
103    ["utf-16-be"] = utf.utf16_to_utf8_be_t,
104    ["utf-16-le"] = utf.utf16_to_utf8_le_t,
105    ["utf-32-be"] = utf.utf32_to_utf8_be_t,
106    ["utf-32-le"] = utf.utf32_to_utf8_le_t,
107}
108
109local function textopener(tag,filename,filehandle,coding)
110    local lines
111    local t_filehandle = type(filehandle)
112    if not filehandle then
113        lines = loaddata(filename)
114    elseif t_filehandle == "string" then
115        lines = filehandle
116    elseif t_filehandle == "table" then
117        lines = filehandle
118    else
119        lines = filehandle:read("*a") -- readall(filehandle) ... but never that large files anyway
120     -- lines = readall(filehandle)
121        filehandle:close()
122    end
123    if type(lines) == "string" then
124        local coding = coding or utffiletype(lines) -- so we can signal no regime
125        if trace_locating then
126            report_tex("%a opener: %a opened using method %a",tag,filename,coding)
127        end
128        local wideutf = wideutfcoding[coding]
129        if wideutf then
130            lines = wideutf(lines)
131        else -- utf8 or unknown (could be a mkvi file)
132            local runner = textfileactions.runner
133            if runner then
134                lines = runner(lines,filename,coding) or lines
135            end
136            lines = splitlines(lines)
137        end
138    elseif trace_locating then
139        report_tex("%a opener: %a opened",tag,filename)
140    end
141    local noflines = #lines
142    if lines[noflines] == "" then -- maybe some special check is needed
143        lines[noflines] = nil
144    end
145    pushinputname(filename)
146    local currentline = 0
147    local noflines    = noflines
148    local handler = {
149        filename    = filename,
150        noflines    = noflines,
151     -- currentline = 0,
152        gotoline    = function(self,n)
153            currentline = n - 1
154            if currentline <= 0 then
155                currentline = 0
156            end
157        end,
158        endoffile   = function()
159            return not lines or currentline >= noflines
160        end,
161        close       = function()
162            local usedname = popinputname() -- should match filename
163            if trace_locating then
164                report_tex("%a closer: %a closed",tag,filename)
165            end
166            handler = nil
167            lines   = nil
168        end,
169        reader      = function(self)
170            self = self or handler
171         -- local currentline, noflines = self.currentline, self.noflines
172            if currentline >= noflines then
173                return nil
174            else
175                currentline = currentline + 1
176             -- self.currentline = currentline
177                local content = lines[currentline]
178             -- lines[currentline] = false
179                if content == "" then
180                 -- return ""
181                    return content
182             -- elseif content == ctrl_d or ctrl_z then
183             --     return nil -- we need this as \endinput does not work in prints
184                elseif content then
185                    local runner = textlineactions.runner
186                    if runner then
187                        return runner(content,filename,currentline,noflines,coding) or content
188                    else
189                        return content
190                    end
191                else
192                    return nil
193                end
194            end
195        end
196    }
197    setmetatableindex(handler,function(t,k)
198        if k == "currentline" then
199            return currentline
200        else
201            -- no such key
202        end
203    end)
204    return handler
205end
206
207helpers.settextopener(textopener) -- can only be done once
208
209function resolvers.findtexfile(filename,filetype)
210    return methodhandler('finders',filename,filetype)
211end
212
213function resolvers.opentexfile(filename)
214    return methodhandler('openers',filename)
215end
216
217function resolvers.openfile(filename)
218    local fullname = methodhandler('finders',filename)
219    return fullname and fullname ~= "" and methodhandler('openers',fullname) or nil
220end
221
222function resolvers.loadtexfile(filename,filetype)
223    -- todo: optionally apply filters
224    local ok, data, size = loadbinfile(filename, filetype)
225    return data or ""
226end
227
228resolvers.texdatablob = resolvers.loadtexfile
229
230local function installhandler(namespace,what,where,func)
231    if not func then
232        where, func = "after", where
233    end
234    if where == "before" or where == "after" then
235        appendaction(namespace,where,func)
236    else
237        report_tex("installing input %a handlers in %a is not possible",what,tostring(where))
238    end
239end
240
241function resolvers.installinputlinehandler(...) installhandler(textlineactions,"line",...) end
242function resolvers.installinputfilehandler(...) installhandler(textfileactions,"file",...) end
243
244-- local basename = file.basename
245-- resolvers.installinputlinehandler(function(str,filename,linenumber,noflines)
246--     report_tex("[lc] file %a, line %a of %a, length %a",basename(filename),linenumber,noflines,#str)
247-- end)
248-- resolvers.installinputfilehandler(function(str,filename)
249--     report_tex("[fc] file %a, length %a",basename(filename),#str)
250-- end)
251