summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/tex/context/base/l-xml-edu.lua
blob: 5aba460434929360aa513c92a2da2dd785ffdc73 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
if not modules then modules = { } end modules ['l-xml-edu'] = {
    version   = 1.001,
    comment   = "this module is the basis for the lxml-* ones",
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
    copyright = "PRAGMA ADE / ConTeXt Development Team",
    license   = "see context related readme files"
}

--[[ldx--
<p>This module contains older code thatwe keep around for educational
purposes. Here you find the find based xml and lpath parsers.</p>
--ldx]]--

if false then

    do

        -- Loading 12 cont-*.xml and keys-*.xml files totaling to 2.62 MBytes takes 1.1 sec
        -- on a windows vista laptop with dual core 7600 (2.3 Ghz), which is not that bad.
        -- Of this half time is spent on doctype etc parsing.

        local doctype_patterns = {
            "<!DOCTYPE%s+(.-%s+PUBLIC%s+%b\"\"%s+%b\"\"%s+%b[])%s*>",
            "<!DOCTYPE%s+(.-%s+PUBLIC%s+%b\"\"%s+%b\"\")%s*>",
            "<!DOCTYPE%s+(.-%s+SYSTEM%s+%b\"\"%s+%b[])%s*>",
            "<!DOCTYPE%s+(.-%s+SYSTEM%s+%b\"\")%s*>",
            "<!DOCTYPE%s+(.-%s%b[])%s*>",
            "<!DOCTYPE%s+(.-)%s*>"
        }

        -- We assume no "<" which is the lunatic part of the xml spec
        -- especially since ">" is permitted; otherwise we need a char
        -- by char parser ... more something for later ... normally
        -- entities will be used anyway.

        -- data = data:gsub(nothing done) is still a copy so we find first

        local function prepare(data,text)
            -- pack (for backward compatibility)
            if type(data) == "table" then
                data = table.concat(data,"")
            end
            -- CDATA
            if data:find("<!%[CDATA%[") then
                data = data:gsub("<!%[CDATA%[(.-)%]%]>", function(txt)
                    text[#text+1] = txt or ""
                    return string.format("<@cd@>%s</@cd@>",#text)
                end)
            end
            -- DOCTYPE
            if data:find("<!DOCTYPE ") then
                data = data:gsub("^(.-)(<[^!?])", function(a,b)
                    if a:find("<!DOCTYPE ") then -- ?
                        for _,v in ipairs(doctype_patterns) do
                            a = a:gsub(v, function(d)
                                text[#text+1] = d or ""
                                return string.format("<@dt@>%s</@dt@>",#text)
                            end)
                        end
                    end
                    return a .. b
                end,1)
            end
            -- comment / does not catch doctype
            if data:find("<!%-%-") then
                data = data:gsub("<!%-%-(.-)%-%->", function(txt)
                    text[#text+1] = txt or ""
                    return string.format("<@cm@>%s</@cm@>",#text)
                end)
            end
            -- processing instructions / altijd 1
            if data:find("<%?") then
                data = data:gsub("<%?(.-)%?>", function(txt)
                    text[#text+1] = txt or ""
                    return string.format("<@pi@>%s</@pi@>",#text)
                end)
            end
            return data, text
        end

        -- maybe we will move the @tg@ stuff to a dedicated key, say 'st'; this will speed up
        -- serializing and testing

        function xml.convert(data,no_root,collapse)
            local crap = { }
            data, crap = prepare(data, crap)
            local nsremap = xml.xmlns
            local remove = table.remove
            local stack, top = {}, {}
            local i, j, errorstr = 1, 1, nil
            stack[#stack+1] = top
            top.dt = { }
            local dt = top.dt
            local mt = { __tostring = xml.text }
            local xmlns = { }
            while true do
                local ni, first, attributes, last, fulltag, resolved
                ni, j, first, fulltag, attributes, last = data:find("<(/-)([^%s>/]+)%s*([^>]-)%s*(/-)>", j)
                if not ni then break end
                local namespace, tag = fulltag:match("^(.-):(.+)$")
                if attributes ~= "" then
                    local t = {}
                    for ns, tag, _, value in attributes:gmatch("(.-):?(.+)=([\"\'])(.-)%3") do -- . was %w
                        if tag == "xmlns" then -- not ok yet
                            xmlns[#xmlns+1] = xml.resolvens(value)
                            t[tag] = value
                        elseif ns == "xmlns" then
                            xml.checkns(tag,value)
                            t["xmlns:" .. tag] = value
                        else
                            t[tag] = value
                        end
                    end
                    attributes = t
                else
                    attributes = { }
                end
                if namespace then -- realtime remapping
                    resolved = nsremap[namespace] or namespace
                else
                    namespace, tag = "", fulltag
                    resolved = xmlns[#xmlns]
                end
                local text = data:sub(i, ni-1)
                if text == "" or (collapse and text:find("^%s*$")) then
                    -- no need for empty text nodes, beware, also packs <a>x y z</a>
                    -- so is not that useful unless used with empty elements
                else
                    dt[#dt+1] = text
                end
                if first == "/" then
                    -- end tag
                    local toclose = remove(stack)  -- remove top
                    top = stack[#stack]
                    if #stack < 1 then
                        errorstr = string.format("nothing to close with %s", tag)
                        break
                    elseif toclose.tg ~= tag then -- no namespace check
                        errorstr = string.format("unable to close %s with %s", toclose.tg, tag)
                        break
                    end
                    if tag:find("^@..@$") then
                        dt[1] = crap[tonumber(dt[1])] or ""
                    end
                    dt = top.dt
                    dt[#dt+1] = toclose
                    if attributes.xmlns then
                        remove(xmlns) -- ?
                    end
                elseif last == "/" then
                    -- empty element tag
                    dt[#dt+1] = { ns = namespace, rn = resolved, tg = tag, dt = { }, at = attributes, __p__ = top }
                    setmetatable(top, mt)
                else
                    -- begin tag
                    top = { ns = namespace, rn = resolved, tg = tag, dt = { }, at = attributes, __p__ = stack[#stack] }
                    setmetatable(top, mt)
                    dt = top.dt
                    stack[#stack+1] = top
                end
                i = j + 1
            end
            if not errorstr then
                local text = data:sub(i)
                if dt and not text:find("^%s*$") then
                    dt[#dt+1] = text
                end
                if #stack > 1 then
                    errorstr = string.format("unclosed %s", stack[#stack].tg)
                end
            end
            if errorstr then
                stack = { { tg = "error", dt = { errorstr } } }
                setmetatable(stack, mt)
            end
            if no_root then
                return stack[1]
            else
                local t = { ns = "", tg = '@rt@', dt = stack[1].dt }
                setmetatable(t, mt)
                for k,v in ipairs(t.dt) do
                    if type(v) == "table" and v.tg ~= "@pi@" and v.tg ~= "@dt@" and v.tg ~= "@cm@" then
                        t.ri = k -- rootindex
                        break
                    end
                end
                return t
            end
        end

    end

    --~ Y a/b
    --~ Y /a/b
    --~ Y a/*/b
    --~ Y a//b
    --~ Y child::
    --~ Y .//
    --~ Y ..
    --~ N id("tag")
    --~ Y parent::
    --~ Y child::
    --~ N preceding-sibling:: (same name)
    --~ N following-sibling:: (same name)
    --~ N preceding-sibling-of-self:: (same name)
    --~ N following-sibling-or-self:: (same name)
    --~ N ancestor::
    --~ N descendent::
    --~ N preceding::
    --~ N following::
    --~ N self::node()
    --~ N node() == alles
    --~ N a[position()=5]
    --~ Y a[5]
    --~ Y a[-5]
    --~ N a[first()]
    --~ N a[last()]
    --~ Y a/(b|c|d)/e/f
    --~ Y a/!(b|c|d)/e/f
    --~ N (c/d|e)
    --~ Y a/b[@bla]
    --~ Y a/b[@bla='oeps']
    --~ Y a/b[@bla=='oeps']
    --~ Y a/b[@bla<>'oeps']
    --~ Y a/b[@bla!='oeps']
    --~ Y a/b/@bla
    --~ Y a['string']
    --~ Y ^/a/c (root)
    --~ Y ^^/a/c (docroot)
    --~ Y root::a/c (docroot)
    --~ no wild card functions (yet)
    --~ s = "/a//b/*/(c|d|e)/(f|g)/h[4]/h/child::i/j/(a/b)/p[-1]/q[4]/ancestor::q/r/../s/./t[@bla='true']/k"
    -- // == /**/
    -- / = ^ (root)

    do

        function analyze(str)
            if not str then
                return ""
            else
                local tmp, result, map, key = { }, { }, { }, str
                str = str:gsub("(%b[])", function(s) tmp[#tmp+1] = s return '[['..#tmp..']]' end)
                str = str:gsub("(%b())", function(s) tmp[#tmp+1] = s return '[['..#tmp..']]' end)
                str = str:gsub("(%^+)([^/])", "%1/%2")
                str = str:gsub("//+", "/**/")
                str = str:gsub(".*root::", "^/")
                str = str:gsub("child::", "")
                str = str:gsub("parent::", "../")
                str = str:gsub("self::", "./")
                str = str:gsub("^/", "^/")
                for s in str:gmatch("([^/]+)") do
                    s = s:gsub("%[%[(%d+)%]%]",function(n) return tmp[tonumber(n)] end)
                    result[#result+1] = s
                end
                return result
            end
        end

        function compose(result)
            if not result or #result == 0 then
                -- wildcard
                return true
            elseif #result == 1 then
                local r = result[1][1]
                if r == "14" or r == "15" then
                    -- wildcard
                    return true
                elseif r == "12" then
                    -- root
                    return false
                end
            end
            local map = { }
            for r=1,#result do
                local ri = result[r]
                if ri == "." then
                    --  skip
                elseif ri == ".." then
                    map[#map+1] = { 11 }
                elseif ri == "^" then
                    map[#map+1] = { 12 }
                elseif ri == "^^" then
                    map[#map+1] = { 13 }
                elseif ri == "*" then
                    map[#map+1] = { 14 }
                elseif ri == "**" then
                    map[#map+1] = { 15 }
                else
                    local negate, m = ri:match("^(!*)%((.*)%)$") -- (a|b|c)
                    if m or ri:find('|') then
                        m = m or ri
                        if m:find("[%[%]%(%)/]") then -- []()/
                            -- error
                        else
                            local t = { (negate and #negate>0 and 25) or 21 }
                            for s in m:gmatch("([^|]+)") do
                                local ns, tg = s:match("^(.-):?([^:]+)$")
                                if ns == "*" then ns = true end
                                t[#t+1] = ns
                                t[#t+1] = tg
                            end
                            map[#map+1] = t
                        end
                    else
                        local s, f = ri:match("^(.-)%[%s*(.+)%s*%]$") --aaa[bbb]
                        if s and f then
                            local ns, tg = s:match("^(.-):?([^:]+)$")
                            local at, op, vl = f:match("^@(.-)([!=<>]?)([^!=<>]+)$") -- [@a=='b']
                            if ns == "*" then ns = true end
                            if vl then
                                if op and op ~= "" then
                                    if op == '=' or op == '==' then
                                        map[#map+1] = { 22, ns, tg, at, (vl:gsub("^([\'\"])(.*)%1$","%2")) }
                                    elseif op == '<>' or op == '!=' then
                                        map[#map+1] = { 23, ns, tg, at, (vl:gsub("^([\'\"])(.*)%1$","%2")) }
                                    else
                                        -- error
                                    end
                                elseif vl ~= "" then -- [@whatever]
                                    map[#map+1] = { 26, ns, tg, vl }
                                else
                                    -- error
                                end
                        --  elseif f:find("^([%-%+%d]+)$") then -- [123]
                            elseif f:find("^([-+%d]+)$") then -- [123]
                                map[#map+1] = { 30, ns, tg, tonumber(f) }
                            else -- [whatever]
                                map[#map+1] = { 27, ns, tg, (f:gsub("^([\'\"])(.*)%1$","%2")) }
                            end
                        else
                            local pi = ri:match("^pi::(.-)$")
                            if pi then
                                map[#map+1] = { 40, pi }
                            else
                                local negate, ns, tg = ri:match("^(!-)(.-):?([^:]+)$")
                                map[#map+1] = { (negate and #negate>0 and 24) or 20, ns, tg }
                            end
                        end
                    end
                end
            end
            -- if we have a symbol, we can prepend that to the string, which is faster
            local mm = map[1] or { }
            local r = mm[1] or 0
            if #map == 1 then
                if r == 14 or r == 15 then
                    -- wildcard
                    return true
                elseif r == 12 then
                    -- root
                    return false
                end
            end
            if r ~= 11 and r ~= 12 and r ~= 13 and r ~= 14 and r ~= 15 then
                table.insert(map, 1, { 16 })
            end
            return map
        end

        cache = { }

        function xml.lpath(pattern)
            if type(pattern) == "string" then
                local result = cache[pattern]
                if not result then
                    result = compose(analyze(pattern))
                    cache[pattern] = result
                end
                if xml.trace_lpath then
                    xml.lshow(result)
                end
                return result
            else
                return pattern
            end
        end

    end

end