From a2a03aa6230e0fdc8d5a7922f2c0c23c05915fe1 Mon Sep 17 00:00:00 2001 From: Karl Berry Date: Thu, 27 Apr 2017 16:43:40 +0000 Subject: make4ht (27apr17) git-svn-id: svn://tug.org/texlive/trunk@44079 c570f23f-e606-0410-a88d-b1316a301751 --- .../scripts/make4ht/filters/make4ht-fix-links.lua | 19 + Master/texmf-dist/scripts/make4ht/make4ht | 5 + Master/texmf-dist/scripts/make4ht/make4ht-dom.lua | 448 +++++++++++++++++++++ Master/texmf-dist/scripts/make4ht/make4ht-lib.lua | 30 +- Master/texmf-dist/scripts/make4ht/mathnode.lua | 87 ++++ Master/texmf-dist/scripts/make4ht/mkparams.lua | 19 +- Master/texmf-dist/scripts/make4ht/mkutils.lua | 13 +- 7 files changed, 607 insertions(+), 14 deletions(-) create mode 100644 Master/texmf-dist/scripts/make4ht/filters/make4ht-fix-links.lua create mode 100755 Master/texmf-dist/scripts/make4ht/make4ht-dom.lua create mode 100755 Master/texmf-dist/scripts/make4ht/mathnode.lua (limited to 'Master/texmf-dist/scripts') diff --git a/Master/texmf-dist/scripts/make4ht/filters/make4ht-fix-links.lua b/Master/texmf-dist/scripts/make4ht/filters/make4ht-fix-links.lua new file mode 100644 index 00000000000..4dbc26824f3 --- /dev/null +++ b/Master/texmf-dist/scripts/make4ht/filters/make4ht-fix-links.lua @@ -0,0 +1,19 @@ +-- replace colons in `id` or `href` attributes for local links with underscores +-- + +local function fix_href_colons(s) + return s:gsub('(href=".-")', function(a) + if a:match("[a-z]%://") then return a end + return a:gsub(":","_") + end) +end + +local function fix_id_colons(s) + return s:gsub('(id=".-")', function(a) + return a:gsub(":", "_") + end) +end + +return function(s) + return fix_id_colons(fix_href_colons(s)) +end diff --git a/Master/texmf-dist/scripts/make4ht/make4ht b/Master/texmf-dist/scripts/make4ht/make4ht index 49b1d0159a5..15bda54aca8 100755 --- a/Master/texmf-dist/scripts/make4ht/make4ht +++ b/Master/texmf-dist/scripts/make4ht/make4ht @@ -24,6 +24,11 @@ make4ht [options] filename ["tex4ht.sty op." "tex4ht op." "t4ht op" "latex op"] (string) Input file name ]] +-- set version number. the template should be replaced by the +-- actual version number by the build script +local version = "v0.1c" +mkparams.version_number = version + local args = mkparams.get_args() local parameters = mkparams.process_args(args) diff --git a/Master/texmf-dist/scripts/make4ht/make4ht-dom.lua b/Master/texmf-dist/scripts/make4ht/make4ht-dom.lua new file mode 100755 index 00000000000..fc90bf67e7c --- /dev/null +++ b/Master/texmf-dist/scripts/make4ht/make4ht-dom.lua @@ -0,0 +1,448 @@ +--- DOM module for LuaXML +-- @module make4ht-dom +local dom = {} +local xml = require("luaxml-mod-xml") +local handler = require("luaxml-mod-handler") +local query = require("make4ht-parse-query") + + +local void = {area = true, base = true, br = true, col = true, hr = true, img = true, input = true, link = true, meta = true, param = true} + +local actions = { + TEXT = {text = "%s"}, + COMMENT = {start = ""}, + ELEMENT = {start = "<%s%s>", stop = "", void = "<%s%s />"}, + DECL = {start = ""}, + DTD = {start = ""} +} + +--- It serializes the DOM object back to XML +-- @function serialize_dom +-- @param parser DOM object +-- @param current +-- @param level +-- @param output +-- @return table +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, 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, 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"], attributes._name, attributes._uri ) + 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 + +local parse = function(x) + local domHandler = handler.domHandler() + local Parser = xml.xmlParser(domHandler) + -- preserve whitespace + Parser.options.stripWS = nil + Parser:parse(x) + Parser.current = Parser._handler.root + Parser.__index = Parser + + local function save_methods(element) + setmetatable(element,Parser) + local children = element._children or {} + for _, x in ipairs(children) do + save_methods(x) + end + end + local parser = setmetatable({}, Parser) + + --- @class Parser + function Parser.root_node(self) + return self._handler.root + end + + + function Parser.get_element_type(self, el) + local el = el or self + return el._type + end + function Parser.is_element(self, el) + local el = el or self + return self:get_element_type(el) == "ELEMENT" + end + + function Parser.is_text(self, el) + local el = el or self + return self:get_element_type(el) == "TEXT" + end + + local lower = string.lower + + function Parser.get_element_name(self, el) + local el = el or self + return el._name or "unnamed" + end + + function Parser.get_attribute(self, name) + local el = self + if self:is_element(el) then + local attr = el._attr or {} + return attr[name] + end + end + + function Parser.set_attribute(self, name, value) + local el = self + if self:is_element(el) then + el._attr[name] = value + return true + end + end + + + function Parser.serialize(self, current) + 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 + + function Parser.get_path(self,path, current) + 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 + + function Parser.calculate_specificity(self, 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 + + function Parser.match_querylist(self, querylist) + local matches = {} + local querylist = 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, self) + if result then matches[#matches+1] = element end + end + end + return matches + end + + function Parser.get_selector_path(self, selectorlist) + local nodelist = {} + self:traverse_elements(function(el) + local matches = el:match_querylist(selectorlist) + print("Matching", el:get_element_name(), #matches) + if #matches > 0 then nodelist[#nodelist+1] = el + end + end) + return nodelist + end + + --- Parse CSS selector to match table + function Parser.prepare_selector(self, selector) + 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 = query.parse_query(selector) or {} + -- several selectors may be separated using ",", we must process them separately + for _, part in ipairs(parts) do + querylist[#querylist+1] = {query = parse_selector(part)} + end + return querylist + end + + function Parser.get_children(self, el) + local el = el or self + local children = el._children or {} + return children + end + + function Parser.get_parent(self, el) + local el = el or self + return el._parent + end + + function Parser.traverse_elements(self, fn, current) + local current = current or self:root_node() + local status = true + if self:is_element(current) or self:get_element_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 + + function Parser.traverse_node_list(self, nodelist, fn) + local nodelist = nodelist or {} + for _, node in ipairs(nodelist) do + for _, element in ipairs(node._children) do + fn(element) + end + end + end + + function Parser.replace_node(self, new) + 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 + + function Parser.add_child_node(self, child) + local parent = self + child._parent = parent + table.insert(parent._children, child) + end + + + function Parser.copy_node(self, element) + 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 + + function Parser.create_element(self, name, attributes, 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 + + function Parser.remove_node(self, element) + 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"}, Parser) + end + end + + function Parser.find_element_pos(self, el) + local el = el or self + local parent = self:get_parent(el) + if not self:is_element(parent) and self:get_element_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 + + function Parser.get_siblibgs(self, el) + local el = el or self + local parent = el:get_parent() + if parent:is_element() then + return parent:get_children() + end + end + + function Parser.get_sibling_node(self, 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 + + function Parser.get_next_node(self, el) + local el = el or self + return el:get_sibling_node(1) + end + + function Parser.get_prev_node(self, el) + 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 + + +local M = {} +M.parse = parse +M.serialize= serialize_dom +return M diff --git a/Master/texmf-dist/scripts/make4ht/make4ht-lib.lua b/Master/texmf-dist/scripts/make4ht/make4ht-lib.lua index 3b550b9995c..918343f20ca 100755 --- a/Master/texmf-dist/scripts/make4ht/make4ht-lib.lua +++ b/Master/texmf-dist/scripts/make4ht/make4ht-lib.lua @@ -116,9 +116,9 @@ Make.file_matches = function(self, files) self.matches[k].params = v end -- Loop over files, run command on matched - for _, file in pairs(files)do + for _, file in ipairs(files)do statuses[file] = {} - for _, s in pairs(self.matches) do + for _, s in ipairs(self.matches) do local pattern= s.pattern if file:match(pattern) then local status, msg = self:run_command(file,s) @@ -134,6 +134,27 @@ Make.file_matches = function(self, files) return statuses end +-- add files from the mk4 file +-- we must add them to the table generated from the lg file, so they can be processed later +-- +Make.add_file = function(self, filename) + -- self.lgfile should be present, as it is created once the lg_file was parsed for the first time + local lg = self.lgfile or {} + local files = lg.files or {} + -- run filters on the file + local filtertable = {filename} + -- should we care about return status? + self:file_matches(filtertable) + -- break if the file is present already + -- start at the end, it it was added by a build file, the file will be likely at the end + for i = #files,1,-1 do + if files[i] == filename then return false, "File was already added" end + end + -- save the added file to the lg_file + table.insert(lg.files, filename) + self.lg = lg +end + Make.run = function(self) local return_codes = {} local params = self.params or {} @@ -174,12 +195,13 @@ Make.run = function(self) end local lgfile = params.input and params.input .. ".lg" or nil if lgfile then - local lg = mkutils.parse_lg(lgfile) + self.lgfile = self.lgfile or mkutils.parse_lg(lgfile) + local lg = self.lgfile -- First convert images from lg files self:image_convert(lg["images"]) -- Then run file matchers on lg files and converted images local files = lg["files"] - for _,v in pairs(lg["images"]) do + for _,v in ipairs(lg["images"]) do local v = v.output -- print(v) table.insert(files,v) diff --git a/Master/texmf-dist/scripts/make4ht/mathnode.lua b/Master/texmf-dist/scripts/make4ht/mathnode.lua new file mode 100755 index 00000000000..6d054ad7b85 --- /dev/null +++ b/Master/texmf-dist/scripts/make4ht/mathnode.lua @@ -0,0 +1,87 @@ +-- local mathnodepath = os.getenv "mathjaxnodepath" +-- +-- print("mathnode", mathnodepath) +local mkutils = require "mkutils" +-- other possible value is page2svg +local mathnodepath = "page2html" +-- options for MathJax command +local options = "--format MathML" +-- math fonts position +-- don't alter fonts if not set +local fontdir = nil +-- if we copy fonts +local fontdest = nil +local fontformat = "otf" + +local function compile(src) + local tmpfile = os.tmpname() + local filename = src + print("Compile using MathJax") + local command = mathnodepath .. " ".. options .. " < " .. filename .. " > " .. tmpfile + print(command) + local status = os.execute(command) + print("Result written to: ".. tmpfile) + mkutils.cp(tmpfile, src) + os.remove(tmpfile) +end + +local function extract_css(file) + local f = io.open(file, "r") + local contents = f:read("*all") + f:close() + local css = "" + local filename = "" + contents = contents:gsub('', function(name, style) + css = style + filename = (name or "") .. ".css" + return '' + end) + local x = assert(io.open(file, "w")) + x:write(contents) + x:close() + return filename, css +end + +-- +local function use_fonts(css) + local family_pattern = "font%-family:%s*(.-);.-%/([^%/]+)%.".. fontformat + local family_build = "@font-face {font-family: %s; src: url('%s/%s.%s') format('%s')}" + local fontdir = fontdir:gsub("/$","") + css = css:gsub("(@font%-face%s*{.-})", function(face) + -- if not face:match("url%(") then return face end + if not face:match("url%(") then return "" end + -- print(face) + local family, filename = face:match(family_pattern) + print(family, filename) + local newfile = string.format("%s/%s.%s", fontdir, filename, fontformat) + Make:add_file(newfile) + return family_build:format(family, fontdir, filename, fontformat, fontformat) + -- return face + end) + return css +end + + +local function save_css(filename, css) + local f = io.open(filename, "w") + f:write(css) + f:close() +end + +return function(text, arguments) + -- if arguments.prg then mathnodepath = arguments.prg end + mathnodepath = arguments.prg or mathnodepath + options = arguments.options or options + fontdir = arguments.fontdir or fontdir + fontdest = arguments.fontdest or fontdest + fontformat = arguments.fontformat or fontformat + compile(text) + filename, css = extract_css(text) + if fontdir then + css = use_fonts(css) + end + save_css(filename, css) + Make:add_file(filename) + -- print(css) + print(filename) +end diff --git a/Master/texmf-dist/scripts/make4ht/mkparams.lua b/Master/texmf-dist/scripts/make4ht/mkparams.lua index 0dc74a0ce16..61c522a2406 100755 --- a/Master/texmf-dist/scripts/make4ht/mkparams.lua +++ b/Master/texmf-dist/scripts/make4ht/mkparams.lua @@ -2,6 +2,12 @@ local lapp = require "lapp-mk4" local mkutils = require "mkutils" local m = {} -- use ugly module system for new lua versions support +-- these two variables will be used in the version number +-- progname will be set in get_args +m.progname = "make4ht" +-- set the version number before call to process_args() +m.version_number = "v0.1" + m.optiontext = [[ ${progname} - build system for tex4ht Usage: @@ -43,7 +49,7 @@ local function process_args(args) end if args.version ==true then - print "make4ht version 0.1b" + print(string.format("%s version %s", m.progname, m.version_number)) os.exit() end @@ -66,14 +72,15 @@ local function process_args(args) local compiler = args.lua and "dvilualatex" or args.xetex and "xelatex --no-pdf" or "latex" + local tex_file = args.filename local input = mkutils.remove_extension(args.filename) - local latex_params = {} - local tex_file = input local insert_latex = get_inserter(args,latex_params) insert_latex("shell-escape","-shell-escape") local latex_cli_params = args[4] or "" if not latex_cli_params:match("%-jobname") then + -- we must strip out directories from jobname when full path to document is given + input = input:match("([^%/]+)$") table.insert(latex_params,"-jobname="..input) else -- when user specifies -jobname, we must change name of the input file, @@ -117,12 +124,13 @@ local function process_args(args) --end local tex4ht = "" + local dvi= args.xetex and "xdv" or "dvi" if args[2] and args[2] ~="" then tex4ht = args[2] else tex4ht = args.utf8 and " -cmozhtf -utf8" or "" - local xdv = args.xetex and " -.xdv" or "" - tex4ht = tex4ht .. xdv + if args.xetex then tex4ht = tex4ht .. " -.xdv" end + -- tex4ht = tex4ht .. xdv end local t4ht = args[3] or "" @@ -146,6 +154,7 @@ local function process_args(args) ,tex4ht_par=tex4ht ,t4ht_par=t4ht ,mode = mode + ,dvi = dvi ,build_file = build_file --,t4ht_dir_format=t4ht_dir_format } diff --git a/Master/texmf-dist/scripts/make4ht/mkutils.lua b/Master/texmf-dist/scripts/make4ht/mkutils.lua index e550ead9801..56c413ffb43 100755 --- a/Master/texmf-dist/scripts/make4ht/mkutils.lua +++ b/Master/texmf-dist/scripts/make4ht/mkutils.lua @@ -92,9 +92,13 @@ function parse_lg(filename) end +-- local cp_func = os.type == "unix" and "cp" or "copy" +-- maybe it would be better to actually move the files +-- in reality it isn't. +-- local cp_func = os.type == "unix" and "mv" or "move" function cp(src,dest) - local command = string.format('%s %s %s', cp_func, src, dest) + local command = string.format('%s "%s" "%s"', cp_func, src, dest) if cp_func == "copy" then command = command:gsub("/",'\\') end print("Copy: "..command) os.execute(command) @@ -262,15 +266,14 @@ env.Make:add("htlatex",function(par) return 1 end local len = f:seek("end") - - f:seek("set", len - 256) + f:seek("set", len - 1256) local text = f:read("*a") f:close() - if text:match("No pages of output") then return 1 end + if text:match("No pages of output") or text:match("TeX capacity exceeded, sorry") then return 1 end return 0 end ,{correct_exit=0}) -env.Make:add("tex4ht","tex4ht ${tex4ht_par} \"${input}\"", nil, 1) +env.Make:add("tex4ht","tex4ht ${tex4ht_par} \"${input}.${dvi}\"", nil, 1) env.Make:add("t4ht","t4ht ${t4ht_par} \"${input}.${ext}\"",{ext="dvi"},1) function load_config(settings, config_name) -- cgit v1.2.3