Modul:Benutzer/Neuneinhalbtel/Vorlage:Play mit Abstand


Informationen zu dieser Dokumentation
Modul Benutzer

Modulbeschreibung

Modul:Benutzer/Neuneinhalbtel/Vorlage:Play mit Abstand/Doku

Funktionen

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}}

_parseEvalStr

Parses an eval string with 0, 1 or 2 operators out of +, -, * or one ++. Alters vars={} i.a., reads global variable ginstr i.a.

example p._parseEvalStr( "id + 2 * 5", {id="3"} )

_parseQueryStr

Parses a query string and returns (tablename, outcolnames={}, where={}, postopts={}). Alters vars={} i.a.

example p._parseQueryStr( "tab1(cont; id==id++, group>=2; limit=3)", vars )

_dumpRecords

Returns a string-dump of a table with given columns

  • param outputFormat= "foo%d..", optional
example p._dumpRecords( {{1, "first"}, {2, "second"}} )

_lookup

Queries rows by col==condition pairs, with other options, returns {string=, table=} to chose from. Table has {data= {{1,"a"}, {2,"b"}}, schema= {fields= {{name="id", type="number"}, {name="cont", type="string"}}}} format like the data table param and the Commons Data tables.
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=, or occurrence=} (may be {} or optional)
  • param outputFormat= "foo%s.." (optional, for _dumpRecords, untested)

Reads global variable ginstr i.a.
example p._lookup(mw.ext.data.get("Data:Fraction tasks abacdeP80max.tab"), {searchColumnName="group", searchValue=1, compare=function(a,b) return a==b end}, {"id", "content"}, nil, {limit=3}).string


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>

--- 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> 
function p.interpret(frame)
	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
	--mw.addWarning( "code "..code )

	local tables, tabledata, queries, vars= {}, {}, {}, {}; local out= ""; 
	ginstr= "" --like-global (readable from closure funcs)
	for k, instr in ipairs(instrs) do
		ginstr= instr
		--mw.addWarning( "instr"..mw.dumpObject( instr ) )
		--//commands
		ms= {} --matches
		--.:table tab1="dd.tab":.
		if (function() 
				ms= {mw.ustring.match(instr, "^%.:%s*table%s+(%w+)%s*=%s*(.+)%s*:%.")}
				return #ms~=0
			end)() then
			key, val= unpack(ms)
			val= mw.ustring.match(val, "^Data%:(.+)$") or val
			tables[key]= val
			tabledata[key]= mw.ext.data.get(val)
		--.:var id==1+1:. .:var s=="-+-":.   var is [%a_][%w_]*
		elseif (function() 
			    ms= {mw.ustring.match(instr, "^%.:%s*var%s+([%a_][%w_]*)%s*=%s*(.+)%s*:%.")}
			    return #ms~=0
		    end)() then
			key, val= unpack(ms)
			if (function() 
				    ms= {mw.ustring.match(instr, '^"(.*)"$')}; 
				    return #ms~=0
				end)() then
	    		val= ms[1]
			elseif (function() 
				    ms= {mw.ustring.match(instr, "^'(.*)'$")}; 
				    return #ms~=0
				end)() then
	    		val= ms[1]
			else
				val= p._parseEvalStr(val, vars)
			end
			vars[key]= val
			--mw.addWarning( "vars "..mw.dumpObject( vars ) )
		--.:query t1q= t1(col;id==1,g==2;l:3):.
		elseif (function() 
				    ms= {mw.ustring.match(instr, "^%.:%s*query%s+(%w+)%s*=%s*(.+)%s*:%.")}
				    return #ms~=0
		    end)() then
			key, val= unpack(ms)
			tab, where, outcols, opts= p._parseQueryStr(val, vars)
			local source
			if tabledata[tab] then source= tabledata[tab] 
			elseif queries[tab] then source= queries[tab] 
			end
			queries[key]= p._lookup( source, where, outcols, nil, opts ).table 
			--mw.addWarning( "queries "..mw.dumpObject( queries ) )
		--.:--comment:.
		elseif mw.ustring.match(instr, "^%.:%s*%-%-.*:%.$") then 
			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
		--//expressions
		--.:tab1(content; id==id++, group==group; l:1):. .:id:. .:!id+1:.
		elseif mw.ustring.match(instr, "^%.:.*:%.$") then 
			local outex= ""
			local token=    mw.ustring.gsub(instr, "^%.:!?%s*(%w+).*", "%1")
			local tokenall= mw.ustring.gsub(instr, "^%.:!?%s*(.*)%s*:%.$", "%1")
			if tables[token] ~= nil then
				q= mw.ustring.gsub(instr, "^%.:!?%s*(.*)%s*:%.$", "%1")
				tab, where, outcols, opts= p._parseQueryStr(q, vars);
				--mw.addWarning( "table access "..mw.dumpObject( tabledata[tab].data ) )
				outex= outex..p._lookup( tabledata[tab], where, outcols, nil, opts ).string
			elseif queries[token] ~= nil then
				q= mw.ustring.gsub(instr, "^%.:!?%s*(.*)%s*:%.$", "%1")
				qry, where, outcols, opts= p._parseQueryStr(q, vars);
				--mw.addWarning( "query access "..mw.dumpObject( queries[qry].data ) )
				outex= outex..p._lookup( queries[qry], where, outcols, nil, opts ).string
			else
				--mw.addWarning( "eval expression "..mw..tokenall )
				outex= outex..p._parseEvalStr( tokenall, vars )
			end
			--.:!debugOutput:. or .:normalOutput:.
			if ({mw.ustring.gsub(instr, "^%.:!", "")})[2]>0 then 
				mw.addWarning(tokenall.."= "..outex)
			else
				out= out..outex
			end
		else --plain text
			out= out..instr
		end
	end
	htmlent= {["&lt;"]="<", ["&#91;"]="[", ["&#61;"]= "=", ["&#43;"]= "+", ["&#58;"]= ":",
		["&#123;"]= "{", ["&#124;"]= "|", ["&#125;"]= "}"}
	for k, v in pairs(htmlent) do
		out= mw.ustring.gsub(out, k, v) 
	end
	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 
			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

