3

I have an XML document with elements I must use to create a LaTeX document. Is there a way to automatically get each of these elements and for each of them, execute some LaTeX commands?

To make it clearer, suppose I have articles stored like this:

<article title="Article title" author='The Author'>
    <section title="Section title">
        <body>
            Lorem ipsum dolor sit amet,
            consectetur adipiscing elit.
        </body>
    </section>
    <image source="image/path/foo.png" />
    <section title=”Another section title">
        <body>
            Nam dui ligula, fringilia a,
            euismod sodales, sollicitudin
            vel, wisi.
        </body>
    </section>
</article>

and that I want to create a LaTeX document that when compiled automatically creates the same output I'd have if I manually did the following for each article:

\chapter{Article title}
by \emph{The Author}

\section{Section title}
Lorem ipsum dolor sit amet,
consectetur adipiscing elit.

\includegraphics[width=\textwidth]{image/path/foo.png}

\section{Another section title}
Nam dui ligula, fringilia a,
euismod sodales, sollicitudin
vel, wisi.

I think something like csvsimple package would do the work, but I couldn't find it. However, at the moment I have only the XML document, so I can try every possible solution without loosing any already done work.

1
  • Hope you are looking for something like XMLTeX else try with the conversion from XML to LaTeX by using XSLT...
    – MadyYuvi
    Commented Sep 6, 2020 at 13:05

1 Answer 1

3

You can use my LuaXML package, which as it's name suggests can process XML documents using LuaTeX. I would create a simple Lua module that can process your document, using modified version of code from my answer about Mathml to LaTeX processing. This is the library, transform-xml.lua:

-- adapted code from https://github.com/michal-h21/luaxml-mathml
--
local domobject = require "luaxml-domobject"

-- we need to define different actions for XML elements. The default action is
-- to just process child elements and return the result
local function default_action(element)
  return process_children(element)
end

-- use template string to place the processed children
local function simple_content(s)
  return function(element)
    local content = process_children(element)
    -- process attrubutes
    -- attribute should be marked as @{name}
    local expanded = s:gsub("@{(.-)}", function(name)
      return element:get_attribute(name) or ""
    end)
    return string.format(expanded, content)
  end
end

local function get_child_element(element, count)
  -- return specified child element 
  local i = 0
  for _, el in ipairs(element:get_children()) do
    -- count elements 
    if el:is_element() then
      -- return the desired numbered element
      i = i + 1
      if i == count then return el end
    end
  end
end

-- actions for particular elements
local actions = {
  
}

-- add more complicated action
local function add_custom_action(name, fn)
  actions[name] = fn
end

-- normal actions
local function add_action(name, template)
  actions[name] = simple_content(template)
end

-- convert Unicode characters to TeX sequences
local unicodes = {
  [35] = "\\#",
  [38] = "\\&",
  [60] = "\\textless{}",
  [62] = "\\textgreater{}",
  [92] = "\\textbackslash{}",
  [123] = "\\{",
  [125] = "\\}"
}

local function process_text(text)
  local t = {}
  -- process all Unicode characters and find if they should be replaced
  for _, char in utf8.codes(text) do
    -- construct new string with replacements or original char
    t[#t+1] = unicodes[char] or utf8.char(char)
  end
  return table.concat(t)
end

function process_children(element)
  -- accumulate text from children elements
  local t = {}
  -- sometimes we may get text node
  if type(element) ~= "table" then return element end
  for i, elem in ipairs(element:get_children()) do
    if elem:is_text() then
      -- concat text
      t[#t+1] = process_text(elem:get_text())
    elseif elem:is_element() then
      -- recursivelly process child elements
      t[#t+1] = process_tree(elem)
    end
  end
  return table.concat(t)
end


function process_tree(element)
  -- find specific action for the element, or use the default action
  local element_name = element:get_element_name()
  local action = actions[element_name] or default_action
  return action(element)
end

function parse_xml(content)
  -- parse XML string and process it
  local dom = domobject.parse(content)
  -- start processing of DOM from the root element
  -- return string with TeX content
  return process_tree(dom:root_node())
end

local function load_file(filename)
  local f = io.open(filename, "r")
  local content = f:read("*all")
  f:close()
  return parse_xml(content)
end


function print_tex(content)
  -- we need to replace "\n" characters with calls to tex.sprint
  for s in content:gmatch("([^\n]*)") do
    tex.sprint(s)
  end
end


local M = {
  parse_xml = parse_xml,
  process_children = process_children,
  print_tex = print_tex,
  add_action = add_action,
  add_custom_action = add_custom_action,
  simple_content = simple_content,
  load_file = load_file
}

return M

I will not describe it in much detail, as it is basically the same as from my other answer, it is only more general.

The main function it provides is transform_xml.add_action. It takes two parameters, the first is name of the element, the second is a TeX template that will be inserted in your document.

The template can insert text from all children elements using %s text. You should use it in all elements that can contain any text. Attributes can be accessed using @{attribute name} placeholder.

Sample template may look like this:

xmltransform.add_action("section", [[\section{@{title}}
%s
\par]])

Note that we use [[ ... ]] to specify the template, in order to make it easier to input newlines and backslashes. You also need to use \par command when you want to insert paragraph.

We can specify necessary actions in custom TeX package, myarticle.sty:

\ProvidesPackage{myarticle}
\RequirePackage{luacode}
\RequirePackage{graphicx}
\RequirePackage{xparse}


\begin{luacode*}
xmltransform = require "transform_xml.lua" 


xmltransform.add_action("article", [[
\chapter{@{title}}
by \emph{@{author}}\par

%s
]])

xmltransform.add_action("section", [[\section{@{title}}
%s
\par]])

xmltransform.add_action("image", '\\includegraphics{@{source}}')

function article_load(filename)
  local transformed = xmltransform.load_file(filename)
  if transformed then
    xmltransform.print_tex(transformed)
  else
    tex.sprint("Error in parsing of the XML file " .. filename)
  end
end

\end{luacode*}


\NewDocumentCommand\processarticle{m}{%
  \directlua{article_load("#1")}
}
 

\endinput

Actions are specified for all elements that need it (article, section, image). When no action is specified for a element, like <body>, it's content is just processed and inserted to the TeX document.

Package also declares a new command, \processarticle. It takes XML file name and process it with transform_xml.

We can now try a sample document:

\documentclass{book}
\usepackage{myarticle}
\begin{document}
\processarticle{article.xml}
\end{document}

And this is the resulting PDF:

enter image description here

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .