OXml - The next generation XML library for Pascal (Delphi, FPC, Lazarus, C++Builder)

Latest Version

1.10 (2016/04/27)

Basic info

OXml is a new XML library for Delphi and Lazarus, developed in late 2013. I took some inspiration from OmniXML but wrote the library completely from scratch.

The aim of OXml is to be the most versatile and fastest XML library for the Pascal language.

OXml features a SAX parser, DOM implementation, a sequential DOM parser a direct XML reader/writer and a vendor for Delphi's XmlIntf.TXMLDocument.

OXml supports all Delphi versions starting from Delphi 5 on all platforms: Win32, Win64, OSX, iOS, Android. Currently I tested: Delphi 7, 2007, 2009, 2010, XE, XE2, XE3, XE4, XE5, XE6, XE7, XE8. Delphi 5, 6, 2005 and 2006 are supported as well.
Corresponding C++Builder compilers should be supported, too (untested yet).
OXml supports Lazarus 1.0 and newer on all platforms (tested Win32, Win64, Linux, MacOSX).

OXml Features

Library design

  • Use the same XML library for all your Pascal projects including:
    1. Delphi for Win32, Win64 and OSX (Delphi 5 and newer).
    2. Delphi ARC/NEXTGEN for iOS and Android (Delphi XE4 and newer).
    3. Lazarus on Win32, Win64, Linux, OSX (Lazarus 1.0 and newer).
    4. Corresponding C++Builder compilers (not tested though).
  • Native pascal object oriented code.
  • No external dll libraries are required.
  • No dependency on a visual library like VCL, LCL or FMX.
  • Full unicode support even for D5-D2007.
  • Powerful XPath engine.
  • Fast, powerful and easy-to-use namespace support for reading documents.
  • Faster than everything else on all platforms thanks to various optimizations.
  • OXml is able to read and write invalid XML documents and correct errors in them (if wanted). If not wanted, OXml throws an exception when you are trying to read/write an invalid XML document.
  • Supports all on the platform available encodings (UTF-16, UTF-8, single-byte ISO, WIN, KOI8...) by all parsers automatically. That means that the encoding is read and set from the <?xml encoding="" ?> tag during both reading and writing.

Readers and writers included in OXml

OXml features 7 classes/units for working with XML documents:
  1. TXMLWriter (OXmlReadWrite.pas): Basic XML writer. All other classes use it.
    Use it directly if performance is crucial for you.
  2. TXMLReader (OXmlReadWrite.pas): Basic XML reader. All other classes use it.
    Don't use it directly. If performance is crucial for you, use SAX which has the same performance but is much more comfortable to work with.
  3. TSAXParser (OXmlSAX.pas): Event-based parser according to the SAX specification.
    Anonymous methods are supported for modern Delphi versions, too. It's very fast and needs practically no memory.
  4. IXMLDocument (OXmlPDOM.pas): Record-based DOM according to the W3C DOM Level 1 specification. (Not strict - some small changes have been made to maximize performance).
    The fastest and most memory-friendly DOM for Pascal.
  5. IXMLDocument (OXmlCDOM.pas): TObject-based DOM according to the W3C DOM Level 1 specification. (Not strict - some small changes have been made to maximize performance).
    For those who don't like the "old-school" approach of OXmlPDOM.pas. There is some performance and memory consumption penalty, though.
  6. TXMLSeqParser (OXmlSeq.pas): Sequential DOM parser based on OXmlPDOM.pas.
    Read huge XML files into the DOM sequentionally. This method combines DOM capabilities without the need to load the whole document at once.
    OXmlSeq is even a little bit faster than OXmlPDOM.
  7. sOXmlDOMVendor (OXmlDOMVendor.pas): fastest DOM vendor for Delphi's own TXMLDocument.
    Use TXMLDocument(MyXmlDoc).DOMVendor := GetDOMVendor(sOXmlDOMVendor) if you want to use Delphi's default TXMLDocument with the fastest and cross-platform vendor.

