summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2018-02-09 00:16:06 +0000
committerKarl Berry <karl@freefriends.org>2018-02-09 00:16:06 +0000
commitb2153506a41b9f934841bc0eb31dd9c3946b407b (patch)
treedebea85a21708749fafa1748c894ab25b0e125e8
parente6304d3f0b0d72c2f38350d3ae4b4fa6bd862793 (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/README10
-rw-r--r--Master/texmf-dist/doc/luatex/luaxml/luaxml.pdfbin114519 -> 98163 bytes
-rw-r--r--Master/texmf-dist/doc/luatex/luaxml/luaxml.tex434
-rw-r--r--Master/texmf-dist/tex/luatex/luaxml/dom-sample.lua29
-rw-r--r--Master/texmf-dist/tex/luatex/luaxml/luaxml-cssquery.lua214
-rw-r--r--Master/texmf-dist/tex/luatex/luaxml/luaxml-domobject.lua510
-rw-r--r--Master/texmf-dist/tex/luatex/luaxml/luaxml-mod-xml.lua13
-rw-r--r--Master/texmf-dist/tex/luatex/luaxml/luaxml-parse-query.lua43
-rw-r--r--Master/texmf-dist/tex/luatex/luaxml/luaxml-selectors.lua76
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
index 3a6967c9c66..07ec3244c70 100644
--- a/Master/texmf-dist/doc/luatex/luaxml/luaxml.pdf
+++ b/Master/texmf-dist/doc/luatex/luaxml/luaxml.pdf
Binary files differ
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 = {
+ [">"] = "&gt;",
+ ["<"] = "&lt;",
+ ["&"] = "&amp;",
+ ['"'] = "&quot;",
+ ["'"] = "&#39;"
+}
+
+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