From f325bd88d1d4db13f51277bc59c8ff8814e937bc Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Sat, 24 Feb 2024 03:01:46 +0000 Subject: CTAN sync 202402240301 --- support/make4ht/README | 43 ++++- support/make4ht/changelog.tex | 211 +++++++++++++++++++++ support/make4ht/domfilters/make4ht-collapsetoc.lua | 2 +- support/make4ht/domfilters/make4ht-idcolons.lua | 30 ++- support/make4ht/domfilters/make4ht-inlinecss.lua | 2 +- support/make4ht/domfilters/make4ht-mathmlfixes.lua | 120 ++++++++++-- support/make4ht/domfilters/make4ht-sectionid.lua | 29 ++- support/make4ht/domfilters/make4ht-tablerows.lua | 8 +- .../make4ht/extensions/make4ht-ext-copy_images.lua | 68 +++++++ .../extensions/make4ht-ext-dvisvgm_hashes.lua | 2 +- .../extensions/make4ht-ext-latexmk_build.lua | 4 +- .../make4ht/extensions/make4ht-ext-staticsite.lua | 17 +- support/make4ht/filters/make4ht-domfilter.lua | 5 +- support/make4ht/formats/make4ht-jats.lua | 6 +- support/make4ht/formats/make4ht-odt.lua | 8 +- support/make4ht/make4ht | 14 +- support/make4ht/make4ht-doc.pdf | Bin 156343 -> 161887 bytes support/make4ht/make4ht-errorlogparser.lua | 22 +++ support/make4ht/make4ht-htlatex.lua | 20 +- support/make4ht/make4ht-indexing.lua | 4 +- support/make4ht/make4ht-lib.lua | 7 +- support/make4ht/mkparams.lua | 14 +- support/make4ht/mkutils.lua | 81 ++++++-- support/make4ht/readme.tex | 52 ++++- 24 files changed, 690 insertions(+), 79 deletions(-) create mode 100644 support/make4ht/extensions/make4ht-ext-copy_images.lua (limited to 'support/make4ht') diff --git a/support/make4ht/README b/support/make4ht/README index 0675e1d0ce..561d6bd14d 100644 --- a/support/make4ht/README +++ b/support/make4ht/README @@ -34,6 +34,7 @@ It will produce a file named `filename.html` if the compilation goes without fat possible values: tex4ht or lua4ht -c,--config (default xhtml) Custom config file -d,--output-dir (default "") Output directory + -B,--build-dir (default nil) Build directory -e,--build-file (default nil) If the build filename is different than `filename`.mk4 -f,--format (default nil) Output file format @@ -207,6 +208,12 @@ should copy all output files to the correct destinations. $ make4ht -d outputdir filename.tex +`make4ht` can also output temporary files to a build directory, thanks to the `--build-dir` (or `-B`) +option. The following command with put `.aux`, `.4tc` and other auxiliary files to the +`build` dir, and the generated `.html` and `.css` files to the `outputdir` directory. + + $ make4ht -B build -d outputdir filename.tex + ## Image conversion and postprocessing of the generated files \TeX4ht\ can convert parts of the document to images. This is useful @@ -248,7 +255,13 @@ may post-process the output files or request additional commands for the compila The extensions can be enabled or disabled by appending `+EXTENSION` or `-EXTENSION` after the output format name: - $ make4ht -uf html5+tidy filename.tex + $ make4ht -f html5+tidy filename.tex + +In `xhtml` and `html5` output formats, the `common_domfilters` extension is triggered automatically, but +it can still be disabled using: + + $ make4ht -f html5-common_domfilters filename.tex + Available extensions: @@ -260,8 +273,14 @@ common\_filters common\_domfilters : clean the HTML file using DOM filters. It is more powerful than -`common_filters`. Used DOM filters are `fixinlines`, `idcolons`, -`joincharacters`, `sectionid` and `tablerows`. + `common_filters`. It used following DOM filters: `fixinlines`, `idcolons`, + `joincharacters`, `mathmlfixes`, `tablerows`,`booktabs`, `sectionid` + and`itemparagraphs` + +copy\_images + +: Copies the images to the output directory. This is useful if the original + images are stored in directories above the document directory. detect\_engine @@ -1015,6 +1034,24 @@ Default values: } } +## The `copy_images` extension + +extensions + +: table with list of image extensions that should be processed. + + +img\_dir + +: name of the output directory where images should be stored + +Default values: + + filter_settings "copy_images" { + extensions = {"png", "jpg", "jpeg", "svg"}, + img_dir = "" + } + ## The `fixinlines` dom filter inline\_elements diff --git a/support/make4ht/changelog.tex b/support/make4ht/changelog.tex index 4516805f3a..e17af6d5db 100644 --- a/support/make4ht/changelog.tex +++ b/support/make4ht/changelog.tex @@ -2,6 +2,217 @@ \section{Changelog}\label{changelog}} \begin{itemize} +\item + 2024/02/22 + + \begin{itemize} + \tightlist + \item + version \texttt{0.4} released + \item + fixed name of the \texttt{\textless{}title-group\textgreater{}} + element in JATS output. + \end{itemize} +\item + 2024/02/21 + + \begin{itemize} + \tightlist + \item + add standalone minus sign to the + \texttt{\textless{}mn\textgreater{}} element in MathML. + \end{itemize} +\item + 2024/01/26 + + \begin{itemize} + \tightlist + \item + fixed passing of the params table in the \texttt{staticsite} + extension. + \end{itemize} +\item + 2024/01/22 + + \begin{itemize} + \tightlist + \item + don't enable parsing of void elements in DOM filter with XML output + formats. + \end{itemize} +\item + 2023/12/15 + + \begin{itemize} + \tightlist + \item + call \texttt{fix\_rel\_mo} MathML fix only in the ODT output. + \end{itemize} +\item + 2023/12/11 + + \begin{itemize} + \tightlist + \item + fixed handling of the \texttt{-\/-build-dir} argument in + \texttt{dvisvgm\_hashes} extension. + \end{itemize} +\item + 2023/11/03 + + \begin{itemize} + \tightlist + \item + remove leading dashes in ids created by the \texttt{sectionid} DOM + filter. + \end{itemize} +\item + 2023/10/26 + + \begin{itemize} + \tightlist + \item + check that removed elements in the \texttt{sectionid} DOM filter are + \texttt{\textless{}a\textgreater{}} elements. + \end{itemize} +\item + 2023/10/25 + + \begin{itemize} + \tightlist + \item + fixed addition of named IDs to figures. + \end{itemize} +\item + 2023/10/19 + + \begin{itemize} + \tightlist + \item + print info about packages with no corresponding \texttt{.4ht} file. + \end{itemize} +\item + 2023/10/05 + + \begin{itemize} + \tightlist + \item + added fix for LibreOffice's bug regarding relation type math + operators. + \end{itemize} +\item + 2023/10/03 + + \begin{itemize} + \tightlist + \item + fixed most features that didn't work with the \texttt{-\/-build-dir} + argument. + \end{itemize} +\item + 2023/10/02 + + \begin{itemize} + \tightlist + \item + working on new command line argument \texttt{-\/-build-dir}. It + should allow moving of temporary files to a temporary directory. The + idea and most of the code comes from Robin Seth Ekman. + \end{itemize} +\item + 2023/09/07 + + \begin{itemize} + \tightlist + \item + fix spacing for the \texttt{dcases*} environment in the + \texttt{mathml\_fixes} DOM filter. + \end{itemize} +\item + 2023/08/24 + + \begin{itemize} + \tightlist + \item + support non-numerical values in index entry destinations (for + example roman numerals). + \end{itemize} +\item + 2023/08/22 + + \begin{itemize} + \tightlist + \item + updated list of DOM filters used by the \texttt{common\_domfilters} + extension, and documented that it is used automatically in the HTML + output. + \end{itemize} +\item + 2023/08/12 + + \begin{itemize} + \tightlist + \item + remove unnecessary \texttt{\textless{}a\textgreater{}} elements with + \texttt{id} attributes and set the \texttt{id} on the parent. + \end{itemize} +\item + 2023/07/06 + + \begin{itemize} + \tightlist + \item + add prefix to section IDs if the section name is empty. + \end{itemize} +\item + 2023/06/20 + + \begin{itemize} + \tightlist + \item + added the \texttt{copy\_images} extension. + \end{itemize} +\item + 2023/06/14 + + \begin{itemize} + \tightlist + \item + fixed bug in the \texttt{mathmlfixes} DOM filter -- non-empty last + rows were removed if they contained only one element. + \end{itemize} +\item + 2023/05/26 + + \begin{itemize} + \tightlist + \item + load \texttt{tex4ht.sty} before input file processing starts. + \end{itemize} +\item + 2023/04/19 + + \begin{itemize} + \tightlist + \item + handle additional characters in the \texttt{idcolons} DOM filter. + \end{itemize} +\item + 2023/04/06 + + \begin{itemize} + \tightlist + \item + fixed handling of ID attributes in the \texttt{idcolons} DOM filter. + \end{itemize} +\item + 2023/03/07 + + \begin{itemize} + \tightlist + \item + remove empty rows in \texttt{longtable}. + \end{itemize} \item 2023/02/24 diff --git a/support/make4ht/domfilters/make4ht-collapsetoc.lua b/support/make4ht/domfilters/make4ht-collapsetoc.lua index ede6e1f908..1c7011c1b7 100644 --- a/support/make4ht/domfilters/make4ht-collapsetoc.lua +++ b/support/make4ht/domfilters/make4ht-collapsetoc.lua @@ -168,7 +168,7 @@ end -- process the .4tc file and convert entries to a tree structure -- based on the sectioning level local function parse_4tc(parameters, toc_levels) - local tcfilename = parameters.input .. ".4tc" + local tcfilename = mkutils.file_in_builddir(parameters.input .. ".4tc", parameters) if not mkutils.file_exists(tcfilename) then log:warning("Cannot find TOC: " .. tcfilename) return {} diff --git a/support/make4ht/domfilters/make4ht-idcolons.lua b/support/make4ht/domfilters/make4ht-idcolons.lua index ccdafbd6d4..589c515ef9 100644 --- a/support/make4ht/domfilters/make4ht-idcolons.lua +++ b/support/make4ht/domfilters/make4ht-idcolons.lua @@ -1,18 +1,36 @@ +local allowed_chars = { + ["-"] = true, + ["."] = true +} +local function fix_colons(id) + -- match every non alphanum character + return id:gsub("[%W]", function(s) + -- some characters are allowed, we don't need to replace them + if allowed_chars[s] then return s end + -- in other cases, replace with underscore + return "_" + end) +end + local function id_colons(obj) - -- replace : characters in links and ids with unserscores + -- replace non-valid characters in links and ids with underscores 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") + local href = el:get_attribute("href") + -- don't replace colons in external links if href and not href:match("[a-z]%://") then - obj:set_attribute(el, "href", href:gsub(":", "_")) + local base, id = href:match("(.*)%#(.*)") + if base and id then + id = fix_colons(id) + el:set_attribute("href", base .. "#" .. id) + end end end - local id = obj:get_attribute( el , "id") + local id = el:get_attribute("id") if id then - obj:set_attribute(el, "id", id:gsub(":", "_")) + el:set_attribute("id", fix_colons(id)) end - -- local id = obj:get_attribute(el, "id") end) return obj end diff --git a/support/make4ht/domfilters/make4ht-inlinecss.lua b/support/make4ht/domfilters/make4ht-inlinecss.lua index b086a2fc42..3240df275c 100644 --- a/support/make4ht/domfilters/make4ht-inlinecss.lua +++ b/support/make4ht/domfilters/make4ht-inlinecss.lua @@ -58,7 +58,7 @@ return function(dom, par) if not processed then -- process the CSS file before everything else, but only once processed = true - local css_file = par.input .. ".css" + local css_file = mkutils.file_in_builddir(par.input .. ".css", par) local status, msg = parse_css(css_file) if not status then log:warning(msg) end end diff --git a/support/make4ht/domfilters/make4ht-mathmlfixes.lua b/support/make4ht/domfilters/make4ht-mathmlfixes.lua index 7fa74f4632..fe8384619a 100644 --- a/support/make4ht/domfilters/make4ht-mathmlfixes.lua +++ b/support/make4ht/domfilters/make4ht-mathmlfixes.lua @@ -35,10 +35,39 @@ local function update_element_name(el, name, prefix) el._name = newname end -local function create_element(el, name, prefix) - return el:create_element(newname) +local function create_element(el, name, prefix, attributes) + local attributes = attributes or {} + local newname = get_new_element_name(name, prefix) + return el:create_element(newname, attributes) end +local function element_pos(el) + local pos, count = 0, 0 + for _, node in ipairs(el:get_siblings()) do + if node:is_element() then + count = count + 1 + if node == el then + pos = count + end + end + end + return pos, count +end + +-- test if element is the first element in the current element list +local function is_first_element(el) + local pos, count = element_pos(el) + return pos == 1 +end + +-- test if element is the last element in the current element list +local function is_last_element(el) + local pos, count = element_pos(el) + return pos == count +end + + + local function is_token_element(el) local name, prefix = get_element_name(el) return token_elements[name], prefix @@ -275,6 +304,14 @@ end local function fix_numbers(el) -- convert 1.3 to 1.3 if get_element_name(el) == "mn" then + -- sometimes minus sign can be outside + local x = el:get_sibling_node(-1) + if x and x:is_text() + and x:get_text() == "−" + then + el:add_child_node(x:copy_node(), 1) + x:remove_node() + end local n = el:get_sibling_node(1) -- test if next element is . if n and n:is_element() @@ -357,18 +394,45 @@ local function fix_operators(x) end end -local function is_last_element(el) - local siblings = el:get_siblings() - -- return true only if the current element is the last in the parent's children - for i = #siblings, 1, -1 do - local curr = siblings[i] - if curr == el then - return true - elseif curr:is_element() then - return false - end - end - return false +local function get_third_parent(el) + local first = el:get_parent() + if not first then return nil end + local second = first:get_parent() + if not second then return nil end + return second:get_parent() +end + +local function add_space(el, pos) + local parent = el:get_parent() + local name, prefix = get_element_name(el) + local space = create_element(parent, "mspace", prefix) + space:set_attribute("width", "0.3em") + parent:add_child_node(space, pos) +end + +local function fix_dcases(el) + -- we need to fix spacing in dcases* environments + -- when you use something like: + -- \begin{dcases*} + -- 1 & if $a=b$ then + -- \end{dcases*} + -- the spaces around $a=b$ will be missing + -- we detect if the elements contains spaces that are collapsed by the browser, and add explicit + -- elements when necessary + if el:get_element_name() == "mtext" then + local parent = get_third_parent(el) + if parent and parent:get_element_name() == "mtable" and parent:get_attribute("class") == "dcases-star" then + local text = el:get_text() + local pos = el:find_element_pos() + if pos == 1 and text:match("%s$") then + add_space(el, 2) + elseif text:match("^%s") and not el._used then + add_space(el, pos) + -- this is necessary to avoid infinite loop, we mark this element as processed + el._used = true + end + end + end end local function is_empty_row(el) @@ -378,6 +442,9 @@ local function is_empty_row(el) for _, child in ipairs(el:get_children()) do if child:is_element() then count = count + 1 end end + else + -- row is not empty if it contains any text + return false end -- if there is one or zero childrens, then it is empty row return count < 2 @@ -398,6 +465,29 @@ local function delete_last_empty_mtr(el) end +local function fix_rel_mo(el) + -- this is necessary for LibreOffice. It has a problem with relative that are + -- first childs in an element list. This often happens in equations, where first + -- element in a table column is an operator, like non-equal-, less-than etc. + local el_name, prefix = get_element_name(el) + if el_name == "mo" + and not get_attribute(el, "fence") -- ignore fences + and not get_attribute(el, "form") -- these should be also ignored + and not get_attribute(el, "accent") -- and accents too + then + local parent = el:get_parent() + if is_first_element(el) then + local mrow = create_element(parent, "mrow", prefix) + parent:add_child_node(mrow, 1) + elseif is_last_element(el) then + local mrow = create_element(parent, "mrow", prefix) + parent:add_child_node(mrow) + end + end + +end + + return function(dom) dom:traverse_elements(function(el) if settings.output_format ~= "odt" then @@ -405,6 +495,7 @@ return function(dom) fix_mfenced(el) else fix_mo_to_mfenced(el) + fix_rel_mo(el) end fix_radicals(el) fix_token_elements(el) @@ -413,6 +504,7 @@ return function(dom) fix_numbers(el) fix_operators(el) fix_mathvariant(el) + fix_dcases(el) top_mrow(el) delete_last_empty_mtr(el) end) diff --git a/support/make4ht/domfilters/make4ht-sectionid.lua b/support/make4ht/domfilters/make4ht-sectionid.lua index 4a1f522e6e..c8e9892bb1 100644 --- a/support/make4ht/domfilters/make4ht-sectionid.lua +++ b/support/make4ht/domfilters/make4ht-sectionid.lua @@ -58,7 +58,9 @@ local escape_name = function(name) --- convert table with normalized characters to string local name = table.concat(result) -- remove spaces - return name:gsub("%s+", "-") + name = name:gsub("%s+", "-") + name = name:gsub("^%-", "") + return name end local function parse_toc_line(line) @@ -78,6 +80,9 @@ local function parse_toc(filename) if not mkutils.file_exists(filename) then return nil, "Cannot open TOC file " .. filename end for line in io.lines(filename) do local id, name = parse_toc_line(line) + -- if section name doesn't contain any text, it would lead to id which contains only number + -- this is invalid in HTML + if name == "" then name = "_" end local orig_name = name -- not all lines in the .4tc file contains TOC entries if id then @@ -109,10 +114,20 @@ local function set_id(el, id) end +-- we want to remove elements from some elements, most notably
+local elements_to_remove = { + figure = true, + figcaption +} + +local function remove_a(el, parent, id) + parent:set_attribute("id", id) + el:remove_node() +end return function(dom, par) local msg - toc, msg = toc or parse_toc(par.input .. ".4tc") + toc, msg = toc or parse_toc(mkutils.file_in_builddir(par.input .. ".4tc", par)) msg = msg or "Cannot load TOC" -- don't do anyting if toc cannot be found if not toc then @@ -135,8 +150,16 @@ return function(dom, par) local id, href = el:get_attribute("id"), el:get_attribute("href") if id then local name = toc[id] + local parent = el:get_parent() + -- remove unnecessary elements if the parent doesn't have id yet + if elements_to_remove[parent:get_element_name()] + and not parent:get_attribute("id") + and el:get_element_name() == "a" + then + remove_a(el, parent, id) + set_id(el, name) -- replace id with new section id - if name and not toc_ids[name] then + elseif name and not toc_ids[name] then set_id(el, name) else if name then diff --git a/support/make4ht/domfilters/make4ht-tablerows.lua b/support/make4ht/domfilters/make4ht-tablerows.lua index ef36a85750..11dbfcaa71 100644 --- a/support/make4ht/domfilters/make4ht-tablerows.lua +++ b/support/make4ht/domfilters/make4ht-tablerows.lua @@ -4,11 +4,17 @@ return function(dom) -- detect if the element contains child elements local child_elements = 0 local children = child:get_children() - for _, el in ipairs(children) do + local last_child_pos + for pos, el in ipairs(children) do + last_child_pos = pos local step = el:is_element() and 1 or 0 -- log:info("element name", el._name) child_elements = child_elements + step end + -- longtable has

