Module:Sandbox/DrMeepster/C yes yes

Revision as of 18:37, 1 February 2020 by imported>DrMeepster (css bugfix)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Documentation for this module may be created at Module:Sandbox/DrMeepster/C yes yes/doc

local p = {}
--------------------------------
-----Code from other people-----
--------------------------------

TableTools = require("Module:TableTools")

--https://stackoverflow.com/a/1283608
--All stack overflow code is CC BY-SA 3.0
local function tableMerge(t1, t2)
	for k,v in pairs(t2) do
		if type(v) == "table" then
			if type(t1[k] or false) == "table" then
				_tableMerge(t1[k] or {}, t2[k] or {})
			else
				t1[k] = v
			end
		else
			t1[k] = v
		end
	end
	return t1
end

--This function is public domain. See http://lua-users.org/wiki/OrderedTable
--Modified to be able to accept args
local function Ordered(t)
	--nextkey and firstkey are used as markers; nextkey[firstkey] is
	--the first key in the table, and nextkey[nextkey] is the last key.
	--nextkey[nextkey[nextkey]] should always be nil.

	local key2val, nextkey, firstkey = {}, {}, {}
	nextkey[nextkey] = firstkey
	
	local function onext(self, key)
		while key ~= nil do
			key = nextkey[key]
			local val = self[key]
			if val ~= nil then return key, val end
			end
		end
 
	-- To save on tables, we use firstkey for the (customised)
	-- metatable; this line is just for documentation
	local selfmeta = firstkey
	
	-- record the nextkey table, for routines lacking the closure
	selfmeta.__nextkey = nextkey
	
	-- setting a new key (might) require adding the key to the chain
	function selfmeta:__newindex(key, val)
		rawset(self, key, val)
		if nextkey[key] == nil then -- adding a new key
			nextkey[nextkey[nextkey]] = key
			nextkey[nextkey] = key
		end
	end
	
	out = setmetatable(key2val, selfmeta)
	
	if t then
		tableMerge(out, t)
	end
	
	return out
end

p.Ordered = Ordered

--------------------------------
----------Code from me----------
--------------------------------

--metatable of mw.html
p.htmlMeta = getmetatable(mw.html.create())

p.orderedMeta = getmetatable(p.Ordered())

function p.filter(tabl, typ)
	local typeCheck = (type(typ) == "string" and type) or (type(typ) == "table" and getmetatable) or error("Invalid type!")
	
	local out = {}
	
	for k,v in pairs(tabl) do
		if typeCheck(v) == typ then
			out[k] = v
		end
	end
	
	return out
end

--------------------------------

--an HTML Element
local Element = {parent=nil, id = nil}

Element.__index = Element

--Create a new Element
function Element.new(type)
	o = setmetatable({}, Element)
	o.type = type
	o.children = {}
	o.style = Ordered()
	o.classes = {}
	
	return o
end

--Add Element or mw.html as child
function Element:add(child)
	if getmetatable(child) == Element then
		child.parent = self
	elseif getmetatable(child) ~= htmlMeta then
		error("Inavlid child!")
	end
	
	table.insert(self.children, child)
	
	return child
end

--Add string as child
function Element:wikitext(child)
	if type(child) ~= "string" then
		error("Inavlid child!")
	end
	
	table.insert(self.children, child)
	
	return self
end

--create a new Element then add it
function Element:tag(name)
	elem = Element.new(name)
	
	return self:add(elem)
end

--add a newline (br)
function Element:newline()
	self:tag("br")
	
	return self
end

--add a css property
function Element:css(k, v)
	
	if type(k) == "table" then
		for _,kv in ipairs(k) do
			self:css(kv[1], kv[2])
		end
	elseif type(k) == "string" and type(v) == "string" then
		self.style[k] = v
	else
		error("CSS keys and values should be string!")
	end
	
	return self
end

--add classes
function Element:class(class)
	if type(class) == "table" then
		tableMerge(self.classes, class)
	elseif type(class) == "string" then
		table.insert(self.classes, class)
	else
		error("Invalid class type!")
	end
	
	return self
end

--set the id
function Element:setId(id)
	if type(id) ~= "string" then
		error("Id must be string!")
	end
	
	self.id = id
	
	return self
end

--returns parent. used for function chaining
function Element:done()
	return self.parent or self
end

--returns the root Element
function Element:allDone()
	local parent = self.parent
	
	if parent == nil then return self end
	
	while parent.parent do
		parent = parent.parent
	end
	
	return parent
end

--turn this Element into an mw.html object
function Element:bake()
	local root = mw.html.create(self.type):css(self.style)
	
	for _,class in ipairs(self.classes) do
		root:addClass(class)
	end
	
	for _, child in ipairs(self.children) do
		if getmetatable(child) == Element then
			--must bake Element
			root:node(child:bake())
		elseif getmetatable(child) == htmlMeta then
			--do not need to bake mw.html
			root:node(child)
		elseif type(child) == "string" then
			--if string add as wikitext
			root:wikitext(child)
		else
			error("Unknown child of element!")
		end
	end
	
	return root
end

--bakes the root Element
function Element:bakeAll()
	return self:allDone():bake()
end

--wip dont use
function Element:stylesheet(css)
	for _,c in ipairs(css) do
		for _,s in ipairs(c.selectors) do
			if s:check(self) then
				self:css(c.properties)
				break
			end
		end
	end
	
	for _,child in pairs(p.filter(self.children, Element)) do
		child:stylesheet(css)
	end
	
	return self
end

--------------------------------

local Selector = {
	yes = false,
	id = nil,
	type = nil,
	class = {},
	
	directParent = nil,
	parent = nil,
	next = nil
}
--[[
	selections ordered roughly by price, cheapest to most expensive
	
	--basic selections--
	yes - Makes the selector always be true (*)
	id - Element must have this id (#id)
	type - Element must be of this type (type)
	class - Element must have all these classes (.class1.class2 etc)
	
	--combinator selections--
	directParent- Element must have its parent element match this selector  (parentElement > element)
	parent - Element must be inside an element matching this selector (parentElement element)
]]

Selector.__index = Selector

function Selector.new(s)
	s = s or {}
	
	if type(s.class) == "string" then
		s.class = {s.class}
	elseif type(s.class) ~= "table" then
		s.class = {}
	end
	
	return setmetatable(s, Selector)
end

function Selector:check(e)
	if getmetatable(e) ~= Element then
		return false
	end
	
	-- all selector
	if self.yes then
		return true
	end
	
	-- id selector
	if self.id and e.id ~= self.id then
		 return false
	end
	
	-- type selector
	if self.type and e.type ~= self.type then
		 return false
	end
	
	-- class selector
	if #self.class > 0 then
		local class = true
		for _,v in pairs(self.class) do
			if not TableTools.inArray(e.classes, v) then
				class = false
				break
			end
		end
		
		if not class then
			return false
		end
	end
	
	-- direct parent check
	
	if self.directParent and not self.directParent:check(e.parent) then
		return false
	end
	
	-- parent check

	if self.parent then
		local nextParent = e.parent
		
		while true do
			if nextParent == nil then
				return false
			end
			
			if self.parent:check(nextParent) then
				break
			end
			
			nextParent = nextParent.parent
		end
	end
	 
	-- all selectors passed
	return true
end

--------------------------------
p.Element = Element
p.Selector = Selector
return p