simple XMLwriter library for Arduino

Triggered by a post of Crossroads - output format question - XML? - Programming Questions - Arduino Forum - I wrote a simple library/class that helps to write XML output. Although still work in progress it seemed to me mature enough to post here so I can get feedback about all that is wrong.

Of course an example script to show how it can be used and its output.
Note that the data is made up or quite trivial.

Note the constructor can use any Print derived class, including Stream, Serial, etc (only tested Serial BTW).
XMLWriter XML(&Serial);

//
//    FILE: XMLWriterTest.ino
//  AUTHOR: Rob Tillaart
// VERSION: 0.1.01
// PURPOSE: make a simple XML generating lib
//    DATE: 2013-11-06
//     URL:
//
// Released to the public domain
//

#include <XMLWriter.h>

XMLWriter XML(&Serial);

char buffer[24];

void setup()
{
  Serial.begin(115200);

  XML.header();
  XML.comment("XMLWriterTest.ino\nThis is a demo of a simple XML lib for Arduino", true); // true means multi line here

  XML.tagOpen("Arduino", "42");

  XML.tagOpen("Ports");
  AnalogPorts("before");
  DigitalPorts();
  AnalogPorts("after");
  XML.tagClose();

  Weather();
  Weather2();

  XML.tagClose();
}

void Weather2()
{
  XML.comment("The weather in South Africa");
  for (int i=0; i<10; i++)
  {
    XML.tagStart("Weather");
    XML.tagField("Date", "20131106");
    XML.tagField("Time", "1:42");
    XML.tagField("Temp", "23.4");
    XML.tagField("Humi", "50%");
    XML.tagField("Rain", "10mm");
    XML.tagField("Sun", "40");
    XML.tagEnd();
  }
}

void Weather()
{
  XML.comment("The weather in Nebraska");
  XML.tagOpen("Weather");
  XML.writeNode("Date", "20131106");
  XML.writeNode("Time", "11:42");
  XML.writeNode("Temp", "23.4");
  XML.writeNode("Humi", "50%");
  XML.writeNode("Rain", "10mm");
  XML.writeNode("Sun", "40");
  XML.tagClose();
}

void AnalogPorts(char* name)
{
  XML.comment("The analog ports are multiplexed");
  XML.tagOpen("Analog", name);
  XML.writeNode("Analog0", itoa(analogRead(A0), buffer, 10));
  XML.writeNode("Analog1", analogRead(A1));
  XML.writeNode("Analog2", (5.0*analogRead(A2))/1023);      // default nr decimals float = 2
  XML.writeNode("Analog2", (5.0*analogRead(A2))/1023, 3);
  XML.tagClose();
}

void DigitalPorts()
{
  XML.comment("The digital ports are not multiplexed");
  XML.tagOpen("Digital");
  XML.writeNode("D1", itoa(digitalRead(1), buffer, 10));
  XML.writeNode("D13", digitalRead(13));
  XML.tagClose();
}

void loop()
{
}

The output looks as follows:

<?xml version="1.0" encoding="UTF-8"?>

<!-- 
XMLWriterTest.ino
This is a demo of a simple XML lib for Arduino
 -->