inside empty rows, we regard them as empty + if child_elements == 1 and children[last_child_pos]:get_element_name() == "p" and child:get_text():gsub("%s", "") == "" then + child_elements = 0 + end return child_elements > 0 end local is_empty_row = function(row) diff --git a/support/make4ht/extensions/make4ht-ext-copy_images.lua b/support/make4ht/extensions/make4ht-ext-copy_images.lua new file mode 100644 index 0000000000..7b927c7d9f --- /dev/null +++ b/support/make4ht/extensions/make4ht-ext-copy_images.lua @@ -0,0 +1,68 @@ +local M = {} +local mkutils = require "mkutils" +local domfilter = require "make4ht-domfilter" + +local copied_images = {} + +local function image_copy(path, parameters, img_dir) + if mkutils.is_url(path) then return nil, "External image" end + -- get image basename + local basename = path:match("([^/]+)$") + -- if outdir is empty, keep it empty, otherwise add / separator + local outdir = parameters.outdir == "" and "" or parameters.outdir .. "/" + if img_dir ~= "" then + outdir = outdir .. img_dir .. "/" + end + -- handle trailing // + outdir = outdir:gsub("%/+","/") + local output_file = outdir .. basename + if outdir == "" then + mkutils.cp(path, output_file) + else + mkutils.copy(path, output_file) + end +end + +-- filters support only html formats +function M.test(format) + current_format = format + if format == "odt" then return false end + return true +end + +function M.modify_build(make) + local ext_settings = get_filter_settings "copy_images" or {} + local img_dir = ext_settings.img_dir or "" + local img_extensions = ext_settings.extensions or {"jpg", "png", "jpeg", "svg"} + local process = domfilter({ + function(dom, par) + for _, img in ipairs(dom:query_selector("img")) do + local src = img:get_attribute("src") + if src and not mkutils.is_url(src) then + -- remove path specification + src = src:match("([^/]+)$") + if img_dir ~= "" then + src = img_dir .. "/" .. src + src = src:gsub("%/+", "/") + end + img:set_attribute("src", src) + end + end + return dom + end + }, "copy_images") + + -- add matcher for all image extensions + for _, ext in ipairs(img_extensions) do + make:match(ext .. "$", function(path, parameters) + image_copy(path, parameters, img_dir) + -- prevent further processing of the image + return false + end) + end + + make:match("html$", process, {img_dir = img_dir}) + return make +end + +return M diff --git a/support/make4ht/extensions/make4ht-ext-dvisvgm_hashes.lua b/support/make4ht/extensions/make4ht-ext-dvisvgm_hashes.lua index 53da32c3be..94183c7dbf 100644 --- a/support/make4ht/extensions/make4ht-ext-dvisvgm_hashes.lua +++ b/support/make4ht/extensions/make4ht-ext-dvisvgm_hashes.lua @@ -177,7 +177,7 @@ local function get_dvi_pages(arg) f:close() local dvi_pages = dvireader.get_pages(content) -- we must find page numbers and output name sfor the generated images - local lg = mkutils.parse_lg(arg.input ..".lg") + local lg = mkutils.parse_lg(arg.input ..".lg", arg.builddir) for _, name in ipairs(lg.images) do local page = tonumber(name.page) local hash = dvi_pages[page] diff --git a/support/make4ht/extensions/make4ht-ext-latexmk_build.lua b/support/make4ht/extensions/make4ht-ext-latexmk_build.lua index b9ed94b0dc..b93f458d62 100644 --- a/support/make4ht/extensions/make4ht-ext-latexmk_build.lua +++ b/support/make4ht/extensions/make4ht-ext-latexmk_build.lua @@ -16,7 +16,7 @@ function M.modify_build(make) -- tex4ht command overwrites content that was set by LaTeX with it's own stuff local tmp_file make:add("save_tmp", function(par) - local f = io.open(par.input .. ".tmp", "r") + local f = io.open(mkutils.file_in_builddir(par.input .. ".tmp", par), "r") if f then tmp_file = f:read("*all") f:close() @@ -25,7 +25,7 @@ function M.modify_build(make) end) make:add("load_tmp", function(par) if tmp_file then - local f = io.open(par.input .. ".tmp", "w") + local f = io.open(mkutils.file_in_builddir(par.input .. ".tmp", par), "w") if f then f:write(tmp_file) end diff --git a/support/make4ht/extensions/make4ht-ext-staticsite.lua b/support/make4ht/extensions/make4ht-ext-staticsite.lua index b9272203a5..dd293b6a23 100644 --- a/support/make4ht/extensions/make4ht-ext-staticsite.lua +++ b/support/make4ht/extensions/make4ht-ext-staticsite.lua @@ -67,7 +67,7 @@ local function insert_filter(make, pattern, fn) local insert_executed = false table.insert(make.matches, 1, { pattern=pattern, - params = {}, + params = make.params or {}, command = function() if not insert_executed then fn() @@ -96,12 +96,17 @@ end local function copy_files(filename, par) local function prepare_path(dir, subdir) - local path = dir .. "/" .. subdir .. "/" .. filename + local f = filename + if par.builddir then + f = f:gsub("^" .. par.builddir .. "/", "") + end + local path = dir .. "/" .. subdir .. "/" .. f return path:gsub("//", "/") end -- get extension settings local site_settings = get_filter_settings "staticsite" - local site_root = site_settings.site_root or "./" + local site_root = site_settings.site_root or par.outdir + if site_root == "" then site_root = "./" end local map = site_settings.map or {} -- default path without subdir, will be used if the file is not matched -- by any pattern in the map @@ -150,8 +155,10 @@ function M.modify_build(make) -- match.params.outdir = outdir -- print(match.pattern, match.params.outdir) -- end - make:match("html?$", process) - make:match(".*", copy_files, {slug=slug}) + local params = make.params + params.slug = slug + make:match("html?$", process, params) + make:match(".*", copy_files, params) end) return make diff --git a/support/make4ht/filters/make4ht-domfilter.lua b/support/make4ht/filters/make4ht-domfilter.lua index f6df769d60..68dd5a488c 100644 --- a/support/make4ht/filters/make4ht-domfilter.lua +++ b/support/make4ht/filters/make4ht-domfilter.lua @@ -37,10 +37,13 @@ local function filter(filters, name) end local input = filter_lib.load_input_file(filename) if not input then return nil, "Cannot load the input file" end + -- in pure XML, we need to ignore void_elements provided by LuaXML, because these can exist only in HTML + local no_void_elements = {docbook = {}, jats = {}, odt = {}, tei = {} } + local void_elements = no_void_elements[parameters.output_format] -- we need to use pcall, because XML error would break the whole build process -- domobject will be error object if DOM parsing failed local status, domobject = pcall(function() - return dom.parse(input) + return dom.parse(input, void_elements) end) if not status then log:warning("DOM parsing of " .. filename .. " failed:") diff --git a/support/make4ht/formats/make4ht-jats.lua b/support/make4ht/formats/make4ht-jats.lua index 41eabf8744..09b9bbbd8e 100644 --- a/support/make4ht/formats/make4ht-jats.lua +++ b/support/make4ht/formats/make4ht-jats.lua @@ -32,8 +32,8 @@ end local function process_moves() if article_meta then if elements_to_move_to_title["article-title"] - and #article_meta:query_selector("title_group") == 0 then -- don't move anything if user added title_group from a config file - local title_group = article_meta:create_element("title_group") + and #article_meta:query_selector("title-group") == 0 then -- don't move anything if user added title-group from a config file + local title_group = article_meta:create_element("title-group") for _, name in ipairs{ "article-title", "subtitle" } do local v = elements_to_move_to_title[name] if v then @@ -41,7 +41,7 @@ local function process_moves() v:remove_node() end end - article_meta:add_child_node(title_group) + article_meta:add_child_node(title_group, 1) end if #elements_to_move_to_contribs > 0 then local contrib_group = article_meta:create_element("contrib-group") diff --git a/support/make4ht/formats/make4ht-odt.lua b/support/make4ht/formats/make4ht-odt.lua index 7511ed481d..3479613c9d 100644 --- a/support/make4ht/formats/make4ht-odt.lua +++ b/support/make4ht/formats/make4ht-odt.lua @@ -169,7 +169,7 @@ local lg_fonts_processed=false local patched_lg_fonts = {} local function fix_lgfile_fonts(ignored_name, params) -- this function is called from file match. we must use the name of the .lg file - local filename = params.input .. ".lg" + local filename = mkutils.file_in_builddir(params.input .. ".lg", params) if not lg_fonts_processed then local lines = {} -- default font_size @@ -209,7 +209,8 @@ end local move_matches = xtpipeslib.move_matches local function insert_lgfile_fonts(make) - local first_file = make.params.input .. ".4oo" + local params = make.params + local first_file = mkutils.file_in_builddir(params.input .. ".4oo", params) -- find the last file and escape it so it can be used -- in filename match make:match(first_file, fix_lgfile_fonts) @@ -310,7 +311,8 @@ function M.modify_build(make) -- odt packing will be done here make:match(lastfile, function(filename, par) local groups = prepare_output_files(make.lgfile.files) - local basename = groups.odt[1] + -- we must remove any path from the basename + local basename = groups.odt[1]:match("([^/]+)$") local odtname = basename .. ".odt" local odt,msg = Odtfile.new(odtname) if not odt then diff --git a/support/make4ht/make4ht b/support/make4ht/make4ht index f635895677..73a1978e5a 100755 --- a/support/make4ht/make4ht +++ b/support/make4ht/make4ht @@ -29,7 +29,7 @@ make4ht [options] filename ["tex4ht.sty op." "tex4ht op." "t4ht op" "latex op"] -- set version number. the template should be replaced by the -- actual version number by the build script -local version = "v0.3m" +local version = "v0.4" mkparams.version_number = version local args = mkparams.get_args() @@ -39,6 +39,11 @@ local parameters = mkparams.process_args(args) log:status("Conversion started") log:status("Input file: " .. parameters.tex_file) + +if parameters.builddir and parameters.builddir ~= "" then + mkutils.make_path(parameters.builddir) +end + local mode = parameters.mode local build_file = parameters.build_file @@ -127,9 +132,10 @@ make:match(".*",function(filename,par) log:info("No output directory") return true end - log:info("outdir: "..outdir) - local outfilename = outdir .. filename - mkutils.copy(filename,outfilename) + log:info("outdir: "..outdir) + local outfilename = filename:gsub("^" .. (par.builddir or ""), "") + outfilename = outdir .. outfilename + mkutils.copy(filename,outfilename) return true end) diff --git a/support/make4ht/make4ht-doc.pdf b/support/make4ht/make4ht-doc.pdf index 207f7135a8..7eacc7bd95 100644 Binary files a/support/make4ht/make4ht-doc.pdf and b/support/make4ht/make4ht-doc.pdf differ diff --git a/support/make4ht/make4ht-errorlogparser.lua b/support/make4ht/make4ht-errorlogparser.lua index e536370ac9..1d16c97650 100644 --- a/support/make4ht/make4ht-errorlogparser.lua +++ b/support/make4ht/make4ht-errorlogparser.lua @@ -81,6 +81,28 @@ local function get_errors(chunks, errors) return errors end +function m.get_missing_4ht_files(log) + local used_files = {} + local used_4ht_files = {} + local missing_4ht_files = {} + local pkg_names = {sty=true, cls=true} + for filename, ext in log:gmatch("[^%s]-([^%/^%\\^%.%s]+)%.([%w][%w]+)") do + -- break ak + if ext == "aux" then break end + if pkg_names[ext] then + used_files[filename .. "." .. ext] = true + elseif ext == "4ht" then + used_4ht_files[filename] = true + end + end + for filename, _ in pairs(used_files) do + if not used_4ht_files[mkutils.remove_extension(filename)] then + table.insert(missing_4ht_files, filename) + end + end + return missing_4ht_files +end + function m.parse(log) local chunks, newtext = get_chunks(log) diff --git a/support/make4ht/make4ht-htlatex.lua b/support/make4ht/make4ht-htlatex.lua index 033b25b632..4975e947e6 100644 --- a/support/make4ht/make4ht-htlatex.lua +++ b/support/make4ht/make4ht-htlatex.lua @@ -5,7 +5,7 @@ local error_logparser = require("make4ht-errorlogparser") local Make = Make or {} -- this function reads the LaTeX log file and tries to detect fatal errors in the compilation local function testlogfile(par) - local logfile = par.input .. ".log" + local logfile = mkutils.file_in_builddir(par.input .. ".log", par) local f = io.open(logfile,"r") if not f then log:warning("Make4ht: cannot open log file "..logfile) @@ -29,6 +29,9 @@ local function testlogfile(par) end end end + -- info about packages with no corresponding .4ht files + local missing_4ht = error_logparser.get_missing_4ht_files(content) + for _, filename in ipairs(missing_4ht) do log:info("Unsupported file: " .. filename) end -- test for fatal errors if text:match("No pages of output") or text:match("TeX capacity exceeded, sorry") or text:match("That makes 100 errors") or text:match("Emergency stop") then return 1 end return 0 @@ -40,18 +43,17 @@ Make.testlogfile = testlogfile --env.Make:add("htlatex", "${htlatex} ${latex_par} '\\\makeatletter\\def\\HCode{\\futurelet\\HCode\\HChar}\\def\\HChar{\\ifx\"\\HCode\\def\\HCode\"##1\"{\\Link##1}\\expandafter\\HCode\\else\\expandafter\\Link\\fi}\\def\\Link#1.a.b.c.{\\g@addto@macro\\@documentclasshook{\\RequirePackage[#1,html]{tex4ht}\\let\\HCode\\documentstyle\\def\\documentstyle{\\let\\documentstyle\\HCode\\expandafter\\def\\csname tex4ht\\endcsname{#1,html}\\def\\HCode####1{\\documentstyle[tex4ht,}\\@ifnextchar[{\\HCode}{\\documentstyle[tex4ht]}}}\\makeatother\\HCode '${config}${tex4ht_sty_par}'.a.b.c.\\input ' ${input}") -- template for calling LaTeX with tex4ht loaded -Make.latex_command = "${htlatex} --interaction=${interaction} ${latex_par} '\\makeatletter".. +Make.latex_command = "${htlatex} --interaction=${interaction} ${build_dir_arg} ${latex_par} '\\makeatletter".. "\\def\\HCode{\\futurelet\\HCode\\HChar}\\def\\HChar{\\ifx\"\\HCode".. "\\def\\HCode\"##1\"{\\Link##1}\\expandafter\\HCode\\else".. -"\\expandafter\\Link\\fi}\\def\\Link#1.a.b.c.{\\AddToHook".. -"{class/before}{\\RequirePackage[#1,html]{tex4ht}${packages}}".. +"\\expandafter\\Link\\fi}\\def\\Link#1.a.b.c.{".. "\\let\\HCode\\documentstyle\\def\\documentstyle{\\let\\documentstyle".. "\\HCode\\expandafter\\def\\csname tex4ht\\endcsname{#1,html}\\def".. "\\HCode####1{\\documentstyle[tex4ht,}\\@ifnextchar[{\\HCode}{".. -"\\documentstyle[tex4ht]}}}\\makeatother\\HCode ${tex4ht_sty_par}.a.b.c.".. +"\\documentstyle[tex4ht]}}\\RequirePackage[#1,html]{tex4ht}${packages}}\\makeatother\\HCode ${tex4ht_sty_par}.a.b.c.".. "\\input \"\\detokenize{${tex_file}}\"'" -Make.plain_command = '${htlatex} --interaction=${interaction} ${latex_par}' .. +Make.plain_command = '${htlatex} --interaction=${interaction} ${build_dir_arg} ${latex_par}' .. "'\\def\\Link#1.a.b.c.{\\expandafter\\def\\csname tex4ht\\endcsname{\\expandafter\\def\\csname tex4ht\\endcsname{#1,html}\\input tex4ht.sty }}" .. "\\def\\HCode{\\futurelet\\HCode\\HChar}\\def\\HChar{\\ifx\"\\HCode\\def\\HCode\"##1\"{\\Link##1}\\expandafter\\HCode\\else\\expandafter\\Link\\fi}" .. "\\HCode ${tex4ht_sty_par}.a.b.c.\\input \"\\detokenize{${tex_file}}\"'" @@ -68,7 +70,11 @@ function m.htlatex(par, latex_command) devnull = " > nul 2>&1" end par.interaction = par.interaction or "batchmode" - -- remove all terminal output from the batchmode + if par.builddir~="" then + par.build_dir_arg = "--output-directory=${builddir}" % par + else + par.build_dir_arg = "" + end if par.interaction == "batchmode" then command = command .. devnull end diff --git a/support/make4ht/make4ht-indexing.lua b/support/make4ht/make4ht-indexing.lua index 746c062cb3..d5c0d07718 100644 --- a/support/make4ht/make4ht-indexing.lua +++ b/support/make4ht/make4ht-indexing.lua @@ -80,7 +80,7 @@ local parse_idx = function(content) elseif line:match("^\\indexentry") then -- replace the page number with the current -- index entry number - local result = line:gsub("{[0-9]+}$", "{"..current_entry .."}") + local result = line:gsub("%b{}$", "{"..current_entry .."}") buffer[#buffer+1] = get_utf8(result) else buffer[#buffer+1] = line @@ -150,7 +150,7 @@ local get_idxname = function(par) end local prepare_tmp_idx = function(par) - par.idxfile = get_idxname(par) + par.idxfile = mkutils.file_in_builddir(get_idxname(par), par) if not par.idxfile or not mkutils.file_exists(par.idxfile) then return nil, "Cannot load idx file " .. (par.idxfile or "''") end -- construct the .ind name, based on the .idx name par.indfile = par.indfile or par.idxfile:gsub("idx$", "ind") diff --git a/support/make4ht/make4ht-lib.lua b/support/make4ht/make4ht-lib.lua index 91418d18b5..0e9715a39d 100644 --- a/support/make4ht/make4ht-lib.lua +++ b/support/make4ht/make4ht-lib.lua @@ -194,9 +194,12 @@ Make.run = function(self) end end end - local lgfile = params.input and params.input .. ".lg" or nil + local lgfile = params.input and params.input .. ".lg" or nil + if params.builddir~="" then + lgfile = params.builddir .. "/" .. lgfile + end if lgfile then - self.lgfile = self.lgfile or mkutils.parse_lg(lgfile) + self.lgfile = self.lgfile or mkutils.parse_lg(lgfile, params.builddir) local lg = self.lgfile -- First convert images from lg files self:image_convert(lg["images"]) diff --git a/support/make4ht/mkparams.lua b/support/make4ht/mkparams.lua index ed766f9770..10ddd04744 100644 --- a/support/make4ht/mkparams.lua +++ b/support/make4ht/mkparams.lua @@ -21,6 +21,7 @@ Available options: possible values: tex4ht or lua4ht -c,--config (default xhtml) Custom config file -d,--output-dir (default nil) Output directory + -B,--build-dir (default nil) Build directory -e,--build-file (default nil) If build file is different than `filename`.mk4 -f,--format (default html5) Output file format -h,--help Display this message @@ -191,12 +192,20 @@ local function process_args(args) local outdir = "" local packages = "" - if args["output-dir"] ~= "nil" then + if args["output-dir"] ~= "nil" then outdir = args["output-dir"] or "" outdir = outdir:gsub('\\','/') outdir = outdir:gsub('/$','') end + local builddir = "" + + if args["build-dir"] ~= "nil" then + builddir = args["build-dir"] or "" + builddir = builddir:gsub('\\','/') + builddir = builddir:gsub('/$','') + end + -- make4ht now requires UTF-8 output, because of DOM filters -- numeric entites are expanded to Unicode characters. These -- characters would be displayed incorrectly in 8 bit encodings. @@ -274,7 +283,7 @@ local function process_args(args) local parameters = { htlatex = compiler ,input=input - ,tex_file=tex_file + ,tex_file=tex_file ,packages=packages ,latex_par=table.concat(latex_params," ") --,config=ebookutils.remove_extension(args.config) @@ -291,6 +300,7 @@ local function process_args(args) --,t4ht_dir_format=t4ht_dir_format } if outdir then parameters.outdir = outdir end + if builddir then parameters.builddir = builddir end log:info("Output dir: "..outdir) log:info("Compiler: "..compiler) log:info("Latex options: ".. table.concat(latex_params," ")) diff --git a/support/make4ht/mkutils.lua b/support/make4ht/mkutils.lua index f7636b9d2a..ec5158d9d2 100644 --- a/support/make4ht/mkutils.lua +++ b/support/make4ht/mkutils.lua @@ -25,6 +25,10 @@ getmetatable("").__add = addProperty --print( "${name} is ${value}" % {name = "foo", value = "bar"} ) -- Outputs "foo is bar" +function is_url(path) + return path:match("^%a+://") +end + -- merge two tables recursively function merge(t1, t2) @@ -81,8 +85,9 @@ end -- searching for converted images -function parse_lg(filename) +function parse_lg(filename, builddir) log:info("Parse LG") + local dir = builddir~="" and builddir .. "/" or "" local outputimages,outputfiles,status={},{},nil local fonts, used_fonts = {},{} if not file_exists(filename) then @@ -95,14 +100,15 @@ function parse_lg(filename) line:gsub("needs %-%-%- (.+)%[([0-9]+)%] ==> (.*) %-%-%-", function(file,page,output) local rec = { - source = file, + source=file, page=page, - output = output + output=dir..output } table.insert(outputimages,rec) end ) - line:gsub("File: (.*)", function(k) + line:gsub("File: (.*)", function(k) + k = dir .. k if not usedfiles[k] then table.insert(outputfiles,k) usedfiles[k] = true @@ -133,6 +139,10 @@ local cp_func = os.type == "unix" and "cp" or "copy" -- in reality it isn't. -- local cp_func = os.type == "unix" and "mv" or "move" function cp(src,dest) + if is_url(src) then + log.info(src .. " is a URL, will leave as is") + return + end if not file_exists(src) then -- try to find file using kpse library if it cannot be found src = kpse.find_file(src) or src @@ -212,14 +222,33 @@ function mkdirectories(dirs) if type(dirs) ~="table" then return false, "mkdirectories: dirs is not table" end + local path = "" for _,d in ipairs(dirs) do - local stat,msg = lfs.mkdir(d) + path = path .. d .. "/" + local stat,msg = lfs.mkdir(path) if not stat then return false, "makedirectories error: "..msg end - lfs.chdir(d) end return true end +function make_path(path) + -- we must create the build dir if it doesn't exist + local cwd = lfs.currentdir() + -- add dummy /foo dir. it won't be created, but without that, the top-level dir wouldn't be created + local parts = mkutils.prepare_path(path .. "/foo") + local to_create = mkutils.find_directories(parts) + mkutils.mkdirectories(to_create) + -- change back to the original dir + lfs.chdir(cwd) +end + +function file_in_builddir(filename, par) + if par.builddir and par.builddir ~= "" then + return par.builddir .. "/" .. filename + end + return filename +end + function copy_filter(src,dest, filter) local src_f=io.open(src,"rb") local dst_f=io.open(dest,"w") @@ -379,7 +408,7 @@ env.Make:add("latexmk", function(par) par.expanded = command % par -- quotes in latex_command must be escaped, they cause Latexmk error par.expanded = par.expanded:gsub('"', '\\"') - local newcommand = 'latexmk -pdf- -ps- -auxdir=. -outdir=. -latex="${expanded}" -dvi -jobname=${input} ${tex_file}' % par + local newcommand = 'latexmk -pdf- -ps- -auxdir=${builddir} -outdir=${builddir} -latex="${expanded}" -dvi -jobname=${input} ${tex_file}' % par log:info("LaTeX call: " .. newcommand) os.execute(newcommand) return Make.testlogfile(par) @@ -392,7 +421,7 @@ env.Make:add("tex4ht",function(par) -- detect if svg output is used -- if yes, we need to pass the -g.svg option to tex4ht command -- to support svg images for character pictures - local logfile = par.input .. ".log" + local logfile = mkutils.file_in_builddir(par.input .. ".log", par) if file_exists(logfile) then for line in io.lines(logfile) do local options = line:match("TeX4ht package options:(.+)") @@ -405,18 +434,35 @@ env.Make:add("tex4ht",function(par) end end end + local cwd = lfs.currentdir() + if par.builddir~="" then + lfs.chdir(par.builddir) + end local command = "tex4ht ${tex4ht_par} \"${input}.${dvi}\"" % par log:info("executing: " .. command) - return execute(command) + local status, output = execute(command) + lfs.chdir(cwd) + return status, output end , nil, 1) -env.Make:add("t4ht","t4ht ${t4ht_par} \"${input}.${ext}\"",{ext="dvi"},1) +env.Make:add("t4ht", function(par) + par.ext = "dvi" + local cwd = lfs.currentdir() + if par.builddir ~= "" then + lfs.chdir(par.builddir) + end + local command = "t4ht ${t4ht_par} \"${input}.${ext}\"" % par + log:info("executing: " .. command) + execute(command) + lfs.chdir(cwd) +end +) env.Make:add("clean", function(par) -- remove all functions that process produced files -- we will provide only one function, that remove all of them Make.matches = {} - local main_name = par.input + local main_name = mkutils.file_in_builddir( par.input, par) local remove_file = function(filename) if file_exists(filename) then log:info("removing file: " .. filename) @@ -424,7 +470,8 @@ env.Make:add("clean", function(par) end end -- try to find if the last converted file was in the ODT format - local lg_file = parse_lg(main_name .. ".lg") + local lg_name = main_name .. ".lg" + local lg_file = parse_lg(lg_name, par.builddir) local is_odt = false if lg_file and lg_file.files then for _, x in ipairs(lg_file.files) do @@ -442,7 +489,7 @@ env.Make:add("clean", function(par) end Make:match("tmp$", function() -- remove temporary and auxilary files - for _,ext in ipairs {"aux", "xref", "tmp", "4tc", "4ct", "idv", "lg","dvi", "log", "ncx"} do + for _,ext in ipairs {"aux", "xref", "tmp", "4tc", "4ct", "idv", "lg","dvi", "log", "ncx", "idx", "ind"} do remove_file(main_name .. "." .. ext) end end) @@ -592,7 +639,13 @@ function load_extension(name,format) local is_extension_file = find_lua_file(extension_library) -- don't try to load the extension if it doesn't exist if not is_extension_file then return nil, "cannot fint extension " .. name end - local extension = require("make4ht.extensions.make4ht-ext-".. name) + local extension = nil + local local_extension_path = package.searchpath(extension_library, package.path) + if local_extension_path then + extension = dofile(local_extension_path) + else + extension = require("make4ht.extensions.make4ht-ext-".. name) + end -- extensions can test if the current output format is supported local test = extension.test if test then diff --git a/support/make4ht/readme.tex b/support/make4ht/readme.tex index 98a0712899..9cff4cbeea 100644 --- a/support/make4ht/readme.tex +++ b/support/make4ht/readme.tex @@ -45,6 +45,7 @@ make4ht [options] filename ["tex4ht.sty op." "tex4ht op." possible values: tex4ht or lua4ht -c,--config (default xhtml) Custom config file -d,--output-dir (default "") Output directory +-B,--build-dir (default nil) Build directory -e,--build-file (default nil) If the build filename is different than `filename`.mk4 -f,--format (default nil) Output file format @@ -274,6 +275,17 @@ correct destinations. $ make4ht -d outputdir filename.tex \end{verbatim} +\texttt{make4ht} can also output temporary files to a build directory, +thanks to the \texttt{-\/-build-dir} (or \texttt{-B}) option. The +following command with put \texttt{.aux}, \texttt{.4tc} and other +auxiliary files to the \texttt{build} dir, and the generated +\texttt{.html} and \texttt{.css} files to the \texttt{outputdir} +directory. + +\begin{verbatim} +$ make4ht -B build -d outputdir filename.tex +\end{verbatim} + \hypertarget{image-conversion-and-postprocessing-of-the-generated-files}{% \subsection{Image conversion and postprocessing of the generated files}\label{image-conversion-and-postprocessing-of-the-generated-files}} @@ -332,7 +344,15 @@ The extensions can be enabled or disabled by appending \texttt{+EXTENSION} or \texttt{-EXTENSION} after the output format name: \begin{verbatim} - $ make4ht -uf html5+tidy filename.tex + $ make4ht -f html5+tidy filename.tex +\end{verbatim} + +In \texttt{xhtml} and \texttt{html5} output formats, the +\texttt{common\_domfilters} extension is triggered automatically, but it +can still be disabled using: + +\begin{verbatim} + $ make4ht -f html5-common_domfilters filename.tex \end{verbatim} Available extensions: @@ -342,9 +362,13 @@ Available extensions: clean the output HTML files using filters. \item[common\_domfilters] clean the HTML file using DOM filters. It is more powerful than -\texttt{common\_filters}. Used DOM filters are \texttt{fixinlines}, -\texttt{idcolons}, \texttt{joincharacters}, \texttt{sectionid} and -\texttt{tablerows}. +\texttt{common\_filters}. It used following DOM filters: +\texttt{fixinlines}, \texttt{idcolons}, \texttt{joincharacters}, +\texttt{mathmlfixes}, \texttt{tablerows},\texttt{booktabs}, +\texttt{sectionid} and\texttt{itemparagraphs} +\item[copy\_images] +Copies the images to the output directory. This is useful if the +original images are stored in directories above the document directory. \item[detect\_engine] detect engine and format necessary for the document compilation from the magic comments supported by \LaTeX~editors such as TeXShop or TeXWorks. @@ -1097,6 +1121,26 @@ filter_settings "collapsetoc" { } \end{verbatim} +\hypertarget{the-copy_images-extension}{% +\subsection{\texorpdfstring{The \texttt{copy\_images} +extension}{The copy\_images extension}}\label{the-copy_images-extension}} + +\begin{description} +\item[extensions] +table with list of image extensions that should be processed. +\item[img\_dir] +name of the output directory where images should be stored +\end{description} + +Default values: + +\begin{verbatim} + filter_settings "copy_images" { + extensions = {"png", "jpg", "jpeg", "svg"}, + img_dir = "" + } +\end{verbatim} + \hypertarget{the-fixinlines-dom-filter}{% \subsection{\texorpdfstring{The \texttt{fixinlines} dom filter}{The fixinlines dom filter}}\label{the-fixinlines-dom-filter}} -- cgit v1.2.3