data-tex.lua /size: 8193 b    last modification: 2021-10-28 13:50
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] = nil
179                if content == "" then
180                    return ""
181             -- elseif content == ctrl_d or ctrl_z then
182             --     return nil -- we need this as \endinput does not work in prints
183                elseif content then
184                    local runner = textlineactions.runner
185                    if runner then
186                        return runner(content,filename,currentline,noflines,coding) or content
187                    else
188                        return content
189                    end
190                else
191                    return nil
192                end
193            end
194        end
195    }
196    setmetatableindex(handler,function(t,k)
197        if k == "currentline" then
198            return currentline
199        else
200            -- no such key
201        end
202    end)
203    return handler
204end
205
206helpers.settextopener(textopener) -- can only be done once
207
208function resolvers.findtexfile(filename,filetype)
209    return methodhandler('finders',filename,filetype)
210end
211
212function resolvers.opentexfile(filename)
213    return methodhandler('openers',filename)
214end
215
216function resolvers.openfile(filename)
217    local fullname = methodhandler('finders',filename)
218    return fullname and fullname ~= "" and methodhandler('openers',fullname) or nil
219end
220
221function resolvers.loadtexfile(filename,filetype)
222    -- todo: optionally apply filters
223    local ok, data, size = loadbinfile(filename, filetype)
224    return data or ""
225end
226
227resolvers.texdatablob = resolvers.loadtexfile
228
229local function installhandler(namespace,what,where,func)
230    if not func then
231        where, func = "after", where
232    end
233    if where == "before" or where == "after" then
234        appendaction(namespace,where,func)
235    else
236        report_tex("installing input %a handlers in %a is not possible",what,tostring(where))
237    end
238end
239
240function resolvers.installinputlinehandler(...) installhandler(textlineactions,"line",...) end
241function resolvers.installinputfilehandler(...) installhandler(textfileactions,"file",...) end
242
243-- local basename = file.basename
244-- resolvers.installinputlinehandler(function(str,filename,linenumber,noflines)
245--     report_tex("[lc] file %a, line %a of %a, length %a",basename(filename),linenumber,noflines,#str)
246-- end)
247-- resolvers.installinputfilehandler(function(str,filename)
248--     report_tex("[fc] file %a, length %a",basename(filename),#str)
249-- end)
250