What are the differences between OXmlPDOM and OmniXML / MS XML?

  1. In general OXmlPDOM is very close to both implementations. They share the same functions and properties.
  2. OmniXML and MS XML are interfaced-based. That means that nodes are created one-by-one and when they are not referenced any more, they are automatically destroyed.
    OXmlPDOM is record-based. Nodes are created by groups of 1024 items, which offers stunning performance. They are automatically destroyed only when the owner XML document is destroyed. Therefore such functions do not free memory used by a node:
    • TXMLNode.RemoveChild()
    • TXMLNode.ReplaceChild()
    When using OXmlPDOM you should call TXMLNode.DeleteChild(), TXMLNode.DeleteAttribute() or TXMLNode.DeleteSelf in order to be sure the node memory is marked as free and can be reused again.
  3. The nodes are of PXMLNode type - pointer to TXMLNode structure. Strictly speaking, PXMLNode nodes have to be dereferenced to TXMLNode when used but Delphi does this dereferencing for you, so you can easily use: XML.DocumentElement.AddChild('child');
    If you use FPC/Lazarus in Delphi mode ({$MODE DELPHI}), the nodes get dereferenced too. But if you use FPC/Lazarus in default mode, you have to dereference it manually with the "^" operator: XML.DocumentElement^.AddChild('child');
    If you don't like this approach, use OXmlCDOM.pas instead of OXmlPDOM.pas.
  4. OXmlPDOM does not store child nodes and attributes in AttributeNodes and ChildNodes lists.
    That means that the lists are created only when they are needed by the user. AttributeNodes and ChildNodes are not typical TList descendants but they are doubly linked lists with fast index iteration and count function support.

Migration table from OmniXML to OXml

OXml offers the same functionality as OmniXML but some functions/properties may have different names. The following table lists them:

IXMLNode

OmniXMLOXml equivalent
IXMLNode, IXMLElement, ... (interface)PXMLNode (pointer to TXMLNode structure)
SelectSingleElementNil()SelectNodeNull()
SelectSingleElementCreate()SelectNodeCreate()
SelectSingleElement()SelectNode()
SelectSingleNode()SelectNode()
AttributesAttributeNodes
Attributes.GetNamedItem()GetAttributeNode()
with Node dowith Node^ do
if Supports(Node, IXMLElement) thenif Node.NodeType = ntElement then

IXMLDocument

OmniXMLOXml equivalent
Load()LoadFromFile()
LoadXML()LoadFromXML()
Save()SaveToFile()
TOutputFormat [ofNone, ofFlat, ofIndent]TXmlIndentType [itNone, itFlat, itIndent]
*Self* (the DOM document node)*Self*.Node
CreateProcessingInstruction('xml', ...)CreateXMLDeclaration
Important: CreateProcessingInstruction exists in OXml too but should not be used for the <?xml ... ?> PI. For that specific PI, CreateXMLDeclaration should be used. Only so the encoding will be correctly detected when saving the document.
SaveToStream(Stream, ofIndent);
 
WriterSettings.IndentType := itIndent;
SaveToStream(Stream);

IXMLNodeList

LengthCount
Item[]Nodes[] or [] (default)

Performance optimizations

The most important approach that is different to OmniXML and other parsers is that OXml doesn't use child and attribute lists natively. They have to be created if you want to use them, which is slow.

Therefore avoid using ChildNodes and AttributeNodes wherever possible. Replace them with GetNextChild (GetNextAttribute) or with FirstChild+NextSibling (FirstAttribute+NextSibling) approach as shown below.

The following table lists concepts that you used in OmniXML and that you can use in OXml as well but if you want maximum performance, you should consider replacing them.

OmniXMLOXml equivalent
if Node.ChildNodes.Count > 0 thenif Node.HasChildNodes then
for I := 0 to Node.ChildNodes.Count-1 do
begin
  ChildNode := Node.ChildNodes.Item[I];
  [...]
