Author Topic: libxml (XML library for Windows/Linux servers)  (Read 14251 times)

0 Members and 1 Guest are viewing this topic.

Offline SyavX

  • Soldat Beta Team
  • Camper
  • ******
  • Posts: 338
libxml (XML library for Windows/Linux servers)
« on: May 26, 2013, 11:14:57 pm »

libxml
Download

The libxml library provides set of functions to easily add XML parsing and creating capability to SoldatServer scripts. It is based on the Expat XML parser, which is licensed under the MIT license which can be viewed in the post footer. Expat is used in many projects (like Mozilla or Perl). It is very stable and very fast.

Important: The Expat license requires that a copyright notice and the license text itself be included in any software that includes the parser. So if this library is used in software that is to be made public, the license below must be included with the software.

This library has partial support for Document Type Definitions (DTD) and Namespaces. The goal is to keep the commandset very simple while still allowing this library to handle any XML compliant document.

The official specification of XML and XML Namespaces by the W3C can be found here:
XML specification
XML Namespaces
Various translations of XML related documents

Also the Wikipedia article on XML provides a good starting point for people new to XML.

// =====================================
//               U S A G E
// =====================================

  • Extract an attached archive into the root of your SoldatServer directory.
    Thus libxml.dll (Windows) and libxml.so (Linux) will be placed near to soldatserver.exe (Windows) and soldatserver (Linux) executable files and /libxml directory with an examples code will be copied into the /scripts directory of your server.
  • Run server using a "-safe 0" command line switch.

// =====================================
//               N O T E S
// =====================================

Windows:
  • Everything should work out of the box

Linux:
  • You  should comment the "libxml_windows.pas" line and uncomment the "libxml_linux.pas" line in the /libxml/Includes.txt file to make an example code work on Linux.
  • There could be a need to do an export of LD_LIBRARY_PATH (export LD_LIBRARY_PATH=/path/to/soldatserver) to use external library functions

// =====================================
//            E X A M P L E S
// =====================================

Code: (Pascal) [Select]
// =====================================
//          C O N S T A N T S
// =====================================
Const
  XML_Any             = -1;
  XML_All             = -1;

  XML_Ascii           = 24;
  XML_Unicode         = 25;
  XML_UTF8            = 2;

  XML_Root            = 0;
  XML_Normal          = 1;
  XML_Comment         = 2;
  XML_CData           = 3;
  XML_DTD             = 4;
  XML_Instruction     = 5;

  XML_WindowsNewline  = 1;   // Changes all newline to CRLF
  XML_LinuxNewline    = 2;   // Changes all newline to LF
  XML_MacNewline      = 4;   // Changes all newline to CR
  XML_CutNewline      = 8;   // Removes all newline
  XML_ReduceNewline   = 16;  // Removes all empty lines
  XML_CutSpace        = 32;  // Removes all spaces
  XML_ReduceSpace     = 64;  // Removes all multiple spaces
  XML_ReFormat        = 128; // Completely reformats the tree structure
  XML_ReIndent        = 256; // Changes the indentation of the lines

  XML_StandaloneYes   = 1;   // The document mode is standalone
  XML_StandaloneNo    = 0;   // The document mode is not standalone
  XML_StandaloneUnset = -1;  // The standalone mode is not specified in the declaration

  XML_StringFormat    = 1;   // Includes a byte order mark (BOM)
  XML_NoDeclaration   = 2;   // Does not include the XML declaration

  XML_Success                    = 0;  // no error
  XML_NoMemory                   = 1;  // out of memory
  XML_Syntax                     = 2;  // syntax error
  XML_NoElements                 = 3;  // no element found
  XML_InvalidToken               = 4;  // not well-formed (invalid token)
  XML_UnclosedToken              = 5;  // unclosed token
  XML_PartialCharacter           = 6;  // partial character
  XML_TagMismatch                = 7;  // mismatched tag
  XML_DublicateAttribute         = 8;  // duplicate attribute
  XML_JunkAfterDocElement        = 9; // junk after document element
  XML_ParamEntityRef             = 10; // illegal parameter entity reference
  XML_UndefinedEntity            = 11; // undefined entity
  XML_RecursiveEntityRef         = 12; // recursive entity reference
  XML_AsyncEntity                = 13; // asynchronous entity
  XML_BadCharacterRef            = 14; // reference to invalid character number
  XML_BinaryEntityRef            = 15; // reference to binary entity
  XML_AttributeExternalEntityRef = 16; // reference to external entity in attribute
  XML_MisplacedXML               = 17; // XML or text declaration not at start of entity
  XML_UnknownEncoding            = 18; // unknown encoding
  XML_IncorrectEncoding          = 19; // encoding specified in XML declaration is incorrect
  XML_UnclosedCDataSection       = 20; // unclosed CDATA section
  XML_ExternalEntityHandling     = 21; // error in processing external entity reference
  XML_NotStandalone              = 22; // document is not standalone
  XML_UnexpectedState            = 23; // unexpected parser state
  XML_EntityDeclaredInPE         = 24; // entity declared in parameter entity
  XML_FeatureRequiresDTD         = 25; // requested feature requires XML_DTD support in Expat
  XML_CantChangeFeatures         = 26; // cannot change setting once parsing has begun
  XML_UnboundPrefix              = 27; // unbound prefix
  XML_UndeclaringPrefix          = 28; // must not undeclare prefix
  XML_IncompletePE               = 29; // incomplete markup in parameter entity
  XML_XMLDeclaration             = 30; // XML declaration not well-formed
  XML_TextDeclaration            = 31; // text declaration not well-formed
  XML_PublicID                   = 32; // illegal character(s) in public id
  XML_Suspended                  = 33; // parser suspended
  XML_NotSuspended               = 34; // parser not suspended
  XML_Aborted                    = 35; // parsing aborted
  XML_Finished                   = 36; // parsing finished
  XML_SuspendedPE                = 37; // cannot suspend in external parameter entity
  XML_ReservedPrefixXML          = 38; // reserved prefix (xml) must not be undeclared or bound to another namespace name
  XML_ReservedPrefixXMLNS        = 39; // reserved prefix (xmlns) must not be declared or undeclared
  XML_ReservedNamespaceURI       = 40; // prefix must not be bound to one of the reserved namespace names

