XML Lua

From Wiki
Jump to navigation Jump to search

General

If you process xml with ConTeXt Mkiv, you have the power of the Lua language at your fingertips. All \xml... commands have their Lua counterparts. Unfortunately, this is not yet fully documented, so this wiki page is a place-holder until Hans finishes this section in the Mkiv manual.

A few general observations to help you getting started:

  • There are two sets of Lua commands: the xml... commands allow you to work with the content of your xml elements within Lua; lxml... commands pass the content to ConTeXt and typeset it.
  • Both commands receive the content as a Lua table.

Now let's get started with a silly example to show you what we can do in Lua.

The xml file

Here is the prices.xml file that we will be processing, a price list for cars. Prices are given in Euros; this used to be a currency in some countries in Europe:

<list>
  <item>
    <model>Volkswagen</model>
    <price>12000</price>
  </item>
  <item>
    <model>Mercedes</model>
    <price>35000</price>
  </item>
  <item>
    <model>Ferrari</model>
    <price>150000</price>
  </item>
</list>

The ConTeXt style file

Next, we write the environment file prices-style.tex which we will use to process our list; on the command line, you use context --environment=prices-style prices.xml. As an exercise, we will use Lua for all xml elements (though this doesn't make sense in most cases). If you have already read the manual, most of this will be familiar. The first line connects our environment with the Lua file where all the Lua code will go. The last lines set up a ConTeXt table, since this is how we want to display the information:

\registerctxluafile{l-prices}{1.001}

\startxmlsetups xml:pricesetups
	\xmlsetsetup{#1}{list|
			 item|
			 model|
			 price}{xml:*}
\stopxmlsetups

\xmlregistersetup{xml:pricesetups}

\startxmlsetups xml:list
	\xmlfunction{#1}{list}
\stopxmlsetups

\startxmlsetups xml:item
	\xmlfunction{#1}{item}
\stopxmlsetups

\startxmlsetups xml:model
	\xmlfunction{#1}{model}
\stopxmlsetups

\startxmlsetups xml:price
	\xmlfunction{#1}{price}
\stopxmlsetups

\setupTABLE [column] [1]         [style=bold,width=0.3\textwidth]
\setupTABLE [column] [2,3,4]     [style=italic,width=0.2\textwidth]

The Lua file

And finally, the Lua file l-prices.lua, which will hold all the lua functions we use to process the xml:

function xml.functions.list(t)
 context.bTABLE()
 context.bTR()
 context.bTD()
 context("Model")
 context.eTD()
 context.bTD()
 context("Euros")
 context.eTD()
 context.bTD()
 context("Dollars")
 context.eTD()
 context.bTD()
 context("Yen")
 context.eTD()
 context.eTR()
 lxml.flush(t)
 context.eTABLE()
end

function xml.functions.item(t)
 context.bTR()
 lxml.flush(t)
 context.eTR()
end

function xml.functions.model(t)
 context.bTD()
 lxml.flush(t)
 context.eTD()
end

function xml.functions.price(t)
 local price = tonumber(xml.text(t, "./"))
 local dollar_price = price * 1.28
 local yen_price = price * 120.4
 context.bTD()
 context(price)
 context.eTD()
 context.bTD()
 context(dollar_price)
 context.eTD()
 context.bTD()
 context(yen_price)
 context.eTD()
end

What does this do? As you can see, we mostly use the context... commands which are described in the cld manual. They are Lua functions which print to ConTeXt. With the list function, we start a ConTeXt table and typeset a first row with the meta information for our list. The functions for item and model do nothing more than print the argument of the connected elements as table rows and as the first table column. Things get a bit more interesting for the price function: this is where we use the power of Lua to do some easy calculations and convert the Euros to other currencies. First, we extract the content of the <price> element. We need to tell Lua that this content is not a string, but a number, hence the use of the tonumber function. Within a ConTeXt environment, we would use \xmltext{#1}{./}. The Lua equivalent is xml.text(t, "./"). Now that we have this content as a number, we can perform all kinds of arithmetic operations on it. Likewise, if it were a string, we could use Lua string manipulations on it – that's the good thing about using Lua, you have the full power of the language at your disposal. And finally, we typeset the results of our arithmetic operations in table cells.

This is just one example of what you can do with Lua. Here are a few more suggestions for things which are easier to do in Lua than in pure ConTeXt:

If you want to format your numbers to make the table easier to understand, you could use the Lua string.format function, e.g.

price = string.format("%08d", price)

which will left-pad your price with 0s.

Another easy thing is string manipulation (OK, the example is very silly, but you see what is meant):

function xml.functions.model(t)
 model_name = xml.text(t, "./")
 model_name = model_name:gsub("o", "x")
 context.bTD()
 context(model_name)
 context.eTD()
end

But where Lua really shines is when it comes to constructions such as loops and conditionals. Here is an easy example for a conditional:

 local price = tonumber(xml.text(t, "./"))
 context.bTD()
 if price > 50000 then
  context.color( { "red" }, price)
 else
  context(price)
 end

Have fun finding more ways to do interesting things with Lua and xml!


--Thomas 09:35, 28 March 2013 (CET)

The XML node at Lua end

In the example above the XML node id (#1) is passed to Lua by \xmlfunction command, being transformed from node string id (e.g. xml:name::6) to Lua table representing the xml elements in the xml tree. To get this table at Lua end lxml.getid() function is used (e.g. xml.text(lxml.getid("#1"), "/price")). There is no performance gain in doing so.

XML preprocessor

Sometimes the XML source is not in the best shape and changing it at the originator side is not possible. You can preprocess it during ConTeXt run to the better shaped XML before it is used.

<?xml version "1.0"?>
<document>
   <p>Altitude 60amp;nbsp;m</p>
</document>

will be transformed to the new xml

<?xml version "1.0"?>
<document>
   <p>Altitude 60<nbsp/>m</p>
</document>

with lxml.preprocessor function

\startluacode
function lxml.preprocessor(data)
    data = string.gsub(data, "amp;nbsp;", "<nbsp/>")
    return data
end
\stopluacode

You can check the result of this transformation with \xmlshow command.