end;
ChildNode := nil;
while Node.GetNextChild(ChildNode) do
begin
  [...]
end;
-- or --
ChildNode := Node.FirstChild;
while Assigned(ChildNode) do
begin
  [...]
  ChildNode := ChildNode.NextSibling;
end;
ChildNodes[0] (if used separately)FirstChild
ChildNodes[1] (if used separately)ChildFromBegin[1]
ChildNodes[ChildNodes.Count-1] (if used separately)LastChild
ChildNodes[ChildNodes.Count-2] (if used separately)ChildFromEnd[1]
Node.Attributes['attr'] := 'value'Node.AddAttribute('attr', 'value')

Example code

OXml should be very close to Delphi's IXMLDocument. Furthermore you can take advantage of new added functionality that makes creating and reading XML documents easier.

Please take a short look into the source code for a full list of properties and methods. Everything should be commented in the source code.

Please see the DEMO application for your compiler (unicode Delphi, non-unicode Delphi, Lazarus) for more code!

Here is a short example code:

OXml DOM (OXmlPDOM.pas)

uses OXmlPDOM;

procedure TestOXmlPDOM;
var
  XML: IXMLDocument;
  Root, Node, Attribute: PXMLNode;
begin
  //CREATE XML DOC
  XML := CreateXMLDoc('root', True);//create XML doc with root node named "root"
  Root := XML.DocumentElement;

  Node := Root.AddChild('child');//add child to root node
  Node.SetAttribute('attribute1', 'value1');//set attribute value

  Node := Root.AddChild('child');
  Node.SetAttribute('attribute2', 'value2');

  XML.SaveToFile('S:\test.xml');//save XML document

  //READ XML DOC
  XML := CreateXMLDoc;//create empty XML doc

  XML.LoadFromFile('S:\test.xml');//load XML document
  Root := XML.DocumentElement;//save the root into local variable
  //iterate through all child nodes -> you MUST set the node to nil
  Node := nil;
  while Root.GetNextChild(Node) do
  begin
    //iterate through all attributes -> you MUST set the node to nil
    Attribute := nil;
    while Node.GetNextAttribute(Attribute) do
      ShowMessage(Node.NodeName+'['+
        Attribute.NodeName+'] = '+
        Attribute.NodeValue);
  end;
end;

OXml SAX (OXmlSAX.pas)

uses OXmlSAX;

function SAXEscapeString(const aString: String): String;
begin
  Result := aString;
  Result := StringReplace(Result, sLineBreak, '\n', [rfReplaceAll]);
  Result := StringReplace(Result, '"', '\"', [rfReplaceAll]);
end;

procedure TestOXmlSAX(const aOutputMemo: TMemo);
var
  xSAX: TSAXParser;
const
  cXML: String =
    '<?xml version="1.0"?>'+sLineBreak+
    '<seminararbeit>'+sLineBreak+
    ' <titel>DOM, SAX und SOAP</titel>'+sLineBreak+
    ' <inhalt>'+sLineBreak+
    '  <kapitel value="1">Einleitung</kapitel>'+sLineBreak+
    '  <kapitel value="2">Hauptteil</kapitel>'+sLineBreak+
    '  <kapitel value="3">Fazit</kapitel>'+sLineBreak+
    ' </inhalt>'+sLineBreak+
    ' <!-- comment -->'+sLineBreak+
    ' <![CDATA[ cdata ]]>'+sLineBreak+
    ' <?php echo "custom processing instruction" ?>'+sLineBreak+
    '</seminararbeit>'+sLineBreak;
