diff options
Diffstat (limited to 'support/make4ht/domfilters')
-rw-r--r-- | support/make4ht/domfilters/make4ht-aeneas.lua | 144 | ||||
-rw-r--r-- | support/make4ht/domfilters/make4ht-fixinlines.lua | 57 | ||||
-rw-r--r-- | support/make4ht/domfilters/make4ht-idcolons.lua | 20 | ||||
-rw-r--r-- | support/make4ht/domfilters/make4ht-joincharacters.lua | 85 | ||||
-rw-r--r-- | support/make4ht/domfilters/make4ht-joincolors.lua | 75 | ||||
-rw-r--r-- | support/make4ht/domfilters/make4ht-odtimagesize.lua | 16 | ||||
-rw-r--r-- | support/make4ht/domfilters/make4ht-t4htlinks.lua | 27 |
7 files changed, 424 insertions, 0 deletions
diff --git a/support/make4ht/domfilters/make4ht-aeneas.lua b/support/make4ht/domfilters/make4ht-aeneas.lua new file mode 100644 index 0000000000..0b93c95840 --- /dev/null +++ b/support/make4ht/domfilters/make4ht-aeneas.lua @@ -0,0 +1,144 @@ +-- DOM filter for Aeneas, tool for automatical text and audio synchronization +-- https://github.com/readbeyond/aeneas +-- It adds elements with id attributes for text chunks, in sentence length. +-- +-- +local cssquery = require "luaxml-cssquery" +local mkutils = require "mkutils" + +-- Table of CSS selectors to be skipped. +local skip_elements = { "math", "svg"} + +-- The id attribute format is configurable +-- Aeneas must be told to search for the ID pattern using is_text_unparsed_id_regex +-- option in Aneas configuration file +local id_prefix = "ast" + +-- Pattern to mach a sentence. It should match two groups, first is actual +-- sentence, the second optional interpunction mark. +local sentence_match = "([^%.^%?^!]*)([%.%?!]?)" + +-- convert table with selectors to a query list +local function prepare_selectors(skips) + local css = cssquery() + for _, selector in ipairs(skips) do + css:add_selector(selector) + end + return css +end + +-- save the HTML language +local function save_config(dom, saves) + local get_lang = function(d) + local html = d:query_selector("html")[1] or {} + return html:get_attribute("lang") + end + local saves = saves or {} + local config = get_filter_settings "aeneas_config" + if config.language then return end + saves.lang = get_lang(dom) + filter_settings "aeneas-config" (saves) +end +-- make span element with unique id for a sentence +local function make_span(id,parent, text) + local newobj = parent:create_element("span", {id=id }) + newobj.processed = true -- to disable multiple processing of the node + local text_node = newobj:create_text_node(text) + newobj:add_child_node(text_node) + return newobj +end + +-- make the id attribute and update the id value +local function make_id(lastid, id_prefix) + local id = id_prefix .. lastid + lastid = lastid + 1 + return id, lastid +end + +-- parse text for sentences and add spans +local function make_ids(parent, text, lastid, id_prefix) + local t = {} + local id + for chunk, punct in text:gmatch(sentence_match) do + id, lastid = make_id(lastid, id_prefix) + local newtext = chunk..punct + -- the newtext is empty string sometimes. we can skipt it then. + if newtext~="" then + table.insert(t, make_span(id, parent, newtext)) + end + end + return t, lastid +end + + + +-- test if the DOM element is in list of skipped CSS selectors +local function is_skipped(el, css) + local matched = css:match_querylist(el) + return #matched > 0 +end + +local function aeneas(dom, par) + local par = par or {} + local id = 1 + local options = get_filter_settings "aeneas" + local skip_elements = options.skip_elements or par.skip_elements or skip_elements + local id_prefix = options.id_prefix or par.id_prefix or id_prefix + local skip_object = prepare_selectors(skip_elements) + sentence_match = options.sentence_match or par.sentence_match or sentence_match + + local body = dom:query_selector("body")[1] + -- process only the document body + if not body then return dom end + -- save information for aeneas_config + save_config(dom, {id_prefix = id_prefix}) + body:traverse_elements(function(el) + -- skip disabled elements + if(is_skipped(el, skip_object)) then return false end + -- skip already processed elements + if el.processed then return false end + local newchildren = {} -- this will contain the new elements + local children = el:get_children() + local first_child = children[1] + + -- if the element contains only text, doesn't already have an id attribute and the text is short, + -- the id is set directly on that element. + if #children == 1 + and first_child:is_text() + and not el:get_attribute("id") + and string.len(first_child._text) < 20 + and el._attr + then + local idtitle + idtitle, id = make_id(id, id_prefix) + print(el._name, first_child._text) + el:set_attribute("id", idtitle) + return el + end + + for _, child in ipairs(children) do + -- process only non-empty text + if child:is_text() and child._text:match("%a+") then + local newnodes + newnodes, id = make_ids(child, child._text, id, id_prefix) + for _, node in ipairs(newnodes) do + table.insert(newchildren, node or {}) + end + else + -- insert the current processing element to the new element list + -- if it isn't only text + table.insert(newchildren, child or {}) + end + end + -- replace element children with the new ones + if #newchildren > 0 then + el._children = {} + for _, c in ipairs(newchildren) do + el:add_child_node(c) + end + end + end) + return dom +end + +return aeneas diff --git a/support/make4ht/domfilters/make4ht-fixinlines.lua b/support/make4ht/domfilters/make4ht-fixinlines.lua new file mode 100644 index 0000000000..25489f59ca --- /dev/null +++ b/support/make4ht/domfilters/make4ht-fixinlines.lua @@ -0,0 +1,57 @@ +local inline_elements = { + b=true, + big=true, + i=true, + small=true, + tt=true, + abbr=true, + acronym=true, + cite=true, + code=true, + dfn=true, + em=true, + kbd=true, + strong=true, + samp=true, + time=true, + var=true, + a=true, + bdo=true, + br=true, + img=true, + map=true, + object=true, + q=true, + script=true, + span=true, + sub=true, + sup=true, + button=true, + input=true, + label=true, + select=true, + textarea=true, + mn=true, + mi=true +} + + +local function fix_inlines(obj) + local settings = get_filter_settings "fixinlines" + local inline_elements = settings.inline_elements or inline_elements + local nodes = obj:get_path("html body") + obj:traverse_node_list(nodes, function(jej) + if jej._type == "ELEMENT" then + local name = string.lower(jej._name) + -- local parent = jej:get_parent_node() + if inline_elements[name] then + local new = obj:create_element("p" ) + new:add_child_node(obj:copy_node(jej)) + jej:replace_node(new) + end + end + end) + return obj +end + +return fix_inlines diff --git a/support/make4ht/domfilters/make4ht-idcolons.lua b/support/make4ht/domfilters/make4ht-idcolons.lua new file mode 100644 index 0000000000..ccdafbd6d4 --- /dev/null +++ b/support/make4ht/domfilters/make4ht-idcolons.lua @@ -0,0 +1,20 @@ +local function id_colons(obj) + -- replace : characters in links and ids with unserscores + obj:traverse_elements(function(el) + local name = string.lower(obj:get_element_name(el)) + if name == "a" then + local href = obj:get_attribute(el, "href") + if href and not href:match("[a-z]%://") then + obj:set_attribute(el, "href", href:gsub(":", "_")) + end + end + local id = obj:get_attribute( el , "id") + if id then + obj:set_attribute(el, "id", id:gsub(":", "_")) + end + -- local id = obj:get_attribute(el, "id") + end) + return obj +end + +return id_colons diff --git a/support/make4ht/domfilters/make4ht-joincharacters.lua b/support/make4ht/domfilters/make4ht-joincharacters.lua new file mode 100644 index 0000000000..99d3058d7d --- /dev/null +++ b/support/make4ht/domfilters/make4ht-joincharacters.lua @@ -0,0 +1,85 @@ +local charclases = { + span=true, + mn = true, +} + +local function join_characters(obj,par) + -- join adjanced span and similar elements inserted by + -- tex4ht to just one object. + local par = par or {} + local options = get_filter_settings "joincharacters" + local charclases = options.charclases or par.charclases or charclases + + obj:traverse_elements(function(el) + local get_name = function(curr) + return string.lower(curr:get_element_name()) + end + local get_class = function(next_el) + return next_el:get_attribute("class") + end + local is_span = function(next_el) + return charclases[get_name(next_el)] + end + + local function get_next(curr, class) + local next_el = curr:get_next_node() + if next_el and next_el:is_element() and is_span(next_el) then + return next_el + -- if the next node is space followed by a matching element, we should add this space + elseif next_el and next_el:is_text() and get_next(next_el, class) then + local text = next_el._text + -- match only text containing just whitespace + if text:match("^%s+$") then return next_el end + end + end + -- loop over all elements and test if the current element is in a list of + -- processed elements (charclases) + if is_span(el) then + local next_el = get_next(el) + -- loop over the following elements and test whether they are of the same type + -- as the current one + while next_el do + -- save the next element because we will remove it later + local real_next = get_next(next_el) + if get_name(el) == get_name(next_el) and get_class(el) == get_class(next_el) and not el:get_attribute("id") then + -- it the following element match, copy it's children to the current element + for _, child in ipairs(next_el:get_children()) do + el:add_child_node(child) + end + -- remove the next element + next_el:remove_node() + -- add the whitespace + elseif next_el:is_text() then + local s = next_el._text + -- we must create a new node + el:add_child_node(el:create_text_node(s)) + next_el:remove_node() + real_next = nil + else + real_next = nil + end + -- use the saved element as a next object + next_el = real_next + end + end + + end) + -- join text nodes in an element into one + obj:traverse_elements(function(el) + -- save the text + local t = {} + local children = el:get_children() + for _, x in ipairs(children) do + if x:is_text() then + t[#t+1] = x._text + else + return nil + end + end + el._text = table.concat(t) + return el + end) + return obj +end + +return join_characters diff --git a/support/make4ht/domfilters/make4ht-joincolors.lua b/support/make4ht/domfilters/make4ht-joincolors.lua new file mode 100644 index 0000000000..94a3a054e6 --- /dev/null +++ b/support/make4ht/domfilters/make4ht-joincolors.lua @@ -0,0 +1,75 @@ +local cssfiles = {} + + +-- keep mapping between span ids and colors +local colors = {} + +local function extract_colors(csscontent) + local used_colors = {} + -- delete the color ids and save the used colors + csscontent = csscontent:gsub("[%a]*%#(textcolor.-)%s*{%s*color%s*%:%s*(.-)%s*%}%s", function(id, color) + -- convert rgb() function to hex value and generate the span name + local converted = "textcolor-" .. color:gsub("rgb%((.-),(.-),(.-)%)", function(r,g,b) + return string.format("%02x%02x%02x", tonumber(r), tonumber(g), tonumber(b)) + end) + -- remove the # characters from the converted color name + converted = converted:gsub("%#", "") + -- save the id and used color + colors[id] = converted + used_colors[converted] = color + return "" + end) + -- add the used colors to css + local t = {} + for class, color in pairs(used_colors) do + t[#t+1] = string.format(".%s{color:%s;}", class, color) + end + return csscontent .. table.concat(t, "\n") +end + +local function process_css(cssfile) + local f = io.open(cssfile,"r") + if not f then return nil, "Cannot open the CSS file: ".. cssfile end + local content = f:read("*all") + f:close() + -- delete color ids and replace them with joined spans + local newcontent = extract_colors(content) + -- save the updated css file + local f=io.open(cssfile, "w") + f:write(newcontent) + f:close() +end + + +local function process_css_files(dom) + for _, el in ipairs(dom:query_selector("link")) do + local href = el:get_attribute("href") or "" + if not cssfiles[href] and href:match("css$") then + print("Load CSS file ", href) + cssfiles[href] = true + process_css(href) + end + end + +end + +local function join_colors(dom) + -- find css files in the current HTML file and join the colors + process_css_files(dom) + for _, span in ipairs(dom:query_selector("span")) do + local id = span:get_attribute("id") + if id then + -- test if the id is in the saved colors + local class = colors[id] + if class then + -- remove the id + span:set_attribute("id", nil) + span:set_attribute("class", class) + end + end + end + + return dom +end + +return join_colors diff --git a/support/make4ht/domfilters/make4ht-odtimagesize.lua b/support/make4ht/domfilters/make4ht-odtimagesize.lua new file mode 100644 index 0000000000..4ce8f0d92e --- /dev/null +++ b/support/make4ht/domfilters/make4ht-odtimagesize.lua @@ -0,0 +1,16 @@ +-- set correct dimensions to frames around images +return function(dom) + local frames = dom:query_selector("draw|frame") + for _, frame in ipairs(frames) do + local images = frame:query_selector("draw|image") + if #images > 0 then + local image = images[1] + local width = image:get_attribute("svg:width") + local height = image:get_attribute("svg:height") + if widht then frame:set_attribute("svg:width", width) end + if height then frame:set_attribute("svg:height", height) end + print("image dimensions", width, height) + end + end + return dom +end diff --git a/support/make4ht/domfilters/make4ht-t4htlinks.lua b/support/make4ht/domfilters/make4ht-t4htlinks.lua new file mode 100644 index 0000000000..9a4efb9739 --- /dev/null +++ b/support/make4ht/domfilters/make4ht-t4htlinks.lua @@ -0,0 +1,27 @@ +-- This filter is used by the ODT output format to fix links +return function(dom) + for _, link in ipairs(dom:query_selector("t4htlink")) do + local name = link:get_attribute("name") + local href = link:get_attribute("href") + local children = link:get_children() + -- print("link", name, href, #link._children, link:get_text()) + -- add a link if it contains any subnodes and has href attribute + if #children > 0 and href then + link._name = "text:a" + link._attr = {["xlink:type"]="simple", ["xlink:href"]=href} + -- if the link is named, add a bookmark + if name then + local bookmark = link:create_element("text:bookmark", {["text:name"] = name}) + link:add_child_node(bookmark) + end + -- add bookmark if element has name + elseif name then + link._name = "text:bookmark" + link._attr = {["text:name"] = name} + else + -- just remove the link in other cases + link:remove_node() + end + end + return dom +end |