Modul:Vorlage:Tabellendaten Skriptessa
Modulbeschreibung
Das Modul interpretiert Tabellendaten-Skripts für die Vorlage:Tabellendaten Skriptessa. Man kann damit Teile von Tabellendaten aus Wikimedia Commons abfragen und als Text einbinden. Das Modul wird im Buchtext über die Vorlage verwendet, nicht direkt mit #invoke.
Beispiel 1
BearbeitenDas folgende Beispiel sucht die Anzahl erfolgreicher Raketenstarts in die Erdumlaufbahn im Jahr 2000. Die direkte Einbindung aus einer Vorlage würde so aussehen:
{{#invoke:Vorlage:Tabellendaten Skriptessa|interpret |code= .:table t1= Data:Orbital launches by year.tab:. Es waren .:t1(success; year==2000):.. }}
Die Wikitext-Einbindung:
{{Tabellendaten Skriptessa |code= .:table t1= Data:Orbital launches by year.tab:. Es waren .:t1(success; year==2000):.. }}
Beide mit dem Ergebnis:
Es waren 81.
Beispiel 2
BearbeitenDas folgende Beispiel sucht die Jahre mit den meisten erfolgreicher Raketenstarts von 1960 bis 2000.
{{Tabellendaten Skriptessa |code= .:table t1= Data:Orbital launches by year.tab:. .:query q1= t1(*; year>=1960, year<=2000; orderBy=success, descending=1, limit=3):. Es waren die Jahre .:q1(year;;occurrence=1):., .:q1(year;;occurrence=2):. und .:q1(year;;occurrence=3):.. }}
Mit dem Ergebnis:
Es waren die Jahre 1 967, 1 984 und 1 976.
Beispiel 3
BearbeitenKurzes Beispiel mit einer Variable:
{{Tabellendaten Skriptessa |code=abc.:var s1=bla:.~.:s1:.}}
Ergebnis:
abc~bla
Beispiel 4
BearbeitenDie Abfrage t1q
ist ein Teil der ganzen Tabelle. Sie erleichtert die Ausgabe der einzelnen Werte im Text.
{{Tabellendaten Skriptessa |code= .:table t1= Data:Fraction tasks abacdeP80max.tab:. .:table t2= Data:Fraction tasks abacdeP80max solušn.tab:. .:var in= <div style="display:inline-block; min-width:70mm; margin:2mm 0 2mm 0;"><math>:. .:var out= </math></div>:. .:--Seitenbeginn (Kommentar)--:..:var pageindex= 0:. .:query t1q= t1(*; group==pageindex+1):..:var pageoffset= pageindex*10:. <div style="outline:yellow thin solid"> ====Aufgabe==== Berechne und kürze mit .:t1q(divisor; id==pageoffset+1):. und .:t1(divisor; id==pageoffset+2):.. .:var id= pageoffset + 1:. .:in:..:t1q(content; id==id++):.=.:out:. .:in:..:t1q(content; id==id++):.=.:out:. </div> <div style="outline:greenyellow thin solid"> ====Lösung==== .:query t2q= t2(*; group==pageindex+1):..:var pageoffset= pageindex*20:..:var id= pageoffset+1:. .:in:..:t2q(content; id==id++):.?.:out:. .:in:..:t2q(content; id==id++):.?.:out:. </div> }}
Ergebnis:
Beispiel 5
BearbeitenBeispiel mit Tabellenauszug. Der Abfrageausdruck gibt mehrere Werte zurück, und zwar im folgenden Format:
| Z1S1 || Z1S2 .. |- | Z2S1 || Z2S2 ..
Der Kopf und Fuß der Wiki-Tabelle muss noch hinzugefügt werden. Die dafür nötigen Zeichen {,|,} sind codiert. Die Tabelle kann nämlich erst nach dem Skriptessa-Durchlauf in eine hübsche Html-Tabelle umgewandelt werden, vorher ist sie ja leer. Wenn man den Tabellenquelltext sehen will, muss man um die Tabelle <pre> und </pre> schreiben. Wieder html-codiert, aus demselben Grund.
Das .::. soll nur einen Absatz verhindern, es kann auch weggelassen werden. Absätze und Leerzeichen zwischen .:Befehlsblöcken:., auch .::., werden entfernt.
{{Tabellendaten Skriptessa |code= .:table t1= Data:Fraction tasks random chart values.tab:. .::.{| class="wikitable" .:t1(*; id<=5):. |}}}
Ergebnis:
1 | 0,63 | 5 | 8 | 8 | 4 | 99 | \frac{5}{8} | \frac{5}{8} | \frac{5}{8} | 1 | false |
2 | 0,5 | 4 | 8 | 8 | 4 | 99 | \frac{1}{2} | \frac{4}{8} | \frac{1}{2} | 4 | false |
3 | 0,8 | 8 | 10 | 10 | 5 | 99 | \frac{4}{5} | \frac{8}{10} | \frac{4}{5} | 2 | false |
4 | 0,56 | 5 | 9 | 9 | 99 | 3 | \frac{5}{9} | \frac{5}{9} | \frac{5}{9} | 1 | false |
5 | 0,43 | 3 | 7 | 7 | 99 | 99 | \frac{3}{7} | \frac{3}{7} | \frac{3}{7} | 1 | false |
Beispiel 6
BearbeitenEine Query-Abfragetabelle kann auch im Quelltext als Json eingegeben werden oder eine bestehende Query-Abfrage mit einer Spalte kann mit einzelnen Werten erweitert werden:
{{Tabellendaten Skriptessa |code= .:jsonquery q1= {"schema": {"fields": [{"name": "val", "type": "number"}]}, "data": [[2], [3]]}:. {| class="wikitable" .:q1(*):. |} .:query q2= q1(*) + 4 + (q1(*;;occurrence=1)*100) + 1:. .:query q3= q2(*;;orderBy=val):. .:var n=1:. .:q3(*;;occurrence=n++):., .:q3(*;;occurrence=n++):., .:q3(*;;occurrence=n++):., .:q3(*;;occurrence=n++):., .:q3(*;;occurrence=n++):.}}
Ergebnis:
2 |
3 |
1, 2, 3, 4, 200
Aufruf mit interpret
Bearbeiten
Verwendung:{{#invoke:Vorlage:Tabellendaten Skriptessa|interpret|code=Skript-text}}
Skript-Syntax
BearbeitenCodekomponenten sind Befehle, Ausdrücke und normaler Text. Befehle und Ausdrücke stehen zwischen .:
und :.
.
Befehle
Bearbeiten.:table t=Data:ABC.tab:.
- Erstellt eine Tabelle aus der angegebenen Commons Datenquelle.
Data:
kann weggelassen werden.t
ist ein wählbarer Name.
.:query q= t(col1, col2, ..; condition1, condition2, ..; option-list):.
- Erstellt eine Teil-Tabelle (Abfrage) aus einer anderen Tabelle.
t
ist alstable
oder als eine anderequery
deklariert.- col1.. sind die Spalten, * ist hier statt der ganzen Liste erlaubt, um alle Spalten zu erhalten.
- condition1.. sind Bedingungen, die alle gelten müssen. Als Operator sind ==, <=, >=, <, >, <> möglich. Rechts vom Operator sind einfache Rechenausdrücke möglich (siehe unten). Die Bedingungsliste kann weggelassen werden, um alle Zeilen zu erhalten.
- Optionen sind
orderBy=Spaltenname
,descending=1
,limit=Zahl
,occurrence=Zahl
oderoccurrenceLooped=Zahl
. Sinnvolle Option-Kombinationen werden mit,
kombiniert. Die orderBy Spalte muss auch im Ergebnis vorkommen. Die Zahlenangaben für limit, occurrenceLooped und occurrence können auch Rechenausdrücke oder einzelne Variablen sein. Bei occurrenceLooped wird wieder von vorne begonnen, wenn die Zahl größer als die Liste lang ist, bei occurrence wird nichts zurückgegeben (und eine Warnung). Die Optionenliste kann natürlich weggelassen werden.
.:query q= t(col1, col2, ..; condition1, condition2, ..; option-list) + Ausdruck + Ausdruck..:.
- Erweitert eine Tabelle mit einer Spalte um weitere Werte (Zeilen). Diese Werte (Ausdrücke) müssen dem Typ der Tabellenspalte entsprechen. Der kann "number", "string" oder "boolean" sein. Die Werte müssen sich also gegebenenfalls in eine Zahl (number) oder einen Binärwert wie 1 oder 0 (boolean) umwandeln lassen.
.:jsonquery q= json:.
- Erstellt eine Tabelle aus einem Json Text.
.:var v= Ausdruck:.
- Der Ausdruck kann ein Text, eine Zahl, eine Variable, eine Abfrage (query) oder ein Rechenausdruck damit sein (siehe unten). Der Ausdruck selbst hat keine
.:
:.
. Beispiel:.:var a= 1+2.5:.
. Wird eine Abfrage (query) einer Variable zugewiesen, enthält die Variable den oder die Werte der Abfrage (etwa Text, eine Zahl..).
- Der Ausdruck kann ein Text, eine Zahl, eine Variable, eine Abfrage (query) oder ein Rechenausdruck damit sein (siehe unten). Der Ausdruck selbst hat keine
.:--Kommentar:.
.:!--Kommentar2:.
- Wird als Meldung in der Seitenvorschau angezeigt.
.:!Ausdruck:.
- Alle Ausdrücke (ohne .: :.) können auch als Meldung in der Seitenvorschau angezeigt werden.
.:!!vars:.
,.:!!tables:.
,.:!!queries:.
- Gibt eine Liste der Variablen, Tabellen, Abfragen als Meldung in der Seitenvorschau aus. Die Liste enthält die Namen und die Anzahl der Einträge (Abfragen), Variablenwerte (Variablen), Data:Datenquellen (Tabellen).
.:option localize= 0:.
,.:option localize= de:.
,.:option localize= deKomma:.
,.:option localize= deHKomma:.
- Steuert die lokale Zahlendarstellung (mit Beistrichkomma) in Abfragen und Rechenausdrücken. 0 ist Punktkomma, de ist Tauserderpunkt und Beistrichkomma, deKomma ist Beistrichkomma, deHKomma oder 1 ist kleiner Tausenderabstand und Beistrichkomma. Standardmäßig ist deHKomma eingeschaltet. Das betrifft nur die Ausgabe, nicht die Eingabe. Zahlen in Ausdrücken müssen mit Punktkomma eingegeben werden.
Ausdrücke
Bearbeiten.:t(col1, col2, ..; condition1, condition2, ..; option-list):.
- Die direkte Abfrage einer Commons-Datentabelle (
t
ist alstable
deklariert, siehe oben). Sie wird als Text ausgegeben.
- Die direkte Abfrage einer Commons-Datentabelle (
.:q(col1, col2, ..; condition1, condition2, ..; option-list):.
- Die Abfrage einer anderen Abfrage (
q
ist alsquery
deklariert, siehe oben). Sie wird als Text ausgegeben.
- Die Abfrage einer anderen Abfrage (
.:v:.
- Die einzelne Variable (oder der wörtliche Text) wird ausgegeben (Variablen werden mit
var
deklariert, siehe oben).
- Die einzelne Variable (oder der wörtliche Text) wird ausgegeben (Variablen werden mit
.:v + w:.
,.:v - w:.
,.:v * w:.
,.:v / w:.
,.:v + w * x:.
,.:v / (w - x):.
,.:v + q(col1; condition1, condition2, ..; option-list):.
...- Rechenausdrücke mit +,-,*,/ und Klammern sind möglich. Rechenausdrücke mit Variablen oder Abfragen funktionierten natürlich nur, wenn sie genau eine Zahl ergeben. Die Mal/ Dividiertrechnung kommt vor der Plus/ Minusrechnung, wie üblich. Ein Rechenausdruck kann auch woanders verwendet werden, z.B. in Abfrage-Bedingungen (siehe oben).
.:v++:.
- Der Operator ++ wertet die Variable aus und erhöht nachher deren Wert um 1. Der Operator ++ akzeptiert nur eine Variable.
Text
BearbeitenAlles außerhalb der .:Befehl/ Ausdruck:. Syntax wird weitgehend unverändert ausgegeben. Nur gewisse Html-Entitäten werden ersetzt: < durch <
, [ durch [
, = durch =
, + durch +
, : durch :
, { durch {
, | durch |
, } durch }
(siehe auch das Ende der Funktion p.interpret). Abstände und Zeilenumbrüche werden nur direkt zwischen .:Befehl/ Ausdruck:. Komponenten entfernt. Gegebenenfalls kann ein .::.
eingefügt werden.
Codekomponenten-Beispiele
Bearbeiten.:query t1q= t1(*; group==pageindex+1):.
wobei t1 eine table Variable ist, mittable t1=ABC.tab
, und pageindex eine numerische var Variable, mitvar pageindex=123
..:var pageoffset= pageindex*10:.
.:t1q(divisor; id==pageoffset+1):.
.:var id= 2 * pageoffset + 1:.
setzt die id Variable auf das Doppelte der pageoffset Variable, plus 1..:t1q(content; id==id++):.
ergibt die content-Spalte mit id, die Variable id selbst ist aber nun um 1 höher.
Skriptcode Richtlinien
BearbeitenHtml-Tags sollen nicht mit der .:Befehl/ Ausdruck-Syntax:. überlappen. Also einem <
sollte ein >
folgen, bevor ein .:
mit irgendeinem Befehl beginnt (und mit :.
endet). Man kann < statt < für verschachtelte Konstruktionen verwenden.
Wikisyntax darf nicht mit der .:Befehl/ Ausdruck-Syntax:. überlappen. Also, [[Hauptseite]].:bla:.
ist ok, <math>.:bla:.</math>
ist nicht ok. Man kann [ statt [ und < statt < verwenden, z.B. <math>
.
Modultests
Bearbeitensiehe Vorlage:Tabellendaten Skriptessa/ Modultest 1 (ev. auch als Beispiel nützlich)
Funktionen
_parseEvalStr
Parses an eval string with numbers, vars, results of queries
and operators +, -, * , / or ++. Returns {data=, length=,
type="string" or "number" or "boolean"}.
Acesses tables, queries, vars, ginstr, glang_formatNum
vars= {id="3"}; p._parseEvalStr( "id + 2 * 5" )
_parseQueryStr
Parses a query string and queries the result. Returns {string=, table=, length=,
type="string" or "number" or "boolean"} from p._lookup() (see there).
Acesses tables, queries, vars, ginstr, glang_formatNum
vars= {id=2}; p._parseQueryStr( "tab1(cont; id==id++, group>=2; limit=3)" )
_lookup
Queries rows by col==condition pairs, with other options, returns {string=, table=, length=,
type="string" or "number" or "boolean"} to choose from. Table has Commons Data table schema like
{data= {{1,"a"}, {2,"b"}}, schema= {fields= {{name="id", type="number"}, {name="cont", type="string"}}}}
original source: w:en:Module:Tabular data by w:en:User:Mxn (permalink, changed)
- param where= {{searchColumnName=, searchValue=, or searchPattern=,
- compare=function(a,b)..}, {same again}, ..} (may be {})
- param opts= {orderBy=, descending=, limit=, occurrence=, occurrenceLooped} (optional, also {})
Reads global variable ginstr i.a.
example p._lookup( mw.ext.data.get("Data:Fraction tasks abacdeP80max.tab"),
{"id", "content"}, {searchColumnName="group", searchValue=1, compare=function(a,b) return a==b end},
{limit=3} ).string
interpret
Interprets a code like in
{{#invoke:Vorlage:Tabellendaten Skriptessa|interpret|code=
.:table t1= "Data:Fraction tasks abacdeP80max.tab":. .:var s1= "<hr>":. .:var id= 3:.
.:t1(content; id==id++, group==1):. .:s1:. jo}}
(main function)
- Diese Dokumentation wurde mithilfe von Modul:LuaDokumentation erstellt und befindet sich im Quelltext.
- Liste der Unterseiten
---{{:{{FULLPAGENAME}}/Doku}}
local p = {}
-- ''Tabular data script interpreter'' for the template [[Vorlage:Tabellendaten Skriptessa]] <br>or<br>
-- <code><nowiki>{{#invoke:Vorlage:Tabellendaten Skriptessa|interpret|code=abc.:var s1=bla:.~.:s1:.}}</nowiki></code><br>
-- {{#invoke:Vorlage:Tabellendaten Skriptessa|interpret|code=abc.:var s1=bla:.~.:s1:.}}<br>
-- splits str at regex pattern and returns table of parts
function splitOutsideBrackets( str, pattern )
str= mw.ustring.gsub(str, pattern, "ᙾ")
str= mw.ustring.gsub(str, "%b()", function(a) return mw.ustring.gsub(a, "ᙾ", pattern) end)
--mw.addWarning( "split "..mw.dumpObject( str ) )
return mw.text.split( str, "ᙾ", true)
end
-- inserts brackets for uniformity and operator precedence, for _parseEvalStr()
-- eg. in 1+a(b+c(d+e))/(id++)*(1+2) returning (1)+(((a(b+c(d+e)))/((id++)))*((1+2)))
function opPrecedence(ev)
local evin= ev; local evout= ""
for i= 1, 50 do -- a+b*c/d match a, +, remainder each iteration with operees a,b..
-- operee is -?[%w_.]+ or [%w_]*%b() matching -1.5, id, a(b+c(d+e)), (id++), (1+2)
ms= {mw.ustring.match(evin, "^(-?[%w_.]+)%s*([+-%*/])%s*(.*)$")}
if #ms==0 then ms= {mw.ustring.match(evin, "^([%w_]*%b())%s*([+-%*/])%s*(.*)$")} end
if #ms~=0 then
evout= evout.."("..ms[1]..")"..ms[2]
evin= ms[3]
else
evout= evout.."("..evin..")"
break
end
end
--mw.addWarning( "evalstr (..)brackets "..mw.dumpObject(evout) )
evin= evout; evout= ""
for i= 1, 50 do -- (a)+(b)*(c)/(d) match (a), +, (b), remainder each iteration
ms= {mw.ustring.match(evin, "^(%b())%s*([+-%*/])%s*(%b())%s*(.*)$")}
if #ms~=0 then
if ms[2]=="*" or ms[2]=="/" then
evin= "("..ms[1]..ms[2]..ms[3]..")"..ms[4]
else
evout= evout..ms[1]..ms[2]
evin= ms[3]..ms[4]
end
else
evout= evout..evin
break
end
end
--mw.addWarning( "evalstr ..+(..*..)brackets "..ev.."--"..evout)
return evout
end
--trim ((brackets over all)) ..and keep (1+2)+(3+4), for _parseEvalStr()
--returns result
function removeOverallBrackets(ev)
local ms= {}
for i= 1, 5 do
ms= {mw.ustring.match(ev, "^%s*(%b())%s*$")}
if #ms==1 then
ev= mw.ustring.match(ms[1], "^%((.*)%)$")
else
break
end
end
return ev
end
--- Parses an eval string with numbers, vars, results of queries
-- and operators +, -, * , / or ++. Returns {data=, length=,
-- type="string" or "number" or "boolean"}.
-- Acesses tables, queries, vars, ginstr, glang_formatNum<br>
-- example <code> vars= {id="3"}; p._parseEvalStr( "id + 2 * 5" ) </code>
function p._parseEvalStr( ev )
local ms= {}; local out; local outType= "string"; local outLength= 1
--trim (brackets over all), whitespace, set precedence brackets, trim () again
ev= removeOverallBrackets(ev)
ev= mw.text.trim(ev) --ev= mw.ustring.match(ev, "^%s*(.*)%s*$")
local evprec= opPrecedence(ev)
evprec= removeOverallBrackets(evprec)
evprec= mw.text.trim(evprec)
--mw.addWarning( "evalstr brackets "..ev.."-->"..evprec)
--quoted text
if (function()
ms= {mw.ustring.match(ev, '^"(.*)"$') or mw.ustring.match(ev, "^'(.*)'$")}
return #ms~=0
end)() then
out= ms[1]
--1 element op like "id++" var is "..vardef.."
elseif (function()
ms= {mw.ustring.match(ev, "^("..vardef..")%+%+$")}
return #ms~=0
end)() then
local v= ms[1]
assert(vars[v], "Variable not found near "..ginstr..v)
assert(tonumber(vars[v].data), "Conversion to number failed near "..ginstr..mw.dumpObject(vars))
out= vars[v].data
vars[v].data= vars[v].data+1
outType= "number"
--2+ element op like "id+1", "a+b+c(d(e))"
-- operee is %b() on evprec
elseif (function()
ms= {mw.ustring.match(evprec, "^(%b())%s*([+-%*/])%s*(.*)$")}
return #ms~=0
end)() then
local opees= {ms[1], ms[3]}
local op= ms[2]
for i, opee in ipairs(opees) do
opees[i]= p._parseEvalStr( opee ).data
assert(tonumber(opees[i]), "Conversion to number failed near "..ginstr..mw.dumpObject(opees[i]))
end
--mw.addWarning("eval aOPb "..opees[1]..op..opees[2])
if op=="+" then
out= opees[1]+opees[2]
elseif op=="-" then
out= opees[1]-opees[2]
elseif op=="*" then
out= opees[1]*opees[2]
elseif op=="/" then
out= opees[1]/opees[2]
else
assert(false, "Unknown +*/- operator near "..ginstr)
end
outType= "number"
--single operee with bracket like a(b;c+d)
elseif (function()
ms= {mw.ustring.match(ev, "^([%w_]+)%b()$")}
return #ms~=0
end)() then
local o= p._parseQueryStr(ev)
out, outType, outLength= o.string, o.type, o.length
--mw.addWarning("eval querytable "..ev.."= "..out)
--single operee without bracket, like id
else
local pt= mw.ustring.match(ev, "^([%w_]+)$")
if pt and vars[pt] ~= nil then
local o= vars[pt]
out, outType= o.data, o.type
else
out= ev
end
end
--mw.addWarning("eval Result "..ev.."= "..mw.dumpObject({data= out, type= outType}))
return {data= out, type= outType, length= outLength}
end --p._parseEvalStr
--- Parses a query string and queries the result. Returns {string=, table=, length=,
-- type="string" or "number" or "boolean"} from p._lookup() (see there).
-- Acesses tables, queries, vars, ginstr, glang_formatNum<br>
-- example <code> vars= {id=2}; p._parseQueryStr( "tab1(cont; id==id++, group>=2; limit=3)" ) </code>
function p._parseQueryStr( q )
local tab, optstr= mw.ustring.match(q, "^%s*("..vardef..")%s*%((.*)%)%s*$")
local opts= {}; local o= ""
for i, match in ipairs(splitOutsideBrackets(optstr, ";")) do
opts[i]= {}
for j, mat in ipairs(splitOutsideBrackets(match, ",")) do
opts[i][j]= mw.text.trim(mat)
end
end
--mw.addWarning( "parsequery all query opts "..mw.dumpObject( opts ) )
local outcolnames= opts[1];
local where= {}; local opts2= {}
if opts[2] and opts[2][1] and opts[2][1]~="" then opts2= opts[2] end
for i, cond in ipairs(opts2) do --"id==id++"," group<>1"
local lt, op, rt= mw.ustring.match(cond, "^%s*([%w_]+)%s*([=<>][=<>]?)%s*(.*)%s*$")
local opf= function() end
if op=="==" then
opf= function(a, b) return a==b end
elseif op=="<=" then
opf= function(a, b) return a<=b end
elseif op==">=" then
opf= function(a, b) return a>=b end
elseif op=="<" then
opf= function(a, b) return a<b end
elseif op==">" then
opf= function(a, b) return a>b end
elseif op=="<>" then
opf= function(a, b) return a~=b end
else
assert(false, "Unknown <>= operator near "..ginstr)
end
local rtp= p._parseEvalStr(rt).data
where[#where+1]= {searchColumnName= lt, searchValue= rtp, compare= opf}
end
--mw.addWarning( "parsequery where "..mw.dumpObject(where) )
local postopts= {}; local opts3= {}
if opts[3] and opts[3][1] and opts[3][1]~="" then opts3= opts[3] end
for i, po in ipairs(opts3) do --"descending=1, limit=3" "occurrence=o++" "orderBy=id"
local lt= mw.ustring.match(po, "^%s*(%w+).*$")
local rt= mw.ustring.match(po, "^%s*%w+%s*=%s*(.*)%s*$")
if not rt then rt= "1" end
if lt=="occurrence" or lt=="occurrenceLooped" or lt=="limit" then
rt= p._parseEvalStr(rt).data
end
postopts[lt]= rt
end
--mw.addWarning("parsequery postopts "..mw.dumpObject(postopts) )
--ready: tab, outcolnames, where, postopts
--mw.addWarning("parsequery "..q.. mw.dumpObject(tab) )
local source
if tables[tab] then source= tables[tab]
elseif queries[tab] then source= queries[tab]
end
return p._lookup( source, outcolnames, where, postopts )
end --p._parseQueryStr
-- returns a string-dump of a table, for _lookup()
-- for 2+ fields return style is wikitable format and localized numbers i.a.
-- * param ''data'' in Commons Data table schema
-- example <code> dumpRecords( {data= {{1,"a"}, {2,"b"}},
-- schema= {fields= {{name="id", type="number"}, {name="cont", type="string"}}}} ) </code>
function dumpRecords( data )
outStr= ""
if #data.data==1 and #data.data[1]==1 then
outStr= tostring(data.data[1][1])
else
outStr= "| "
for i, record in ipairs(data.data) do
if i~=1 then outStr= outStr.."\n|-\n| " end
for j, col in ipairs(record) do
if j~=1 then outStr= outStr.."|| " end
outStr= outStr..glang_formatNum({data=col, type=data.schema.fields[j].type})
end
end
end
--mw.addWarning( "dumpRecords "..mw.dumpObject(record) ) --addWarning doesnt show same messages twice!
return outStr
end
-- returns retyped/ typechecked var, for _lookup()
function typecast(s, typ)
local v= s
if typ == "number" then
v= assert(tonumber(s), "Number expected near "..ginstr..mw.dumpObject(s))
elseif typ == "boolean" then
v= s ~= "false" and s ~= "0"
end
--mw.addWarning( "typecast "..s.." "..mw.dumpObject( v==false ) )
return v
end
--- Queries rows by col==condition pairs, with other options, returns {string=, table=, length=,
-- type="string" or "number" or "boolean"} to choose from. Table has Commons Data table schema like
-- {data= {{1,"a"}, {2,"b"}}, schema= {fields= {{name="id", type="number"}, {name="cont", type="string"}}}}<br>
-- ''original source'': [[w:en:Module:Tabular data]] by [[w:en:User:Mxn]] ([[Special:PermaLink/979265259|permalink]], changed)<br>
-- * param ''where''= {{searchColumnName=, searchValue=, or searchPattern=,
-- : compare=function(a,b)..}, {same again}, ..} (may be {})
-- * param ''opts''= {orderBy=, descending=, limit=, occurrence=, occurrenceLooped} (optional, also {})
-- Reads global variable ginstr i.a.<br>
-- example <code> p._lookup( mw.ext.data.get("Data:Fraction tasks abacdeP80max.tab"),
-- {"id", "content"}, {searchColumnName="group", searchValue=1, compare=function(a,b) return a==b end},
-- {limit=3} ).string </code>
function p._lookup(data, outputColumnNames, where, opts)
assert(data, "Table or query not found near "..ginstr)
--//colIdxByName, colTypeByName, colNameByIdx
local colIdxByName, colTypeByName, colNameByIdx= {}, {}, {}
for i, field in ipairs(data.schema.fields) do
colIdxByName[field.name]= i
colTypeByName[field.name]= field.type
colNameByIdx[i]= field.name
end
--mw.addWarning( "colIdxByName "..mw.dumpObject( colIdxByName ) )
--//outputColumnNames column check
assert(outputColumnNames[1] ~= nil and outputColumnNames[1] ~= "", "Output cols empty near "..ginstr)
if outputColumnNames[1] == "*" and #outputColumnNames == 1 then
outputColumnNames= colNameByIdx
end
for j, outputColumnName in ipairs(outputColumnNames) do
assert(colIdxByName[outputColumnName], "Output column not found near "..ginstr)
end
local outColIdxByName= {}
for i, name in ipairs(outputColumnNames) do
outColIdxByName[name]= i
end
--mw.addWarning( "lookup outputColumnNames "..mw.dumpObject( outputColumnNames) )
--//where column check==
for j, condition in ipairs(where) do
assert(condition.searchColumnName ~= nil and condition.searchColumnName ~= "",
"Where.colname empty near "..ginstr)
local typ= assert(colTypeByName[condition.searchColumnName],
"Where.colname not found near "..ginstr)
where[j].searchValue= typecast(where[j].searchValue, typ)
end
--mw.addWarning( "lookup where "..mw.dumpObject( where ) )
--//query
local matchdata= {data= {}, schema= {fields= {}}}
for j, outputColumnName in ipairs(outputColumnNames) do
matchdata.schema.fields[#matchdata.schema.fields+1]= {
name= outputColumnName,
type= colTypeByName[outputColumnName]
}
end
for i, record in ipairs(data.data) do --each record row in data
local isMatch= true
for j, condition in ipairs(where) do --each conditon in where
isMatch= isMatch and (
(condition.searchValue~=nil and
condition.compare( record[ colIdxByName[condition.searchColumnName] ],
condition.searchValue) ) or
(condition.searchPattern and
mw.ustring.match(tostring(record[ colIdxByName[condition.searchColumnName] ]),
condition.searchPattern)))
--mw.addWarning( "i j isMatch "..i..j..tostring(isMatch))
end
if isMatch then
local matchingRecord= {}
for j, outputColumnName in ipairs(outputColumnNames) do
matchingRecord[#matchingRecord+1]= record[ colIdxByName[outputColumnName] ]
end
matchdata.data[#matchdata.data+1]= matchingRecord
end
end
--mw.addWarning( "lookup matchdata "..mw.dumpObject( matchdata ) )
--//post processing options
if opts.orderBy then
local ocoli= assert(outColIdxByName[opts.orderBy], "OrderBy column not found near "..ginstr
..opts.orderBy..mw.dumpObject(outColIdxByName))
table.sort( matchdata.data, function(a,b) return a[ocoli]<b[ocoli] end )
--mw.addWarning( "lookup opts order "..mw.dumpObject( matchdata.data ) )
end
if opts.descending then
local d2= {}
for i, record in ipairs(matchdata.data) do
d2[#matchdata.data-i+1]= record
end
matchdata.data= d2
--mw.addWarning( "lookup opts desc "..mw.dumpObject( matchdata.data ) )
end
if opts.limit then
local l= assert(tonumber(opts.limit), "Limit nonumber error near "..ginstr)
l= math.abs(math.floor(l))
for i= l+1, #matchdata.data do
table.remove(matchdata.data)
end
--mw.addWarning( "lookup opts limit "..mw.dumpObject( matchdata.data ) )
end
if opts.occurrence or opts.occurrenceLooped then
if opts.occurrenceLooped then
opts.occurrence= opts.occurrenceLooped
end
local o= assert(tonumber(opts.occurrence), "Occurrence nonumber error near "..ginstr)
o= math.abs(math.floor(o))
local md= {matchdata.data[o]} or {}
if opts.occurrenceLooped then
md= {matchdata.data[(o-1)%#matchdata.data+1]}
end
matchdata.data= md
if #matchdata.data==0 then
mw.addWarning("<span style='color:#d33; font-size:120%'>"
.."Warning: No occurrence, near "..ginstr..", "..o..". Liste aus?</span>")
end
--mw.addWarning( "lookup opts occ "..mw.dumpObject( matchdata.data ))
end
local datatype= "string"
if #matchdata.data==1 and #matchdata.data[1]==1 then
datatype= matchdata.schema.fields[1].type
end
--mw.addWarning( "lookup datatype "..mw.dumpObject( datatype ))
return {["string"]= dumpRecords( matchdata ), ["table"]= matchdata,
["type"]= datatype, length= #matchdata.data}
end --p._lookup()
-- interprets code line, for interpret()
-- Acesses tables, queries, vars, ginstr, glang_formatNum<br>
function interpretLine(instr)
vardef= "[%a_][%w_]*"
--mw.addWarning("interpretLine <pre>"..instr.."</pre>")
--//commands
local out= ""; local ms= {} --matches
--.:table tab1="dd.tab":.
if (function()
ms= {mw.ustring.match(instr, "^%.:%s*table%s+("..vardef..")%s*=%s*(.+)%s*:%.")}
return #ms~=0
end)() then
local key, val= unpack(ms)
val= mw.ustring.match(val, "^Data%:(.+)$") or val --ä
tables[key]= assert( mw.ext.data.get(val), "Table not found near "..ginstr)
tables[key]["filename"]= val
--.:var id==1+1:. .:var s=="-+-":.
elseif (function()
ms= {mw.ustring.match(instr, "^%.:%s*var%s+("..vardef..")%s*=%s*(.+)%s*:%.")}
return #ms~=0
end)() then
vars[ms[1]]= p._parseEvalStr(ms[2])
--mw.addWarning( "vars "..ms[1].."="..ms[2]..mw.dumpObject( vars ) )
--.:query q1= t1(col; id==1,g==2; occurrence=3):.
elseif (function()
ms= {mw.ustring.match(instr,
"^%.:%s*query%s+("..vardef..")%s*=%s*("..vardef.."%s*%b())%s*:%.")}
return #ms~=0
end)() then
local key, val= unpack(ms)
queries[key]= p._parseQueryStr(val).table
--mw.addWarning( "queries "..mw.dumpObject( queries ) )
--.:query q2= q2(*)+123+q3(n;;occurrence=1)+"ww":. q+
elseif (function()
ms= {mw.ustring.match(instr,
"^%.:%s*query%s+("..vardef..")%s*=%s*("..vardef.."%s*%b())%s*%+%s*(.+)%s*:%.")}
return #ms~=0
end)() then
local key, qu, evin= unpack(ms)
local pqu= p._parseQueryStr(qu).table
assert(#pqu.schema.fields==1, "Attempted to + on query with >1 column near "..ginstr)
local pev
for i= 1, 50 do -- a+(b+c)+d(ef)+"g"+'h' match a, remainder each iteration with operees a,b..
-- operee is [%w_.]+ or [%w_]*%b() or others matching 1.5, id, a(b+c(d+e))..
ms= {mw.ustring.match(evin, "^([%w_.]+)%s*%+%s*(.*)$")}
if #ms==0 then ms= {mw.ustring.match(evin, "^([%w_]*%b())%s*%+%s*(.*)$")} end
if #ms==0 then ms= {mw.ustring.match(evin, "^('[^']*')%s*%+%s*(.*)$")} end
if #ms==0 then ms= {mw.ustring.match(evin, '^("[^"]*")%s*%+%s*(.*)$')} end
pev= p._parseEvalStr(ms[1] or evin)
assert(pev.length==1, "Single result value expected in query "..(ms[1] or evin)
.." near "..ginstr)
pqu.data[#pqu.data+1]= {typecast(pev.data, pqu.schema.fields[1].type)}
if #ms~=0 then
evin= ms[2]
else
break
end
end
queries[key]= pqu
--mw.addWarning( "query "..key..": "..mw.dumpObject( queries[key] ) )
--.:jsonquery j= {"schema": {"fields": [{"name": "id", "type": "number"}]}, "data": [[1]]}:.
elseif (function()
ms= {mw.ustring.match(instr, "^%.:%s*jsonquery%s+("..vardef..")%s*=%s*(.+)%s*:%.")}
return #ms~=0
end)() then
local key, val= unpack(ms)
queries[key]= mw.text.jsonDecode(val)
--mw.addWarning( "queries "..mw.dumpObject( queries ) )
--.:--comment:.
elseif mw.ustring.match(instr, "^%.:%s*%-%-.*:%.$") then
local i= 1
--.:!--logcomment:.
elseif (function()
ms= {mw.ustring.match(instr, "^%.:%s*!%-%-(.+)%s*:%.$")}
return #ms~=0
end)() then
if ms[1] then mw.addWarning( "--"..ms[1] ) end
--.:option localize= deHKomma:.
elseif (function()
ms= {mw.ustring.match(instr, "^%.:%s*option%s*localize%s*=%s*(.+)%s*:%.$")}
return #ms~=0
end)() then
goptionLocalize= ms[1]
--//expressions
--.:!!vars:. table, query or var list
elseif (function()
ms= {mw.ustring.match(instr, "^%.:%s*!!%s*(.+)%s*:%.$")}
return #ms~=0
end)() then
local dic= {vars= vars, queries= queries, tables= tables}
if dic[ms[1]] then
local ot= {}
for k, v in pairs(dic[ms[1]] or {}) do
if ms[1]=="vars" then
ot[#ot+1]= ""..k.."= "..v.data.." ("..v.type..")"
elseif ms[1]=="queries" then
ot[#ot+1]= ""..k.." ("..#v.data.." items)"
else
ot[#ot+1]= ""..k.."= "..v.filename.." ("..#v.data.." items)"
end
end
table.sort(ot)
mw.addWarning(ms[1]..": <pre>"..table.concat(ot, "\n").."</pre>")
end
--.:tab1(*; id==id++):. .:id:. .:!q(width)*q(height):.
elseif (function()
ms= {mw.ustring.match(instr, "^%.:(!?)%s*(.*)%s*:%.$")}
return #ms~=0
end)() then
local o= p._parseEvalStr(ms[2])
local outex= glang_formatNum(o)
--.:!debugOutput:. or .:normalOutput:.
if ms[1]=="!" then
mw.addWarning(ms[2].."= "..outex)
else
out= out..outex
end
--plain text without .::.
else
out= instr
end
return out
end
--- Interprets a code like in<br>
-- <code><nowiki> {{#invoke:Vorlage:Tabellendaten Skriptessa|interpret|code=
-- .:table t1= "Data:Fraction tasks abacdeP80max.tab":. .:var s1= "&lt;hr>":. .:var id= 3:.
-- .:t1(content; id==id++, group==1):. .:s1:. jo}} </nowiki></code> (main function)
function p.interpret(frame)
--if true then return mw.dumpObject(splitOutsideBrackets("ab(ac(dae)f)gah", "a")) end
code= frame.args.code
code= mw.ustring.gsub(code, "ᙾ", "x") --separator ᙾ
code= mw.ustring.gsub(code, ":%.%s*%.:", ":..:") --dont trim whitespace around wikisyntax strip markers
code= mw.ustring.gsub(code, ":%.", ":.ᙾ")
code= mw.ustring.gsub(code, "%.:", "ᙾ.:")
code= mw.ustring.gsub(code, "ᙾᙾ", "ᙾ")
instrs= {}
for match in mw.ustring.gmatch(code, "[^ᙾ]+") do
instrs[#instrs+1]= match
end
--
ginstr= "" --like-global (accessible from subfuncs)
lang= mw.language.getContentLanguage() --for number display
goptionLocalize= "deHKomma"
glang_formatNum=
function(a)
local anum, out
if a.type=="number" and tonumber(a.data) then
anum= tonumber(a.data)
end
if anum and (goptionLocalize=="deHKomma" or goptionLocalize=="1") then
out= lang:formatNum(anum)
out= mw.ustring.gsub(tostring(out), "%.", " ") --nonbreaking hairspace
elseif anum and goptionLocalize=="deKomma" then
out= lang:formatNum(anum)
out= mw.ustring.gsub(tostring(out), "%.", "")
elseif anum and goptionLocalize=="de" then
out= lang:formatNum(anum)
else
out= tostring(a.data)
end
return out
end
tables, queries, vars= {}, {}, {}; --like global
local out, o= "", ""
for k, instr in ipairs(instrs) do
ginstr= instr
--mw.addWarning( "instr"..mw.dumpObject( instr ) )
if false then --set to true for unprotected debug traceback
o= interpretLine(instr)
else
local lucky
lucky, o= pcall(interpretLine, instr)
if not lucky then
if not mw.ustring.match(o, "^Modul:Vorlage:Tabellendaten_Skriptessa:") then
o= o.." near "..ginstr
end
assert(false, o)
end
end
out= out..o
end
local htmlent= {["<"]="<", ["["]="[", ["="]= "=", ["+"]= "+", [":"]= ":",
["{"]= "{", ["|"]= "|", ["}"]= "}"}
for k, v in pairs(htmlent) do
out= mw.ustring.gsub(out, k, v)
end
local pout= frame:preprocess(out)
--Lua-Fehler: .. or <span class="scribunto-error" ..?
if mw.ustring.match((pout), 'class="scribunto%-error"') then
for template in mw.ustring.gmatch(out, "{{[^}]*}}") do
local ptout= frame:preprocess(template)
if mw.ustring.match(ptout, 'class="scribunto%-error"') then
mw.addWarning("<span style='color:#d33; font-size:120%'>"
.."Fehler im Templateaufruf <nowiki>"..template.."</nowiki></span>")
break
end
end
end
return pout
end
return p