--[[--

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).

---

<br>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.

&nbsp;
@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:
	<!-- file «filename», generated by LuaXML -->\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:
	<?xml version="1.0"?>\n

@usage
var:save("simple.xml")
var:save("no-comment.xml", nil, "")
var:save("custom.xml", "a+", "<!-- append mode, no header -->\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 '<?xml version="1.0"?>\n')
		file:write(comment or
			'<!-- file "' .. filename .. '", generated by LuaXML -->\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('<foo><a /><b bar="no" /><c bar="yes" /><a /></foo>')

-- 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)