// =====================================
//        D E C L A R A T I O N S
// =====================================
//Function XML_ChildNode(Node: Integer; Index: Integer = -1): Integer;
Function XML_ChildNode(Node, Index: Integer): Integer;
External 'XML_ChildNode@libxml.dll cdecl';

//Function XML_CopyNode(Node, ParentNode: Integer; PreviousNode: Integer = 0): Integer;
Function XML_CopyNode(Node, ParentNode, PreviousNode: Integer): Integer;
External 'XML_CopyNode@libxml.dll cdecl';

//Function XML_Create(XML: Integer; Encoding: Integer = XML_UTF8): Integer;
Function XML_Create(XML, Encoding: Integer): Integer;
External 'XML_Create@libxml.dll cdecl';

//Function XML_CreateNode(ParentNode: Integer; PreviousNode: Integer = 0; TType: Integer = XML_Normal): Integer;
Function XML_CreateNode(ParentNode, PreviousNode, TType: Integer): Integer;
External 'XML_CreateNode@libxml.dll cdecl';

Procedure XML_DeleteNode(Node: Integer);
External 'XML_DeleteNode@libxml.dll cdecl';

Function XML_ExamineAttributes(Node: Integer): Integer;
External 'XML_ExamineAttributes@libxml.dll cdecl';

//Procedure XML_Format(XML, Flags: Integer; IndentStep: Integer = -1);
Procedure XML_Format(XML, Flags, IndentStep: Integer);
External 'XML_Format@libxml.dll cdecl';

Procedure XML_Free(XML: Integer);
External 'XML_Free@libxml.dll cdecl';

Function XML_GetAttribute(Node: Integer; Attribute: PChar): PChar;
External 'XML_GetAttribute@libxml.dll cdecl';

Function XML_GetEncoding(XML: Integer): Integer;
External 'XML_GetEncoding@libxml.dll cdecl';

Function XML_GetNodeName(Node: Integer): PChar;
External 'XML_GetNodeName@libxml.dll cdecl';

Function XML_GetNodeOffset(Node: Integer): Integer;
External 'XML_GetNodeOffset@libxml.dll cdecl';

Function XML_GetNodeText(Node: Integer): PChar;
External 'XML_GetNodeText@libxml.dll cdecl';

Function XML_GetStandalone(XML: Integer): Integer;
External 'XML_GetStandalone@libxml.dll cdecl';

Function XML_IsXML(XML: Integer): Integer;
External 'XML_IsXML@libxml.dll cdecl';

//Function XML_Load(XML: Integer; Filename: PChar; Encoding: Integer = -1): Integer;
Function XML_Load(XML: Integer; Filename: PChar; Encoding: Integer): Integer;
External 'XML_Load@libxml.dll cdecl';

Function XML_MainNode(XML: Integer): Integer;
External 'XML_MainNode@libxml.dll cdecl';

//Function XML_MoveNode(Node, ParentNode: Integer; PreviousNode: Integer = -1): Integer;
Function XML_MoveNode(Node, ParentNode, PreviousNode: Integer): Integer;
External 'XML_MoveNode@libxml.dll cdecl';

Function XML_NextAttribute(Node: Integer): Integer;
External 'XML_NextAttribute@libxml.dll cdecl';

Function XML_NextNode(Node: Integer): Integer;
External 'XML_NextNode@libxml.dll cdecl';

Function XML_ParentNode(Node: Integer): Integer;
External 'XML_ParentNode@libxml.dll cdecl';

Function XML_PreviousNode(Node: Integer): Integer;
External 'XML_PreviousNode@libxml.dll cdecl';

Procedure XML_RemoveAttribute(Node: Integer; Attribute: PChar);
External 'XML_RemoveAttribute@libxml.dll cdecl';

//Function XML_ResolveAttributeName(Node: Integer; Attribute: PChar; Separator: PChar = '/'): PChar;
Function XML_ResolveAttributeName(Node: Integer; Attribute, Separator: PChar): PChar;
External 'XML_ResolveAttributeName@libxml.dll cdecl';

//Function XML_ResolveNodeName(Node: Integer; Separator: PChar = '/'): PChar;
Function XML_ResolveNodeName(Node: Integer; Separator: PChar): PChar;
External 'XML_ResolveNodeName@libxml.dll cdecl';

Function XML_RootNode(XML: Integer): Integer;
External 'XML_RootNode@libxml.dll cdecl';

//Function XML_Save(XML: Integer; Filename: PChar; Flags: Integer = -1): Integer;
Function XML_Save(XML: Integer; Filename: PChar; Flags: Integer): Integer;
External 'XML_Save@libxml.dll cdecl';

Procedure XML_SetAttribute(Node: Integer; Attribute, Value: PChar);
External 'XML_SetAttribute@libxml.dll cdecl';

Procedure XML_SetEncoding(XML, Encoding: Integer);
External 'XML_SetEncoding@libxml.dll cdecl';

Procedure XML_SetNodeName(Node: Integer; Name: PChar);
External 'XML_SetNodeName@libxml.dll cdecl';

Procedure XML_SetNodeOffset(Node, Offset: Integer);
External 'XML_SetNodeOffset@libxml.dll cdecl';

Procedure XML_SetNodeText(Node: Integer; Text: PChar);
External 'XML_SetNodeText@libxml.dll cdecl';

Procedure XML_SetStandalone(XML, Standalone: Integer);
External 'XML_SetStandalone@libxml.dll cdecl';

Function XML_AttributeName(Node: Integer): PChar;
External 'XML_AttributeName@libxml.dll cdecl';

Function XML_AttributeValue(Node: Integer): PChar;
External 'XML_AttributeValue@libxml.dll cdecl';

Function XML_ChildCount(Node: Integer): Integer;
External 'XML_ChildCount@libxml.dll cdecl';

Function XML_Error(XML: Integer): PChar;
External 'XML_Error@libxml.dll cdecl';

Function XML_ErrorLine(XML: Integer): Integer;
External 'XML_ErrorLine@libxml.dll cdecl';

Function XML_ErrorPosition(XML: Integer): Integer;
External 'XML_ErrorPosition@libxml.dll cdecl';

Function XML_NodeFromID(XML: Integer; ID: PChar): Integer;
External 'XML_NodeFromID@libxml.dll cdecl';