<Arduino name="42">
  <Ports>

    <!-- The analog ports are multiplexed -->
    <Analog name="before">
      <Analog0>448</Analog0>
      <Analog1>362</Analog1>
      <Analog2>1.52</Analog2>
      <Analog2>1.584</Analog2>
    </Analog>

    <!-- The digital ports are not multiplexed -->
    <Digital>
      <D1>0</D1>
      <D13>0</D13>
    </Digital>

    <!-- The analog ports are multiplexed -->
    <Analog name="after">
      <Analog0>393</Analog0>
      <Analog1>428</Analog1>
      <Analog2>2.08</Analog2>
      <Analog2>1.994</Analog2>
    </Analog>
  </Ports>

  <!-- The weather in Nebraska -->
  <Weather>
    <Date>20131106</Date>
    <Time>11:42</Time>
    <Temp>23.4</Temp>
    <Humi>50%</Humi>
    <Rain>10mm</Rain>
    <Sun>40</Sun>
  </Weather>

  <!-- The weather in South Africa -->
  <Weather Date="20131106" Time="1:42" Temp="23.4" Humi="50%" Rain="10mm" Sun="40"/>
  <Weather Date="20131106" Time="1:42" Temp="23.4" Humi="50%" Rain="10mm" Sun="40"/>
  <Weather Date="20131106" Time="1:42" Temp="23.4" Humi="50%" Rain="10mm" Sun="40"/>
  <Weather Date="20131106" Time="1:42" Temp="23.4" Humi="50%" Rain="10mm" Sun="40"/>
  <Weather Date="20131106" Time="1:42" Temp="23.4" Humi="50%" Rain="10mm" Sun="40"/>
  <Weather Date="20131106" Time="1:42" Temp="23.4" Humi="50%" Rain="10mm" Sun="40"/>
  <Weather Date="20131106" Time="1:42" Temp="23.4" Humi="50%" Rain="10mm" Sun="40"/>
  <Weather Date="20131106" Time="1:42" Temp="23.4" Humi="50%" Rain="10mm" Sun="40"/>
  <Weather Date="20131106" Time="1:42" Temp="23.4" Humi="50%" Rain="10mm" Sun="40"/>
  <Weather Date="20131106" Time="1:42" Temp="23.4" Humi="50%" Rain="10mm" Sun="40"/>
</Arduino>

Source see - Arduino/libraries/XMLWriter at master · RobTillaart/Arduino · GitHub -