--- Parses an eval string with 0, 1 or 2 operators out of +, -, * or one ++.
-- Alters vars={} i.a., reads global variable ginstr i.a.<br>
-- example <code> p._parseEvalStr( "id + 2 * 5", {id="3"} ) </code> 
function p._parseEvalStr( ev, vars )
	ms= {}  --matches
	--1 element op like "id++"   var is [%a_][%w_]*
	if (function() 
			ms= {mw.ustring.match(ev, "^%s*([%a_][%w_]*)%+%+%s*$")}
			return #ms~=0
		end)() then  
		pt= ms[1]
		assert(vars[pt], "Variable not found near "..ginstr)
		out= vars[pt]
		assert(tonumber(vars[pt]), "Conversion to number failed near "..ginstr..mw.dumpObject(vars))
		vars[pt]= vars[pt]+1
	--2 element op like "id+1"   var or number is [%w_]*
	elseif (function() 
			ms= {mw.ustring.match(ev, "^%s*([%w_]*)%s*([+-%*])%s*"
										.."([%w_]*)%s*$")}
			return #ms~=0
		end)() then 
		pts= {ms[1], ms[3]}
		op= ms[2]
		for i, pt in ipairs(pts) do
			if vars[pt] ~= nil then pts[i]= vars[pt] end
			assert(tonumber(pts[i]), "Conversion to number failed near "..ginstr..mw.dumpObject(pts))
		end
		if op=="+" then 
			out= pts[1]+pts[2]
		elseif op=="-" then
			out= pts[1]-pts[2]
		elseif op=="*" then
			out= pts[1]*pts[2]
		else 
			assert(false, "Unknown +*- operator near "..ginstr)	
		end
	--3 element op like "1-2*3"
	elseif (function() 
			ms= {mw.ustring.match(ev, "^%s*([%w_]*)%s*([+-%*])%s*"
										.."([%w_]*)%s*([+-%*])%s*"
										.."([%w_]*)%s*$")}
			return #ms~=0
		end)() then
		if ms[4]=="*" then
			calc2= p._parseEvalStr(ms[3].."*"..ms[5], vars)
			out= p._parseEvalStr(ms[1]..ms[2]..calc2, vars)
		else
			calc1= p._parseEvalStr(ms[1]..ms[2]..ms[3], vars)
			out= p._parseEvalStr(calc1..ms[4]..ms[5], vars)
		end
	--quoted text
	elseif (function() 
			ms= {mw.ustring.match(ev, '^"(.*)"$')}
			return #ms~=0
		end)() then
		out= ms[1]
	--plain text
	else
		pt= mw.ustring.match(ev, "^%s*(%w+)%s*$")
		if pt and vars[pt] ~= nil then 
			out= vars[pt]
		else
			out= ev	
		end
	end
	return out