Function XML_NodeFromPath(ParentNode: Integer; Path: PChar): Integer;
External 'XML_NodeFromPath@libxml.dll cdecl';

//Function XML_NodePath(Node: Integer; ParentNode: Integer = -1): PChar;
Function XML_NodePath(Node, ParentNode: Integer): PChar;
External 'XML_NodePath@libxml.dll cdecl';

Function XML_NodeType(Node: Integer): Integer;
External 'XML_NodeType@libxml.dll cdecl';

Function XML_Status(XML: Integer): Integer;
External 'XML_Status@libxml.dll cdecl';

Function XML_GetVersion(): Integer;
External 'XML_GetVersion@libxml.dll cdecl';

// =====================================
//            E X A M P L E S
// =====================================
procedure test_save();
var
  FileName: String;
  XML, MainNode, Item, SubItem: Integer;
begin
  FileName := 'weapons.xml';
 
  // Create xml tree
  XML := XML_Create(XML_Any, XML_Ascii);
  MainNode := XML_CreateNode(XML_RootNode(XML), -1, XML_Normal);
  XML_SetNodeName(MainNode, 'Weapons');
 
  // Create first xml node (in main node)
  Item := XML_CreateNode(MainNode, -1, XML_Normal);
  XML_SetNodeName(Item, 'Weapon');
  XML_SetAttribute(Item, 'ID', '1');
  XML_SetAttribute(Item, 'ShortName', 'DE');
  XML_SetNodeText(Item, 'Desert Eagles');
 
    // Create a child node (in first node)
    SubItem := XML_CreateNode(Item, 0, XML_Normal);
    XML_SetNodeName(SubItem, 'Properties');
    XML_SetAttribute(SubItem, 'Damage', '186');
    XML_SetAttribute(SubItem, 'Ammo', '7');
 
  // Create second xml node (in main node)
  Item := XML_CreateNode(MainNode, -1, XML_Normal);
  XML_SetNodeName(Item, 'Weapon');
  XML_SetAttribute(Item, 'ID', '2');
  XML_SetAttribute(Item, 'ShortName', 'MP5');
  XML_SetNodeText(Item, 'HK MP5');
 
    // Create a child node (in second node)
    SubItem := XML_CreateNode(Item, 0, XML_Normal);
    XML_SetNodeName(SubItem, 'Properties');
    XML_SetAttribute(SubItem, 'Damage', '104');
    XML_SetAttribute(SubItem, 'Ammo', '30');
 
  // Save the xml tree into a xml file
  XML_Format(XML, XML_ReFormat, 2);
  if XML_Save(XML, FileName, XML_StringFormat) <> 0 then
    WriteLn('File "' + FileName + '" has been saved...')
  else
    WriteLn('Can not save "' + FileName + '" file...');
   
  XML_Free(XML);
end;


// This procedure prints all childnodes by recursively calling itself
procedure PrintNodes(CurrentNode, CurrentSublevel: Integer);
var
  Text: String;
  ChildNode: Integer;
begin
  // Ignore anything except normal nodes. See the manual for
  // XML_NodeType() for an explanation of the other node types
  if XML_NodeType(CurrentNode) = XML_Normal then begin
    // Print this node. Add name and attributes
    Text := '';
    while Length(Text) < CurrentSublevel do
      Text := Text + '+';
   
    Text := Text + XML_GetNodeName(CurrentNode) + ' ( Attributes: ';
   
    if XML_ExamineAttributes(CurrentNode) <> 0 then
      while XML_NextAttribute(CurrentNode) <> 0 do
        Text := Text + XML_AttributeName(CurrentNode) + '=' + #34 + XML_AttributeValue(CurrentNode) + #34 + ' ';
   
    Text := Text + ')';
    WriteLn(Text);
   
    // Now get the first child node (if any)
    ChildNode := XML_ChildNode(CurrentNode, 1);
   
    // Loop through all available child nodes and call this procedure again
    while ChildNode <> 0 do begin
      PrintNodes(ChildNode, CurrentSublevel + 1);
      ChildNode := XML_NextNode(ChildNode);
    end;
  end;
end;


procedure test_load();
var
  FileName: String;
  XML, MainNode: Integer;
begin
  FileName := 'weapons.xml';
  XML := XML_Load(XML_Any, FileName, XML_Any);
  if XML <> 0 then begin
    {  Note:
       The XML_Load() succeed if the file could be read. This does not mean that
       there was no error in the XML though. To check this, XML_Status() can be
       used.
    }
       
    // Display an error message if there was a markup error
    if XML_Status(XML) <> XML_Success then begin
      WriteLn('Error in the XML file:');
      WriteLn('Message: ' + XML_Error(XML));
      WriteLn('Line: ' + IntToStr(XML_ErrorLine(XML)) + '; Character: ' + IntToStr(XML_ErrorPosition(XML)));
    end;
   
    {  Note:
       Even if there was an error in the XML, all nodes before the error position
       are still accessible, so open the window and show the tree anyway.
    }
   
    // Get the main XML node and call the PrintNodes() procedure with it
    MainNode := XML_MainNode(XML);
    if MainNode <> 0 then
      PrintNodes(MainNode, 0);
     
    XML_Free(XML);
  end
  else
    WriteLn('Can not load "' + FileName + '" file...');
 
end;


procedure OnAdminMessage(IP, Msg: String);
begin
  case LowerCase(Trim(Msg)) of
  '/save' : test_save();
  '/load' : test_load();
  '/ver'  : WriteLn('libxml v0.'+IntToStr(XML_GetVersion()));
  end;
end;

// =====================================

License for the Expat XML parser
Copyright (c) 1998, 1999, 2000 Thai Open Source Software Center Ltd
                               and Clark Cooper
Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006 Expat maintainers.

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


P.S. The icon was based on the art made by New Mooon.

P.P.S. Unicode version of the library (Windows & Linux) attached in case you would like to use it in some of your apps or SoldatServer will someday become a fully Unicode compatible.
« Last Edit: June 14, 2018, 04:51:51 pm by SyavX »

Offline SyavX

  • Soldat Beta Team
  • Camper
  • ******
  • Posts: 338