As always comments and remarks and improvements and ... are welcome.
(and no an XMLreader is not yet in the planning :wink:

Nice !

Library is at your github-site I suppose ?

How about adding check to escape the following characters

" "
' '
< <

>
& &

or if not, to warn not to use those characters.

RbSCR:
Nice !

Library is at your github-site I suppose ?

yep I added the link in the first post.

doughboy:
How about adding check to escape the following characters

" "
' '
< <

>
& &

or if not, to warn not to use those characters.

Good point, might make the footprint larger, added to TODO

handling escapes looks quit good

  • need more testing
  • refactor code
  • performance?
void testEscapes()
{
  XML.comment("Test escape chars");
  XML.tagOpen("char");
  XML.writeNode("quote", "\"hello\"");
  XML.writeNode("apos", "\'hello\'");
  XML.writeNode("less", "5 < 33");
  XML.writeNode("great", "Peter > John");
  XML.writeNode("amp", " &array[0] ");
  XML.tagClose();
}

==>

  <!-- Test escape chars -->
  <char>
    <quote>&quot;hello&quot;</quote>
    <apos>&apos;hello&apos;</apos>
    <less>5 &lt; 33</less>
    <great>Peter &gt; John</great>
    <amp> &amp;array[0] </amp>
  </char>

upgraded to 0.1.02 - Arduino/libraries/XMLWriter at master · RobTillaart/Arduino · GitHub -

  • added setIndentSize(uint8_t n); 0 = no indent, 2 = default; 4 looks OK too (spaces, no tabs)
  • corrected history
  • added escape() for special chars. - tip doughboy
  • added comments in the form of 'example' output in .h file
  • added some #defines to make code more readable.

as always comments remarks welcome.

hello guys,
I am not an expert in programming in arduino but I would like to make a log as XML stored on an SD card.

This is why I come to you to find out which parameter set instead of (&Serial) on line 14.
Otherwise if you have other ideas to parse XML,I'm interested.
The aim being to insert tags into an existing XML.

Example:

<?xml version="1.0" encoding="UTF-8"?>
<Log>
<element id="1" year="2014" month="1" day="1" hour="00" min="00" sec="00">
	<sensor id="T1">20.0</sensor>
	<sensor id="T2">20.0</sensor>
	<sensor id="T3">20.0</sensor>
	<sensor id="T4">20.0</sensor>
</element>
</Log>

To :

<?xml version="1.0" encoding="UTF-8"?>
<Log>
<element id="1" year="2014" month="1" day="1" hour="00" min="00" sec="00">
	<sensor id="T1">20.0</sensor>
	<sensor id="T2">20.0</sensor>
	<sensor id="T3">20.0</sensor>
	<sensor id="T4">20.0</sensor>
</element>
<element id="2" year="2014" month="1" day="1" hour="00" min="01" sec="00">
	<sensor id="T1">20.0</sensor>
	<sensor id="T2">20.0</sensor>
	<sensor id="T3">20.0</sensor>
	<sensor id="T4">20.0</sensor>
</element>
</Log>

This is why I come to you to find out which parameter set instead of (&Serial) on line 14.

Should be something that implements the Print class,
The File Class is derived from the Stream Class which is derived from the Print class so that should work (to certain level)

The XML writer class does not support appending directly. What I would try is the following. Let your application go to the end of the of the file on SD card, do a reverse search for the last entry and start writing from that point. MIght work or ... not :slight_smile:

Give it a try.

upgraded to 0.1.03 - Arduino/libraries/XMLWriter at master · RobTillaart/Arduino · GitHub -

  • re-factored for smaller footprint (- ~60)
  • added support for more datatypes
  • extended example sketch

as always comments & remarks are welcome.

Hi there,

Thank you very much for your XML Support. Is there anyway to read MusicXML wth an Arduino or Processing?

Thank you very much for your support.

Best Regards,

Federico

In theory it should be possible to parse specific XML format's, but in practice the resources of an UNO are limited.

Can you post a (small) example and/or provide a link to the specification of MusicXML?

Hello...
First of all sorry for my English...

my question is:

is it possible save XML Data directly in File on SD CARD??

I mean is it possible use some how XMLWriter XML(&Serial) with file output intead of &Serial??? ;

please help

yes, the FILE class is derived from print and can be used as parameter.

The sketch below is not tested but it compiles so please give it a try

//
//    FILE: .ino
//  AUTHOR: Rob Tillaart
// VERSION: 0.1.00
// PURPOSE: quick and dirty demo of xmlwriter to file
//    DATE:
//     URL:
//
// Released to the public domain
//

int x;

#include <SPI.h>
#include <SD.h>

const int chipSelect = 4;
File dataFile;

#include <XMLWriter.h>

char buffer[24]; // for analogreads

void setup()
{
  Serial.begin(115200);
  Serial.println("Start ");

  Serial.print("Initializing SD card...");
  pinMode(10, OUTPUT);

  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    while (1); // block;
  }
  Serial.println("card initialized.");

  dataFile = SD.open("datalog.txt", FILE_WRITE);
  if (dataFile)
  {
    XMLWriter XML(&dataFile);
    XML.header();
    XML.comment("XMLWriterTest.ino\nThis is a demo of a simple XML lib for Arduino", true);

    XML.tagOpen("Arduino", "42");

    XML.tagOpen("Ports");

    XML.comment("The analog ports are multiplexed");
    XML.tagOpen("Analog", "testing123");
    
    XML.writeNode("Analog0", itoa(analogRead(A0), buffer, 10));
    XML.writeNode("Analog1", analogRead(A1));
    XML.writeNode("Analog2", (5.0 * analogRead(A2)) / 1023); // default nr decimals = 2
    XML.writeNode("Analog2", (5.0 * analogRead(A2)) / 1023, 3);
    XML.tagClose();

    XML.comment("The digital ports are not multiplexed");
    XML.tagOpen("Digital");
    XML.writeNode("D1", itoa(digitalRead(1), buffer, 10));
    XML.writeNode("D13", digitalRead(13));
    XML.tagClose();

    XML.tagClose();
    XML.tagClose();

    dataFile.close();
  }
}

void loop()
{
}

hey,

what if I want to do KML files?