begin
  aOutputMemo.Lines.Clear;

  xSAX := TSAXParser.Create;
  try
    xSAX.OnCharacters := (
      procedure(aSaxParser: TSAXParser; const aText: OWideString)
      begin
        aOutputMemo.Lines.Add('characters("'+SAXEscapeString(aText)+'")');
      end);

    xSAX.OnStartElement := (
      procedure(aSaxParser: TSAXParser; const aName: String;
        const aAttributes: TSAXAttributes)
      var
        xValueAttr, xAttrStr: String;
      begin
        if aAttributes.Find('value', xValueAttr) then
          xAttrStr := 'value="'+SAXEscapeString(xValueAttr)+'"'
        else
          xAttrStr := '[[attribute "value" not found]]';

        aOutputMemo.Lines.Add(
          'startElement("'+SAXEscapeString(aName)+'", '+xAttrStr+')');
      end);

    xSAX.OnEndElement := (
      procedure(aSaxParser: TSAXParser; const aName: String)
      begin
        aOutputMemo.Lines.Add('endElement("'+SAXEscapeString(aName)+'")');
      end);

    xSAX.ParseXML(cXML);
  finally
    xSAX.Free;
  end;
end;

OXml DOM vendor (OXmlDOMVendor.pas)

uses XmlIntf, XmlDoc, OXmlDOMVendor;

procedure TestOXmlVendor(const aOutputMemo: TMemo);
var
  xXml: XmlDoc.TXMLDocument;
  xXmlI: XmlIntf.IXMLDocument;
  xRoot: XmlIntf.IXMLNode;
begin
  xXml := XmlDoc.TXMLDocument.Create(nil);
  xXml.DOMVendor := xmldom.GetDOMVendor(sOXmlDOMVendor);
  xXmlI := xXml;

  //now use xXmlI just like every other TXMLDocument
  xXmlI.Active := True;
  xRoot := xXmlI.Node.AddChild('root');
  xRoot.ChildNodes.Add(xXmlI.CreateNode('text', ntText));
  xRoot.ChildNodes.Add(xXmlI.CreateNode('node', ntElement));

  aOutputMemo.Lines.Text := xXmlI.Node.XML;
end;

License

Author, initial developer of the OXml library:

Ondrej Pokorny
http://www.kluug.net
All rights reserved.

The contents of all files in the library are subject to the:

1.) Common Public Attribution License Version 1.0 (CPAL-1.0).
-> see "license.CPAL 1.0.html" for full license text.

OR

2.) OXml commercial license (if you purchased OXml from Kluug.net).
-> see "OXml commercial license.txt" for full license text.

There is an exception: the demos (in "demos/" directory) are public domain (if not stated differently).

You may not use any of the files from OXml library except

1.) in compliance with the "CPAL 1.0" License

OR

2.) if you purchased OXml, in compliance with the "OXml commercial license".

License FAQ:

1.) Can I use OXml in commercial applications without purchasing a commercial license?

Yes, you can. But as stated in CPAL, you have to display the Original Developer's Attribution Information each time an Executable or Source Code or a Larger Work is launched or initially run (which includes initiating a session).

You can find the text of the required attribution text in the file "license.CPAL 1.0.html" in EXHIBIT B.

If this is not an alternative for you, you can purchase a full commercial license from http://www.kluug.net that allows you to use OXml without any attribution.

2.) Can I use OXml in freeware applications without purchasing a commercial license?

Yes, you can. But you have to attribute OXml in the same way as described in 1.).

3.) If I buy an OXml license, how long is the commercial license valid?

The commercial license is perpetual for OXml source code that has been released within 2 years after your purchase. If you want to update to OXml source code that has been released after 2 years of your purchase, you have to obtain a new license (for 60% of the current price).

Performance

The following performance test can be found in the DEMO application and you can run it for yourself.

All figures in the following tables are the best achieved values from more tests.

As you can see, OXml DOM's (OXmlPDOM.pas) overall reading and writing performance is the best across all compilers. It is even a little bit better than the C-based libxml2 ported to Delphi (DIXml). Furthermore, it's also the least memory hungry DOM.
The slightly worse non-unicode Delphi (D7) performance is a result of Delphi's poor WideString performance.