Re: libxml (XML library for Windows/Linux servers)
« Reply #1 on: May 27, 2013, 12:02:59 am »
// =====================================
//              I N D E X
// =====================================

    XML_ChildNode()
    XML_CopyNode()
    XML_Create()
    XML_CreateNode()
    XML_DeleteNode()
    XML_ExamineAttributes()
    XML_Format()
    XML_Free()
    XML_GetAttribute()
    XML_GetEncoding()
    XML_GetNodeName()
    XML_GetNodeOffset()
    XML_GetNodeText()
    XML_GetStandalone()
    XML_IsXML()
    XML_Load()
    XML_MainNode()
    XML_MoveNode()
    XML_NextAttribute()
    XML_NextNode()
    XML_ParentNode()
    XML_PreviousNode()
    XML_RemoveAttribute()
    XML_ResolveAttributeName()
    XML_ResolveNodeName()
    XML_RootNode()
    XML_Save()
    XML_SetAttribute()
    XML_SetEncoding()
    XML_SetNodeName()
    XML_SetNodeOffset()
    XML_SetNodeText()
    XML_SetStandalone()
    XML_AttributeName()
    XML_AttributeValue()
    XML_ChildCount()
    XML_Error()
    XML_ErrorLine()
    XML_ErrorPosition()
    XML_NodeFromID()
    XML_NodeFromPath()
    XML_NodePath()
    XML_NodeType()
    XML_Status()
    XML_GetVersion()

// =====================================
//       D O C U M E N T A T I O N
// =====================================

Function XML_ChildNode(Node, Index: Integer): Integer;


Syntax
Result := XML_ChildNode(Node, Index);

Description
Returns a pointer to a child node of the given XML node. Index specifies the one based index of the node to return.

Return value
Returns the node pointer for the requested child node or 0 if there are no children or index is too high.

// [Index] =======================================================================================
Function XML_CopyNode(Node, ParentNode, PreviousNode: Integer): Integer;


Syntax
Result := XML_CopyNode(Node, ParentNode, PreviousNode);

Description
Copies the given XML node and all its contained text and children to a new location. This function can even be used to copy nodes into a different XML tree. For moving a complete node to a new location XML_MoveNode() can be used.

Parameters
Node specifies the node to copy.
ParentNode is the node into which to insert the new node. To insert the new node at the root of the tree, XML_RootNode() can be used here.
PreviousNode specifies a childnode of ParentNode after which the new node should be inserted. If this value is 0, the new node is inserted as the first child of its parent. If this value is -1, the node is inserted as the last child of its parent.

The following rules must be followed for a successful copying:
- The root node of a tree cannot be copied
- ParentNode may not be of type XML_Comment or XML_CData
- PreviousNode must be a direct child of ParentNode
- If the XML tree already has a main node, only nodes other than XML_Normal and XML_CData can be inserted at the root level

Return value
Returns the pointer to the new XML node if it was copied successfully or 0 if copying was not possible.

// [Index] =======================================================================================
Function XML_Create(XML, Encoding: Integer): Integer;


Syntax
Result := XML_Create(XML, Encoding);

Description
Creates a new empty XML tree identified by the XML number. If XML_Any is used as XML parameter, the new XML tree number will be returned as "Result". Encoding specifies the encoding to use for the tree. Valid values are XML_Ascii, XML_Unicode or XML_UTF8.

The new tree will only have a root node which can be accessed with XML_RootNode(). To add new nodes, XML_CreateNode() can be used.

Example
Code: (Pascal) [Select]
  // Create XML tree
  XML := XML_Create(XML_Any);
  MainNode := XML_CreateNode(XML_RootNode(XML));
  XML_SetNodeName(mainNode, 'Settings');

  // Create first XML node (in main node)
  Item := XML_CreateNode(mainNode);
  XML_SetNodeName(Item, 'Bot');
  XML_SetAttribute(Item, 'id', '1');
  XML_SetNodeText(Item, 'Admiral');

  // Create second XML node (in main node)
  Item := CreateXMLNode(mainNode);
  XML_SetNodeName(Item, 'Bot');
  XML_SetAttribute(Item, 'id', '2');
  XML_SetNodeText(Item, 'Danko');

  // Save the XML tree into a XML file
  XML_Save(XML, 'settings.xml');

// [Index] =======================================================================================
Function XML_CreateNode(ParentNode, PreviousNode, TType: Integer): Integer;


Syntax
Result := XML_CreateNode(ParentNode, PreviousNode, TType);

Description
Creates a new XML node and inserts it into the given parent node.

Parameters
ParentNode is the node into which to insert the new node. To insert the new node at the root of the tree, XML_RootNode() can be used here.
PreviousNode specifies a childnode of ParentNode after which the new node should be inserted. If this value is 0, the new node is inserted as the first child of its parent. If this value is -1, the node is inserted as the last child of its parent.
TType specifies the type for the new node. The default is XML_Normal. Note that the node type cannot be changed after the node was created.

The following rules must be followed for a successful insertion:
- ParentNode may not be of type XML_Comment or XML_CData
- PreviousNode must be a direct child of ParentNode
- A node of type XML_Root cannot be created manually
- If the XML tree already has a main node, only nodes other than XML_Normal and XML_CData can be inserted at the root level

Return value
Returns the pointer to the new XML node if it was created successfully or 0 if no node could be inserted at this point.


// [Index] =======================================================================================
Procedure XML_DeleteNode(Node: Integer);


Syntax
XML_DeleteNode(Node);

Description
Deletes the given XML node and all its contained text and children from its XML tree.

Note: the root node of a tree cannot be deleted.

// [Index] =======================================================================================
Function XML_ExamineAttributes(Node: Integer): Integer;


Syntax
Result := XML_ExamineAttributes(Node);

Description
Starts to examine the attributes of the given XML node.

Return value
Returns nonzero if the node is of type XML_Normal and zero else (as such nodes cannot have attributes).


// [Index] =======================================================================================
Procedure XML_Format(XML, Flags, IndentStep: Integer);


Syntax
XML_Format(XML, Flags, IndentStep);

Description
Cleans up or reformats the XML tree for a better look when saving. It can be used to have a very compact output for efficient transfer or a more formatted output for better reading.

The formatting of the parsed XML document is stored in the "text" and "offset" fields of each node in the tree (see XML_GetNodeText() and XML_GetNodeOffset() for more information).