A minimal KML file like this should be not too difficult

source KML - http://www-user.uni-bremen.de/koelling/kml.html -

<?xml version="1.0" encoding="UTF-8"?> 
<kml xmlns="http://earth.google.com/kml/2.0">
  <Document>
  <Placemark>
   <name>my office</name>
   <LookAt>
      <longitude>8.853193712983327</longitude>
      <latitude>53.10919982492059</latitude>
      <range>500000</range><tilt>0</tilt><heading>0</heading>
   </LookAt>
   <Point>
     <coordinates>8.853193712983327,53.10919982492059,10</coordinates>
   </Point>
  </Placemark>
 </Document>
</kml>

how does your KML file look like?

some time later, a simple KMLWriter

//
//    FILE: KMLWriterTest.ino
//  AUTHOR: Rob Tillaart
// VERSION: 0.1.00
// PURPOSE: simple KML writer
//    DATE: 2015-05-21
//     URL:
//
// Released to the public domain
//

#include <XMLWriter.h>

XMLWriter KML(&Serial);

char buffer[24];

void setup()
{
  Serial.begin(115200);

  KMLTest();
}

void loop()
{
}

void KMLTest()
{
  KML.setIndentSize(2);

  KML.comment("KMLWriterTest.ino\nThis is a demo of the XMLWriter lib for Arduino", MULTILINE); // == true (default == false)

  KML.header();
  KML.tagStart("kml");
  KML.tagField("xmlns", "http://earth.google.com/kml/2.0");
  KML.tagEnd(NEWLINE, NOSLASH);

  KML.tagOpen("Document");
  KML.tagOpen("Placemark");
  KML.writeNode("name", "MyOffice");

  // should be a function
  KML.tagOpen("LookAt");
  KML.writeNode("longitude", "8.853193712983327");
  KML.writeNode("latitude", "53.10919982492059");
  KML.writeNode("range", "500000");
  KML.writeNode("tilt", "0");
  KML.writeNode("heading", "0");
  KML.tagClose();

  // should be a function
  KML.tagOpen("Point");
  KML.writeNode("coordinates", "8.853193712983327,53.10919982492059,10");
  KML.tagClose();

  KML.tagClose();  // Placemark
  KML.tagClose();  // Document

  KML.tagStart("/kml");
  KML.tagEnd(NEWLINE, NOSLASH);
}

/* output
<!-- 
KMLWriterTest.ino
This is a demo of the XMLWriter lib for Arduino
 -->
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://earth.google.com/kml/2.0">
<Document>
  <Placemark>
    <name>MyOffice</name>
    <LookAt>
      <longitude>8.853193712983327</longitude>
      <latitude>53.10919982492059</latitude>
      <range>500000</range>
      <tilt>0</tilt>
      <heading>0</heading>
    </LookAt>
    <Point>
      <coordinates>8.853193712983327,53.10919982492059,10</coordinate>
    </Point>
  </Placemark>
</Document>
</kml>
*/

Sketch uses 3,392 bytes (10%) of program storage space. Maximum is 32,256 bytes.
Global variables use 640 bytes (31%) of dynamic memory, leaving 1,408 bytes for local variables. Maximum is 2,048 bytes.

what if I want to put temperature in my mapping?

Then you can do that in a similar way, likee

KML.writeNode("Celsius", "25");
or
KML.writeNode("Fahrenheit", "82");

I do not know if KML has a predefined temperature syntax

upgraded to 0.1.04 - Arduino/libraries/XMLWriter at master · RobTillaart/Arduino · GitHub -

  • refactored to use less RAM
  • added KML example above to the examples

Used the F() macro to move const char arrays to FLASH, but it did not improve always. For single chars using no F() was more memory efficient except in one case. Need to dive in that one sometime.

When compiling the new KML example the 0.1.04 lib added 40 bytes FLASH to gain 70 bytes RAM with respect to the 0.1.03 version.

As always comments remarks welcome.