Read Test

The read test returns the time the parser needs to read a custom XML DOM from a file (column "load") and to write node values to a constant dummy function (column "navigate").
The file is encoded in UTF-8, it's size is about 5,6 MB and node count (including attribute nodes) is 700'000.

Win32 Delphi XE2:

PC: Intel Core 2 Duo laptop from 2007 [1]
Libraryunitloadnavigateload+navigatememory
[s]%[s]%[s]%[MB]%
OXml DOMOXmlPDOM.pas0,611000,111000,7210034100
OXml sequential DOMOXmlSeq.pas0,55900,08730,63880,21
OXml SAX parserOXmlSAX.pas0,36590,0190,37510,10
OXml direct readerOXmlReadWrite.pas0,36590,0190,37510,10
Delphi XML + OXml vendorXMLIntf.pas, OXmlDOMVendor.pas0,59973,5131914,10569325956
Delphi XML + MSXML vendorXMLIntf.pas, msxmldom.pas1,232025,8953557,129894431303
Delphi XML + ADOM vendorXMLIntf.pas, adomxmldom.pas11,9819643,56323615,5421585031479
MSXMLmsxml.pas1,232023,3230184,5563236106
OmniXML (SVN)OmniXML.pas2,223640,837553,0542492271
NativeXmlNativeXml.pas4,437260,877915,3073657168
SimpleXMLSimpleXML.pas0,871430,524731,3919376224
DIXml (libxml2)DIXml.dcu0,34560,423820,7610665191
Alcinoe DOMAlXmlDoc.pas2,654340,847643,4948596282
Alcinoe SAXAlXmlDoc.pas1,652700,524732,1730100
VerySimpleXMLXml.VerySimple.pasfailed

Win32 Delphi 7:

PC: Intel Core 2 Duo laptop from 2007 [1]
Libraryunitloadnavigateload+navigatememory
[s]%[s]%[s]%[MB]%
OXml DOMOXmlPDOM.pas0,981000,271001,2510034100
OXml sequential DOMOXmlSeq.pas0,94960,20741,14910,21
OXml SAX parserOXmlSAX.pas0,62630,0140,63500,10
OXml direct readerOXmlReadWrite.pas0,62630,0140,63500,10
Delphi XML + OXml vendorXMLIntf.pas, OXmlDOMVendor.pas1,001026,7224897,72618280824
Delphi XML + MSXML vendorXMLIntf.pas, msxmldom.pas1,221246,7625047,986384091203
MSXMLmsxml.pas1,221243,4512784,6737436106
OmniXML (SVN)OmniXML.pas4,674771,786596,4551678229
NativeXmlNativeXml.pas5,025121,565786,5852644129
SimpleXMLSimpleXML.pas1,111131,284742,3919154159
DIXml (libxml2)DIXml.dcu0,39401,234561,6213063185

Win32 Lazarus 1.0.8:

PC: Intel Core 2 Duo laptop from 2007 [1]
Libraryunitloadnavigateload+navigatememory
[s]%[s]%[s]%[MB]%
OXml DOMOXmlPDOM.pas0,891000,091000,9810036100
OXml sequential DOMOXmlSeq.pas0,83930,06670,89910,21
OXml SAX parserOXmlSAX.pas0,58650,01110,59600,10
OXml direct readerOXmlReadWrite.pas0,56630,01110,57580,10
OmniXML (SVN)OmniXML.pas3,744201,1713004,91501132367
NativeXmlNativeXml.pas4,234751,4416005,6757972200
Lazarus DOMDOM.pas0,891000,849331,7317797269

Write Test

The write test returns the time the parser needs to create a DOM (column "create") and write this DOM to a file (column "save").
The file is encoded in UTF-8, it's size is about 11 MB and node count (including attribute nodes) is 900'000.

Win32 Delphi XE2:

PC: Intel Core 2 Duo laptop from 2007 [1]
Libraryunitcreatesavecreate+savememory
[s]%[s]%[s]%[MB]%
OXml DOMOXmlPDOM.pas0,341000,311000,6510048100
OXml direct writerOXmlReadWrite.pas000,30970,30460,10
Delphi XML + OXml vendorXMLIntf.pas, OXmlDOMVendor.pas4,3512790,311004,66717296617
Delphi XML + MSXML vendorXMLIntf.pas, msxmldom.pasfailed
Delphi XML + ADOM vendorXMLIntf.pas, adomxmldom.pas6,82200610,55340317,3726725431131
MSXMLmsxml.pas4,3112680,391264,7072376158
OmniXML (SVN)OmniXML.pas1,364000,943032,30354126262
NativeXmlNativeXml.pas4,1712261,544975,7187878162
SimpleXMLSimpleXML.pas0,641881,093521,73266119248
DIXml (libxml2)DIXml.dcu0,361060,531710,8913792192
Alcinoe DOMAlXmlDoc.pas0,942761,203872,14329110229
VerySimpleXMLXml.VerySimple.pas0,611791,815842,42372127265

Win32 Delphi 7:

PC: Intel Core 2 Duo laptop from 2007 [1]
Libraryunitcreatesavecreate+savememory
[s]%[s]%[s]%[MB]%
OXml DOMOXmlPDOM.pas0,621000,731001,3510048100
OXml direct writerOXmlReadWrite.pas000,61840,61450,10
Delphi XML + OXml vendorXMLIntf.pas, OXmlDOMVendor.pas7,1011450,751037,85581253527
Delphi XML + MSXML vendorXMLIntf.pas, msxmldom.pasfailed
MSXMLmsxml.pas4,597400,34474,9336580167
OmniXML (SVN)OmniXML.pas2,984813,014125,99444104217
NativeXmlNativeXml.pas4,747652,753777,4955560125
SimpleXMLSimpleXML.pas2,313732,613584,9236479165
DIXml (libxml2)DIXml.dcu1,141841,281752,4217987181

Win32 Lazarus 1.0.8:

PC: Intel Core 2 Duo laptop from 2007 [1]
Libraryunitcreatesavecreate+savememory
[s]%[s]%[s]%[MB]%
OXml DOMOXmlPDOM.pas0,501000,391000,8910044100
OXml direct writerOXmlReadWrite.pas000,411050,41460,10
OmniXML (SVN)OmniXML.pas1,923842,015153,93442143325
NativeXmlNativeXml.pas4,238461,594085,82654100227
Lazarus DOMDOM.pas0,841681,162972,0022587198

Download

Please be sure you check the license information before downloading any of the files below.

OXml repositories (full source code)

Installation

OXml is a runtime library. Just add the source code directory to your Delphi library path.

If you want to (or need) you can compile the supplied package for your Delphi version.

Support

The preferred way to contact me about OXml is through the forum at SourceForge.net: https://sourceforge.net/p/oxml/discussion/.

Order

You may order a roality-free commercial license for a specified number of developers using OXml in your company. A commercial license allows you to use OXml in any kind of end-user application.

The license applies to OXml version available at the moment of purchase and all OXml updates released within one year after the purchase.

Pricing & Order

Online orders are managed by PayPal. I also accept bank transfers to my bank account. In this case, please send me an email with your billing address and I send you my account number.

You receive an invoice per email after your payment.

All prices are without VAT.

I offer you a 30-days money-back guarantee if you can't use OXml for what is advertised on this page (because of bugs, compatibility problems etc.).

Commercial licenses

for 1 developer
+ 2 years of updates
EUR 75,- (~ USD 85,-)
for max. 3 developers within one company
+ 2 years of updates
EUR 150,- (~ USD 170,-)
for unlimited developers within one company
+ 2 years of updates
EUR 300,- (~ USD 340,-)

Contact me for more information (you can write in czech/english/german/russian): Email