Parameters
Flags can be a combination of the following values (with the Or operator):
  XML_WindowsNewline: Changes all newline to CRLF
  XML_LinuxNewline  : Changes all newline to LF
  XML_MacNewline    : Changes all newline to CR

  XML_CutNewline    : Removes all newline
  XML_ReduceNewline : Removes all empty lines

  XML_CutSpace      : Removes all spaces
  XML_ReduceSpace   : Removes all multiple spaces

  XML_ReFormat      : Completely reformats the tree structure
  XML_ReIndent      : Changes the indentation of the lines

For XML_ReFormat and XML_ReIndent the IndentStep parameter specifies how many spaces of indentation to add for each level.

Note: There is no reformatting in CData sections and Processing Instructions except for the newline changes, as the whitespace contained inside these sections may be important depending on what is contained in the section.


// [Index] =======================================================================================
Procedure XML_Free(XML: Integer);


Syntax
XML_Free(XML);

Description
Frees the XML object and all data it contains.

Parameters
XML      : The XML object to free. If XML_All is specified, all the remaining XML objects are freed.

// [Index] =======================================================================================
Function XML_GetAttribute(Node: Integer; Attribute: PChar): PChar;


Syntax
Result := XML_GetAttribute(Node, Attribute);

Description
Returns the value of an attribute in the given XML node. If the attribute does not exist an empty string is returned.

Only nodes of type XML_Normal can have attributes. For all other node types the compiler raises an error.

// [Index] =======================================================================================
Function XML_GetEncoding(XML: Integer): Integer;


Syntax
Result := XML_GetEncoding(XML);

Description
Returns the text encoding used for saving the given XML tree.

Return value
Returns either XML_Ascii, XML_Unicode (= UTF16) or XML_UTF8.

// [Index] =======================================================================================
Function XML_GetNodeName(Node: Integer): PChar;


Syntax
Result := XML_GetNodeName(Node);

Description
Returns the tagname of the given XML node. If the node is not of type XML_Normal or XML_Instruction, an empty string is returned.

// [Index] =======================================================================================
Function XML_GetNodeOffset(Node: Integer): Integer;


Syntax
Result := XML_GetNodeOffset(Node);

Description
Returns the character offset of this Node within its parent.

The returned value represents the number of characters in the parent nodes text data that lie between this node and the previous child node. So if this node directly follows the previous one, this value will be 0.

// [Index] =======================================================================================
Function XML_GetNodeText(Node: Integer): PChar;


Syntax
Result := XML_GetNodeText(Node);

Description
Returns the text inside the given XML node.

For a node of type XML_Normal, this is all text and whitespace within the node that is not contained within a child node.
For the root node, this is all whitespace outside of the main node (there can be no text outside of the main node).
For XML_Comment or XML_CData nodes, this is all text contained in the node.

// [Index] =======================================================================================
Function XML_GetStandalone(XML: Integer): Integer;


Syntax
Result := XML_GetStandalone(XML);

Description
Returns the value of the "standalone" attribute in the XML declaration of the document.

Return value
Returns one of the following values:
  XML_StandaloneYes  : The document mode is standalone
  XML_StandaloneNo   : The document mode is not standalone
  XML_StandaloneUnset: The standalone mode is not specified in the declaration

// [Index] =======================================================================================
Function XML_IsXML(XML: Integer): Integer;


Syntax
Result := XML_IsXML(XML);

Description
Returns nonzero if XML refers to an existing XML tree.

// [Index] =======================================================================================
Function XML_Load(XML: Integer; Filename: PChar; Encoding: Integer): Integer;


Syntax
Result := XML_Load(XML, Filename, Encoding);

Description
Loads a XML tree from the given file. The tree can later be accessed trough the XML value. If XML_Any is used as XML parameter, the new XML tree number will be returned as "Result".

Encoding parameter can be used to force the parser to use a specific encoding. (This overwrites the encoding set in the XML declaration!) Possible values are XML_Ascii, XML_Unicode or XML_UTF8. This parameter should be used when the document does not have an XML declaration, or the encoding information is provided outside of the XML document, for example through a mime type header in a communication protocol.
If XML_Any is used as Encoding parameter, parser will use the encoding set in the XML declaration.

Return value
Returns nonzero if the file could be opened and read. Note that this does not mean that the XML contained in the file was valid. To check for parser errors XML_Status() should be used. In case of a parsing error, all data parsed before the error is accessible in the XML tree.

// [Index] =======================================================================================
Function XML_MainNode(XML: Integer): Integer;


Syntax
Result := XML_MainNode(XML);

Description
Returns the main XML node of the tree. A valid XML document must have one "main" or "document" node which contains all other nodes. Other than this node, there can only be comments on the first level below the root node. The type of this node is XML_Normal.

Return value
Returns a pointer to the main node, or 0 if the tree has no main node (which happens if the tree is empty or the main node was deleted).

// [Index] =======================================================================================
Function XML_MoveNode(Node, ParentNode, PreviousNode: Integer): Integer;


Syntax
Result := XML_MoveNode(Node, ParentNode, PreviousNode);

Description
Moves the given XML node and all its contained text and children to a new location. This function can even be used to move nodes into a different XML tree. For copying a complete node to a new location XML_CopyNode() can be used.

Parameters
Node specifies the node to move.
ParentNode is the node into which to insert the node. To insert the node at the root of the tree, XML_RootNode() can be used here.
PreviousNode specifies a childnode of ParentNode after which the node should be inserted. If this value is 0, the node is inserted as the first child of its parent. If this value is -1, the node is inserted as the last child of its parent.

The following rules must be followed for a successful move:
- The root node of a tree cannot be moved
- ParentNode may not be of type XML_Comment or XML_CData
- PreviousNode must be a direct child of ParentNode
- Node and PreviousNode cannot be equal
- ParentNode cannot be equal to, or a child of Node (a node cannot be moved into itself)
- If the XML tree already has a main node, only nodes other than XML_Normal and XML_CData can be inserted at the root level

Return value
Returns nonzero if the move was successful or zero if the node could not be moved.

// [Index] =======================================================================================
Function XML_NextAttribute(Node: Integer): Integer;


Syntax
Result := XML_NextAttribute(Node);

