--[[--
A module that maps between Lua and XML without much ado.
LuaXML provides a set of functions for processing XML data in Lua.
It offers a very simple and natural mapping between the XML format and Lua tables,
which allows one to work with and construct XML data just using Lua's normal
table access and iteration methods (e.g. `ipairs()`).
Substatements and tag content are represented as array data having numerical
keys (`1 .. n`), attributes use string keys, and tags the numerical index `0`.
This representation makes sure that the structure of XML data is preserved
exactly across read/write cycles (i.e. `xml.eval(var:str())` should equal `var`
again).
---
To use LuaXML, first import the module - for example like this:
local xml = require("LuaXML")
LuaXML consists of a Lua file (`LuaXML.lua`) and a corresponding C module
(`LuaXML_lib`) - normally a shared library (`.dll`/`.so`), although a static
linking is possible as well. Both parts are imported by this call, provided
that they are found in Lua's package search path.
@module LuaXML
]]
local _M = require("LuaXML_lib")
--[[-- saves a Lua var as XML file.
Basically this simply exports the string representation `xml.str(var)`
(or `var:str()`), plus a standard header.
@function save
@param var the variable to be saved, normally a table
@tparam string filename the filename to be used. An existing file of the same name gets overwritten.
@tparam ?string filemode the file mode to pass to `io.open`, defaults to "w"
@tparam ?string cmt
custom _comment_ to be written at the top of the file (after the header). You
may pass an empty string if you don't want any comment at all, otherwise it
should preferably end with at least one newline. Defaults to:
\n\n
@tparam ?string hdr
custom _header_, written before anything else. You may pass an empty string if
you don't want any header at all, otherwise it should preferably end with a
newline. Defaults to the standard XML 1.0 declaration:
\n
@usage
var:save("simple.xml")
var:save("no-comment.xml", nil, "")
var:save("custom.xml", "a+", "\n", "")
]]
function _M.save(var, filename, filemode, comment, header)
if var and filename and #filename > 0 then
local file, err = io.open(filename, filemode or "w")
if not file then
error('error opening "' .. filename .. '" for saving: ' .. err)
end
file:write(header or '\n')
file:write(comment or
'\n\n')
file:write(_M.str(var))
file:close()
end
end
--[[-- iterate subelements ("XML children") as _key - value_ pairs.
This function is meant to be called in a generic `for` loop, similar to what
`ipairs(var)` would do. However you can easily specify additional criteria
to `match` against here, possibly reducing the overhead needed to test for
specific subelements.
For the resulting `(k, v)` pairs, note that `k` is just a sequential number
in the array of matched child elements, and has no direct relation to the
actual "position" (subtag index) within each `v`'s parent object.
@function children
@param var the table (LuaXML object) to work on
@tparam ?string tag XML tag to be matched
@tparam ?string key attribute key to be matched
@param value (optional) attribute value to be matched
@tparam ?number maxdepth
maximum depth allowed, defaults to 1 (only immediate children).
You can pass 0 or `true` to iterate _all_ children recursively.
@return Lua iterator function and initial state (Lua-internal use) - suitable
for a `for` loop
@see match
@usage
local xml = require("LuaXML")
local foobar = xml.eval('')
-- iterate over those children that have a "bar" attribute:
for k, v in foobar:children(nil, "bar") do
print(k, v:tag(), v.bar)
end
-- will print
-- 1 b no
-- 2 c yes
-- require "bar" to be "yes":
for k, v in foobar:children(nil, "bar", "yes") do
print(k, v:tag(), v.bar)
end
-- will print
-- 1 c yes
-- iterate "a" tags: (the first and fourth child will match)
for k, v in foobar:children("a") do
print(k, v:tag(), v.bar)
end
-- will print
-- 1 a nil
-- 2 a nil
]]
function _M.children(var, tag, key, value, maxdepth)
local function get_children(var, tag, key, value, maxdepth)
-- pass maxdepth = 1 to retrieve only immediate child nodes
local result = {}
_M.iterate(var,
function(node, depth)
-- add matching node to result table
if depth > 0 then table.insert(result, node); end
end,
tag, key, value, true, maxdepth)
return result
end
local function child_iterator(matched, k)
k = (k or 0) + 1
local v = matched[k]
return v and k, v -- key/value pair from matches, or `nil` if no value
end
maxdepth = maxdepth or 1 -- default to 1...
-- ...but enumerate all children if it was set to 0 or `true`
if maxdepth == 0 or maxdepth == true then maxdepth = nil; end
-- our "invariant state" will be a table of matched children
return child_iterator,
get_children(var, tag, key, value, maxdepth)
end
return _M -- return module (table)