1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "nel/misc/i_xml.h"
24 #ifndef NL_DONT_USE_EXTERNAL_CODE
26 // Include from libxml2
27 #include <libxml/xmlerror.h>
31 #define NLMISC_READ_BUFFER_SIZE 1024
40 // *********************************************************
42 const char SEPARATOR
= ' ';
44 std::string
CIXml::_ErrorString
;
46 bool CIXml::_LibXmlIntialized
= false;
48 // ***************************************************************************
50 #define readnumber(dest,digits) \
51 string number_as_string; \
52 serialSeparatedBufferIn( number_as_string ); \
53 NLMISC::fromString(number_as_string, dest);
55 // ***************************************************************************
57 inline void CIXml::flushContentString ()
60 _ContentString
.erase ();
63 _ContentStringIndex
= 0;
66 // ***************************************************************************
68 CIXml::CIXml () : IStream (true /* Input mode */)
72 _CurrentElement
= NULL
;
75 _AttribPresent
= false;
76 _TryBinaryMode
= false;
80 // ***************************************************************************
82 CIXml::CIXml (bool tryBinaryMode
) : IStream (true /* Input mode */)
86 _CurrentElement
= NULL
;
89 _AttribPresent
= false;
90 _TryBinaryMode
= tryBinaryMode
;
94 // ***************************************************************************
102 // ***************************************************************************
104 void CIXml::release ()
106 // Release the parser
110 xmlClearParserCtxt (_Parser
);
111 xmlFreeParserCtxt (_Parser
);
117 _CurrentElement
= NULL
;
120 _AttribPresent
= false;
121 _ErrorString
.clear();
126 // ***************************************************************************
128 void xmlGenericErrorFuncRead (void *ctx
, const char *msg
, ...)
130 // Get the error string
132 NLMISC_CONVERT_VARGS (str
, msg
, NLMISC::MaxCStringSize
);
133 CIXml::_ErrorString
+= str
;
136 // ***************************************************************************
138 bool CIXml::init (IStream
&stream
)
145 // Default : XML mode
146 _BinaryStream
= NULL
;
149 if (stream
.isReading())
154 // Get current position
155 sint32 pos
= stream
.getPos ();
158 bool seekGood
= stream
.seek (0, end
);
161 // Get input stream length
162 sint32 length
= stream
.getPos () - pos
;
165 stream
.seek (pos
, begin
);
168 char buffer
[NLMISC_READ_BUFFER_SIZE
];
171 stream
.serialBuffer ((uint8
*)buffer
, 4);
178 header
[0] = buffer
[0];
179 header
[1] = buffer
[1];
180 header
[2] = buffer
[2];
181 header
[3] = buffer
[3];
183 toLowerAscii(header
);
185 // Does it a xml stream ?
186 if (strcmp(header
, "<?xm"))
188 // NO ! Go in binary mode
189 _BinaryStream
= &stream
;
191 // Seek back to the start
192 stream
.seek (pos
, begin
);
199 _ErrorString
.clear();
201 // The parser context
202 _Parser
= xmlCreatePushParserCtxt(NULL
, NULL
, buffer
, 4, NULL
);
206 while (length
>=NLMISC_READ_BUFFER_SIZE
)
209 stream
.serialBuffer ((uint8
*)buffer
, NLMISC_READ_BUFFER_SIZE
);
212 int res
= xmlParseChunk(_Parser
, buffer
, NLMISC_READ_BUFFER_SIZE
, 0);
217 throw EXmlParsingError (_ErrorString
);
221 length
-= NLMISC_READ_BUFFER_SIZE
;
225 stream
.serialBuffer ((uint8
*)buffer
, length
);
227 // Parse the last buffer
228 int res
= xmlParseChunk(_Parser
, buffer
, length
, 1);
233 throw EXmlParsingError (_ErrorString
);
241 nlwarning ("XML: The stream is not an input stream.");
248 // ***************************************************************************
250 void CIXml::serialSeparatedBufferIn ( string
&value
, bool checkSeparator
)
252 nlassert( isReading() );
254 // Output stream has been initialized ?
257 // Current node presents ?
260 // Write a push attribute ?
263 // Current attrib is set ?
266 // Should have a current element
267 nlassert (_CurrentElement
);
270 xmlChar
*attributeValue
= xmlGetProp (_CurrentElement
, (const xmlChar
*)_AttribName
.c_str());
272 // Attribute is here ?
276 value
= (const char*)attributeValue
;
279 xmlFree ((void*)attributeValue
);
283 // Node name must not be NULL
284 nlassert (_CurrentElement
->name
);
286 // Make an error message
288 smprintf (tmp
, 512, "NeL XML Syntax error in block line %d\nAttribute \"%s\" is missing in node \"%s\"",
289 (int)_CurrentElement
->line
, _AttribName
.c_str(), _CurrentElement
->name
);
290 throw EXmlParsingError (tmp
);
293 // The attribute has been used
294 _AttribPresent
= false;
298 // * Error, the stream don't use XML streaming properly
299 // * You must take care of this in your last serial call:
300 // * - Between xmlPushBegin() and xmlPushEnd(), before each serial, you must set the attribute name with xmlSetAttrib.
301 // * - Between xmlPushBegin() and xmlPushEnd(), you must serial only basic objects (numbers and strings).
302 nlerror ( "Error, the stream don't use XML streaming properly" );
308 uint length
= (uint
)_ContentString
.length();
313 // Try to open the node
316 // If no more node, empty string
317 if (_CurrentNode
== NULL
)
320 _ContentStringIndex
= 0;
321 _ContentString
.erase ();
325 // Node with the good name
326 if (_CurrentNode
->type
== XML_TEXT_NODE
)
333 _CurrentNode
= _CurrentNode
->next
;
335 while (_CurrentNode
);
338 if (_CurrentNode
!= NULL
)
341 const char *content
= (const char*)xmlNodeGetContent (_CurrentNode
);
344 _ContentString
= content
;
347 xmlFree ((void*)content
);
350 _ContentString
.erase ();
352 // Set the current index
353 _ContentStringIndex
= 0;
356 length
= (uint
)_ContentString
.length();
360 // Keyword in the buffer ?
361 if (_ContentStringIndex
< length
)
364 uint first
= _ContentStringIndex
;
366 // Have to take care of separators ?
370 while (_ContentStringIndex
< length
)
373 if ( (_ContentString
[_ContentStringIndex
]==SEPARATOR
) || (_ContentString
[_ContentStringIndex
]=='\n') )
375 _ContentStringIndex
++;
380 _ContentStringIndex
++;
385 // Copy all the string
386 _ContentStringIndex
= length
;
390 value
.assign (_ContentString
, first
, _ContentStringIndex
-first
);
394 // Should have a name
395 nlassert (_CurrentElement
->name
);
397 // Make an error message
399 smprintf (tmp
, 512, "NeL XML Syntax error in block line %d \nMissing keywords in text child node in the node %s",
400 (int)_CurrentElement
->line
, _CurrentElement
->name
);
401 throw EXmlParsingError (tmp
);
407 // * Error, no current node present.
408 // * Check that your serial is initialy made between a xmlPushBegin and xmlPushEnd calls.
409 nlerror ( "Error, the stream don't use XML streaming properly" );
414 nlerror ( "Output stream has not been initialized" );
418 // ***************************************************************************
420 void CIXml::serial(uint8
&b
)
424 _BinaryStream
->serial(b
);
433 // ***************************************************************************
435 void CIXml::serial(sint8
&b
)
439 _BinaryStream
->serial(b
);
447 // ***************************************************************************
449 void CIXml::serial(uint16
&b
)
453 _BinaryStream
->serial(b
);
461 // ***************************************************************************
463 void CIXml::serial(sint16
&b
)
467 _BinaryStream
->serial(b
);
475 // ***************************************************************************
477 inline uint32
atoui( const char *ident
)
479 return (uint32
) strtoul (ident
, NULL
, 10);
482 void CIXml::serial(uint32
&b
)
486 _BinaryStream
->serial(b
);
494 // ***************************************************************************
496 void CIXml::serial(sint32
&b
)
500 _BinaryStream
->serial(b
);
508 // ***************************************************************************
510 void CIXml::serial(uint64
&b
)
514 _BinaryStream
->serial(b
);
522 // ***************************************************************************
524 void CIXml::serial(sint64
&b
)
528 _BinaryStream
->serial(b
);
536 // ***************************************************************************
538 void CIXml::serial(float &b
)
542 _BinaryStream
->serial(b
);
546 readnumber( b
, 128 );
550 // ***************************************************************************
552 void CIXml::serial(double &b
)
556 _BinaryStream
->serial(b
);
560 readnumber( b
, 128 );
564 // ***************************************************************************
566 void CIXml::serial(bool &b
)
570 _BinaryStream
->serial(b
);
578 // ***************************************************************************
580 void CIXml::serialBit(bool &bit
)
584 _BinaryStream
->serialBit(bit
);
594 // ***************************************************************************
597 void CIXml::serial(char &b
)
601 _BinaryStream
->serial(b
);
606 serialSeparatedBufferIn ( toto
);
609 if (toto
.length()!=1)
614 // Should have a name
615 nlassert (_CurrentElement
->name
);
617 // Make an error message
619 smprintf (tmp
, 512, "NeL XML Syntax error in block line %d \nValue is not a char in the node named %s",
620 (int)_CurrentElement
->line
, _CurrentElement
->name
);
621 throw EXmlParsingError (tmp
);
625 nlerror ( "Output stream has not been initialized" );
632 #endif // NL_OS_CYGWIN
634 // ***************************************************************************
636 void CIXml::serial(std::string
&b
)
638 nlassert( isReading() );
642 _BinaryStream
->serial(b
);
649 // Only serial the string
650 serialSeparatedBufferIn ( b
, false );
654 // Open a string node
658 serialSeparatedBufferIn ( b
, false );
666 // ***************************************************************************
668 void CIXml::serial(ucstring
&b
)
670 nlassert( isReading() );
674 _BinaryStream
->serial(b
);
678 // Serial a simple string
681 // Serial this string
689 // ***************************************************************************
691 void CIXml::serialBuffer(uint8
*buf
, uint len
)
695 _BinaryStream
->serialBuffer(buf
, len
);
702 // Serialize the buffer
703 for (uint i
=0; i
<len
; i
++)
717 // ***************************************************************************
719 bool CIXml::xmlPushBeginInternal (const std::string
&nodeName
)
721 nlassert( isReading() );
732 // Can make a xmlPushBegin ?
735 // Current node exist ?
736 if (_CurrentNode
==NULL
)
738 // Get the first node
739 _CurrentNode
= xmlDocGetRootElement (_Parser
->myDoc
);
744 // Node name must not be NULL
745 nlassert (_CurrentNode
->name
);
747 // Node element with the good name ?
748 if ( (_CurrentNode
->type
!= XML_ELEMENT_NODE
) || ( (const char*)_CurrentNode
->name
!= nodeName
) )
750 // Make an error message
752 smprintf (tmp
, 512, "NeL XML Syntax error : root node has the wrong name : \"%s\" should have \"%s\"",
753 _CurrentNode
->name
, nodeName
.c_str());
754 throw EXmlParsingError (tmp
);
759 // Make an error message
761 smprintf (tmp
, 512, "NeL XML Syntax error : no root node found.");
762 throw EXmlParsingError (tmp
);
766 // Try to open the node
769 // Node name must not be NULL
770 nlassert (_CurrentNode
->name
);
772 // Node with the good name
773 if ( (_CurrentNode
->type
== XML_ELEMENT_NODE
) && ( (const char*)_CurrentNode
->name
== nodeName
) )
775 // Save current element
776 _CurrentElement
= _CurrentNode
;
783 _CurrentNode
= _CurrentNode
->next
;
785 while (_CurrentNode
);
788 if (_CurrentNode
== NULL
)
790 // Make an error message
792 smprintf (tmp
, 512, "NeL XML Syntax error in block line %d \nCan't open the node named %s in node named %s",
793 (int)_CurrentElement
->line
, nodeName
.c_str(), _CurrentElement
->name
);
794 throw EXmlParsingError (tmp
);
798 _CurrentNode
= _CurrentNode
->children
;
803 // Flush current string
804 flushContentString ();
808 nlerror ( "You must close your xmlPushBegin - xmlPushEnd before calling a new xmlPushBegin.");
814 nlerror ( "Output stream has not been initialized.");
823 // ***************************************************************************
825 bool CIXml::xmlPushEndInternal ()
827 nlassert( isReading() );
838 // Can make a xmlPushEnd ?
846 nlerror ( "You must call xmlPushBegin before calling xmlPushEnd.");
852 nlerror ( "Output stream has not been initialized.");
861 // ***************************************************************************
863 bool CIXml::xmlPopInternal ()
865 nlassert( isReading() );
876 // Not in the push mode ?
879 // Some content to write ?
880 flushContentString ();
883 _CurrentNode
= _CurrentElement
;
884 _CurrentElement
= _CurrentElement
->parent
;
885 _CurrentNode
= _CurrentNode
->next
;
889 nlerror ( "You must call xmlPop after xmlPushEnd.");
895 nlerror ( "Output stream has not been initialized.");
904 // ***************************************************************************
906 bool CIXml::xmlSetAttribInternal (const std::string
&attribName
)
908 nlassert( isReading() );
919 // Can make a xmlPushEnd ?
922 // Set attribute name
923 _AttribName
= attribName
;
925 // Attribute name is present
926 _AttribPresent
= true;
930 nlerror ( "You must call xmlSetAttrib between xmlPushBegin and xmlPushEnd calls.");
936 nlerror ( "Output stream has not been initialized.");
945 // ***************************************************************************
947 bool CIXml::xmlBreakLineInternal ()
953 // ***************************************************************************
955 bool CIXml::xmlCommentInternal (const std::string
&/* comment */)
961 // ***************************************************************************
963 xmlNodePtr
CIXml::getFirstChildNode (xmlNodePtr parent
, const std::string
&childName
)
965 xmlNodePtr child
= parent
->children
;
968 if (childName
== (const char*)child
->name
)
975 // ***************************************************************************
977 xmlNodePtr
CIXml::getNextChildNode (xmlNodePtr last
, const std::string
&childName
)
982 if (childName
== (const char*)last
->name
)
989 // ***************************************************************************
991 xmlNodePtr
CIXml::getFirstChildNode (xmlNodePtr parent
, sint
/* xmlElementType */ type
)
993 xmlNodePtr child
= parent
->children
;
996 if (child
->type
== (xmlElementType
)type
)
1003 // ***************************************************************************
1005 xmlNodePtr
CIXml::getNextChildNode (xmlNodePtr last
, sint
/* xmlElementType */ type
)
1010 if (last
->type
== (xmlElementType
)type
)
1017 // ***************************************************************************
1019 uint
CIXml::countChildren (xmlNodePtr node
, const std::string
&childName
)
1022 xmlNodePtr child
= getFirstChildNode (node
, childName
);
1026 child
= getNextChildNode (child
, childName
);
1031 // ***************************************************************************
1033 uint
CIXml::countChildren (xmlNodePtr node
, sint
/* xmlElementType */ type
)
1036 xmlNodePtr child
= getFirstChildNode (node
, type
);
1040 child
= getNextChildNode (child
, type
);
1045 // ***************************************************************************
1047 xmlNodePtr
CIXml::getRootNode () const
1051 return xmlDocGetRootElement (_Parser
->myDoc
);
1055 // ***************************************************************************
1057 bool CIXml::getPropertyString (std::string
&result
, xmlNodePtr node
, const std::string
&property
)
1060 const char *value
= (const char*)xmlGetProp (node
, (xmlChar
*)property
.c_str());
1067 xmlFree ((void*)value
);
1076 // ***************************************************************************
1078 int CIXml::getIntProperty(xmlNodePtr node
, const std::string
&property
, int defaultValue
)
1082 bool b
= getPropertyString(s
, node
, property
);
1085 return defaultValue
;
1087 // remove leading and trailing spaces
1092 if (!fromString(s
, val
) || (val
== 0 && s
!= "0"))
1094 nlwarning("Bad integer value: %s",s
.c_str());
1095 return defaultValue
;
1101 // ***************************************************************************
1103 double CIXml::getFloatProperty(xmlNodePtr node
, const std::string
&property
, float defaultValue
)
1107 bool b
= getPropertyString(s
, node
, property
);
1110 return defaultValue
;
1112 // remove leading and trailing spaces
1117 if (!fromString(s
, val
))
1119 nlwarning("Bad float value: %s", s
.c_str());
1120 return defaultValue
;
1126 // ***************************************************************************
1128 std::string
CIXml::getStringProperty(xmlNodePtr node
, const std::string
&property
, const std::string
& defaultValue
)
1132 bool b
= getPropertyString(s
, node
, property
);
1135 return defaultValue
;
1140 // ***************************************************************************
1142 bool CIXml::getContentString (std::string
&result
, xmlNodePtr node
)
1144 const char *valueText
= (const char*)xmlNodeGetContent (node
);
1150 xmlFree ((void*)valueText
);
1158 // ***************************************************************************
1160 void CIXml::initLibXml()
1162 if (_LibXmlIntialized
) return;
1164 _ErrorString
.clear();
1166 // Set error handler
1167 xmlSetGenericErrorFunc (NULL
, xmlGenericErrorFuncRead
);
1171 // an error occured during initialization
1172 if (!_ErrorString
.empty())
1174 throw EXmlParsingError (_ErrorString
);
1177 // Ask to get debug info
1178 xmlLineNumbersDefault(1);
1180 _LibXmlIntialized
= true;
1183 // ***************************************************************************
1185 void CIXml::releaseLibXml()
1187 if (!_LibXmlIntialized
) return;
1191 _LibXmlIntialized
= false;
1194 std::string
CIXml::getErrorString()
1196 return _ErrorString
;
1201 #endif // NL_DONT_USE_EXTERNAL_CODE