Description
This function must be called after XML_ExamineAttributes() to move step by step through the attributes of the given XML node.

Return value
Returns zero if there are no more attributes or nonzero if there still is one.

// [Index] =======================================================================================
Function XML_NextNode(Node: Integer): Integer;


Syntax
Result := XML_NextNode(Node);

Description
Returns the next XML node after the given one (inside their parent node).

Return value
Returns the node pointer to the next node or 0 if there are no more nodes after the given one.

// [Index] =======================================================================================
Function XML_ParentNode(Node: Integer): Integer;


Syntax
Result := XML_ParentNode(Node);

Description
Returns the parent node of the given XML node. Every XML node has a parent, except the root node.

Return value
Returns the parent node pointer or 0 if Node was the root node.

// [Index] =======================================================================================
Function XML_PreviousNode(Node: Integer): Integer;


Syntax
Result := XML_PreviousNode(Node);

Description
Returns the previous XML node from the given one (inside their parent node).

Return value
Returns the node pointer to the previous node or 0 if the given node was the first child of its parent.


// [Index] =======================================================================================
Procedure XML_RemoveAttribute(Node: Integer; Attribute: PChar);


Syntax
XML_RemoveAttribute(Node, Attribute);

Description
Removes the attribute from the given XML node.

Only nodes of type XML_Normal can have attributes. For all other node types this function is ignored.

// [Index] =======================================================================================
Function XML_ResolveAttributeName(Node: Integer; Attribute, Separator: PChar): PChar;


Syntax
Result := XML_ResolveAttributeName(Node, Attribute, Separator);

Description
Returns the expanded name of the given node's attribute in a document that uses XML namespaces. The expanded name consists of the namespace URI (if any) and the local attribute name, separated by the separator character given in Separator.

Note: Unlike with node names, the default namespace is not applied to attribute names that do not have a namespace prefix. So attribute names without a namespace prefix simply get their local name returned.

Return value
In a document using namespaces, returns the expanded name of the attribute if it could be correctly resolved or an empty string if a namespace prefix is used that is never declared (which is invalid).

In a document without namespaces, returns the attribute name itself.

// [Index] =======================================================================================
Function XML_ResolveNodeName(Node: Integer; Separator: PChar): PChar;


Syntax
Result := XML_ResolveNodeName(Node, Separator);

Description
Returns the expanded name of the given node in a document that uses XML namespaces. The expanded name consists of the namespace URI (if any) and the local node name, separated by the separator character given in Separator.

Return value
In a document using namespaces, returns the expanded name of the node if it could be correctly resolved or an empty string if a namespace prefix is used that is never declared (which is invalid).

In a document without namespaces, returns the node name itself.

// [Index] =======================================================================================
Function XML_RootNode(XML: Integer): Integer;


Syntax
Result := XML_RootNode(XML);

Description
Returns a pointer to the root node of the XML tree. This node is always present. It represents the XML document itself. The text contained in this node represents the whitespace outside of any XML node (there can be no text outside of nodes). The children of this node are the main node and any comments outside the main node. The type of this node is XML_Root.

Return value
Always returns a valid XML node pointer if XML is an existing XML tree.

// [Index] =======================================================================================
Function XML_Save(XML: Integer; Filename: PChar; Flags: Integer): Integer;


Syntax
Result := XML_Save(XML, Filename, Flags);

Description
Saves the XML tree to the given file.

The created XML markup is not reformatted. It is written back as it was initially parsed/created. The amount of newline/whitespace written between the tags is stored in the "text" of each XML node. (see XML_GetNodeText() for more information) To reformat the XML markup before saving, the "text" for each XML node can be altered or XML_Format() can be used to apply some common reformatting options to the tree.

Parameters
Flags can be a combination of the following values (with the Or operator):
  XML_StringFormat : Includes a byte order mark (BOM).
  XML_NoDeclaration: Does not include the XML declaration.

Note: According to the XML specification, the XML declaration can only be omitted if the document is encoded in UTF-8 or UTF-16 or if the encoding information is provided externally through a transfer protocol for example. Even then, it is advised to keep the declaration in the document.

Return value
Returns nonzero if the file was successfully saved and zero otherwise.


// [Index] =======================================================================================
Procedure XML_SetAttribute(Node: Integer; Attribute, Value: PChar);


Syntax
XML_SetAttribute(Node, Attribute, Value);

Description
Sets the value of the attribute on the given XML node. If the attribute does not exist yet, it will be added.

Only nodes of type XML_Normal can have attributes. For all other node types this function is ignored.


// [Index] =======================================================================================
Procedure XML_SetEncoding(XML, Encoding: Integer);


Syntax
XML_SetEncoding(XML, Encoding);

Description
Changes the text encoding used for saving the given XML tree. Encoding can be either XML_Ascii, XML_Unicode (= UTF16) or XML_UTF8.

Note: This only affects the saving of the tree. The data in the XML object is always stored in the library internal string format (Ascii or Unicode depending on the compiled version). So a unicode executable that uses unicode library can safely change the encoding to XML_Ascii for saving and then back to something else without loosing any information in the tree in memory.


// [Index] =======================================================================================
Procedure XML_SetNodeName(Node: Integer; Name: PChar);


Syntax
XML_SetNodeName(Node, Name);

Description
Changes the tagname of the given XML node. If the node is not of type XML_Normal or XML_Instruction, this function is ignored.


// [Index] =======================================================================================
Procedure XML_SetNodeOffset(Node, Offset: Integer);


Syntax
XML_SetNodeOffset(Node, Offset);

Description
Changes the character offset of the given XML node within its parent nodes text data. See XML_GetNodeOffset() for more information.


// [Index] =======================================================================================
Procedure XML_SetNodeText(Node: Integer; Text: PChar);


Syntax
XML_SetNodeText(Node, Text);

Description
Changes the text contained within the given XML node. See XML_GetNodeText() for more information.

Note: If the node contains children, changing its contained text may require an adjustment of the child nodes offset values as well.


// [Index] =======================================================================================
Procedure XML_SetStandalone(XML, Standalone: Integer);


Syntax
XML_SetStandalone(XML, Standalone);

Description
Changes the "standalone" attribute of the XML declaration when saving the document.

