LuaNXML is a Lua port of NXML.
NXML is a pseudo-XML parser that can read and write the oddly formatted and often invalid XML files from Noita. NXML (the C#/.NET version linked before) is itself a port of the XML parser from Poro, which is used by the custom Falling Everything engine on which Noita runs.
In short, LuaNXML is an XML parser that is 100% equivalent to Noita's parser. As opposed to something like xml2lua, LuaNXML is just as non-conformant to the XML specification as Noita itself. It can also produce semantically equivalent output in the form of a string.
The code below enables all mods in the mod_config.xml file by setting the enabled attribute on all children to true. Boolean values are automatically converted to Noita XML style "1" or "0" values.
local nxml = require("nxml")
local f = io.open("save00/mod_config.xml", "r")
local xml = nxml.parse(f:read("*a"))
f:close()
for elem in xml:each_child() do
elem.attr.enabled = true
end
f = io.open("save00/mod_config.xml", "w")
f:write(tostring(xml))
f:close()LuaNXML's API is a bit more object oriented and abstract than something like xml2lua. This is to avoid any unexpected surprises like child elements suddenly becoming tables if there's more than one of them or being unable to have attributes with certain names.
-
table
nxml-
function
.parse(data : string)returnsnxml.elementparses
datainto annxml.elementtype table
datamust be a single element (which may have children), like:<foo />or
<foo> <bar> <baz/> </bar> </foo>
-
function
.parse_many(data : string)returnstable[nxml.element]parses
datainto a a table ofnxml.elements
the difference between.parse_manyand.parseis that.parse_manycan read multiple elements written one after another, like this:<foo /> <bar /> <baz />
and it will spit out a table of all root elements
-
function
.tostring(elem : nxml.element, [packed : boolean, [indent_char : string, [cur_indent : string]]])returnsstringserializes the
nxml.elementobject into a valid Noita XML string that can be read by the game
ifpackedis set totrue, the string is compressed into a single line - otherwise, line breaks and indents are used
indent_chardetermines what character to use for indentation - it is equal to"\t"(a single tab) if not passed
cur_indentdetermines the string to prefix any child element indentation - it is equal to""(an empty string) if not passed
this function is used by thenxml.element__tostringmetamethod for convenience, but the full form allows more fine tuning of the output -
function
.new_element(name : string[, attr : table{string => any}])returnsnxml.elementcreates a new element with the name passed in
name
ifattris not nil, the.attrfield of the newnxml.elementis initialized with the passed value
-
-
table type
nxml.element-
field
.nameof typestringcontains the name of the element (e.g.
<Foo a="1" />has the nameFoo) -
field
.childrenof typetable[nxml.element]contains a table of all children (see:
:each_child()) -
field
.attrof typetable{string => any}contains a table of the element's attributes
nxml.parsewill always return elements where all the values inside.attrare of typestring, but it is perfectly fine to assign values of different types like numbers or booleans
booleans will be converted to"1"or"0"when generating the XML string withnxml.string/tostring- all other types are simply ran throughtostringto produce the serialized value -
field
.contentof typetable{string}contains a list of content tokens within the element
as is the case in Noita, text content inside elements is parsed on a token-by-token basis, and this table contains the list of those tokens
may benilor contain 0 elements, in both cases that means the text element has no content
example:<foo>foo/bar</foo>and<foo> foo / bar </foo>will both result in this field containing the stringsfoo,/andbar
see::text() -
function
:text()returnsstringreturns the text content of this element as a single string
text that is not punctuation (i.e. is not<,>,/and=) is joined with a single space, punctuation is joined without any separators
example:<foo> foo bar </foo>will result infoo bar, while<foo>foo/bar/baz</foo>will result infoo/bar/baz
see:.content -
iterator
:each_child()yieldsnxml.elementiterates every child element
used like this:for child in elem:each_child() ... -
iterator
:each_of(element_name : string)yieldsnxml.elementiterates every child element with the same name as
element_name
used like this:for child in elem:each_of("Foo") ... -
function
:first_of(element_name : string)returnsnxml.elementreturns the first child element with the name of
element_name, ornilif no such element exists -
function
:all_of(element_name : string)returnstable[nxml.element]returns a table with every child element with the same name as
element_name -
function
:add_child(element : nxml.element)adds a child element
-
function
:add_children(elements : table[nxml.element])adds all elements from the table as children
-
function
:remove_child(element : nxml.element)removes the passed element from this element's list of children
-
function
:remove_child_at(index : number)removes the element at index
indexfrom this element's list of children -
function
:clear_children()removes all children of this element
-
function
:clear_attrs()removes all attributes of this element
-
metamethod
__tostringusing the Lua
tostringfunction on annxml.elementwill produce a valid Noita XML string
see:nxml.tostring
-
If the require function is available and the ffi module can be loaded (which is the case in a Noita mod if your mod.xml has request_no_api_restrictions="1" in it), minimal optimizations in string handling are used (which should result in less intermediate strings). The output and behavior is equivalent to when ffi is not available.