Modify XML files using LUA

LUA is a scripting language which gained some popularity over the years thanks to its lightweigth interpreter, its support for OO programming and interoperability with Microsoft .NET environment. Next into this post a brief overview about how to use it to perform simple but useful tasks like manipulate XML files.

Development environment

I decided to write this post to give simple hints to startup with LUA in the fastest way. First of all, we have at least two ways to start using LUA::

  1. Standalone installing a local interpreter, just looking at Windows environment it is enough download latest installer for “luaforwindows” project (link).
  2. From his own application, suppose to develop a .NET app it is enough to add reference to luanet assembly (link). This is the more useful option because allow to add an interpreter embedded with application so to later execute scripts from inside an application, thus enabling for new functionalities by changing or adding later on scripts without need for application build. Also end users may customize some aspects using this powerful scripting language, interesting enough in my opinion!

Documentation about language syntax is available from lua.org website(link).

XML file manipulation with LUA

Considerando lo script standalone, attraverso la direttiva “require” è possibile aggiungere dei riferimenti a librerie LUA esterne, cominciamo caricando “luanet” ed utilizzando poi il metodo statico “load_assembly” per caricare le dll .NET che ci interessano. Dietro le quinte, il meccanismo di interoperabilità COM permetterà all’interprete LUA di istanziare ed utilizzare oggetti del framework .NET.

require "luanet"

luanet.load_assembly("System.Xml")
luanet.load_assembly("System.IO")

File = luanet.import_type("System.IO.File")
Directory = luanet.import_type("System.IO.Directory")
XmlDocument = luanet.import_type("System.Xml.XmlDocument")
XmlNode = luanet.import_type("System.Xml.XmlNode")
XmlAttributeCollection = luanet.import_type("System.Xml.XmlAttributeCollection")
XmlAttribute = luanet.import_type("System.Xml.XmlAttribute")
XmlNamespaceManager = luanet.import_type("System.Xml.XmlNamespaceManager")

In questo snippet abbiamo preparato il campo alla manipolazione attraverso gli oggetti .NET di uso comune, ad esempio la classe XmlDocument che implementa il DOM oppure le classi File e Directory. Piccola nota sulla sintassi del linguaggio: i metodi statici richiedono l’utilizzo del punto singolo “.” (ad esempio File.Open() oppure Directory.GetCurrentDirectory()) mentre i metodi degli oggetti richiedono la notazione a due punti (es: xml_doc:Load()).

base_path = Directory.GetCurrentDirectory()
xml_doc = XmlDocument()
print("Load file.xml and update it") 
xml_doc:Load(base_path .. "\\file.xml") 
updateFile( xml_doc ) 
xml_doc:Save(base_path .. "\\file.xml")

La creazione dell’oggetto xml_doc è banalmente effettuata attraverso il costruttore XmlDocument, è possibile utilizzare direttamente i metodi mentre per le proprietà bisogna invocare i setter e getter associati, nell’esempio che segue alcune cose interessanti:

  1. Viene utilizzato un namespace definito nel file xml, deve quindi essere instanziato il relativo oggetto XmlNamespaceManager da utilizzare sulle successive chiamate di selezione dei nodi.
  2. L’utilizzo del formato di selezione dei nodi tramite XPath (la stringa parametri delle chiamate a SelectSingleNode
  3. L’utilizzo della proprietà “Attributes”, attraverso il metodo “get_Attributes()”. Come regola generale, ogni proprietà deve essere sostituita nello script dall’utilizzo dei metodi set_nomeProprietà e get_nomeProprietà
function updateFile(xml_doc)
  local nsmgr = XmlNamespaceManager(xml_doc:get_NameTable()) 
  nsmgr:AddNamespace("def", "mynamespace")

  -- XmlNode selection
  if (xml_doc:SelectSingleNode("def:NODO[@Name = \"AttributoEsempio\"]", nsmgr) == nil) then
    -- DO STUFF
  end
  
  -- Change attribute example
  local equipmentNode = xml_doc:SelectSingleNode("def:EACB/def:EQUIPMENTS/def:EQUIPMENT", nsmgr) 
  equipmentNode:get_Attributes():GetNamedItem("SimuName"):set_Value("127.0.0.1")
end

How to use .NET ICollection interfaces

Non essendo pratico di LUA ho avuto qualche difficoltà ad agire su proprietà che ritornano collezioni, molto semplicemente l’operazione può essere eseguita nel modo seguente:

function collection_example(xml_node)
  local child_nodes = root_node:get_ChildNodes() 
  if child_nodes then 
    local num_child = child_nodes:get_Count()
    if (num_child > 0) then 
      for i=0,(num_child-1),1 do 
        if child_nodes:Item(i) then 
          -- DO SOMETHING on child_nodes:Item(i)
        end
      end 
    end 
  end 
end

Che dire infine.. detesto i linguaggi non tipizzati !

Leave a Reply