diff options
author | Karl Berry <karl@freefriends.org> | 2018-02-09 00:16:06 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2018-02-09 00:16:06 +0000 |
commit | b2153506a41b9f934841bc0eb31dd9c3946b407b (patch) | |
tree | debea85a21708749fafa1748c894ab25b0e125e8 | |
parent | e6304d3f0b0d72c2f38350d3ae4b4fa6bd862793 (diff) |
luaxml (8feb18)
git-svn-id: svn://tug.org/texlive/trunk@46570 c570f23f-e606-0410-a88d-b1316a301751
-rw-r--r-- | Master/texmf-dist/doc/luatex/luaxml/README | 10 | ||||
-rw-r--r-- | Master/texmf-dist/doc/luatex/luaxml/luaxml.pdf | bin | 114519 -> 98163 bytes | |||
-rw-r--r-- | Master/texmf-dist/doc/luatex/luaxml/luaxml.tex | 434 | ||||
-rw-r--r-- | Master/texmf-dist/tex/luatex/luaxml/dom-sample.lua | 29 | ||||
-rw-r--r-- | Master/texmf-dist/tex/luatex/luaxml/luaxml-cssquery.lua | 214 | ||||
-rw-r--r-- | Master/texmf-dist/tex/luatex/luaxml/luaxml-domobject.lua | 510 | ||||
-rw-r--r-- | Master/texmf-dist/tex/luatex/luaxml/luaxml-mod-xml.lua | 13 | ||||
-rw-r--r-- | Master/texmf-dist/tex/luatex/luaxml/luaxml-parse-query.lua | 43 | ||||
-rw-r--r-- | Master/texmf-dist/tex/luatex/luaxml/luaxml-selectors.lua | 76 |
9 files changed, 1178 insertions, 151 deletions
diff --git a/Master/texmf-dist/doc/luatex/luaxml/README b/Master/texmf-dist/doc/luatex/luaxml/README index cb48cbab9bd..0ebf891e74b 100644 --- a/Master/texmf-dist/doc/luatex/luaxml/README +++ b/Master/texmf-dist/doc/luatex/luaxml/README @@ -7,6 +7,16 @@ In this version, some files not useful for luaTeX were droped. Original files ca http://manoelcampos.com/files/LuaXML--0.0.0-lua5.1.tgz +Install +======= + +LuaXML is installed in TeX distributions, so you don't need to install it yourself. If you want to try the development version, +then clone this repository and run + + make install + +Please note that you will need [LDoc](http://stevedonovan.github.io/ldoc/manual/doc.md.html#Processing_Single_Modules) installed +on your system. License: ======== diff --git a/Master/texmf-dist/doc/luatex/luaxml/luaxml.pdf b/Master/texmf-dist/doc/luatex/luaxml/luaxml.pdf Binary files differindex 3a6967c9c66..07ec3244c70 100644 --- a/Master/texmf-dist/doc/luatex/luaxml/luaxml.pdf +++ b/Master/texmf-dist/doc/luatex/luaxml/luaxml.pdf diff --git a/Master/texmf-dist/doc/luatex/luaxml/luaxml.tex b/Master/texmf-dist/doc/luatex/luaxml/luaxml.tex index 4dcff6a51a1..88ab450a50e 100644 --- a/Master/texmf-dist/doc/luatex/luaxml/luaxml.tex +++ b/Master/texmf-dist/doc/luatex/luaxml/luaxml.tex @@ -1,28 +1,347 @@ \documentclass{ltxdoc} -\usepackage{tgschola,url} +% \usepackage{tgschola,url} +\usepackage{url} \usepackage[english]{babel} +\usepackage{hyperref} +\usepackage{luacode} +\usepackage{framed} +% Version is defined in the makefile, use default values when compiled directly +\ifdefined\version\else +\def\version{0} +\let\gitdate\date +\fi +\newcommand\modulename[1]{\subsection{#1}\label{sec:#1}} +\newcommand\modulesummary[1]{#1\\} +\newcommand\moduleclass[1]{\subsubsection{Class: #1}} +\newcommand\functionname[2]{\par\noindent\textbf{#1(#2)}\\} +\newcommand\functionsummary[1]{#1\\\textbf{Parameters:}\\} +\newcommand\functionparam[2]{\texttt{#1}: #2\\} +\newcommand\functionreturn[1]{\textbf{Return: }\\#1\\} + \begin{document} \title{The \textsc{LuaXML} library} \author{Paul Chakravarti \and Michal Hoftich} - \date{Version 0.0.2\\May 25, 2013} + \date{Version \version\\\gitdate} \maketitle \tableofcontents -\section*{Introduction} -|LuaXML| is pure lua library for reading and serializing of the |xml| files. -Current release is aimed mainly as support for the odsfile package. -In first release it was included with the odsfile package, -but as it is general library which can be used also with other packages, -I decided to distribute it as separate library. +\section{Introduction} + +|LuaXML| is pure lua library for processing and serializing of the |xml| files. +The base code code has been written by Paul Chakravarti, with minor changes +which brings Lua 5.3 or HTML 5 support. On top of that, new modules for +accessing the |xml| files using |DOM| like methods or |CSS| +selectors\footnote{Thanks to Leaf Corcoran for |CSS selector| parsing code.} +have been added. + +The documentation is divided to three parts -- first part deals with the |DOM| +library, second part describes the low-level libraries and the third part is +original documentation by Paul Chakravarti. +% Current release is aimed mainly as support for the odsfile package. +% In first release it was included with the odsfile package, +% but as it is general library which can be used also with other packages, +% I decided to distribute it as separate library. + +\section{The \texttt{DOM\_Object} library} + +This library can process a |xml| sources using |DOM| like functions. To load +it, you need to require |luaxml-domobject.lua| file. The |parse| function +provided by the library creates \texttt{DOM\_Object} object, which provides several +methods for processing the |xml| tree. + +\begin{verbatim} +local dom = require "luaxml-domobject" +local document = [[ +<html> +<head><title>sample</title></head> +<body> +<h1>test</h1> +<p>hello</p> +</body> +</html> + ]] + +-- dom.parse returns the DOM_Object +local obj = dom.parse(document) +-- it is possible to call methods on the object +local root_node = obj:root_node() +for _, x in ipairs(root_node:get_children()) do + print(x:get_element_name()) +end +\end{verbatim} + +The details about available methods can be found in the API docs, section +\ref{sec:luaxml-domobject}. The above code will load a |xml| document, it will +get the ROOT element and print all it's children element names. The +\verb|DOM_Object:get_children| function returns Lua table, so it is possible to +loop over it using standard table functions. + +\begin{framed} +\begin{luacode*} +dom = require "luaxml-domobject" +local document = [[ +<html> +<head><title>sample</title></head> +<body> +<h1>test</h1> +<p>hello</p> +</body> +</html> + ]] + +-- dom.parse returns the DOM_Object +obj = dom.parse(document) +-- it is possible to call methods on the object +local root_node = obj:root_node() +for _, x in ipairs(root_node:get_children()) do + tex.print(x:get_element_name().. "\\par") +end +\end{luacode*} +\end{framed} + +\subsection{Node selection methods} +There are some other methods for element retrieving. + +\subsubsection{The \texttt{DOM\_Object:get\_path} method} +If you want to print text content of all child elements of the body element, you can use \verb|DOM_Object:get_path|: + +\begin{verbatim} +local path = obj:get_path("html body") +for _, el in ipairs(path[1]:get_children()) do + print(el:get_text()) +end +\end{verbatim} + +The \verb|DOM_Object:get_path| function always return array with all elements +which match the requested path, even it there is only one such element. In this +case, it is possible to use standard Lua table indexing to get the first and +only one matched element and get it's children using +\verb|DOM_Object:get_children| method. It the children node is an element, it's +text content is printed using \verb|DOM_Object:get_text|. + + + +\begin{framed} + \begin{luacode*} +local path = obj:get_path("html body") + +for _, el in ipairs(path[1]:get_children()) do + if el:is_element() then + tex.print(el:get_text().."\\par") + end +end + \end{luacode*} +\end{framed} + +\subsubsection{The \texttt{DOM\_Object:query\_selector} method} + +This method uses |CSS selector| syntax to select elements, similarly to JavaScript \textit{jQuery} library. + +\begin{verbatim} +for _, el in ipairs(obj:query_selector("h1,p")) do + print(el:get_text()) +end +\end{verbatim} + + +\begin{framed} + \begin{luacode*} +for _, el in ipairs(obj:query_selector("h1,p")) do + tex.print(el:get_text().."\\par") +end + \end{luacode*} +\end{framed} + +\subsection{Element traversing} + +\subsubsection{The \texttt{DOM\_Object:traverse\_elements} method} + +It may be useful to traverse over all elements and apply a function on all of them. + +\begin{verbatim} +obj:traverse_elements(function(node) + print(node:get_text()) +end) +\end{verbatim} + +\begin{framed} + \begin{luacode*} +obj:traverse_elements(function(node) + tex.print(node:get_text().."\\par") +end) + \end{luacode*} +\end{framed} + +The \verb|get_text| method gets text from all children elements, so the first +line shows all text contained in the \verb|<html>| element, the second one in +\verb|<head>| element and so on. + +\subsection{DOM modifications} + +It is possible to add new elements, text nodes, or to remove them. -\noindent Example of usage: +\begin{verbatim} +local headers = obj:query_selector("h1") +for _, header in ipairs(headers) do + header:remove_node() +end +-- query selector returns array, we must retrieve the first element +-- to get the actual body element +local body = obj:query_selector("body")[1] +local paragraph = body:create_element("p", {}) +body:add_child_node(paragraph) +paragraph:add_child_node(paragraph:create_text_node("This is a second paragraph")) + +for _, el in ipairs(body:get_children()) do + if el:is_element() then + print(el:get_element_name().. ": ".. el:get_text()) + end +end +\end{verbatim} + +In this example, \verb|<h1>| element is being removed from the sample document, and new +paragraph is added. Two paragraphs should be shown in the output: + +\begin{framed} + \begin{luacode*} +local headers = obj:query_selector("h1") +-- query selector returns array, we must retrieve the first element +-- to get the actual body element +local body = obj:query_selector("body")[1] +local oldbody = body:copy_node() +for _, header in ipairs(headers) do + header:remove_node() +end +local paragraph = body:create_element("p", {}) +body:add_child_node(paragraph) +paragraph:add_child_node(paragraph:create_text_node("This is a second paragraph")) + +for _, el in ipairs(body:get_children()) do +if el:is_element() then + tex.print(el:get_element_name().. ": ".. el:get_text() .. "\\par") +end +end + +body:replace_node(oldbody) + \end{luacode*} +\end{framed} + + +\section{The \texttt{CssQuery} library} + +This library serves mainly as a support for the +\texttt{DOM\_Object:query\_selector} function. It also supports adding +information to the DOM tree. + +\subsection{Example usage} + +\begin{verbatim} +local cssobj = require "luaxml-cssquery" +local domobj = require "luaxml-domobject" + +local xmltext = [[ +<html> +<body> +<h1>Header</h1> +<p>Some text, <i>italics</i></p> +</body> +</html> +]] + +local dom = domobj.parse(xmltext) +local css = cssobj() + +css:add_selector("h1", function(obj) + print("header found: " .. obj:get_text()) +end) + +css:add_selector("p", function(obj) + print("paragraph found: " .. obj:get_text()) +end) + +css:add_selector("i", function(obj) + print("found italics: " .. obj:get_text()) +end) + +dom:traverse_elements(function(el) + -- find selectors that match the current element + local querylist = css:match_querylist(el) + -- add templates to the element + css:apply_querylist(el,querylist) +end) +\end{verbatim} + +\begin{framed} + \begin{luacode*} +local cssobj = require "luaxml-cssquery" +local domobj = require "luaxml-domobject" +local print = function(s) tex.print(s .. "\\par") end + +local xmltext = [[ +<html> +<body> +<h1>Header</h1> +<p>Some text, <i>italics</i></p> +</body> +</html> +]] + +local dom = domobj.parse(xmltext) +local css = cssobj() + +css:add_selector("h1", function(obj) + print("header found: " .. obj:get_text()) +end) + +css:add_selector("p", function(obj) + print("paragraph found: " .. obj:get_text()) +end) + +css:add_selector("i", function(obj) + print("found italics: " .. obj:get_text()) +end) + +dom:traverse_elements(function(el) + -- find selectors that match the current element + local querylist = css:match_querylist(el) + -- add templates to the element + css:apply_querylist(el,querylist) +end) + \end{luacode*} +\end{framed} + +More complete example may be found in the \texttt{examples} directory in the +\texttt{LuaXML} source code +repository\footnote{\url{https://github.com/michal-h21/LuaXML/blob/master/examples/xmltotex.lua}}. + +\section{The API documentation} + +\input{doc/api.tex} + +\section{Low-level functions usage} + +% The processing is done with several handlers, their usage will be shown in the +% following section. Full description of handlers is given in the original +% documentation in section \ref{sec:handlers}. + +% \subsection{Usage examples} + +The original |LuaXML| library provides some low-level functions for |XML| handling. +First of all, we need to load the libraries: \begin{verbatim} xml = require('luaxml-mod-xml') handler = require('luaxml-mod-handler') \end{verbatim} -First load the libraries. In |luaxml-mod-xml|, there is xml parser and also serializer. In |luaxml-mod-handler|, there are various handlers for dealing with xml data. Handlers are objects with callback functions which are invoked for every type of content in the |xml| file. More information about handlers can be found in the original documentation, section \ref{sec:handlers}. + +The |luaxml-mod-xml| file contains the xml parser and also the serializer. In +|luaxml-mod-handler|, various handlers for dealing with xml data are defined. +Handlers transforms the |xml| file to data structures which can be handled from +the Lua code. More information about handlers can be found in the original +documentation, section \ref{sec:handlers}. + +\subsection{The simpleTreeHandler} \begin{verbatim} sample = [[ <a> @@ -35,7 +354,10 @@ x = xml.xmlParser(treehandler) x:parse(sample) \end{verbatim} -You have to create handler object, using |handler.simpleTreeHandler()| and xml parser object using |xml.xmlParser(handler object)|. |simpleTreehandler| creates simple table hierarchy, with top root node in |treehandler.root| +You have to create handler object, using |handler.simpleTreeHandler()| and xml +parser object using |xml.xmlParser(handler object)|. |simpleTreehandler| +creates simple table hierarchy, with top root node in |treehandler.root| + \begin{verbatim} -- pretty printing function function printable(tb, level) @@ -58,46 +380,63 @@ printable(treehandler.root) print(xml.serialize(treehandler.root)) -- direct access to the element print(treehandler.root["a"]["b"][1]) +\end{verbatim} --- output: --- a --- d=hello --- b --- 1=world. --- 2 --- 1=another --- _attr --- at=Hi --- <?xml version="1.0" encoding="UTF-8"?> --- <a> --- <d>hello</d> --- <b>world.</b> --- <b at="Hi"> --- another --- </b> --- </a> --- --- world. +This code produces the following output: + +\begin{verbatim} + output: + a + d=hello + b + 1=world. + 2 + 1=another + _attr + at=Hi + <?xml version="1.0" encoding="UTF-8"?> + <a> + <d>hello</d> + <b>world.</b> + <b at="Hi"> + another + </b> + </a> + + world. \end{verbatim} -Note that |simpleTreeHandler| creates tables that can be easily accessed using standard lua functions, but in case of mixed content, like +First part is pretty-printed dump of Lua table structure contained in the handler, the second +part is |xml| serialized from that table and the last part demonstrates direct access to particular +elements. + +Note that |simpleTreeHandler| creates tables that can be easily accessed using +standard lua functions, but if the xml document is of mixed-content type\footnote{% +This means that element may contain both children elements and text.}: + \begin{verbatim} <a>hello <b>world</b> </a> \end{verbatim} -it produces wrong results. It is useful mostly for data |xml| files, not for text formats like |xhtml|. -For complex xml documents with mixed content, |domHandler| is capable of representing any valid XML document: +\noindent then it produces wrong results. It is useful mostly for data |xml| files, not for +text formats like |xhtml|. + +\subsection{The domHandler} + +% For complex xml documents with mixed content, |domHandler| is capable of representing any valid XML document: +For complex xml documents, it is best to use the |domHandler|, which creates object which contains all information +from the |xml| document. \begin{verbatim} --- dom-sample.lua +-- file dom-sample.lua -- next line enables scripts called with texlua to use luatex libraries -kpse.set_program_name("luatex") -function traverseDom(parser, current,level) +--kpse.set_program_name("luatex") +function traverseDom(current,level) local level = level or 0 local spaces = string.rep(" ",level) - local root= current or parser._handler.root + local root= current or current.root local name = root._name or "unnamed" local xtype = root._type or "untyped" local attributes = root._attr or {} @@ -111,7 +450,7 @@ function traverseDom(parser, current,level) end local children = root._children or {} for _, child in ipairs(children) do - traverseDom(parser,child, level + 1) + traverseDom(child, level + 1) end end @@ -121,10 +460,16 @@ local x = '<p>hello <a href="http://world.com/">world</a>, how are you?</p>' local domHandler = handler.domHandler() local parser = xml.xmlParser(domHandler) parser:parse(x) -traverseDom(parser) +traverseDom(domHandler.root) \end{verbatim} -This produces after running with |texlua dom-sample.lua|: +The ROOT element is stored in |domHandler.root| table, it's child nodes are stored in |_children| +tables. Node type is saved in |_type| field, if the node type is |ELEMENT|, then |_name| field contains +element name, |_attr| table contains element attributes. |TEXT| node contains text content in |_text| +field. + +The previous code produces following output in the terminal: % after command +% |texlua dom-sample.lua| running: \begin{verbatim} ROOT : unnamed @@ -136,16 +481,15 @@ ROOT : unnamed TEXT : , how are you? \end{verbatim} -With \verb|domHandler|, you can process documents with mixed content, like \verb|xhtml|. +% With \verb|domHandler|, you can process documents with mixed content, like +% \verb|xhtml|, so it is a most powerful handler. -Because at the moment it is intended mainly as support for the odsfile package, there is little documentation, -what follows is the original documentation of |LuaXML|, which may be little bit obsolete now. \clearpage -\noindent{\Huge Original documentation} +\part{Original \texttt{LuaXML} documentation by Paul Chakravarti} \medskip -\noindent This document was created automatically from original source code comments using Pandoc\footnote{\url{http://johnmacfarlane.net/pandoc/}} +\noindent This document was created automatically from the original source code comments using Pandoc\footnote{\url{http://johnmacfarlane.net/pandoc/}} \section{Overview} diff --git a/Master/texmf-dist/tex/luatex/luaxml/dom-sample.lua b/Master/texmf-dist/tex/luatex/luaxml/dom-sample.lua deleted file mode 100644 index d3fcde51ff0..00000000000 --- a/Master/texmf-dist/tex/luatex/luaxml/dom-sample.lua +++ /dev/null @@ -1,29 +0,0 @@ ---kpse.set_program_name("luatex") -function traverseDom(parser, current,level) - local level = level or 0 - local spaces = string.rep(" ",level) - local root= current or parser._handler.root - local name = root._name or "unnamed" - local xtype = root._type or "untyped" - local attributes = root._attr or {} - if xtype == "TEXT" then - print(spaces .."TEXT : " .. root._text) - else - print(spaces .. xtype .. " : " .. name) - end - for k, v in pairs(attributes) do - print(spaces .. " ".. k.."="..v) - end - local children = root._children or {} - for _, child in ipairs(children) do - traverseDom(parser,child, level + 1) - end -end - -local xml = require('luaxml-mod-xml') -local handler = require('luaxml-mod-handler') -local x = '<p>hello <a href="http://world.com/">world</a>, how are you?</p>' -local domHandler = handler.domHandler() -local parser = xml.xmlParser(domHandler) -parser:parse(x) -traverseDom(parser) diff --git a/Master/texmf-dist/tex/luatex/luaxml/luaxml-cssquery.lua b/Master/texmf-dist/tex/luatex/luaxml/luaxml-cssquery.lua new file mode 100644 index 00000000000..9f283ed6e64 --- /dev/null +++ b/Master/texmf-dist/tex/luatex/luaxml/luaxml-cssquery.lua @@ -0,0 +1,214 @@ +--- CSS query module for LuaXML +-- @module luaxml-cssquery +-- @author Michal Hoftich <michal.h21@gmail.com +local parse_query = require("luaxml-parse-query") + +--- CssQuery constructor +-- @function cssquery +-- @return CssQuery object +local function cssquery() + --- @type CssQuery + local CssQuery = {} + CssQuery.__index = CssQuery + CssQuery.__debug = false + CssQuery.querylist = {} + + function CssQuery.debug(self) + self.__debug = true + end + + function CssQuery:debug_print(text) + if self.__debug then + print("[CSS Object]: " .. text) + end + end + --- Calculate CSS specificity of the query + -- @param query table created by CssQuery:prepare_selector() function + -- @return integer speficity value + function CssQuery:calculate_specificity(query) + local query = query or {} + local specificity = 0 + for _, item in ipairs(query.query or {}) do + for key, value in pairs(item) do + if key == "id" then + specificity = specificity + 100 + elseif key == "tag" then + specificity = specificity + 1 + else + specificity = specificity + 10 + end + end + end + return specificity + end + + --- Test prepared querylist + -- @param domobj DOM element to test + -- @param querylist [optional] List of queries to test + -- @return table with CSS queries, which match the selected DOM element + function CssQuery:match_querylist(domobj, querylist) + local matches = {} + -- querylist can be explicit, saved queries can be used otherwise + local querylist = querylist or self.querylist + + local function test_part(key, value, el) + -- print("testing", key, value, el:get_element_name()) + if key == "tag" then + return el:get_element_name() == value + elseif key == "id" then + local id = el:get_attribute "id" + return id and id == value + elseif key == "class" then + local class = el:get_attribute "class" + if not class then return false end + local c = {} + for part in class:gmatch "([^%s]+)" do + c[part] = true + end + return c[value] == true + end + -- TODO: Add more cases + -- just return true for not supported selectors + return true + end + + local function test_object(query, el) + -- test one object in CSS selector + local matched = {} + for key, value in pairs(query) do + matched[#matched+1] = test_part(key, value, el) + end + if #matched == 0 then return false end + for k, v in ipairs(matched) do + if v ~= true then return false end + end + return true + end + + local function match_query(query, el) + local query = query or {} + local object = table.remove(query) -- get current object from the query stack + if not object then return true end -- if the query stack is empty, then we can be sure that it matched previous items + if not el:is_element() then return false end -- if there is object to test, but current node isn't element, test failed + local result = test_object(object, el) + if result then + return match_query(query, el:get_parent()) + end + return false + end + for _,element in ipairs(querylist) do + local query = {} + for k,v in ipairs(element.query) do query[k] = v end + if #query > 0 then -- don't try to match empty query + local result = match_query(query, domobj) + if result then matches[#matches+1] = element end + end + end + return matches + end + + --- Get elements that match the selector + -- @return table with DOM_Object elements + function CssQuery:get_selector_path( + domobj, -- DOM_Object + selectorlist -- querylist table created using CssQuery:prepare_selector + ) + local nodelist = {} + domobj:traverse_elements(function(el) + local matches = self:match_querylist(el, selectorlist) + self:debug_print("Matching " .. el:get_element_name() .." "..#matches) + if #matches > 0 then nodelist[#nodelist+1] = el + end + end) + return nodelist + end + + --- Parse CSS selector to query table + -- @return table querylist + function CssQuery:prepare_selector( + selector -- string CSS selector query + ) + local querylist = {} + local function parse_selector(item) + local query = {} + -- for i = #item, 1, -1 do + -- local part = item[i] + for _, part in ipairs(item) do + local t = {} + for _, atom in ipairs(part) do + local key = atom[1] + local value = atom[2] + t[key] = value + end + query[#query + 1] = t + end + return query + end + -- for item in selector:gmatch("([^%s]+)") do + -- elements[#elements+1] = parse_selector(item) + -- end + local parts = parse_query.parse_query(selector) or {} + -- several selectors may be separated using ",", we must process them separately + local sources = selector:explode(",") + for i, part in ipairs(parts) do + querylist[#querylist+1] = {query = parse_selector(part), source = sources[i]} + end + return querylist + end + + --- Add selector to CSS object list of selectors, + -- func is called when the selector matches a DOM object + -- params is table which will be passed to the func + -- @return integer number of elements in the prepared selector + function CssQuery:add_selector( + selector, -- CSS selector string + func, -- function which will be executed on matched elements + params -- table with parameters for the function + ) + local selector_list = self:prepare_selector(selector) + for k, query in ipairs(selector_list) do + query.specificity = self:calculate_specificity(query) + query.func = func + query.params = params + table.insert(self.querylist, query) + end + self:sort_querylist() + return #selector_list + end + + --- Sort selectors according to their specificity + -- It is called automatically when the selector is added + -- @return querylist table + function CssQuery:sort_querylist( + querylist -- [optional] querylist table + ) + local querylist = querylist or self.querylist + table.sort(self.querylist, function(a,b) + return a.specificity > b.specificity + end) + return querylist + end + + --- It tests list of queries agaings a DOM element and executes the + --- coresponding function that is saved for the matched query. + -- @return nothing + function CssQuery:apply_querylist( + domobj, -- DOM element + querylist -- querylist table + ) + for _, query in ipairs(querylist) do + -- use default empty function which will pass to another match + local func = query.func or function() return true end + local params = query.params or {} + local status = func(domobj, params) + -- break the execution when the function return false + if status == false then + break + end + end + end + + return setmetatable({}, CssQuery) +end + +return cssquery diff --git a/Master/texmf-dist/tex/luatex/luaxml/luaxml-domobject.lua b/Master/texmf-dist/tex/luatex/luaxml/luaxml-domobject.lua new file mode 100644 index 00000000000..7047c76a937 --- /dev/null +++ b/Master/texmf-dist/tex/luatex/luaxml/luaxml-domobject.lua @@ -0,0 +1,510 @@ +--- DOM module for LuaXML +-- @module luaxml-domobject +-- @author Michal Hoftich <michal.h21@gmail.com +local dom = {} +local xml = require("luaxml-mod-xml") +local handler = require("luaxml-mod-handler") +local css_query = require("luaxml-cssquery") + + +local void = {area = true, base = true, br = true, col = true, hr = true, img = true, input = true, link = true, meta = true, param = true} + +local escapes = { + [">"] = ">", + ["<"] = "<", + ["&"] = "&", + ['"'] = """, + ["'"] = "'" +} + +local function escape(search, text) + return text:gsub(search, function(ch) + return escapes[ch] or "" + end) +end + +local function escape_element(text) + return escape("([<>&])", text) +end + +local function escape_attr(text) + return escape("([<>&\"'])", text) +end + +local actions = { + TEXT = {text = "%s"}, + COMMENT = {start = "<!-- ", text = "%s", stop = " -->"}, + ELEMENT = {start = "<%s%s>", stop = "</%s>", void = "<%s%s />"}, + DECL = {start = "<?%s %s?>"}, + DTD = {start = "<!DOCTYPE ", text = "%s" , stop=">"} +} + +--- It serializes the DOM object back to the XML. +-- This function is mainly used for internal purposes, it is better to +-- use the `DOM_Object:serialize()`. +-- @param parser DOM object +-- @param current Element which should be serialized +-- @param level +-- @param output +-- @return table Table with XML strings. It can be concenated using table.concat() function to get XML string corresponding to the DOM_Object. +local function serialize_dom(parser, current,level, output) + local output = output or {} + local function get_action(typ, action) + local ac = actions[typ] or {} + local format = ac[action] or "" + return format + end + local function insert(format, ...) + table.insert(output, string.format(format, ...)) + end + local function prepare_attributes(attr) + local t = {} + local attr = attr or {} + for k, v in pairs(attr) do + t[#t+1] = string.format("%s='%s'", k, escape_attr(v)) + end + if #t == 0 then return "" end + -- add space before attributes + return " " .. table.concat(t, " ") + end + local function start(typ, el, attr) + local format = get_action(typ, "start") + insert(format, el, prepare_attributes(attr)) + end + local function text(typ, text) + local format = get_action(typ, "text") + insert(format, escape_element(text)) + end + local function stop(typ, el) + local format = get_action(typ, "stop") + insert(format,el) + end + local level = level or 0 + local spaces = string.rep(" ",level) + local root= current or parser._handler.root + local name = root._name or "unnamed" + local xtype = root._type or "untyped" + local text_content = root._text or "" + local attributes = root._attr or {} + -- if xtype == "TEXT" then + -- print(spaces .."TEXT : " .. root._text) + -- elseif xtype == "COMMENT" then + -- print(spaces .. "Comment : ".. root._text) + -- else + -- print(spaces .. xtype .. " : " .. name) + -- end + -- for k, v in pairs(attributes) do + -- print(spaces .. " ".. k.."="..v) + -- end + if xtype == "DTD" then + text_content = string.format('%s %s "%s" "%s"', name, attributes["_type"] or "", attributes._name, attributes._uri ) + -- remove unused fields + text_content = text_content:gsub('"nil"','') + text_content = text_content:gsub('%s*$','') + attributes = {} + elseif xtype == "ELEMENT" and void[name] then + local format = get_action(xtype, "void") + insert(format, name, prepare_attributes(attributes)) + return output + end + + start(xtype, name, attributes) + text(xtype,text_content) + local children = root._children or {} + for _, child in ipairs(children) do + output = serialize_dom(parser,child, level + 1, output) + end + stop(xtype, name) + return output +end + +--- XML parsing function +-- Parse the XML text and create the DOM object. +-- @return DOM_Object +local parse = function( + xmltext --- String to be parsed + ) + local domHandler = handler.domHandler() + --- @type DOM_Object + local DOM_Object = xml.xmlParser(domHandler) + -- preserve whitespace + DOM_Object.options.stripWS = nil + DOM_Object:parse(xmltext) + DOM_Object.current = DOM_Object._handler.root + DOM_Object.__index = DOM_Object + DOM_Object.css_query = css_query() + + local function save_methods(element) + setmetatable(element,DOM_Object) + local children = element._children or {} + for _, x in ipairs(children) do + save_methods(x) + end + end + local parser = setmetatable({}, DOM_Object) + + --- Returns root element of the DOM_Object + -- @return DOM_Object + function DOM_Object:root_node() + return self._handler.root + end + + + --- Get current node type + -- @param el [optional] node to get the type of + function DOM_Object:get_node_type( + el --- [optional] element to test + ) + local el = el or self + return el._type + end + + --- Test if the current node is an element. + -- You can pass different element as parameter + -- @return boolean + function DOM_Object:is_element( + el --- [optional] element to test + ) + local el = el or self + return self:get_node_type(el) == "ELEMENT" -- @bool + end + + + --- Test if current node is text + -- @return boolean + function DOM_Object:is_text( + el --- [optional] element to test + ) + local el = el or self + return self:get_node_type(el) == "TEXT" + end + + local lower = string.lower + + --- Return name of the current element + -- @return string + function DOM_Object:get_element_name( + el --- [optional] element to test + ) + local el = el or self + return el._name or "unnamed" + end + + --- Get value of an attribute + -- @return string + function DOM_Object:get_attribute( + name --- Attribute name + ) + local el = self + if self:is_element(el) then + local attr = el._attr or {} + return attr[name] + end + end + + --- Set value of an attribute + -- @return boolean + function DOM_Object:set_attribute( + name --- Attribute name + , value --- Value to be set + ) + local el = self + if self:is_element(el) then + el._attr[name] = value + return true + end + end + + + --- Serialize the current node back to XML + -- @return string + function DOM_Object:serialize( + current --- [optional] element to be serialized + ) + local current = current + -- if no current element is added and self is not plain parser object + -- (_type is then nil), use the current object as serialized root + if not current and self._type then + current = self + end + return table.concat(serialize_dom(self, current)) + end + + --- Get text content from the node and all of it's children + -- @return string + function DOM_Object:get_text( + current --- [optional] element which should be converted to text + ) + local current = current or self + local text = {} + for _, el in ipairs(current:get_children()) do + if el:is_text() then + text[#text+1] = el._text or "" + elseif el:is_element() then + text[#text+1] = el:get_text() + end + end + return table.concat(text) + end + + + + --- Retrieve elements from the given path. + -- The path is list of elements separated by space, + -- starting from the top element of the current element + -- @return table of elements which match the path + function DOM_Object:get_path( + path --- path to be traversed + , current --- [optional] element which should be traversed. Default element is the root element of the DOM_Object + ) + local function traverse_path(path_elements, current, t) + local t = t or {} + if #path_elements == 0 then + -- for _, x in ipairs(current._children or {}) do + -- table.insert(t,x) + -- end + table.insert(t,current) + return t + end + local current_path = table.remove(path_elements, 1) + for _, x in ipairs(self:get_children(current)) do + if self:is_element(x) then + local name = string.lower(self:get_element_name(x)) + if name == current_path then + t = traverse_path(path_elements, x, t) + end + end + end + return t + end + local current = current or self:root_node() -- self._handler.root + local path_elements = {} + local path = string.lower(path) + for el in path:gmatch("([^%s]+)") do table.insert(path_elements, el) end + return traverse_path(path_elements, current) + end + + --- Select elements chidlren using CSS selector syntax + -- @return table with elements matching the selector. + function DOM_Object:query_selector( + selector --- String using the CSS selector syntax + ) + local css_query = self.css_query + local css_parts = css_query:prepare_selector(selector) + return css_query:get_selector_path(self, css_parts) + end + + --- Get table with children of the current element + -- @return table with children of the selected element + function DOM_Object:get_children( + el --- [optional] element to be selected + ) + local el = el or self + local children = el._children or {} + return children + end + + --- Get the parent element + -- @return DOM_Object parent element + function DOM_Object:get_parent( + el --- [optional] element to be selected + ) + local el = el or self + return el._parent + end + + --- Execute function on the current element and all it's children elements + -- @return nothing + function DOM_Object:traverse_elements( + fn, --- function which will be executed on the current element and all it's children + current --- [optional] element to be selected + ) + local current = current or self -- + -- Following situation may happen when this method is called directly on the parsed object + if not current:get_node_type() then + current = self:root_node() + end + local status = true + if self:is_element(current) or self:get_node_type(current) == "ROOT"then + local status = fn(current) + -- don't traverse child nodes when the user function return false + if status ~= false then + for _, child in ipairs(self:get_children(current)) do + self:traverse_elements(fn, child) + end + end + end + end + + --- Execute function on list of elements returned by DOM_Object:get_path() + function DOM_Object:traverse_node_list( + nodelist --- table with nodes selected by DOM_Object:get_path() + , fn --- function to be executed + ) + local nodelist = nodelist or {} + for _, node in ipairs(nodelist) do + for _, element in ipairs(node._children) do + fn(element) + end + end + end + + --- Replace the current node with new one + -- @return boolean, message + function DOM_Object:replace_node( + new --- element which should replace the current element + ) + local old = self + local parent = self:get_parent(old) + local id,msg = self:find_element_pos( old) + if id then + parent._children[id] = new + return true + end + return false, msg + end + + --- Add child node to the current node + function DOM_Object:add_child_node( + child --- element to be inserted as a current node child + ) + local parent = self + child._parent = parent + table.insert(parent._children, child) + end + + + --- Create copy of the current node + -- @return DOM_Object element + function DOM_Object:copy_node( + element --- [optional] element to be copied + ) + local element = element or self + local t = {} + for k, v in pairs(element) do + if type(v) == "table" and k~="_parent" then + t[k] = self:copy_node(v) + else + t[k] = v + end + end + save_methods(t) + return t + end + + + --- Create a new element + -- @return DOM_Object element + function DOM_Object:create_element( + name, -- New tag name + attributes, -- Table with attributes + parent -- [optional] element which should be saved as the element's parent + ) + local parent = parent or self + local new = {} + new._type = "ELEMENT" + new._name = name + new._attr = attributes or {} + new._children = {} + new._parent = parent + save_methods(new) + return new + end + + --- Create new text node + -- @return DOM_Object text object + function DOM_Object:create_text_node( + text, -- string + parent -- [optional] element which should be saved as the element's parent + ) + local parent = parent or self + local new = {} + new._type = "TEXT" + new._parent = parent + new._text = text + save_methods(new) + return new + end + + --- Delete current node + function DOM_Object:remove_node( + element -- [optional] element to be removed + ) + local element = element or self + local parent = self:get_parent(element) + local pos = self:find_element_pos(element) + -- if pos then table.remove(parent._children, pos) end + if pos then + -- table.remove(parent._children, pos) + parent._children[pos] = setmetatable({_type = "removed"}, DOM_Object) + end + end + + --- Find the element position in the current node list + -- @return integer position of the current element in the element table + function DOM_Object:find_element_pos( + el -- [optional] element which should be looked up + ) + local el = el or self + local parent = self:get_parent(el) + if not self:is_element(parent) and self:get_node_type(parent) ~= "ROOT" then return nil, "The parent isn't element" end + for i, x in ipairs(parent._children) do + if x == el then return i end + end + return false, "Cannot find element" + end + + --- Get node list which current node is part of + -- @return table with elements + function DOM_Object:get_siblibgs( + el -- [optional] element for which the sibling element list should be retrieved + ) + local el = el or self + local parent = el:get_parent() + if parent:is_element() then + return parent:get_children() + end + end + + --- Get sibling node of the current node + -- @param change Distance from the current node + -- @return DOM_Object node + function DOM_Object:get_sibling_node( change) + local el = self + local pos = el:find_element_pos() + local siblings = el:get_siblibgs() + if pos and siblings then + return siblings[pos + change] + end + end + + --- Get next node + -- @return DOM_Object node + function DOM_Object:get_next_node( + el --- [optional] node to be used + ) + local el = el or self + return el:get_sibling_node(1) + end + + --- Get previous node + -- @return DOM_Object node + function DOM_Object:get_prev_node( + el -- [optional] node to be used + ) + local el = el or self + return el:get_sibling_node(-1) + end + + + -- include the methods to all xml nodes + save_methods(parser._handler.root) + -- parser: + return parser +end + +--- @export +return { + parse = parse, + serialize_dom= serialize_dom +} diff --git a/Master/texmf-dist/tex/luatex/luaxml/luaxml-mod-xml.lua b/Master/texmf-dist/tex/luatex/luaxml/luaxml-mod-xml.lua index 4da0edc43bb..3e52ecec8f8 100644 --- a/Master/texmf-dist/tex/luatex/luaxml/luaxml-mod-xml.lua +++ b/Master/texmf-dist/tex/luatex/luaxml/luaxml-mod-xml.lua @@ -293,8 +293,14 @@ local xmlParser = function(handler) break end end + local errorstring = tagstr:sub(errstart, errend) + -- it seems that it causes error if an attribute starts with `=` + if errorstring:match("^=") then break end + extstart,extend,endt2 = string.find(str,self._TAGEXT,endmatch+1) - tagstr = tagstr .. string.sub(string,endmatch,extend-1) + if not extstart then break end + + tagstr = tagstr .. string.sub(str,endmatch,extend-1) if not match then self:_err(self._errstr.xmlErr,pos) end @@ -362,6 +368,7 @@ local xmlParser = function(handler) obj._DTD3 = '<!DOCTYPE%s+(.-)%s*(%b[])%s*>' obj._DTD4 = '<!DOCTYPE%s+(.-)%s+(SYSTEM)%s+["\'](.-)["\']%s*>' obj._DTD5 = '<!DOCTYPE%s+(.-)%s+(PUBLIC)%s+["\'](.-)["\']%s+["\'](.-)["\']%s*>' + obj._DTD6 = '<!DOCTYPE%s+(.-)%s*>' --obj._DTD6 = "<!DOCTYPE%s+(.-)%s+(PUBLIC)%s+[\"'](.-)[\"']%s+[\"'](.-)[\"']%s*>" obj._ATTRERR1 = '=%s*"[^"]*$' @@ -453,6 +460,10 @@ local xmlParser = function(handler) if m then return m,e,{_root=r,_type=t,_uri=u} end + m,e,r = string.find(s, self._DTD6, pos) + if m then + return m,e, {_root=r } + end return nil end diff --git a/Master/texmf-dist/tex/luatex/luaxml/luaxml-parse-query.lua b/Master/texmf-dist/tex/luatex/luaxml/luaxml-parse-query.lua new file mode 100644 index 00000000000..95679670529 --- /dev/null +++ b/Master/texmf-dist/tex/luatex/luaxml/luaxml-parse-query.lua @@ -0,0 +1,43 @@ +-- Source: https://github.com/leafo/web_sanitize +-- Author: Leaf Corcoran +local R, S, V, P +do + local _obj_0 = require("lpeg") + R, S, V, P = _obj_0.R, _obj_0.S, _obj_0.V, _obj_0.P +end +local C, Cs, Ct, Cmt, Cg, Cb, Cc, Cp +do + local _obj_0 = require("lpeg") + C, Cs, Ct, Cmt, Cg, Cb, Cc, Cp = _obj_0.C, _obj_0.Cs, _obj_0.Ct, _obj_0.Cmt, _obj_0.Cg, _obj_0.Cb, _obj_0.Cc, _obj_0.Cp +end +local alphanum = R("az", "AZ", "09") +local num = R("09") +local white = S(" \t\n") ^ 0 +local word = (alphanum + S("_-")) ^ 1 +local mark +mark = function(name) + return function(...) + return { + name, + ... + } + end +end +local parse_query +parse_query = function(query) + local tag = word / mark("tag") + local cls = P(".") * (word / mark("class")) + local id = P("#") * (word / mark("id")) + local any = P("*") / mark("any") + local nth = P(":nth-child(") * C(num ^ 1) * ")" / mark("nth-child") + local first = P(":first-child") / mark("first-child") + local attr = P("[") * C(word) * P("]") / mark("attr") + local selector = Ct((any + nth + first + tag + cls + id + attr) ^ 1) + local pq = Ct(selector * (white * selector) ^ 0) + local pqs = Ct(pq * (white * P(",") * white * pq) ^ 0) + pqs = pqs * (white * -1) + return pqs:match(query) +end +return { + parse_query = parse_query +} diff --git a/Master/texmf-dist/tex/luatex/luaxml/luaxml-selectors.lua b/Master/texmf-dist/tex/luatex/luaxml/luaxml-selectors.lua deleted file mode 100644 index 1e65d3b8058..00000000000 --- a/Master/texmf-dist/tex/luatex/luaxml/luaxml-selectors.lua +++ /dev/null @@ -1,76 +0,0 @@ ---module(...,package.seeall) - -local M = {} -local inside = 0 -local function makeTag(s) - local function makeTag(fuf) - return fuf .. "[^>]*" - end - local print = texio.write_nl - if inside > 0 then print ("inside "..inside) else print("outside") end - --[[if inside then - return s .. "[^>]*" - else - inside = true--]] - inside = inside + 1 - local f = "<"..s.."[^>]*>" - inside = inside - 1 - return f - --end -end - -M.makeTag = makeTag -local function matchTag(tg) - return makeTag(tg) -end -M.matchTag=matchTag -local function matchDescendand(a,b) - return makeTag(a)..makeTag(b) -end -M.matchDescendand = matchDescendand - -local function matchChild(a,b) - return makeTag(a)..".*"..makeTag(b) -end -M.matchChild = matchChild - -local function matchSibling(a,b) - return a .. "[^>]*".."@%("..b.."[^>]*%)" -end -M.matchSibling = matchSibling - -local function matchClass(tg,class) - return tg.."[^>]*class=[|]*[^>]*|"..class.."[^>]*|" -end - -M.matchClass = matchClass -local function matchId(tg,id) - return tg.."[^>]*id="..id -end -M.matchId = matchId -local matcher = {} -M.matcher= matcher -local function makeElement(s) - local function makeTag(fuf) - return fuf .. "[^>]*" - end - return "<"..s .. "[^>]*>" -end - -M.makeElement = makeElement -function matcher.new() - local self = {} - local selectors={} - function self:addSelector(sel,val) - selectors[sel.."$"] = val - end - function self:testPath(path,fn) - for k, v in pairs(selectors) do - if path:match(k) then - fn(v) - end - end - end - return self -end -return M |