diff options
26 files changed, 1547 insertions, 732 deletions
diff --git a/scripts/context/lua/mtxrun.lua b/scripts/context/lua/mtxrun.lua index 0d8f3ac50..93901f665 100644 --- a/scripts/context/lua/mtxrun.lua +++ b/scripts/context/lua/mtxrun.lua @@ -4133,30 +4133,49 @@ function boolean.tonumber(b) end function toboolean(str,tolerant) - if str == true or str == false then - return str - elseif tolerant then - local tstr = type(str) - if tstr == "string" then - return str == "true" or str == "yes" or str == "on" or str == "1" or str == "t" - elseif tstr == "number" then - return tonumber(str) ~= 0 - elseif tstr == "nil" then - return false - else - return str - end + if str == nil then + return false + elseif str == false then + return false + elseif str == true then + return true elseif str == "true" then return true elseif str == "false" then return false + elseif not tolerant then + return false + elseif str == 0 then + return false + elseif (tonumber(str) or 0) > 0 then + return true else - return str + return str == "yes" or str == "on" or str == "t" end end string.toboolean = toboolean +function string.booleanstring(str) + if str == nil then + return false + elseif str == false then + return false + elseif str == true then + return true + elseif str == "true" then + return true + elseif str == "false" then + return false + elseif str == 0 then + return false + elseif (tonumber(str) or 0) > 0 then + return true + else + return str == "yes" or str == "on" or str == "t" + end +end + function string.is_boolean(str,default) if type(str) == "string" then if str == "true" or str == "yes" or str == "on" or str == "t" then @@ -4217,34 +4236,38 @@ if not utf.char then function utf.char(n) if n < 0x80 then + -- 0aaaaaaa : 0x80 return char(n) elseif n < 0x800 then + -- 110bbbaa : 0xC0 : n >> 6 + -- 10aaaaaa : 0x80 : n & 0x3F return char( 0xC0 + floor(n/0x40), 0x80 + (n % 0x40) ) elseif n < 0x10000 then + -- 1110bbbb : 0xE0 : n >> 12 + -- 10bbbbaa : 0x80 : (n >> 6) & 0x3F + -- 10aaaaaa : 0x80 : n & 0x3F return char( 0xE0 + floor(n/0x1000), 0x80 + (floor(n/0x40) % 0x40), 0x80 + (n % 0x40) ) - elseif n < 0x40000 then + elseif n < 0x200000 then + -- 11110ccc : 0xF0 : n >> 18 + -- 10ccbbbb : 0x80 : (n >> 12) & 0x3F + -- 10bbbbaa : 0x80 : (n >> 6) & 0x3F + -- 10aaaaaa : 0x80 : n & 0x3F + -- dddd : ccccc - 1 return char( - 0xF0 + floor(n/0x40000), - 0x80 + floor(n/0x1000), + 0xF0 + floor(n/0x40000), + 0x80 + (floor(n/0x1000) % 0x40), 0x80 + (floor(n/0x40) % 0x40), 0x80 + (n % 0x40) ) else - -- return char( - -- 0xF1 + floor(n/0x1000000), - -- 0x80 + floor(n/0x40000), - -- 0x80 + floor(n/0x1000), - -- 0x80 + (floor(n/0x40) % 0x40), - -- 0x80 + (n % 0x40) - -- ) - return "?" + return "" end end @@ -10214,6 +10237,32 @@ function xml.inspect(collection,pattern) end end +-- texy (see xfdf): + +local function split(e) + local dt = e.dt + if dt then + for i=1,#dt do + local dti = dt[i] + if type(dti) == "string" then + dti = gsub(dti,"^[\n\r]*(.-)[\n\r]*","%1") + dti = gsub(dti,"[\n\r]+","\n\n") + dt[i] = dti + else + split(dti) + end + end + end + return e +end + +function xml.finalizers.paragraphs(c) + for i=1,#c do + split(c[i]) + end + return c +end + end -- of closure diff --git a/scripts/context/stubs/mswin/mtxrun.lua b/scripts/context/stubs/mswin/mtxrun.lua index 0d8f3ac50..93901f665 100644 --- a/scripts/context/stubs/mswin/mtxrun.lua +++ b/scripts/context/stubs/mswin/mtxrun.lua @@ -4133,30 +4133,49 @@ function boolean.tonumber(b) end function toboolean(str,tolerant) - if str == true or str == false then - return str - elseif tolerant then - local tstr = type(str) - if tstr == "string" then - return str == "true" or str == "yes" or str == "on" or str == "1" or str == "t" - elseif tstr == "number" then - return tonumber(str) ~= 0 - elseif tstr == "nil" then - return false - else - return str - end + if str == nil then + return false + elseif str == false then + return false + elseif str == true then + return true elseif str == "true" then return true elseif str == "false" then return false + elseif not tolerant then + return false + elseif str == 0 then + return false + elseif (tonumber(str) or 0) > 0 then + return true else - return str + return str == "yes" or str == "on" or str == "t" end end string.toboolean = toboolean +function string.booleanstring(str) + if str == nil then + return false + elseif str == false then + return false + elseif str == true then + return true + elseif str == "true" then + return true + elseif str == "false" then + return false + elseif str == 0 then + return false + elseif (tonumber(str) or 0) > 0 then + return true + else + return str == "yes" or str == "on" or str == "t" + end +end + function string.is_boolean(str,default) if type(str) == "string" then if str == "true" or str == "yes" or str == "on" or str == "t" then @@ -4217,34 +4236,38 @@ if not utf.char then function utf.char(n) if n < 0x80 then + -- 0aaaaaaa : 0x80 return char(n) elseif n < 0x800 then + -- 110bbbaa : 0xC0 : n >> 6 + -- 10aaaaaa : 0x80 : n & 0x3F return char( 0xC0 + floor(n/0x40), 0x80 + (n % 0x40) ) elseif n < 0x10000 then + -- 1110bbbb : 0xE0 : n >> 12 + -- 10bbbbaa : 0x80 : (n >> 6) & 0x3F + -- 10aaaaaa : 0x80 : n & 0x3F return char( 0xE0 + floor(n/0x1000), 0x80 + (floor(n/0x40) % 0x40), 0x80 + (n % 0x40) ) - elseif n < 0x40000 then + elseif n < 0x200000 then + -- 11110ccc : 0xF0 : n >> 18 + -- 10ccbbbb : 0x80 : (n >> 12) & 0x3F + -- 10bbbbaa : 0x80 : (n >> 6) & 0x3F + -- 10aaaaaa : 0x80 : n & 0x3F + -- dddd : ccccc - 1 return char( - 0xF0 + floor(n/0x40000), - 0x80 + floor(n/0x1000), + 0xF0 + floor(n/0x40000), + 0x80 + (floor(n/0x1000) % 0x40), 0x80 + (floor(n/0x40) % 0x40), 0x80 + (n % 0x40) ) else - -- return char( - -- 0xF1 + floor(n/0x1000000), - -- 0x80 + floor(n/0x40000), - -- 0x80 + floor(n/0x1000), - -- 0x80 + (floor(n/0x40) % 0x40), - -- 0x80 + (n % 0x40) - -- ) - return "?" + return "" end end @@ -10214,6 +10237,32 @@ function xml.inspect(collection,pattern) end end +-- texy (see xfdf): + +local function split(e) + local dt = e.dt + if dt then + for i=1,#dt do + local dti = dt[i] + if type(dti) == "string" then + dti = gsub(dti,"^[\n\r]*(.-)[\n\r]*","%1") + dti = gsub(dti,"[\n\r]+","\n\n") + dt[i] = dti + else + split(dti) + end + end + end + return e +end + +function xml.finalizers.paragraphs(c) + for i=1,#c do + split(c[i]) + end + return c +end + end -- of closure diff --git a/scripts/context/stubs/unix/mtxrun b/scripts/context/stubs/unix/mtxrun index 0d8f3ac50..93901f665 100755 --- a/scripts/context/stubs/unix/mtxrun +++ b/scripts/context/stubs/unix/mtxrun @@ -4133,30 +4133,49 @@ function boolean.tonumber(b) end function toboolean(str,tolerant) - if str == true or str == false then - return str - elseif tolerant then - local tstr = type(str) - if tstr == "string" then - return str == "true" or str == "yes" or str == "on" or str == "1" or str == "t" - elseif tstr == "number" then - return tonumber(str) ~= 0 - elseif tstr == "nil" then - return false - else - return str - end + if str == nil then + return false + elseif str == false then + return false + elseif str == true then + return true elseif str == "true" then return true elseif str == "false" then return false + elseif not tolerant then + return false + elseif str == 0 then + return false + elseif (tonumber(str) or 0) > 0 then + return true else - return str + return str == "yes" or str == "on" or str == "t" end end string.toboolean = toboolean +function string.booleanstring(str) + if str == nil then + return false + elseif str == false then + return false + elseif str == true then + return true + elseif str == "true" then + return true + elseif str == "false" then + return false + elseif str == 0 then + return false + elseif (tonumber(str) or 0) > 0 then + return true + else + return str == "yes" or str == "on" or str == "t" + end +end + function string.is_boolean(str,default) if type(str) == "string" then if str == "true" or str == "yes" or str == "on" or str == "t" then @@ -4217,34 +4236,38 @@ if not utf.char then function utf.char(n) if n < 0x80 then + -- 0aaaaaaa : 0x80 return char(n) elseif n < 0x800 then + -- 110bbbaa : 0xC0 : n >> 6 + -- 10aaaaaa : 0x80 : n & 0x3F return char( 0xC0 + floor(n/0x40), 0x80 + (n % 0x40) ) elseif n < 0x10000 then + -- 1110bbbb : 0xE0 : n >> 12 + -- 10bbbbaa : 0x80 : (n >> 6) & 0x3F + -- 10aaaaaa : 0x80 : n & 0x3F return char( 0xE0 + floor(n/0x1000), 0x80 + (floor(n/0x40) % 0x40), 0x80 + (n % 0x40) ) - elseif n < 0x40000 then + elseif n < 0x200000 then + -- 11110ccc : 0xF0 : n >> 18 + -- 10ccbbbb : 0x80 : (n >> 12) & 0x3F + -- 10bbbbaa : 0x80 : (n >> 6) & 0x3F + -- 10aaaaaa : 0x80 : n & 0x3F + -- dddd : ccccc - 1 return char( - 0xF0 + floor(n/0x40000), - 0x80 + floor(n/0x1000), + 0xF0 + floor(n/0x40000), + 0x80 + (floor(n/0x1000) % 0x40), 0x80 + (floor(n/0x40) % 0x40), 0x80 + (n % 0x40) ) else - -- return char( - -- 0xF1 + floor(n/0x1000000), - -- 0x80 + floor(n/0x40000), - -- 0x80 + floor(n/0x1000), - -- 0x80 + (floor(n/0x40) % 0x40), - -- 0x80 + (n % 0x40) - -- ) - return "?" + return "" end end @@ -10214,6 +10237,32 @@ function xml.inspect(collection,pattern) end end +-- texy (see xfdf): + +local function split(e) + local dt = e.dt + if dt then + for i=1,#dt do + local dti = dt[i] + if type(dti) == "string" then + dti = gsub(dti,"^[\n\r]*(.-)[\n\r]*","%1") + dti = gsub(dti,"[\n\r]+","\n\n") + dt[i] = dti + else + split(dti) + end + end + end + return e +end + +function xml.finalizers.paragraphs(c) + for i=1,#c do + split(c[i]) + end + return c +end + end -- of closure diff --git a/tex/context/base/char-utf.lua b/tex/context/base/char-utf.lua index bd7463522..52fdfc0d0 100644 --- a/tex/context/base/char-utf.lua +++ b/tex/context/base/char-utf.lua @@ -486,6 +486,14 @@ if sequencers then sequencers.enableaction(textfileactions,"characters.filters.utf.decompose") end + directives.register("filters.utf.collapse", function(v) + sequencers[v and "enableaction" or "disableaction"](textfileactions,"characters.filters.utf.collapse") + end) + + directives.register("filters.utf.decompose", function(v) + sequencers[v and "enableaction" or "disableaction"](textfileactions,"characters.filters.utf.decompose") + end) + end --[[ldx-- diff --git a/tex/context/base/cont-new.mkii b/tex/context/base/cont-new.mkii index d6ff107a3..6ceeafd46 100644 --- a/tex/context/base/cont-new.mkii +++ b/tex/context/base/cont-new.mkii @@ -11,7 +11,7 @@ %C therefore copyrighted by \PRAGMA. See mreadme.pdf for %C details. -\newcontextversion{2012.09.25 21:44} +\newcontextversion{2012.10.02 15:13} %D This file is loaded at runtime, thereby providing an %D excellent place for hacks, patches, extensions and new diff --git a/tex/context/base/cont-new.mkiv b/tex/context/base/cont-new.mkiv index e8be14c7f..7604ff84e 100644 --- a/tex/context/base/cont-new.mkiv +++ b/tex/context/base/cont-new.mkiv @@ -11,7 +11,7 @@ %C therefore copyrighted by \PRAGMA. See mreadme.pdf for %C details. -\newcontextversion{2012.09.25 21:44} +\newcontextversion{2012.10.02 15:13} %D This file is loaded at runtime, thereby providing an excellent place for %D hacks, patches, extensions and new features. diff --git a/tex/context/base/context-version.pdf b/tex/context/base/context-version.pdf Binary files differindex 62b3117f7..07042d62c 100644 --- a/tex/context/base/context-version.pdf +++ b/tex/context/base/context-version.pdf diff --git a/tex/context/base/context-version.png b/tex/context/base/context-version.png Binary files differindex 91f5d5658..33ac9f216 100644 --- a/tex/context/base/context-version.png +++ b/tex/context/base/context-version.png diff --git a/tex/context/base/context.mkii b/tex/context/base/context.mkii index db6a3d13d..0a1f16567 100644 --- a/tex/context/base/context.mkii +++ b/tex/context/base/context.mkii @@ -20,7 +20,7 @@ %D your styles an modules. \edef\contextformat {\jobname} -\edef\contextversion{2012.09.25 21:44} +\edef\contextversion{2012.10.02 15:13} %D For those who want to use this: diff --git a/tex/context/base/context.mkiv b/tex/context/base/context.mkiv index c4d1a313b..f18a92afa 100644 --- a/tex/context/base/context.mkiv +++ b/tex/context/base/context.mkiv @@ -25,7 +25,7 @@ %D up and the dependencies are more consistent. \edef\contextformat {\jobname} -\edef\contextversion{2012.09.25 21:44} +\edef\contextversion{2012.10.02 15:13} %D For those who want to use this: diff --git a/tex/context/base/l-boolean.lua b/tex/context/base/l-boolean.lua index 4294cf8f0..2b94de76b 100644 --- a/tex/context/base/l-boolean.lua +++ b/tex/context/base/l-boolean.lua @@ -16,30 +16,49 @@ function boolean.tonumber(b) end function toboolean(str,tolerant) - if str == true or str == false then - return str - elseif tolerant then - local tstr = type(str) - if tstr == "string" then - return str == "true" or str == "yes" or str == "on" or str == "1" or str == "t" - elseif tstr == "number" then - return tonumber(str) ~= 0 - elseif tstr == "nil" then - return false - else - return str - end + if str == nil then + return false + elseif str == false then + return false + elseif str == true then + return true elseif str == "true" then return true elseif str == "false" then return false + elseif not tolerant then + return false + elseif str == 0 then + return false + elseif (tonumber(str) or 0) > 0 then + return true else - return str + return str == "yes" or str == "on" or str == "t" end end string.toboolean = toboolean +function string.booleanstring(str) + if str == nil then + return false + elseif str == false then + return false + elseif str == true then + return true + elseif str == "true" then + return true + elseif str == "false" then + return false + elseif str == 0 then + return false + elseif (tonumber(str) or 0) > 0 then + return true + else + return str == "yes" or str == "on" or str == "t" + end +end + function string.is_boolean(str,default) if type(str) == "string" then if str == "true" or str == "yes" or str == "on" or str == "t" then diff --git a/tex/context/base/l-unicode.lua b/tex/context/base/l-unicode.lua index cbcec8329..630c34960 100644 --- a/tex/context/base/l-unicode.lua +++ b/tex/context/base/l-unicode.lua @@ -42,34 +42,38 @@ if not utf.char then function utf.char(n) if n < 0x80 then + -- 0aaaaaaa : 0x80 return char(n) elseif n < 0x800 then + -- 110bbbaa : 0xC0 : n >> 6 + -- 10aaaaaa : 0x80 : n & 0x3F return char( 0xC0 + floor(n/0x40), 0x80 + (n % 0x40) ) elseif n < 0x10000 then + -- 1110bbbb : 0xE0 : n >> 12 + -- 10bbbbaa : 0x80 : (n >> 6) & 0x3F + -- 10aaaaaa : 0x80 : n & 0x3F return char( 0xE0 + floor(n/0x1000), 0x80 + (floor(n/0x40) % 0x40), 0x80 + (n % 0x40) ) - elseif n < 0x40000 then + elseif n < 0x200000 then + -- 11110ccc : 0xF0 : n >> 18 + -- 10ccbbbb : 0x80 : (n >> 12) & 0x3F + -- 10bbbbaa : 0x80 : (n >> 6) & 0x3F + -- 10aaaaaa : 0x80 : n & 0x3F + -- dddd : ccccc - 1 return char( - 0xF0 + floor(n/0x40000), - 0x80 + floor(n/0x1000), + 0xF0 + floor(n/0x40000), + 0x80 + (floor(n/0x1000) % 0x40), 0x80 + (floor(n/0x40) % 0x40), 0x80 + (n % 0x40) ) else - -- return char( - -- 0xF1 + floor(n/0x1000000), - -- 0x80 + floor(n/0x40000), - -- 0x80 + floor(n/0x1000), - -- 0x80 + (floor(n/0x40) % 0x40), - -- 0x80 + (n % 0x40) - -- ) - return "?" + return "" end end diff --git a/tex/context/base/lxml-lpt.lua b/tex/context/base/lxml-lpt.lua index 3293300b4..0c10998a0 100644 --- a/tex/context/base/lxml-lpt.lua +++ b/tex/context/base/lxml-lpt.lua @@ -1366,3 +1366,29 @@ function xml.inspect(collection,pattern) report_lpath("pattern %q\n\n%s\n",pattern,xml.tostring(e)) end end + +-- texy (see xfdf): + +local function split(e) + local dt = e.dt + if dt then + for i=1,#dt do + local dti = dt[i] + if type(dti) == "string" then + dti = gsub(dti,"^[\n\r]*(.-)[\n\r]*","%1") + dti = gsub(dti,"[\n\r]+","\n\n") + dt[i] = dti + else + split(dti) + end + end + end + return e +end + +function xml.finalizers.paragraphs(c) + for i=1,#c do + split(c[i]) + end + return c +end diff --git a/tex/context/base/math-ini.mkiv b/tex/context/base/math-ini.mkiv index 641faf44a..77441e092 100644 --- a/tex/context/base/math-ini.mkiv +++ b/tex/context/base/math-ini.mkiv @@ -253,15 +253,15 @@ \def\utfmathcommand#1{\ctxcommand{utfmathcommand(\!!bs#1\!!es)}} \def\utfmathfiller #1{\ctxcommand{utfmathfiller (\!!bs#1\!!es)}} -\unexpanded\def\doifelseutfmathaccent#1{\ctxcommand{doifelseutfmathaccent("#1")}} +\unexpanded\def\doifelseutfmathaccent#1{\ctxcommand{doifelseutfmathaccent(\!!bs#1\!!es)}} %D Not used that much: \installcorenamespace{mathcodecommand} -\unexpanded\def\mathlimop #1{\mathop{#1}} %no \limits -\unexpanded\def\mathbox #1{\dontleavehmode\hbox\Ustartmath\mathsurround\zeropoint#1\Ustopmath} -\unexpanded\def\mathnolop #1{\mathop{#1}\nolimits} +\unexpanded\def\mathlimop#1{\mathop{#1}} %no \limits +\unexpanded\def\mathbox #1{\dontleavehmode\hbox\Ustartmath\mathsurround\zeropoint#1\Ustopmath} +\unexpanded\def\mathnolop#1{\mathop{#1}\nolimits} \let\mathnothing\firstofoneunexpanded \let\mathalpha \firstofoneunexpanded diff --git a/tex/context/base/status-files.pdf b/tex/context/base/status-files.pdf Binary files differindex 9e72bd5c2..c7d5a188a 100644 --- a/tex/context/base/status-files.pdf +++ b/tex/context/base/status-files.pdf diff --git a/tex/context/base/status-lua.pdf b/tex/context/base/status-lua.pdf Binary files differindex 1e5074cb6..9507ef95c 100644 --- a/tex/context/base/status-lua.pdf +++ b/tex/context/base/status-lua.pdf diff --git a/tex/context/base/strc-num.lua b/tex/context/base/strc-num.lua index 2fff8b3c5..b82132a00 100644 --- a/tex/context/base/strc-num.lua +++ b/tex/context/base/strc-num.lua @@ -388,7 +388,7 @@ function counters.setown(name,n,value) local level = cd.level if not level or level == -1 then -- -1 is signal that we reset manually - elseif level > 0 then + elseif level > 0 or level == -3 then check(name,d,n+1) elseif level == 0 then -- happens elsewhere, check this for block @@ -444,7 +444,7 @@ function counters.add(name,n,delta) report_counters("adding, name: %s, level: text, action: checking",name) end check(name,data,n+1) - elseif level > 0 then + elseif level > 0 or level == -3 then -- within countergroup if trace_counters then report_counters("adding, name: %s, level: %s, action: checking within group",name,level) @@ -468,9 +468,14 @@ end function counters.check(level) for name, cd in next, counterdata do - if cd.level == level then + if level > 0 and cd.level == -3 then -- could become an option if trace_counters then - report_counters("resetting, name: %s, level: %s",name,level) + report_counters("resetting, name: %s, level: %s (head)",name,level) + end + reset(name) + elseif cd.level == level then + if trace_counters then + report_counters("resetting, name: %s, level: %s (normal)",name,level) end reset(name) end diff --git a/tex/context/base/strc-sec.mkiv b/tex/context/base/strc-sec.mkiv index bc6e62372..826de59bf 100644 --- a/tex/context/base/strc-sec.mkiv +++ b/tex/context/base/strc-sec.mkiv @@ -478,6 +478,7 @@ \setvalue{\??headlevel\v!block}{0} \setvalue{\??headlevel\v!none }{-1} \setvalue{\??headlevel\v!text }{-2} +\setvalue{\??headlevel\v!head }{-3} \newtoks\everydefinesection diff --git a/tex/context/base/syst-aux.mkiv b/tex/context/base/syst-aux.mkiv index 815a26b33..058a251cb 100644 --- a/tex/context/base/syst-aux.mkiv +++ b/tex/context/base/syst-aux.mkiv @@ -2008,13 +2008,15 @@ %D us to do some checking, we reimplemented the non||empty %D ones. -\unexpanded\def\dosingleargument {\let\expectedarguments\plusone \dosingleempty } -\unexpanded\def\dodoubleargument {\let\expectedarguments\plustwo \dodoubleempty } -\unexpanded\def\dotripleargument {\let\expectedarguments\plusthree \dotripleempty } -\unexpanded\def\doquadrupleargument {\let\expectedarguments\plusfour \doquadrupleempty } -\unexpanded\def\doquintupleargument {\let\expectedarguments\plusfive \doquintupleempty } -\unexpanded\def\dosixtupleargument {\let\expectedarguments\plussix \dosixtupleempty } -\unexpanded\def\doseventupleargument{\let\expectedarguments\plusseven \doseventupleempty} +% no longer a mesage: +% +% \unexpanded\def\dosingleargument {\let\expectedarguments\plusone \dosingleempty } +% \unexpanded\def\dodoubleargument {\let\expectedarguments\plustwo \dodoubleempty } +% \unexpanded\def\dotripleargument {\let\expectedarguments\plusthree \dotripleempty } +% \unexpanded\def\doquadrupleargument {\let\expectedarguments\plusfour \doquadrupleempty } +% \unexpanded\def\doquintupleargument {\let\expectedarguments\plusfive \doquintupleempty } +% \unexpanded\def\dosixtupleargument {\let\expectedarguments\plussix \dosixtupleempty } +% \unexpanded\def\doseventupleargument{\let\expectedarguments\plusseven \doseventupleempty} %D \macros %D {iffirstagument,ifsecondargument,ifthirdargument, @@ -2576,6 +2578,14 @@ \def\syst_helpers_seventuple_empty_seven_spaced#1#2#3#4#5#6#7{#1[{#2}][{#3}][{#4}][{#5}][{#6}][{#7}][] } \def\syst_helpers_seventuple_empty_seven_normal#1#2#3#4#5#6#7{#1[{#2}][{#3}][{#4}][{#5}][{#6}][{#7}][]} +\let\dosingleargument \dosingleempty +\let\dodoubleargument \dodoubleempty +\let\dotripleargument \dotripleempty +\let\doquadrupleargument \doquadrupleempty +\let\doquintupleargument \doquintupleempty +\let\dosixtupleargument \dosixtupleempty +\let\doseventupleargument\doseventupleempty + %D \macros %D {strippedcsname} %D diff --git a/tex/context/base/util-sql-imp-client.lua b/tex/context/base/util-sql-imp-client.lua new file mode 100644 index 000000000..8e174fc99 --- /dev/null +++ b/tex/context/base/util-sql-imp-client.lua @@ -0,0 +1,253 @@ +if not modules then modules = { } end modules ['util-sql-client'] = { + version = 1.001, + comment = "companion to util-sql.lua", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +-- todo: make a converter + +local rawset, setmetatable = rawset, setmetatable +local P, S, V, C, Cs, Ct, Cc, Cg, Cf, patterns, lpegmatch = lpeg.P, lpeg.S, lpeg.V, lpeg.C, lpeg.Cs, lpeg.Ct, lpeg.Cc, lpeg.Cg, lpeg.Cf, lpeg.patterns, lpeg.match + +local trace_sql = false trackers.register("sql.trace", function(v) trace_sql = v end) +local trace_queries = false trackers.register("sql.queries",function(v) trace_queries = v end) +local report_state = logs.reporter("sql","client") + +local sql = require("util-sql") +local helpers = sql.helpers +local methods = sql.methods +local validspecification = helpers.validspecification +local preparetemplate = helpers.preparetemplate +local splitdata = helpers.splitdata +local replacetemplate = utilities.templates.replace +local serialize = sql.serialize +local deserialize = sql.deserialize + +-- Experiments with an p/action demonstrated that there is not much gain. We could do a runtime +-- capture but creating all the small tables is not faster and it doesn't work well anyway. + +local separator = P("\t") +local newline = patterns.newline +local empty = Cc("") + +local entry = C((1-separator-newline)^0) -- C 10% faster than Cs + +local unescaped = P("\\n") / "\n" + + P("\\t") / "\t" + + P("\\0") / "\000" + + P("\\\\") / "\\" + +local entry = Cs((unescaped + (1-separator-newline))^0) -- C 10% faster than Cs but Cs needed due to nesting + +local getfirst = Ct( entry * (separator * (entry+empty))^0) + newline +local skipfirst = (1-newline)^1 * newline +local getfirstline = C((1-newline)^0) + +local cache = { } + +local function splitdata(data) -- todo: hash on first line ... maybe move to client module + if data == "" then + if trace_sql then + report_state("no data") + end + return { }, { } + end + local first = lpegmatch(getfirstline,data) + if not first then + if trace_sql then + report_state("no data") + end + return { }, { } + end + local p = cache[first] + if p then + -- report_state("reusing: %s",first) + local entries = lpegmatch(p.parser,data) + return entries or { }, p.keys + elseif p == false then + return { }, { } + elseif p == nil then + local keys = lpegmatch(getfirst,first) or { } + if #keys == 0 then + if trace_sql then + report_state("no banner") + end + cache[first] = false + return { }, { } + end + -- quite generic, could be a helper + local n = #keys + if n == 0 then + report_state("no fields") + cache[first] = false + return { }, { } + end + if n == 1 then + local key = keys[1] + if trace_sql then + report_state("one field with name",key) + end + p = Cg(Cc(key) * entry) + else + for i=1,n do + local key = keys[i] + if trace_sql then + report_state("field %s has name %q",i,key) + end + local s = Cg(Cc(key) * entry) + if p then + p = p * separator * s + else + p = s + end + end + end + p = Cf(Ct("") * p,rawset) * newline^1 + p = skipfirst * Ct(p^0) + cache[first] = { parser = p, keys = keys } + local entries = lpegmatch(p,data) + return entries or { }, keys + end +end + +local splitter = skipfirst * Ct((Ct(entry * (separator * entry)^0) * newline^1)^0) + +local function getdata(data) + return lpegmatch(splitter,data) +end + +helpers.splitdata = splitdata +helpers.getdata = getdata + +local function dataprepared(specification) + local query = preparetemplate(specification) + if query then + io.savedata(specification.queryfile,query) + os.remove(specification.resultfile) + if trace_queries then + report_state("query: %s",query) + end + return true + else + -- maybe push an error + os.remove(specification.queryfile) + os.remove(specification.resultfile) + end +end + +local function datafetched(specification,runner) + local command = replacetemplate(runner,specification) + if trace_sql then + local t = osclock() + report_state("command: %s",command) + local okay = os.execute(command) + report_state("fetchtime: %.3f sec",osclock()-t) -- not okay under linux + return okay == 0 + else + return os.execute(command) == 0 + end +end + +local function dataloaded(specification) + if trace_sql then + local t = osclock() + local data = io.loaddata(specification.resultfile) or "" + report_state("datasize: %.3f MB",#data/1024/1024) + report_state("loadtime: %.3f sec",osclock()-t) + return data + else + return io.loaddata(specification.resultfile) or "" + end +end + +local function dataconverted(data,converter) + if converter then + local data = getdata(data) + if data then + converter.client(data) + end + return data + elseif trace_sql then + local t = osclock() + local data, keys = splitdata(data) + report_state("converttime: %.3f",osclock()-t) + report_state("keys: %s ",#keys) + report_state("entries: %s ",#data) + return data, keys + else + return splitdata(data) + end +end + +-- todo: new, etc + +local function execute(specification) + if trace_sql then + report_state("executing client") + end + if not validspecification(specification) then + report_state("error in specification") + return + end + if not dataprepared(specification) then + report_state("error in preparation") + return + end + if not datafetched(specification,methods.client.runner) then + report_state("error in fetching, query: %s",string.collapsespaces(io.loaddata(specification.queryfile))) + return + end + local data = dataloaded(specification) + if not data then + report_state("error in loading") + return + end + local data, keys = dataconverted(data,specification.converter) + if not data then + report_state("error in converting") + return + end + local one = data[1] + if one then + setmetatable(data,{ __index = one } ) + end + return data, keys +end + +-- The following is not that (memory) efficient but normally we will use +-- the lib anyway. Of course we could make a dedicated converter and/or +-- hook into the splitter code but ... it makes not much sense because then +-- we can as well move the builder to the library modules. + +local wraptemplate = [[ +local converters = utilities.sql.converters +local deserialize = utilities.sql.deserialize + +local tostring = tostring +local tonumber = tonumber +local booleanstring = string.booleanstring + +%s + +return function(data) + for i=1,#data do + local cells = data[i] + data[i] = { + %s + } + end + return data +end +]] + +local celltemplate = "cells[%s]" + +methods.client = { + runner = [[mysql --batch --user="%username%" --password="%password%" --host="%host%" --port=%port% --database="%database%" --default-character-set=utf8 < "%queryfile%" > "%resultfile%"]], + execute = execute, + usesfiles = true, + wraptemplate = wraptemplate, + celltemplate = celltemplate, +} diff --git a/tex/context/base/util-sql-imp-library.lua b/tex/context/base/util-sql-imp-library.lua new file mode 100644 index 000000000..e04c4ac44 --- /dev/null +++ b/tex/context/base/util-sql-imp-library.lua @@ -0,0 +1,282 @@ +if not modules then modules = { } end modules ['util-sql-library'] = { + version = 1.001, + comment = "companion to util-sql.lua", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +-- For some reason the sql lib partially fails in luatex when creating hashed row. So far +-- we couldn't figure it out (some issue with adapting the table that is passes as first +-- argument in the fetch routine. Apart from this it looks like the mysql binding has some +-- efficiency issues (like creating a keys and types table for each row) but that could be +-- optimized. Anyhow, fecthing results can be done as follows: + +-- local function collect_1(r) +-- local t = { } +-- for i=1,r:numrows() do +-- t[#t+1] = r:fetch({},"a") +-- end +-- return t +-- end +-- +-- local function collect_2(r) +-- local keys = r:getcolnames() +-- local n = #keys +-- local t = { } +-- for i=1,r:numrows() do +-- local v = { r:fetch() } +-- local r = { } +-- for i=1,n do +-- r[keys[i]] = v[i] +-- end +-- t[#t+1] = r +-- end +-- return t +-- end +-- +-- local function collect_3(r) +-- local keys = r:getcolnames() +-- local n = #keys +-- local t = { } +-- for i=1,r:numrows() do +-- local v = r:fetch({},"n") +-- local r = { } +-- for i=1,n do +-- r[keys[i]] = v[i] +-- end +-- t[#t+1] = r +-- end +-- return t +-- end +-- +-- On a large table with some 8 columns (mixed text and numbers) we get the following +-- timings (the 'a' alternative is already using the more efficient variant in the +-- binding). +-- +-- collect_1 : 1.31 +-- collect_2 : 1.39 +-- collect_3 : 1.75 +-- +-- Some, as a workaround for this 'bug' the second alternative can be used. + +local format = string.format +local lpegmatch = lpeg.match +local setmetatable, type = setmetatable, type + +local trace_sql = false trackers.register("sql.trace", function(v) trace_sql = v end) +local trace_queries = false trackers.register("sql.queries",function(v) trace_queries = v end) +local report_state = logs.reporter("sql","library") + +local sql = require("util-sql") +local mysql = require("luasql.mysql") +local cache = { } +local helpers = sql.helpers +local methods = sql.methods +local validspecification = helpers.validspecification +local querysplitter = helpers.querysplitter +local dataprepared = helpers.preparetemplate +local serialize = sql.serialize +local deserialize = sql.deserialize + +local initialize = mysql.mysql + +local function connect(session,specification) + return session:connect( + specification.database or "", + specification.username or "", + specification.password or "", + specification.host or "", + specification.port + ) +end + +local function datafetched(specification,query,converter) + if not query or query == "" then + report_state("no valid query") + return { }, { } + end + local id = specification.id + local session, connection + if id then + local c = cache[id] + if c then + session = c.session + connection = c.connection + end + if not connection then + session = initialize() + connection = connect(session,specification) + cache[id] = { session = session, connection = connection } + end + else + session = initialize() + connection = connect(session,specification) + end + if not connection then + report_state("error in connection: %s@%s to %s:%s", + specification.database or "no database", + specification.username or "no username", + specification.host or "no host", + specification.port or "no port" + ) + return { }, { } + end + query = lpegmatch(querysplitter,query) + local result, message, okay + for i=1,#query do + local q = query[i] + local r, m = connection:execute(q) + if m then + report_state("error in query, stage 1: %s",string.collapsespaces(q)) + message = message and format("%s\n%s",message,m) or m + end + local t = type(r) + if t == "userdata" then + result = r + okay = true + elseif t == "number" then + okay = true + end + end + if not okay and id then -- can go + if session then + session:close() + end + if connection then + connection:close() + end + session = initialize() -- maybe not needed + connection = connect(session,specification) + if connection then + cache[id] = { session = session, connection = connection } + for i=1,#query do + local q = query[i] + local r, m = connection:execute(q) + if m then + report_state("error in query, stage 2: %s",string.collapsespaces(q)) + message = message and format("%s\n%s",message,m) or m + end + local t = type(r) + if t == "userdata" then + result = r + okay = true + elseif t == "number" then + okay = true + end + end + else + message = "unable to connect" + report_state(message) + end + end + local data, keys + if result then + if converter then + data = converter.library(result) + -- data = converter(result) + else + keys = result:getcolnames() + if keys then + local n = result:numrows() or 0 + if n == 0 then + data = { } + -- elseif n == 1 then + -- -- data = { result:fetch({},"a") } + else + data = { } + -- for i=1,n do + -- data[i] = result:fetch({},"a") + -- end + local k = #keys + for i=1,n do + local v = { result:fetch() } + local d = { } + for i=1,k do + d[keys[i]] = v[i] + end + data[#data+1] = d + end + end + end + end + result:close() + elseif message then + report_state("message %s",message) + end + if not keys then + keys = { } + end + if not data then + data = { } + end + if not id then + connection:close() + session:close() + end + return data, keys +end + +local function execute(specification) + if trace_sql then + report_state("executing library") + end + if not validspecification(specification) then + report_state("error in specification") + return + end + local query = dataprepared(specification) + if not query then + report_state("error in preparation") + return + end + local data, keys = datafetched(specification,query,specification.converter) + if not data then + report_state("error in fetching") + return + end + local one = data[1] + if one then + setmetatable(data,{ __index = one } ) + end + return data, keys +end + +local wraptemplate = [[ +local converters = utilities.sql.converters +local deserialize = utilities.sql.deserialize + +local tostring = tostring +local tonumber = tonumber +local booleanstring = string.booleanstring + +%s + +return function(result) + if not result then + return { } + end + local nofrows = result:numrows() or 0 + if nofrows == 0 then + return { } + end + local data = { } + for i=1,nofrows do + local cells = { result:fetch() } + data[i] = { + %s + } + end + return data +end +]] + +local celltemplate = "cells[%s]" + +methods.library = { + runner = function() end, -- never called + execute = execute, + usesfiles = false, + wraptemplate = wraptemplate, + celltemplate = celltemplate, +} diff --git a/tex/context/base/util-sql-imp-swiglib.lua b/tex/context/base/util-sql-imp-swiglib.lua new file mode 100644 index 000000000..4b9cda896 --- /dev/null +++ b/tex/context/base/util-sql-imp-swiglib.lua @@ -0,0 +1,419 @@ +if not modules then modules = { } end modules ['util-sql-swiglib'] = { + version = 1.001, + comment = "companion to util-sql.lua", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +-- As the regular library is flawed (i.e. there are crashes in the table +-- construction code) and also not that efficient, Luigi Scarso looked into +-- a swig binding. This is a bit more low level approach but as we stay +-- closer to the original library it's also less dependant. + +local concat = table.concat +local format = string.format +local lpegmatch = lpeg.match +local setmetatable, type = setmetatable, type + +local trace_sql = false trackers.register("sql.trace", function(v) trace_sql = v end) +local trace_queries = false trackers.register("sql.queries",function(v) trace_queries = v end) +local report_state = logs.reporter("sql","swiglib") + +local sql = require("util-sql") +local mysql = require("swigluamysql") +local cache = { } +local helpers = sql.helpers +local methods = sql.methods +local validspecification = helpers.validspecification +local querysplitter = helpers.querysplitter +local dataprepared = helpers.preparetemplate +local serialize = sql.serialize +local deserialize = sql.deserialize + +local mysql_initialize = mysql.mysql_init + +local mysql_open_connection = mysql.mysql_real_connect +local mysql_execute_query = mysql.mysql_real_query +local mysql_close_connection = mysql.mysql_close + +local mysql_field_seek = mysql.mysql_field_seek +local mysql_num_fields = mysql.mysql_num_fields +local mysql_fetch_field = mysql.mysql_fetch_field +local mysql_num_rows = mysql.mysql_num_rows +local mysql_fetch_row = mysql.mysql_fetch_row +local mysql_fetch_lengths = mysql.mysql_fetch_lengths +local mysql_init = mysql.mysql_init +local mysql_store_result = mysql.mysql_store_result +local mysql_free_result = mysql.mysql_free_result +local mysql_use_result = mysql.mysql_use_result + +local mysql_error_message = mysql.mysql_error +local mysql_options_argument = mysql.mysql_options_argument + +local instance = mysql.MYSQL() + +local mysql_constant_false = mysql_options_argument(false) -- 0 "\0" +local mysql_constant_true = mysql_options_argument(true) -- 1 "\1" + +-- print(swig_type(mysql_constant_false)) +-- print(swig_type(mysql_constant_true)) + +mysql.mysql_options(instance,mysql.MYSQL_OPT_RECONNECT,mysql_constant_true); + +local typemap = { + [mysql.MYSQL_TYPE_VAR_STRING ] = "string", + [mysql.MYSQL_TYPE_STRING ] = "string", + [mysql.MYSQL_TYPE_DECIMAL ] = "number", + [mysql.MYSQL_TYPE_SHORT ] = "number", + [mysql.MYSQL_TYPE_LONG ] = "number", + [mysql.MYSQL_TYPE_FLOAT ] = "number", + [mysql.MYSQL_TYPE_DOUBLE ] = "number", + [mysql.MYSQL_TYPE_LONGLONG ] = "number", + [mysql.MYSQL_TYPE_INT24 ] = "number", + [mysql.MYSQL_TYPE_YEAR ] = "number", + [mysql.MYSQL_TYPE_TINY ] = "number", + [mysql.MYSQL_TYPE_TINY_BLOB ] = "binary", + [mysql.MYSQL_TYPE_MEDIUM_BLOB] = "binary", + [mysql.MYSQL_TYPE_LONG_BLOB ] = "binary", + [mysql.MYSQL_TYPE_BLOB ] = "binary", + [mysql.MYSQL_TYPE_DATE ] = "date", + [mysql.MYSQL_TYPE_NEWDATE ] = "date", + [mysql.MYSQL_TYPE_DATETIME ] = "datetime", + [mysql.MYSQL_TYPE_TIME ] = "time", + [mysql.MYSQL_TYPE_TIMESTAMP ] = "time", + [mysql.MYSQL_TYPE_ENUM ] = "set", + [mysql.MYSQL_TYPE_SET ] = "set", + [mysql.MYSQL_TYPE_NULL ] = "null", +} + +-- real_escape_string + +local function finish(t) + mysql_free_result(t._result_) +end + +-- will become metatable magic + +-- local function analyze(result) +-- mysql_field_seek(result,0) +-- local nofrows = mysql_num_rows(result) or 0 +-- local noffields = mysql_num_fields(result) +-- local names = { } +-- local types = { } +-- for i=1,noffields do +-- local field = mysql_fetch_field(result) +-- names[i] = field.name +-- types[i] = field.type +-- end +-- return names, types, noffields, nofrows +-- end + +local function getcolnames(t) + return t.names +end + +local function getcoltypes(t) + return t.types +end + +local function numrows(t) + return t.nofrows +end + +-- swig_type + +-- local ulongArray_getitem = mysql.ulongArray_getitem +-- local util_getbytearray = mysql.util_getbytearray + +-- local function list(t) +-- local result = t._result_ +-- local row = mysql_fetch_row(result) +-- local len = mysql_fetch_lengths(result) +-- local result = { } +-- for i=1,t.noffields do +-- local r = i - 1 -- zero offset +-- result[i] = util_getbytearray(row,r,ulongArray_getitem(len,r)) +-- end +-- return result +-- end + +-- local function hash(t) +-- local list = util_mysql_fetch_fields_from_current_row(t._result_) +-- local result = t._result_ +-- local fields = t.names +-- local row = mysql_fetch_row(result) +-- local len = mysql_fetch_lengths(result) +-- local result = { } +-- for i=1,t.noffields do +-- local r = i - 1 -- zero offset +-- result[fields[i]] = util_getbytearray(row,r,ulongArray_getitem(len,r)) +-- end +-- return result +-- end + +local util_mysql_fetch_fields_from_current_row = mysql.util_mysql_fetch_fields_from_current_row + +local function list(t) + return util_mysql_fetch_fields_from_current_row(t._result_) +end + +local function hash(t) + local list = util_mysql_fetch_fields_from_current_row(t._result_) + local fields = t.names + local data = { } + for i=1,t.noffields do + data[fields[i]] = list[i] + end + return data +end + +local mt = { __index = { + -- regular + finish = finish, + list = list, + hash = hash, + -- compatibility + numrows = numrows, + getcolnames = getcolnames, + getcoltypes = getcoltypes, + } +} + +-- session + +local function close(t) + mysql_close_connection(t._connection_) +end + +local function execute(t,query) + if query and query ~= "" then + local connection = t._connection_ + local result = mysql_execute_query(connection,query,#query) + if result == 0 then + local result = mysql_store_result(connection) + mysql_field_seek(result,0) + local nofrows = mysql_num_rows(result) or 0 + local noffields = mysql_num_fields(result) + local names = { } + local types = { } + for i=1,noffields do + local field = mysql_fetch_field(result) + names[i] = field.name + types[i] = field.type + end + local t = { + _result_ = result, + names = names, + types = types, + noffields = noffields, + nofrows = nofrows, + } + return setmetatable(t,mt) + end + end + return false +end + +local mt = { __index = { + close = close, + execute = execute, + } +} + +local function open(t,database,username,password,host,port) + local connection = mysql_open_connection(t._session_,host or "localhost",username or "",password or "",database or "",port or 0,0,0) + if connection then + local t = { + _connection_ = connection, + } + return setmetatable(t,mt) + end +end + +local function message(t) + return mysql_error_message(t._session_) +end + +local function close(t) + -- dummy, as we have a global session +end + +local mt = { + __index = { + connect = open, + close = close, + message = message, + } +} + +local function initialize() + local session = { + _session_ = mysql_initialize(instance) -- maybe share, single thread anyway + } + return setmetatable(session,mt) +end + +-- -- -- -- + +local function connect(session,specification) + return session:connect( + specification.database or "", + specification.username or "", + specification.password or "", + specification.host or "", + specification.port + ) +end + +local function datafetched(specification,query,converter) + if not query or query == "" then + report_state("no valid query") + return { }, { } + end + local id = specification.id + local session, connection + if id then + local c = cache[id] + if c then + session = c.session + connection = c.connection + end + if not connection then + session = initialize() + connection = connect(session,specification) + cache[id] = { session = session, connection = connection } + end + else + session = initialize() + connection = connect(session,specification) + end + if not connection then + report_state("error in connection: %s@%s to %s:%s", + specification.database or "no database", + specification.username or "no username", + specification.host or "no host", + specification.port or "no port" + ) + return { }, { } + end + query = lpegmatch(querysplitter,query) + local result, message, okay + for i=1,#query do + local q = query[i] + local r, m = connection:execute(q) + if m then + report_state("error in query, stage: %s",string.collapsespaces(q)) + message = message and format("%s\n%s",message,m) or m + end + if type(r) == "table" then + result = r + okay = true + elseif not m then + okay = true + end + end + local data, keys + if result then + if converter then + data = converter.swiglib(result) + else + keys = result.names + data = { } + for i=1,result.nofrows do + data[i] = result:hash() + end + end + result:finish() -- result:close() + elseif message then + report_state("message %s",message) + end + if not keys then + keys = { } + end + if not data then + data = { } + end + if not id then + connection:close() + session:close() + end + return data, keys +end + +local function execute(specification) + if trace_sql then + report_state("executing library") + end + if not validspecification(specification) then + report_state("error in specification") + return + end + local query = dataprepared(specification) + if not query then + report_state("error in preparation") + return + end + local data, keys = datafetched(specification,query,specification.converter) + if not data then + report_state("error in fetching") + return + end + local one = data[1] + if one then + setmetatable(data,{ __index = one } ) + end + return data, keys +end + +local wraptemplate = [[ +local mysql = require("swigluamysql") -- will be stored in method + +----- mysql_fetch_row = mysql.mysql_fetch_row +----- mysql_fetch_lengths = mysql.mysql_fetch_lengths +----- util_unpackbytearray = mysql.util_unpackbytearray +local util_mysql_fetch_fields_from_current_row + = mysql.util_mysql_fetch_fields_from_current_row + +local converters = utilities.sql.converters +local deserialize = utilities.sql.deserialize + +local tostring = tostring +local tonumber = tonumber +local booleanstring = string.booleanstring + +%s + +return function(result) + if not result then + return { } + end + local nofrows = result.nofrows or 0 + if nofrows == 0 then + return { } + end + local noffields = result.noffields or 0 + local data = { } + result = result._result_ + for i=1,nofrows do + -- local row = mysql_fetch_row(result) + -- local len = mysql_fetch_lengths(result) + -- local cells = util_unpackbytearray(row,noffields,len) + local cells = util_mysql_fetch_fields_from_current_row(result) + data[i] = { + %s + } + end + return data +end +]] + +local celltemplate = "cells[%s]" + +methods.swiglib = { + runner = function() end, -- never called + execute = execute, + usesfiles = false, + wraptemplate = wraptemplate, + celltemplate = celltemplate, +} diff --git a/tex/context/base/util-sql-users.lua b/tex/context/base/util-sql-users.lua index 4389b905c..81324afcf 100644 --- a/tex/context/base/util-sql-users.lua +++ b/tex/context/base/util-sql-users.lua @@ -13,8 +13,9 @@ if not modules then modules = { } end modules ['util-sql-users'] = { local sql = require("util-sql") local md5 = require("md5") -local format, upper, find, gsub = string.format, string.upper, string.find, string.gsub +local format, upper, find, gsub, escapedpattern = string.format, string.upper, string.find, string.gsub, string.escapedpattern local sumhexa = md5.sumhexa +local booleanstring = string.booleanstring local users = { } sql.users = users @@ -37,6 +38,9 @@ local function cleanuppassword(str) end local function samepasswords(one,two) + if not one or not two then + return false + end if not find(one,"^MD5:") then one = encryptpassword(one) end @@ -46,9 +50,22 @@ local function samepasswords(one,two) return one == two end +local function validaddress(address,addresses) + if address and addresses and address ~= "" and addresses ~= "" then + if find(address,"^" .. escapedpattern(addresses,true)) then -- simple escapes + return true, "valid remote address" + end + return false, "invalid remote address" + else + return true, "no remote address check" + end +end + + users.encryptpassword = encryptpassword users.cleanuppassword = cleanuppassword users.samepasswords = samepasswords +users.validaddress = validaddress -- print(users.encryptpassword("test")) -- MD5:098F6BCD4621D373CADE4E832627B4F6 @@ -86,13 +103,14 @@ users.groupnumbers = groupnumbers local template =[[ CREATE TABLE `users` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `name` varchar(80) NOT NULL, - `password` varbinary(50) DEFAULT NULL, - `group` int(11) NOT NULL, - `enabled` int(11) DEFAULT '1', - `email` varchar(80) DEFAULT NULL, - `data` longtext, + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(80) NOT NULL, + `password` varchar(50) DEFAULT NULL, + `group` int(11) NOT NULL, + `enabled` int(11) DEFAULT '1', + `email` varchar(80) DEFAULT NULL, + `address` varchar(256) DEFAULT NULL, + `data` longtext, PRIMARY KEY (`id`), UNIQUE KEY `name_unique` (`name`) ) DEFAULT CHARSET = utf8 ; @@ -105,6 +123,7 @@ local converter, fields = sql.makeconverter { { name = "group", type = groupnames }, { name = "enabled", type = "boolean" }, { name = "email", type = "string" }, + { name = "address", type = "string" }, { name = "data", type = "deserialize" }, } @@ -137,7 +156,43 @@ local template =[[ ; ]] -function users.valid(db,username,password) +-- function users.valid(db,username,password) +-- +-- local data = db.execute { +-- template = template, +-- converter = converter, +-- variables = { +-- basename = db.basename, +-- fields = fields, +-- name = username, +-- password = encryptpassword(password), +-- }, +-- } +-- +-- local data = data and data[1] +-- +-- if not data then +-- return false, "unknown" +-- elseif not data.enabled then +-- return false, "disabled" +-- else +-- data.password = nil +-- return data, "okay" +-- end +-- +-- end + +local template =[[ + SELECT + %fields% + FROM + %basename% + WHERE + `name` = '%name%' + ; +]] + +function users.valid(db,username,password,address) local data = db.execute { template = template, @@ -146,16 +201,19 @@ function users.valid(db,username,password) basename = db.basename, fields = fields, name = username, - password = encryptpassword(password), }, } local data = data and data[1] if not data then - return false, "unknown" + return false, "unknown user" elseif not data.enabled then - return false, "disabled" + return false, "disabled user" + elseif data.password ~= encryptpassword(password) then + return false, "wrong password" + elseif not validaddress(address,data.address) then + return false, "invalid address" else data.password = nil return data, "okay" @@ -170,6 +228,7 @@ local template =[[ `group`, `enabled`, `email`, + `address`, `data` ) VALUES ( '%name%', @@ -177,6 +236,7 @@ local template =[[ '%group%', '%enabled%', '%email%', + '%address%', '%[data]%' ) ; ]] @@ -189,6 +249,8 @@ function users.add(db,specification) return end + local data = specification.data + db.execute { template = template, variables = { @@ -196,9 +258,10 @@ function users.add(db,specification) name = name, password = encryptpassword(specification.password or ""), group = groupnumbers[specification.group] or groupnumbers.guest, - enabled = toboolean(specification.enabled,true) and "1" or "0", + enabled = booleanstring(specification.enabled) and "1" or "0", email = specification.email, - data = type(specification.data) == "table" and db.serialize(specification.data,"return") or "", + address = specification.address, + data = type(data) == "table" and db.serialize(data,"return") or "", }, } @@ -264,6 +327,7 @@ local template =[[ `group` = '%group%', `enabled` = '%enabled%', `email` = '%email%', + `address` = '%address%', `data` = '%[data]%' WHERE `id` = '%id%' @@ -286,6 +350,7 @@ function users.save(db,id,specification) local group = specification.group == nil and user.group or specification.group local enabled = specification.enabled == nil and user.enabled or specification.enabled local email = specification.email == nil and user.email or specification.email + local address = specification.address == nil and user.address or specification.address local data = specification.data == nil and user.data or specification.data -- table.print(data) @@ -297,8 +362,9 @@ function users.save(db,id,specification) id = id, password = encryptpassword(password), group = groupnumbers[group], - enabled = toboolean(enabled,true) and "1" or "0", + enabled = booleanstring(enabled) and "1" or "0", email = email, + address = address, data = type(data) == "table" and db.serialize(data,"return") or "", }, } diff --git a/tex/context/base/util-sql.lua b/tex/context/base/util-sql.lua index 4e9a5bcd4..e2edf0ffe 100644 --- a/tex/context/base/util-sql.lua +++ b/tex/context/base/util-sql.lua @@ -19,62 +19,42 @@ if not modules then modules = { } end modules ['util-sql'] = { -- context in a regular tds tree (the standard distribution) it makes sense to put shared -- code in the context distribution. That way we don't need to reinvent wheels every time. --- For some reason the sql lib partially fails in luatex when creating hashed row. So far --- we couldn't figure it out (some issue with adapting the table that is passes as first --- argument in the fetch routine. Apart from this it looks like the mysql binding has some --- efficiency issues (like creating a keys and types table for each row) but that could be --- optimized. Anyhow, fecthing results can be done as follows: - -- We use the template mechanism from util-tpl which inturn is just using the dos cq -- windows convention of %whatever% variables that I've used for ages. --- local function collect_1(r) --- local t = { } --- for i=1,r:numrows() do --- t[#t+1] = r:fetch({},"a") --- end --- return t --- end --- --- local function collect_2(r) --- local keys = r:getcolnames() --- local n = #keys --- local t = { } --- for i=1,r:numrows() do --- local v = { r:fetch() } --- local r = { } --- for i=1,n do --- r[keys[i]] = v[i] --- end --- t[#t+1] = r --- end --- return t --- end +-- util-sql-imp-client.lua +-- util-sql-imp-library.lua +-- util-sql-imp-swiglib.lua +-- util-sql-imp-lmxsql.lua + +-- local sql = require("util-sql") -- --- local function collect_3(r) --- local keys = r:getcolnames() --- local n = #keys --- local t = { } --- for i=1,r:numrows() do --- local v = r:fetch({},"n") --- local r = { } --- for i=1,n do --- r[keys[i]] = v[i] --- end --- t[#t+1] = r --- end --- return t --- end +-- local converter = sql.makeconverter { +-- { name = "id", type = "number" }, +-- { name = "data",type = "string" }, +-- } -- --- On a large table with some 8 columns (mixed text and numbers) we get the following --- timings (the 'a' alternative is already using the more efficient variant in the --- binding). +-- local execute = sql.methods.swiglib.execute +-- -- local execute = sql.methods.library.execute +-- -- local execute = sql.methods.client.execute +-- -- local execute = sql.methods.lmxsql.execute -- --- collect_1 : 1.31 --- collect_2 : 1.39 --- collect_3 : 1.75 +-- result = execute { +-- presets = { +-- host = "localhost", +-- username = "root", +-- password = "test", +-- database = "test", +-- id = "test", -- forces persistent session +-- }, +-- template = "select * from `test` where `id` > %criterium% ;", +-- variables = { +-- criterium = 2, +-- }, +-- converter = converter +-- } -- --- Some, as a workaround for this 'bug' the second alternative can be used. +-- inspect(result) local format, match = string.format, string.match local random = math.random @@ -102,12 +82,18 @@ local loadtemplate = utilities.templates.load local methods = { } sql.methods = methods +local helpers = { } +sql.helpers = helpers + local serialize = table.fastserialize local deserialize = table.deserialize sql.serialize = serialize sql.deserialize = deserialize +helpers.serialize = serialize -- bonus +helpers.deserialize = deserialize -- bonus + local defaults = { __index = { resultfile = "result.dat", @@ -122,92 +108,105 @@ local defaults = { __index = }, } --- Experiments with an p/action demonstrated that there is not much gain. We could do a runtime --- capture but creating all the small tables is not faster and it doesn't work well anyway. +table.setmetatableindex(sql.methods,function(t,k) + require("util-sql-imp-"..k) + return rawget(t,k) +end) -local separator = P("\t") -local newline = patterns.newline -local empty = Cc("") +-- converters -local entry = C((1-separator-newline)^0) -- C 10% faster than Cs - -local unescaped = P("\\n") / "\n" - + P("\\t") / "\t" - + P("\\\\") / "\\" +local converters = { } +sql.converters = converters -local entry = Cs((unescaped + (1-separator-newline))^0) -- C 10% faster than Cs but Cs needed due to nesting +local function makeconverter(entries,celltemplate,wraptemplate) + local shortcuts = { } + local assignments = { } + local function cell(i) + return format(celltemplate,i,i) + end + for i=1,#entries do + local entry = entries[i] + local nam = entry.name + local typ = entry.type + if typ == "boolean" then + assignments[i] = format("[%q] = booleanstring(%s),",nam,cell(i)) + elseif typ == "number" then + assignments[i] = format("[%q] = tonumber(%s),",nam,cell(i)) + elseif type(typ) == "function" then + local c = #converters + 1 + converters[c] = typ + shortcuts[#shortcuts+1] = format("local fun_%s = converters[%s]",c,c) + assignments[i] = format("[%q] = fun_%s(%s),",nam,c,cell(i)) + elseif type(typ) == "table" then + local c = #converters + 1 + converters[c] = typ + shortcuts[#shortcuts+1] = format("local tab_%s = converters[%s]",c,c) + assignments[i] = format("[%q] = tab_%s[%s],",nam,#converters,cell(i)) + elseif typ == "deserialize" then + assignments[i] = format("[%q] = deserialize(%s),",nam,cell(i)) + else + assignments[i] = format("[%q] = %s,",nam,cell(i)) + end + end + local code = format(wraptemplate,concat(shortcuts,"\n"),concat(assignments,"\n ")) + local func = loadstring(code) + return func and func() +end -local getfirst = Ct( entry * (separator * (entry+empty))^0) + newline -local skipfirst = (1-newline)^1 * newline -local getfirstline = C((1-newline)^0) +function sql.makeconverter(entries) + local fields = { } + for i=1,#entries do + fields[i] = format("`%s`",entries[i].name) + end + fields = concat(fields, ", ") + local converter = { + fields = fields + } + table.setmetatableindex(converter, function(t,k) + local sqlmethod = methods[k] + local v = makeconverter(entries,sqlmethod.celltemplate,sqlmethod.wraptemplate) + t[k] = v + return v + end) + return converter, fields +end -local cache = { } +-- helper for libraries: -local function splitdata(data) -- todo: hash on first line - if data == "" then - if trace_sql then - report_state("no data") - end - return { }, { } - end - local first = lpegmatch(getfirstline,data) - if not first then - if trace_sql then - report_state("no data") - end - return { }, { } +local function validspecification(specification) + local presets = specification.presets + if type(presets) == "string" then + presets = dofile(presets) end - local p = cache[first] - if p then - -- report_state("reusing: %s",first) - local entries = lpegmatch(p.parser,data) - return entries or { }, p.keys - elseif p == false then - return { }, { } - elseif p == nil then - local keys = lpegmatch(getfirst,first) or { } - if #keys == 0 then - if trace_sql then - report_state("no banner") - end - cache[first] = false - return { }, { } - end - -- quite generic, could be a helper - local n = #keys - if n == 0 then - report_state("no fields") - cache[first] = false - return { }, { } - end - if n == 1 then - local key = keys[1] - if trace_sql then - report_state("one field with name",key) - end - p = Cg(Cc(key) * entry) - else - for i=1,n do - local key = keys[i] - if trace_sql then - report_state("field %s has name %q",i,key) - end - local s = Cg(Cc(key) * entry) - if p then - p = p * separator * s - else - p = s - end - end - end - p = Cf(Ct("") * p,rawset) * newline^1 - p = skipfirst * Ct(p^0) - cache[first] = { parser = p, keys = keys } - local entries = lpegmatch(p,data) - return entries or { }, keys + if type(presets) == "table" then + setmetatable(presets,defaults) + setmetatable(specification,{ __index = presets }) + else + setmetatable(specification,defaults) end + return true end +helpers.validspecification = validspecification + +local whitespace = patterns.whitespace^0 +local eol = patterns.eol +local separator = P(";") +local escaped = patterns.escaped +local dquote = patterns.dquote +local squote = patterns.squote +local dsquote = squote * squote +---- quoted = patterns.quoted +local quoted = dquote * (escaped + (1-dquote))^0 * dquote + + squote * (escaped + dsquote + (1-squote))^0 * squote +local comment = P("--") * (1-eol) / "" +local query = whitespace + * Cs((quoted + comment + 1 - separator)^1 * Cc(";")) + * whitespace +local splitter = Ct(query * (separator * query)^0) + +helpers.querysplitter = splitter + -- I will add a bit more checking. local function validspecification(specification) @@ -263,411 +262,35 @@ local function preparetemplate(specification) report_state("no query template or templatefile") end +helpers.preparetemplate = preparetemplate -local function dataprepared(specification) - local query = preparetemplate(specification) - if query then - io.savedata(specification.queryfile,query) - os.remove(specification.resultfile) - if trace_queries then - report_state("query: %s",query) - end - return true - else - -- maybe push an error - os.remove(specification.queryfile) - os.remove(specification.resultfile) - end -end - -local function datafetched(specification,runner) - local command = replacetemplate(runner,specification) - if trace_sql then - local t = osclock() - report_state("command: %s",command) - local okay = os.execute(command) - report_state("fetchtime: %.3f sec",osclock()-t) -- not okay under linux - return okay == 0 - else - return os.execute(command) == 0 - end -end - -local function dataloaded(specification) - if trace_sql then - local t = osclock() - local data = io.loaddata(specification.resultfile) or "" - report_state("datasize: %.3f MB",#data/1024/1024) - report_state("loadtime: %.3f sec",osclock()-t) - return data - else - return io.loaddata(specification.resultfile) or "" - end -end - -local function dataconverted(data) - if trace_sql then - local t = osclock() - local data, keys = splitdata(data) - report_state("converttime: %.3f",osclock()-t) - report_state("keys: %s ",#keys) - report_state("entries: %s ",#data) - return data, keys - else - return splitdata(data) - end -end - -sql.splitdata = splitdata - --- todo: new, etc - -local function execute(specification) - if trace_sql then - report_state("executing client") - end - if not validspecification(specification) then - report_state("error in specification") - return - end - if not dataprepared(specification) then - report_state("error in preparation") - return - end - if not datafetched(specification,methods.client.runner) then - report_state("error in fetching, query: %s",string.collapsespaces(io.loaddata(specification.queryfile))) - return - end - local data = dataloaded(specification) - if not data then - report_state("error in loading") - return - end - local data, keys = dataconverted(data) - if not data then - report_state("error in converting") - return - end - local one = data[1] - if one then - setmetatable(data,{ __index = one } ) - end - return data, keys -end - -methods.client = { - runner = [[mysql --batch --user="%username%" --password="%password%" --host="%host%" --port=%port% --database="%database%" < "%queryfile%" > "%resultfile%"]], - execute = execute, - serialize = serialize, - deserialize = deserialize, - usesfiles = true, -} - -local function dataloaded(specification) - if trace_sql then - local t = osclock() - local data = table.load(specification.resultfile) - report_state("loadtime: %.3f sec",osclock()-t) - return data - else - return table.load(specification.resultfile) - end -end - -local function dataconverted(data) - if trace_sql then - local data, keys = data.data, data.keys - report_state("keys: %s ",#keys) - report_state("entries: %s ",#data) - return data, keys - else - return data.data, data.keys - end -end - -local function execute(specification) - if trace_sql then - report_state("executing lmxsql") - end - if not validspecification(specification) then - report_state("error in specification") - return - end - if not dataprepared(specification) then - report_state("error in preparation") - return - end - if not datafetched(specification,methods.lmxsql.runner) then - report_state("error in fetching, query: %s",string.collapsespaces(io.loaddata(specification.queryfile))) - return - end - local data = dataloaded(specification) - if not data then - report_state("error in loading") - return - end - local data, keys = dataconverted(data) - if not data then - report_state("error in converting") - return - end - local one = data[1] - if one then - setmetatable(data,{ __index = one } ) - end - return data, keys -end - -methods.lmxsql = { - runner = [[lmx-sql %host% %port% "%username%" "%password%" "%database%" "%queryfile%" "%resultfile%"]], - execute = execute, - serialize = serialize, - deserialize = deserialize, - usesfiles = true, -} - -local mysql = nil -local cache = { } - -local function validspecification(specification) - local presets = specification.presets - if type(presets) == "string" then - presets = dofile(presets) - end - if type(presets) == "table" then - setmetatable(presets,defaults) - setmetatable(specification,{ __index = presets }) - else - setmetatable(specification,defaults) - end - return true -end - -local dataprepared = preparetemplate - -local function connect(session,specification) - return session:connect( - specification.database or "", - specification.username or "", - specification.password or "", - specification.host or "", - specification.port - ) -end - -local whitespace = patterns.whitespace^0 -local eol = patterns.eol -local separator = P(";") -local escaped = patterns.escaped -local dquote = patterns.dquote -local squote = patterns.squote -local dsquote = squote * squote ----- quoted = patterns.quoted -local quoted = dquote * (escaped + (1-dquote))^0 * dquote - + squote * (escaped + dsquote + (1-squote))^0 * squote -local comment = P("--") * (1-eol) / "" -local query = whitespace - * Cs((quoted + comment + 1 - separator)^1 * Cc(";")) - * whitespace -local splitter = Ct(query * (separator * query)^0) +-- -- -- we delay setting this -- -- -- -local function datafetched(specification,query,converter) - if not query or query == "" then - report_state("no valid query") - return { }, { } - end - local id = specification.id - local session, connection - if id then - local c = cache[id] - if c then - session = c.session - connection = c.connection - end - if not connection then - session = mysql() - connection = connect(session,specification) - cache[id] = { session = session, connection = connection } - end - else - session = mysql() - connection = connect(session,specification) - end - if not connection then - report_state("error in connection: %s@%s to %s:%s", - specification.database or "no database", - specification.username or "no username", - specification.host or "no host", - specification.port or "no port" - ) - return { }, { } - end - query = lpegmatch(splitter,query) - local result, message, okay - for i=1,#query do - local q = query[i] - local r, m = connection:execute(q) - if m then - report_state("error in query, stage 1: %s",string.collapsespaces(q)) - message = message and format("%s\n%s",message,m) or m - end - local t = type(r) - if t == "userdata" then - result = r - okay = true - elseif t == "number" then - okay = true - end - end - if not okay and id then - if session then - session:close() - end - if connection then - connection:close() - end - session = mysql() -- maybe not needed - connection = connect(session,specification) - cache[id] = { session = session, connection = connection } - for i=1,#query do - local q = query[i] - local r, m = connection:execute(q) - if m then - report_state("error in query, stage 2: %s",string.collapsespaces(q)) - message = message and format("%s\n%s",message,m) or m - end - local t = type(r) - if t == "userdata" then - result = r - okay = true - elseif t == "number" then - okay = true - end - end - end - local data, keys - if result then - if converter then - data = converter(result,deserialize) - else - keys = result:getcolnames() - if keys then - local n = result:numrows() or 0 - if n == 0 then - data = { } - -- elseif n == 1 then - -- -- data = { result:fetch({},"a") } - else - data = { } - -- for i=1,n do - -- data[i] = result:fetch({},"a") - -- end - local k = #keys - for i=1,n do - local v = { result:fetch() } - local d = { } - for i=1,k do - d[keys[i]] = v[i] - end - data[#data+1] = d - end - end - end - end - result:close() - elseif message then - report_state("message %s",message) - end - if not keys then - keys = { } - end - if not data then - data = { } - end - if not id then - connection:close() - session:close() - end - return data, keys -end +local currentmethod -local function execute(specification) - if not mysql then - local lib = require("luasql.mysql") - if lib then - mysql = lib.mysql - else - report_state("error in loading luasql.mysql") - end - end - if trace_sql then - report_state("executing library") - end - if not validspecification(specification) then - report_state("error in specification") - return - end - local query = dataprepared(specification) - if not query then - report_state("error in preparation") - return - end - local data, keys = datafetched(specification,query,specification.converter) - if not data then - report_state("error in fetching") - return - end - local one = data[1] - if one then - setmetatable(data,{ __index = one } ) - end - return data, keys +local function firstexecute(...) + local execute = methods[currentmethod].execute + sql.execute = execute + return execute(...) end -methods.library = { - runner = function() end, -- never called - execute = execute, - serialize = serialize, - deserialize = deserialize, - usesfiles = false, -} - --- -- -- - -local currentmethod - function sql.setmethod(method) - local m = methods[method] - if m then - currentmethod = method - sql.execute = m.execute - return m - else - return methods[currentmethod] - end + currentmethod = method + sql.execute = firstexecute end sql.setmethod("client") -- helper: -local execute = sql.execute - function sql.usedatabase(presets,datatable) local name = datatable or presets.datatable if name then - local method = presets.method and sql.methods[presets.method] or sql.methods.client - local base = presets.database or "test" - local basename = format("`%s`.`%s`",base,name) - m_execute = execute - deserialize = deserialize - serialize = serialize - if method then - m_execute = method.execute or m_execute - deserialize = method.deserialize or deserialize - serialize = method.serialize or serialize - end - local execute + local method = presets.method and sql.methods[presets.method] or sql.methods.client + local base = presets.database or "test" + local basename = format("`%s`.`%s`",base,name) + local execute = nil + local m_execute = method.execute if method.usesfiles then local queryfile = presets.queryfile or format("%s-temp.sql",name) local resultfile = presets.resultfile or format("%s-temp.dat",name) @@ -756,74 +379,6 @@ sql.tokens = { -- -- -- -local converters = { } - -sql.converters = converters - -local template = [[ -local converters = utilities.sql.converters - -local tostring = tostring -local tonumber = tonumber -local toboolean = toboolean - -%s - -return function(result,deserialize) - if not result then - return { } - end - local nofrows = result:numrows() or 0 - if nofrows == 0 then - return { } - end - local data = { } - for i=1,nofrows do - local v = { result:fetch() } - data[i] = { - %s - } - end - return data -end -]] - -function sql.makeconverter(entries,deserialize) - local shortcuts = { } - local assignments = { } - local fields = { } - for i=1,#entries do - local entry = entries[i] - local nam = entry.name - local typ = entry.type - if typ == "boolean" then - assignments[i] = format("[%q] = toboolean(v[%s],true),",nam,i) - elseif typ == "number" then - assignments[i] = format("[%q] = tonumber(v[%s]),",nam,i) - elseif type(typ) == "function" then - local c = #converters + 1 - converters[c] = typ - shortcuts[#shortcuts+1] = format("local fun_%s = converters[%s]",c,c) - assignments[i] = format("[%q] = fun_%s(v[%s]),",nam,c,i) - elseif type(typ) == "table" then - local c = #converters + 1 - converters[c] = typ - shortcuts[#shortcuts+1] = format("local tab_%s = converters[%s]",c,c) - assignments[i] = format("[%q] = tab_%s[v[%s]],",nam,#converters,i) - elseif typ == "deserialize" then - assignments[i] = format("[%q] = deserialize(v[%s]),",nam,i) - else - assignments[i] = format("[%q] = v[%s],",nam,i) - end - fields[#fields+1] = format("`%s`",nam) - end - local code = string.format(template,table.concat(shortcuts,"\n"),table.concat(assignments,"\n ")) - local func = loadstring(code) - if type(func) == "function" then - return func(), concat(fields, ", ") - end -end - -- local func, code = sql.makeconverter { -- { name = "a", type = "number" }, -- { name = "b", type = "string" }, diff --git a/tex/context/base/x-xfdf.mkiv b/tex/context/base/x-xfdf.mkiv index c4ca0c427..460220ed9 100644 --- a/tex/context/base/x-xfdf.mkiv +++ b/tex/context/base/x-xfdf.mkiv @@ -60,6 +60,7 @@ \def\xfdfload #1#2{\xmlloadonly{#1}{#2}{xfdf}} \def\xfdfvalue#1#2{\xmlfirst{#1}{/xfdf/fields/field[@name='#2']/value}} +\def\xfdftext #1#2{\xmlfirst{#1}{/xfdf/fields/field[@name='#2']/value/paragraphs()}} % \startxmlsetups xfdf:b % \bold{\xmlflush{#1}} diff --git a/tex/generic/context/luatex/luatex-fonts-merged.lua b/tex/generic/context/luatex/luatex-fonts-merged.lua index 9862853db..844ff18cf 100644 --- a/tex/generic/context/luatex/luatex-fonts-merged.lua +++ b/tex/generic/context/luatex/luatex-fonts-merged.lua @@ -1,6 +1,6 @@ -- merged file : luatex-fonts-merged.lua -- parent file : luatex-fonts.lua --- merge date : 09/25/12 21:44:34 +-- merge date : 10/02/12 15:13:09 do -- begin closure to overcome local limits and interference @@ -2033,30 +2033,49 @@ function boolean.tonumber(b) end function toboolean(str,tolerant) - if str == true or str == false then - return str - elseif tolerant then - local tstr = type(str) - if tstr == "string" then - return str == "true" or str == "yes" or str == "on" or str == "1" or str == "t" - elseif tstr == "number" then - return tonumber(str) ~= 0 - elseif tstr == "nil" then - return false - else - return str - end + if str == nil then + return false + elseif str == false then + return false + elseif str == true then + return true elseif str == "true" then return true elseif str == "false" then return false + elseif not tolerant then + return false + elseif str == 0 then + return false + elseif (tonumber(str) or 0) > 0 then + return true else - return str + return str == "yes" or str == "on" or str == "t" end end string.toboolean = toboolean +function string.booleanstring(str) + if str == nil then + return false + elseif str == false then + return false + elseif str == true then + return true + elseif str == "true" then + return true + elseif str == "false" then + return false + elseif str == 0 then + return false + elseif (tonumber(str) or 0) > 0 then + return true + else + return str == "yes" or str == "on" or str == "t" + end +end + function string.is_boolean(str,default) if type(str) == "string" then if str == "true" or str == "yes" or str == "on" or str == "t" then |