texlive[46570] Master/texmf-dist: luaxml (8feb18)

commits+karl at tug.org commits+karl at tug.org
Fri Feb 9 01:16:07 CET 2018


Revision: 46570
          http://tug.org/svn/texlive?view=revision&revision=46570
Author:   karl
Date:     2018-02-09 01:16:06 +0100 (Fri, 09 Feb 2018)
Log Message:
-----------
luaxml (8feb18)

Modified Paths:
--------------
    trunk/Master/texmf-dist/doc/luatex/luaxml/README
    trunk/Master/texmf-dist/doc/luatex/luaxml/luaxml.pdf
    trunk/Master/texmf-dist/doc/luatex/luaxml/luaxml.tex
    trunk/Master/texmf-dist/tex/luatex/luaxml/luaxml-mod-xml.lua

Added Paths:
-----------
    trunk/Master/texmf-dist/tex/luatex/luaxml/luaxml-cssquery.lua
    trunk/Master/texmf-dist/tex/luatex/luaxml/luaxml-domobject.lua
    trunk/Master/texmf-dist/tex/luatex/luaxml/luaxml-parse-query.lua

Removed Paths:
-------------
    trunk/Master/texmf-dist/tex/luatex/luaxml/dom-sample.lua
    trunk/Master/texmf-dist/tex/luatex/luaxml/luaxml-selectors.lua

Modified: trunk/Master/texmf-dist/doc/luatex/luaxml/README
===================================================================
--- trunk/Master/texmf-dist/doc/luatex/luaxml/README	2018-02-08 09:31:53 UTC (rev 46569)
+++ trunk/Master/texmf-dist/doc/luatex/luaxml/README	2018-02-09 00:16:06 UTC (rev 46570)
@@ -7,7 +7,17 @@
 
     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:
 ========
 

Modified: trunk/Master/texmf-dist/doc/luatex/luaxml/luaxml.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/luatex/luaxml/luaxml.tex
===================================================================
--- trunk/Master/texmf-dist/doc/luatex/luaxml/luaxml.tex	2018-02-08 09:31:53 UTC (rev 46569)
+++ trunk/Master/texmf-dist/doc/luatex/luaxml/luaxml.tex	2018-02-09 00:16:06 UTC (rev 46570)
@@ -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}
 
-\noindent Example of usage:
+|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. 
+
+\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: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 @@
 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 @@
   end
   local children = root._children or {}
   for _, child in ipairs(children) do
-    traverseDom(parser,child, level + 1)
+    traverseDom(child, level + 1)
   end
 end
 
@@ -121,11 +460,17 @@
 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
  ELEMENT : p
@@ -136,16 +481,15 @@
   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}
 

Deleted: trunk/Master/texmf-dist/tex/luatex/luaxml/dom-sample.lua
===================================================================
--- trunk/Master/texmf-dist/tex/luatex/luaxml/dom-sample.lua	2018-02-08 09:31:53 UTC (rev 46569)
+++ trunk/Master/texmf-dist/tex/luatex/luaxml/dom-sample.lua	2018-02-09 00:16:06 UTC (rev 46570)
@@ -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)

Added: trunk/Master/texmf-dist/tex/luatex/luaxml/luaxml-cssquery.lua
===================================================================
--- trunk/Master/texmf-dist/tex/luatex/luaxml/luaxml-cssquery.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/tex/luatex/luaxml/luaxml-cssquery.lua	2018-02-09 00:16:06 UTC (rev 46570)
@@ -0,0 +1,214 @@
+--- CSS query module for LuaXML
+-- @module luaxml-cssquery
+-- @author Michal Hoftich <michal.h21 at 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


Property changes on: trunk/Master/texmf-dist/tex/luatex/luaxml/luaxml-cssquery.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/tex/luatex/luaxml/luaxml-domobject.lua
===================================================================
--- trunk/Master/texmf-dist/tex/luatex/luaxml/luaxml-domobject.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/tex/luatex/luaxml/luaxml-domobject.lua	2018-02-09 00:16:06 UTC (rev 46570)
@@ -0,0 +1,510 @@
+--- DOM module for LuaXML
+-- @module luaxml-domobject
+-- @author Michal Hoftich <michal.h21 at 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
+}


Property changes on: trunk/Master/texmf-dist/tex/luatex/luaxml/luaxml-domobject.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Modified: trunk/Master/texmf-dist/tex/luatex/luaxml/luaxml-mod-xml.lua
===================================================================
--- trunk/Master/texmf-dist/tex/luatex/luaxml/luaxml-mod-xml.lua	2018-02-08 09:31:53 UTC (rev 46569)
+++ trunk/Master/texmf-dist/tex/luatex/luaxml/luaxml-mod-xml.lua	2018-02-09 00:16:06 UTC (rev 46570)
@@ -293,8 +293,14 @@
                             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 @@
     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 @@
         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
 

Added: trunk/Master/texmf-dist/tex/luatex/luaxml/luaxml-parse-query.lua
===================================================================
--- trunk/Master/texmf-dist/tex/luatex/luaxml/luaxml-parse-query.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/tex/luatex/luaxml/luaxml-parse-query.lua	2018-02-09 00:16:06 UTC (rev 46570)
@@ -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
+}


Property changes on: trunk/Master/texmf-dist/tex/luatex/luaxml/luaxml-parse-query.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Deleted: trunk/Master/texmf-dist/tex/luatex/luaxml/luaxml-selectors.lua
===================================================================
--- trunk/Master/texmf-dist/tex/luatex/luaxml/luaxml-selectors.lua	2018-02-08 09:31:53 UTC (rev 46569)
+++ trunk/Master/texmf-dist/tex/luatex/luaxml/luaxml-selectors.lua	2018-02-09 00:16:06 UTC (rev 46570)
@@ -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



More information about the tex-live-commits mailing list