Standalone can be one of these values:
  XML_StandaloneYes  : The document mode is standalone
  XML_StandaloneNo   : The document mode is not standalone
  XML_StandaloneUnset: The standalone mode is not specified in the declaration

Note: Since this library does not validate document type definitions (DTDs), the value of this attribute has no effect on the parsing/saving of documents with this library except that it is read from and written to the XML declaration. This value is however important when working with XML documents intended for validating parsers, that's why this command exists.

// [Index] =======================================================================================
Function XML_AttributeName(Node: Integer): PChar;


Syntax
Result := XML_AttributeName(Node: Integer): PChar;

Description
After calling XML_ExamineAttributes() and XML_NextAttribute() this function returns the attribute name of the currently examined attribute on the given XML node.

// [Index] =======================================================================================
Function XML_AttributeValue(Node: Integer): PChar;


Syntax
Result := XML_AttributeValue(Node);

Description
After calling XML_ExamineAttributes() and XML_NextAttribute() this function returns the attribute value of the currently examined attribute on the given XML node.

// [Index] =======================================================================================
Function XML_ChildCount(Node: Integer): Integer;


Syntax
Result := XML_ChildCount(Node);

Description
Returns the number of child nodes inside the given XML node.

// [Index] =======================================================================================
Function XML_Error(XML: Integer): PChar;


Syntax
Result := XML_Error(XML);

Description
In case of an error while parsing XML data this function returns an error-message describing the error. XML_Status() can be used to detect parsing errors.

To get more information about the error, XML_ErrorLine() or XML_ErrorPosition() can be used.

// [Index] =======================================================================================
Function XML_ErrorLine(XML: Integer): Integer;


Syntax
Result := XML_ErrorLine(XML);

Description
In case of an error while parsing XML data this function returns the line in the input that caused the error (one based). XML_Status() can be used to detect parsing errors.

To get the position within the line at which the error happened, XML_ErrorPosition() can be used.

// [Index] =======================================================================================
Function XML_ErrorPosition(XML: Integer): Integer;


Syntax
Result := XML_ErrorPosition(XML);

Description
In case of an error while parsing XML data this function returns character position within the line returned by XML_ErrorLine() at which the error was caused. The first character of the line is at position 1. XML_Status() can be used to detect parsing errors.

// [Index] =======================================================================================
Function XML_NodeFromID(XML: Integer; ID: PChar): Integer;


Syntax
Result := Function XML_NodeFromID(XML, ID);

Description
In valid XML, if a node has an attribute called "ID", the value of this attribute must be unique within the XML document. This function can be used to search for a node in the document based on its ID attribute.

Return value
Returns the node pointer of the node with the given ID tag or 0 if no such node exists within the tree.

// [Index] =======================================================================================
Function XML_NodeFromPath(ParentNode: Integer; Path: PChar): Integer;


Syntax
Result := XML_NodeFromPath(ParentNode, Path);

Description
Returns the XML node inside ParentNode who's relation to ParentNode is described through Path. XML_NodePath() can be used to get such a path to a node.

Parameters
Path contains a list of node names separated by "/" to indicate the way to follow from the parent to the target node. For example "childtag/subchildtag" specifies the first node with name "subchildtag" inside the first node with name "childtag" inside ParentNode.

A node name can have an index (one based) to specify which of multiple child tags of the same name should be selected. "childtag/subchildtag[3]" specifies the 3rd "subchildtag" inside the first "childtag" of ParentNode.

Other rules:
- If a path starts with "/" it is relative to the tree's root. No matter which node ParentNode specifies.
- A wildcard "*" can be used instead of a tag name to specify that any tag is to be selected.
- A Comment node has the tagname "#comment"
- A CData node has the tagname "#cdata"
- A DTD node has the tagname "#dtd"
- A Processing Instruction node has the tagname "#instruction"

Some examples of valid paths:
  "/mainnode/#comment[4]" - the 4th comment inside the "mainnode" node inside the root of the tree
  "*[10]"                 - the 10th node (of any type) inside ParentNode
  "*/*/*"                 - the 1st node 3 levels below ParentNode independent of its type
  "node[3]/*[3]/#cdata"   - the first CData section inside the 3rd node of any kind inside the 3rd "node" node inside ParentNode

Note: This command is not an implementation of the XPath specification. The syntax used and understood by this command is only a small subset of XPath. This means a path returned from XML_NodePath() is a valid XPath query, but this command only understands the syntax described here, not just any XPath query.

Return value
Returns the node pointer of the target node or 0 if the path did not lead to a valid node.

// [Index] =======================================================================================
Function XML_NodePath(Node, ParentNode: Integer): PChar;


Syntax
Result := XML_NodePath(Node, ParentNode);

Description
Returns a string representing the relation between Node and ParentNode. If ParentNode is specified, it must be a parent or grandparent of Node. If it is not specified, the root node of the tree is used.

See XML_NodeFromPath() for a description of the returned path string.

// [Index] =======================================================================================
Function XML_NodeType(Node: Integer): Integer;


Syntax
Result := Function XML_NodeType(Node: Integer): Integer;

Description
Returns the type of the given XML node. It can be one of the following:

XML_Root
This is the trees root node. It represents the document itself. This node cannot be created or deleted manually. Inside the root node, there can be only one node of type XML_Normal and also no plain text (this is required to be a well-formed XML document).

XML_Normal
This is a normal node in the tree. It can have a list of attributes and contain text and/or child nodes.
Example: <node attribute="hello"> contained text </node>

XML_Comment
This node represents a comment. It can have no children or attributes. Its text represents the content of the comment.
Example: <!-- comment text -->

XML_CData
This is a CData section. A CData section contains only text. Its content is not interpreted by the parser so it can contain unescaped "<", ">" and "&" characters for example. CData sections can be used to include other markup or code inside a document without having to escape all characters that could be interpreted as XML.
Example: <![CDATA[ cdata content ]]>

XML_DTD
This is a document type declaration (DTD). This library does not use a validating parser, so these declarations are actually ignored when parsing a document. In order to save them back correctly, they are contained within such a DTD node. The text content of the node is the entire DTD tag. It can be read and modified through commands like XML_SetNodeText() and will be written back to the document when saving without modification. The XML_SetStandalone() command could be useful as well when working with DTDs.
Example: <!DOCTYPE name SYSTEM "external dtd uri">

