Modul:Vorlage:Tabellendaten Skriptessa

Aus Wikibooks
Zur Navigation springen Zur Suche springen

Informationen zu dieser Dokumentation
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[Bearbeiten]

Das 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[Bearbeiten]

Das 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[Bearbeiten]

Kurzes Beispiel mit einer Variable:

{{Tabellendaten Skriptessa |code=abc.:var s1=bla:.~.:s1:.}}

Ergebnis:

abc~bla

Beispiel 4[Bearbeiten]

Die 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:


Aufgabe[Bearbeiten]

Berechne und kürze mit 2 und 5.

Lösung[Bearbeiten]

Beispiel 5[Bearbeiten]

Beispiel 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 &lt;pre> und &lt;/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:.
.::.&#123;&#124; class="wikitable"
.:t1(*; id<=5):.
&#124;&#125;}}

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[Bearbeiten]

Eine 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]]}:.
&#123;&#124; class="wikitable"
.:q1(*):.
&#124;&#125;
.: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[Bearbeiten]

Codekomponenten 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 als table oder als eine andere query 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 oder occurrenceLooped=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..).
  • .:--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 als table deklariert, siehe oben). Sie wird als Text ausgegeben.
  • .:q(col1, col2, ..; condition1, condition2, ..; option-list):.
    • Die Abfrage einer anderen Abfrage (q ist als query deklariert, siehe oben). Sie wird als Text ausgegeben.
  • .:v:.
    • Die einzelne Variable (oder der wörtliche Text) wird ausgegeben (Variablen werden mit var deklariert, siehe oben).
  • .: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[Bearbeiten]

Alles außerhalb der .:Befehl/ Ausdruck:. Syntax wird weitgehend unverändert ausgegeben. Nur gewisse Html-Entitäten werden ersetzt: &lt; durch <, &#91; durch [, &#61; durch =, &#43; durch +, &#58; durch :, &#123; durch {, &#124; durch |, &#125; 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]

  1. .:query t1q= t1(*; group==pageindex+1):. wobei t1 eine table Variable ist, mit table t1=ABC.tab, und pageindex eine numerische var Variable, mit var pageindex=123.
  2. .:var pageoffset= pageindex*10:.
  3. .:t1q(divisor; id==pageoffset+1):.
  4. .:var id= 2 * pageoffset + 1:. setzt die id Variable auf das Doppelte der pageoffset Variable, plus 1.
  5. .:t1q(content; id==id++):. ergibt die content-Spalte mit id, die Variable id selbst ist aber nun um 1 höher.

Skriptcode Richtlinien[Bearbeiten]

Html-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 &lt; 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 &#91; statt [ und &lt; statt < verwenden, z.B. &lt;math>.

Modultests[Bearbeiten]

siehe 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

example 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

example 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= "&lt;hr>":. .:var id= 3:. .:t1(content; id==id++, group==1):. .:s1:. jo}} (main function)


Information


---{{:{{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= "&amp;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), "%.", "&#8239;")  --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= {["&lt;"]="<", ["&#91;"]="[", ["&#61;"]= "=", ["&#43;"]= "+", ["&#58;"]= ":",
		["&#123;"]= "{", ["&#124;"]= "|", ["&#125;"]= "}"}
	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