end

--- Parses a query string and returns (tablename, outcolnames={}, where={}, postopts={}).
-- Alters vars={} i.a.<br>
-- example <code> p._parseQueryStr( "tab1(cont; id==id++, group>=2; limit=3)", vars ) </code> 
function p._parseQueryStr( q, vars )
	local tab, optstr= mw.ustring.match(q, "^%s*(%w+)%s*%((.*)%)%s*$")
	local opts= {};	local o=""
	for match in mw.text.gsplit(optstr, ";") do 
		i= #opts+1; opts[i]= {}
		for mat in mw.text.gsplit(match, ",") do 
			opts[i][#opts[i]+1]= mw.text.trim(mat)
		end
	end
	--mw.addWarning( "parsequery all query opts "..mw.dumpObject( opts ) )
	local outcolname= opts[1][1];
	
	local where= {}; 
	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"
		lt, op, rt= mw.ustring.match(cond, "^%s*(%w+)%s*([=<>][=<>]?)%s*(.*)%s*$")
		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
		rtp= p._parseEvalStr(rt, vars)
		where[#where+1]= {searchColumnName= lt, searchValue= rtp, compare= opf}
	end
	--mw.addWarning( "parsequery where "..mw.dumpObject(where) )
	
	local postopts= {}; 
	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"
		lt= mw.ustring.match(po, "^%s*(%w+).*$")
		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, vars)
		end
		postopts[lt]= rt
	end
	--mw.addWarning( "parsequery postopts "..mw.dumpObject(postopts) )
	
	return tab, where, {outcolname}, postopts
end

--- Returns a string-dump of a table with given columns<br>
-- * param ''outputFormat''= "foo%d..", optional<br>
-- example <code> p._dumpRecords( {{1, "first"}, {2, "second"}} ) </code> 
function p._dumpRecords( records, outputFormat )
	local outStr= ""; 
	for i, record in ipairs(records) do
		if outStr ~= "" then outStr= outStr.."; " end
		if outputFormat then
			outStr= outStr.. mw.ustring.format(outputFormat, unpack(record))
		else
			for j, col in ipairs(record) do
				if j~=1 then outStr= outStr..", " end
				outStr= outStr..tostring(col)
			end
		end
	end
	--mw.addWarning( "dumpRecords "..mw.dumpObject(record) )  --addWarning doesnt show same messages twice!
	return outStr
end

function typecast(s, typ)
	local v
	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=} to chose from.
-- Table has {data= {{1,"a"}, {2,"b"}}, schema= {fields= {{name="id", type="number"}, {name="cont", type="string"}}}}
-- format like the data table param and the Commons Data tables.<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 {})<br>
-- * param ''opts''= {orderBy=, descending=, limit=, or occurrence=} (may be {} or optional)<br>
-- * param ''outputFormat''= "foo%s.."  (optional, for _dumpRecords, untested)<br>
-- Reads global variable ginstr i.a.<br>
-- example <code> p._lookup(mw.ext.data.get("Data:Fraction tasks abacdeP80max.tab"),
-- {searchColumnName="group", searchValue=1, compare=function(a,b) return a==b end},
-- {"id", "content"}, nil, {limit=3}).string </code> 
function p._lookup(data, where, outputColumnNames, outputFormat, 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 col 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
	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)
		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
			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
		ocoli= assert(outColIdxByName[opts.orderBy], "OrderBy column not found near "..ginstr)
		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
		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
		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
		o= assert(tonumber(opts.occurrence), "Occurrence nonumber error near "..ginstr)
		o= math.abs(math.floor(o))
		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

	return {["string"]= p._dumpRecords( matchdata.data, outputFormat ),
		["table"]= matchdata}
end







return p