XML_Instruction
This node represents a Processing Instruction. Processing Instructions contain information that is intended to be interpreted/executed by the target application. They have a name to specify the content of the instruction and the instruction data which can be accessed with XML_GetNodeText().
Example: <?php if (...) ... ?>
(here "php" is the node name, and the rest up to the "?>" is the node text.)

// [Index] =======================================================================================
Function XML_Status(XML: Integer): Integer;


Syntax
Result := XML_Status(XML);

Description
Returns the status of the last parsing operation done on this XML tree (using XML_Load()). This function should be called after every XML_Load() call to ensure that the parsing succeeded. A string representation of the parsing status (i.e. a readable error-message) is returned by the XML_Error() function.

Return value
A value of 0 (XML_Success) indicates a successful parsing, all other values indicate various error conditions.

The following returnvalues are possible:
  XML_Success             : no error
  XML_NoMemory            : out of memory
  XML_Syntax              : syntax error
  XML_NoElements          : no element found
  XML_InvalidToken        : not well-formed (invalid token)
  XML_UnclosedToken       : unclosed token
  XML_PartialCharacter    : partial character
  XML_TagMismatch         : mismatched tag
  XML_DublicateAttribute  : duplicate attribute
  XML_JunkAfterDocElement : junk after document element
  XML_ParamEntityRef      : illegal parameter entity reference
  XML_UndefinedEntity     : undefined entity
  XML_RecursiveEntityRef  : recursive entity reference
  XML_AsyncEntity         : asynchronous entity
  XML_BadCharacterRef     : reference to invalid character number
  XML_BinaryEntityRef     : reference to binary entity
  XML_AttributeExternalEntityRef: reference to external entity in attribute
  XML_MisplacedXML        : XML or text declaration not at start of entity
  XML_UnknownEncoding     : unknown encoding
  XML_IncorrectEncoding   : encoding specified in XML declaration is incorrect
  XML_UnclosedCDataSection: unclosed CDATA section
  XML_ExternalEntityHandling: error in processing external entity reference
  XML_NotStandalone       : document is not standalone
  XML_UnexpectedState     : unexpected parser state
  XML_EntityDeclaredInPE  : entity declared in parameter entity
  XML_FeatureRequiresDTD  : requested feature requires XML_DTD support in Expat
  XML_CantChangeFeatures  : cannot change setting once parsing has begun
  XML_UnboundPrefix       : unbound prefix
  XML_UndeclaringPrefix   : must not undeclare prefix
  XML_IncompletePE        : incomplete markup in parameter entity
  XML_XMLDeclaration      : XML declaration not well-formed
  XML_TextDeclaration     : text declaration not well-formed
  XML_PublicID            : illegal character(s) in public id
  XML_Suspended           : parser suspended
  XML_NotSuspended        : parser not suspended
  XML_Aborted             : parsing aborted
  XML_Finished            : parsing finished
  XML_SuspendedPE         : cannot suspend in external parameter entity
  XML_ReservedPrefixXML   : reserved prefix (xml) must not be undeclared or bound to another namespace name
  XML_ReservedPrefixXMLNS : reserved prefix (xmlns) must not be declared or undeclared
  XML_ReservedNamespaceURI: prefix must not be bound to one of the reserved namespace names

// [Index] =======================================================================================
Function XML_GetVersion(): Integer;


Syntax
Result := XML_GetVersion();

Description
Returns the version of the libxml library.
« Last Edit: June 14, 2018, 04:52:25 pm by SyavX »

Offline DorkeyDear

  • Veteran
  • *****
  • Posts: 1507
  • I also go by Curt or menturi
Re: libxml (XML library for Windows/Linux servers)
« Reply #2 on: May 27, 2013, 07:08:31 am »
This look familiar! :) Glad it made it here, with the documentation; can make life easy for a lot of people. Good job.

Offline jrgp

  • Administrator
  • Flamebow Warrior
  • *****
  • Posts: 5037
Re: libxml (XML library for Windows/Linux servers)
« Reply #3 on: May 27, 2013, 01:09:41 pm »
Very nice, but I feel like JSON is a far more useful and ubiquitous data serialization format than these days as it's easier to parse/generate than XML.
There are other worlds than these

Offline DorkeyDear

  • Veteran
  • *****
  • Posts: 1507
  • I also go by Curt or menturi
Re: libxml (XML library for Windows/Linux servers)
« Reply #4 on: May 27, 2013, 04:33:47 pm »
jrgp: I found a post that has a couple points comparing JSON and XML.

I'm currently using this library, and your comment makes me wonder if I should switch to JSON if it would be a better option for my application. Personally I've never used JSON and am not even sure if it can do what I want. In any case, do you know of any numbers on how many people know XML and how many people know JSON? I mean 'know' as in are able to edit it with a text editor.

Offline jrgp

  • Administrator
  • Flamebow Warrior
  • *****
  • Posts: 5037
Re: libxml (XML library for Windows/Linux servers)
« Reply #5 on: May 28, 2013, 09:35:09 am »
jrgp: I found a post that has a couple points comparing JSON and XML.

I'm currently using this library, and your comment makes me wonder if I should switch to JSON if it would be a better option for my application. Personally I've never used JSON and am not even sure if it can do what I want. In any case, do you know of any numbers on how many people know XML and how many people know JSON? I mean 'know' as in are able to edit it with a text editor.

XML: pros include adding lots of attributes to your data (as in <tag attribute="value">tag contents</tag>) which makes it absolutely essential for things like HTML, but extremely difficult to parse as you have to examine each node's value and any number of attributes and nested children.

JSON: it's literally javascript dictionaries/arrays. It's as easy to parse and generate as it is to make an array in javascript.

{'tag' : {
 'key': 'value',
 'arr': ['stuff, 'stuff']
}}

You can get to that with stuff like tag.key or tag['key'] or tag['arr'][0], and the syntax for accessing decoded JSON in php and python and perl is mostly identical, even in c# except then you need to create a class that has the exact same form as the json which you decode to. There's simply no way to parse XML that easily, as you need to account for both attributes and children of each node. I use JSON a lot at home and work when I need to throw dictionaries of data between programs written in different languages with ease.
« Last Edit: May 28, 2013, 09:51:09 am by jrgp »
There are other worlds than these