SHARE
TWEET

Untitled

a guest Apr 26th, 2019 61 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. -- vim: ft=lua ts=2
  2. local Set = {}
  3. Set.mt = {__index = Set}
  4. function Set:new(values)
  5.     local instance = {}
  6.     local isSet if getmetatable(values) == Set.mt then isSet = true end
  7.     if type(values) == "table" then
  8.         if not isSet and #values > 0 then
  9.             for _,v in ipairs(values) do
  10.                 instance[v] = true
  11.             end
  12.         else
  13.             for k in pairs(values) do
  14.                 instance[k] = true
  15.             end
  16.         end
  17.     elseif values ~= nil then
  18.         instance = {[values] = true}
  19.     end
  20.     return setmetatable(instance, Set.mt)
  21. end
  22.  
  23. function Set:add(e)
  24.     if e ~= nil then self[e] = true end
  25.     return self
  26. end
  27.  
  28. function Set:remove(e)
  29.     if e ~= nil then self[e] = nil end
  30.     return self
  31. end
  32.  
  33. function Set:tolist()
  34.     local res = {}
  35.     for k in pairs(self) do
  36.         table.insert(res, k)
  37.     end
  38.     return res
  39. end
  40.  
  41. Set.mt.__add = function (a, b)
  42.     local res, a, b = Set:new(), Set:new(a), Set:new(b)
  43.     for k in pairs(a) do res[k] = true end
  44.     for k in pairs(b) do res[k] = true end
  45.     return res
  46. end
  47.  
  48. -- Subtraction
  49. Set.mt.__sub = function (a, b)
  50.     local res, a, b = Set:new(), Set:new(a), Set:new(b)
  51.     for k in pairs(a) do res[k] = true end
  52.     for k in pairs(b) do res[k] = nil end
  53.     return res
  54. end
  55.  
  56. -- Intersection
  57. Set.mt.__mul = function (a, b)
  58.     local res, a, b = Set:new(), Set:new(a), Set:new(b)
  59.     for k in pairs(a) do
  60.         res[k] = b[k]
  61.     end
  62.     return res
  63. end
  64.  
  65. -- String representation
  66. Set.mt.__tostring = function (set)
  67.     local s = "{"
  68.     local sep = ""
  69.     for k in pairs(set) do
  70.         s = s .. sep .. tostring(k)
  71.         sep = ", "
  72.     end
  73.     return s .. "}"
  74. end
  75.  
  76.  
  77. local ElementNode = {}
  78. ElementNode.mt = {__index = ElementNode}
  79. function ElementNode:new(index, nameortext, node, descend, openstart, openend)
  80.     local instance = {
  81.         index = index,
  82.         name = nameortext,
  83.         level = 0,
  84.         parent = nil,
  85.         root = nil,
  86.         nodes = {},
  87.         _openstart = openstart, _openend = openend,
  88.         _closestart = openstart, _closeend = openend,
  89.         attributes = {},
  90.         id = nil,
  91.         classes = {},
  92.         deepernodes = Set:new(),
  93.         deeperelements = {}, deeperattributes = {}, deeperids = {}, deeperclasses = {}
  94.     }
  95.     if not node then
  96.         instance.name = "root"
  97.         instance.root = instance
  98.         instance._text = nameortext
  99.         local length = string.len(nameortext)
  100.         instance._openstart, instance._openend = 1, length
  101.         instance._closestart, instance._closeend = 1, length
  102.     elseif descend then
  103.         instance.root = node.root
  104.         instance.parent = node
  105.         instance.level = node.level + 1
  106.         table.insert(node.nodes, instance)
  107.     else
  108.         instance.root = node.root
  109.         instance.parent = node.parent
  110.         instance.level = node.level
  111.         table.insert(node.parent.nodes, instance)
  112.     end
  113.     return setmetatable(instance, ElementNode.mt)
  114. end
  115.  
  116. function ElementNode:gettext()
  117.     return string.sub(self.root._text, self._openstart, self._closeend)
  118. end
  119.  
  120. function ElementNode:settext(c)
  121.     self.root._text=c
  122. end
  123.  
  124. function ElementNode:textonly()
  125.     return (self:gettext():gsub("<[^>]*>",""))
  126. end
  127.  
  128. function ElementNode:getcontent()
  129.     return string.sub(self.root._text, self._openend + 1, self._closestart - 1)
  130. end
  131.  
  132. function ElementNode:addattribute(k, v)
  133.     self.attributes[k] = v
  134.     if string.lower(k) == "id" then
  135.         self.id = v
  136.     -- class attribute contains "space-separated tokens", each of which we'd like quick access to
  137.     elseif string.lower(k) == "class" then
  138.         for class in string.gmatch(v, "%S+") do
  139.             table.insert(self.classes, class)
  140.         end
  141.     end
  142. end
  143.  
  144. local function insert(table, name, node)
  145.     table[name] = table[name] or Set:new()
  146.     table[name]:add(node)
  147. end
  148.  
  149. function ElementNode:close(closestart, closeend)
  150.     if closestart and closeend then
  151.         self._closestart, self._closeend = closestart, closeend
  152.     end
  153.     -- inform hihger level nodes about this element's existence in their branches
  154.     local node = self
  155.     while true do
  156.         node = node.parent
  157.         if not node then break end
  158.         node.deepernodes:add(self)
  159.         insert(node.deeperelements, self.name, self)
  160.         for k in pairs(self.attributes) do
  161.             insert(node.deeperattributes, k, self)
  162.         end
  163.         if self.id then
  164.             insert(node.deeperids, self.id, self)
  165.         end
  166.         for _,v in ipairs(self.classes) do
  167.             insert(node.deeperclasses, v, self)
  168.         end
  169.     end
  170. end
  171.  
  172. local function escape(s)
  173.     -- escape all ^, $, (, ), %, ., [, ], *, +, - , and ? with a % prefix
  174.     return string.gsub(s, "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%" .. "%1")
  175. end
  176.  
  177. local function select(self, s)
  178.     if not s or type(s) ~= "string" or s == "" then return Set:new() end
  179.     local sets = {[""]  = self.deeperelements, ["["] = self.deeperattributes,
  180.                                 ["#"] = self.deeperids,         ["."] = self.deeperclasses}
  181.     local function match(t, w)
  182.         local m, e, v
  183.         if t == "[" then w, m, e, v = string.match(w,
  184.                 "([^=|%*~%$!%^]+)" .. -- w = 1 or more characters up to a possible "=", "|", "*", "~", "$", "!", or "^"
  185.                 "([|%*~%$!%^]?)" ..   -- m = an optional "|", "*", "~", "$", "!", or "^", preceding the optional "="
  186.                 "(=?)" ..             -- e = the optional "="
  187.                 "(.*)"                -- v = anything following the "=", or else ""
  188.             )
  189.         end
  190.         local matched = Set:new(sets[t][w])
  191.         -- attribute value selectors
  192.         if e == "=" then
  193.             if #v < 2 then v = "'" .. v .. "'" end -- values should be quoted
  194.             v = string.sub(v, 2, #v - 1) -- strip quotes
  195.             if m == "!" then matched = Set:new(self.deepernodes) end -- include those without that attribute
  196.             for node in pairs(matched) do
  197.                 local a = node.attributes[w]
  198.                 -- equals
  199.                 if m == "" and a ~= v then matched:remove(node)
  200.                 -- not equals
  201.                 elseif m == "!" and a == v then matched:remove(node)
  202.                 -- prefix
  203.                 elseif m =="|" and string.match(a, "^[^-]*") ~= v then matched:remove(node)
  204.                 -- contains
  205.                 elseif m =="*" and string.match(a, escape(v)) ~= v then matched:remove(node)
  206.                 -- word
  207.                 elseif m =="~" then matched:remove(node)
  208.                     for word in string.gmatch(a, "%S+") do
  209.                         if word == v then matched:add(node) break end
  210.                     end
  211.                 -- starts with
  212.                 elseif m =="^" and string.match(a, "^" .. escape(v)) ~= v then matched:remove(node)
  213.                 -- ends with
  214.                 elseif m =="$" and string.match(a, escape(v) .. "$") ~= v then matched:remove(node)
  215.                 end
  216.             end -- for node
  217.         end -- if v
  218.         return matched
  219.     end
  220.  
  221.     local subjects, resultset, childrenonly = Set:new({self})
  222.     for part in string.gmatch(s, "%S+") do
  223.     repeat
  224.         if part == ">" then childrenonly = true --[[goto nextpart]] break end
  225.         resultset = Set:new()
  226.         for subject in pairs(subjects) do
  227.             local star = subject.deepernodes
  228.             if childrenonly then star = Set:new(subject.nodes) end
  229.             resultset = resultset + star
  230.         end
  231.         childrenonly = false
  232.         if part == "*" then --[[goto nextpart]] break end
  233.         local excludes, filter = Set:new()
  234.         local start, pos = 0, 0
  235.         while true do
  236.             local switch, stype, name, eq, quote
  237.             start, pos, switch, stype, name, eq, quote = string.find(part,
  238.                 "(%(?%)?)" ..         -- switch = a possible ( or ) switching the filter on or off
  239.                 "([:%[#.]?)" ..       -- stype = a possible :, [, #, or .
  240.                 "([%w-_\\]+)" ..      -- name = 1 or more alfanumeric chars (+ hyphen, reverse slash and uderscore)
  241.                 "([|%*~%$!%^]?=?)" .. -- eq = a possible |=, *=, ~=, $=, !=, ^=, or =
  242.                 "(['\"]?)",           -- quote = a ' or " delimiting a possible attribute value
  243.                 pos + 1
  244.             )
  245.             if not name then break end
  246.     repeat
  247.             if ":" == stype then
  248.                 filter = name
  249.                 --[[goto nextname]] break
  250.             end
  251.             if ")" == switch then
  252.                 filter = nil
  253.             end
  254.             if "[" == stype and "" ~= quote then
  255.                 local value
  256.                 start, pos, value = string.find(part, "(%b" .. quote .. quote .. ")]", pos)
  257.                 name = name .. eq .. value
  258.             end
  259.             local matched = match(stype, name)
  260.             if filter == "not" then
  261.                 excludes = excludes + matched
  262.             else
  263.                 resultset = resultset * matched
  264.             end
  265.             --::nextname::
  266.     break
  267.     until true
  268.         end
  269.         resultset = resultset - excludes
  270.         subjects = Set:new(resultset)
  271.         --::nextpart::
  272. break
  273. until true
  274.     end
  275.     resultset = resultset:tolist()
  276.     table.sort(resultset, function (a, b) return a.index < b.index end)
  277.     return resultset
  278. end
  279.  
  280. function ElementNode:select(s) return select(self, s) end
  281. ElementNode.mt.__call = select
  282.  
  283. return ElementNode
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top