Merge branch 'fixes' into main/rendor-staging
[ryzomcore.git] / nel / src / misc / i_xml.cpp
blob2790a05907aa751ba6969d6156727b35a77d38a0
1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
6 //
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/>.
20 #include "stdmisc.h"
22 #include "nel/misc/i_xml.h"
24 #ifndef NL_DONT_USE_EXTERNAL_CODE
26 // Include from libxml2
27 #include <libxml/xmlerror.h>
29 using namespace std;
31 #define NLMISC_READ_BUFFER_SIZE 1024
33 #ifdef DEBUG_NEW
34 #define new DEBUG_NEW
35 #endif
37 namespace NLMISC
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 ()
59 // String size
60 _ContentString.erase ();
62 // Reset
63 _ContentStringIndex = 0;
66 // ***************************************************************************
68 CIXml::CIXml () : IStream (true /* Input mode */)
70 // Not initialized
71 _Parser = NULL;
72 _CurrentElement = NULL;
73 _CurrentNode = NULL;
74 _PushBegin = false;
75 _AttribPresent = false;
76 _TryBinaryMode = false;
77 _BinaryStream = NULL;
80 // ***************************************************************************
82 CIXml::CIXml (bool tryBinaryMode) : IStream (true /* Input mode */)
84 // Not initialized
85 _Parser = NULL;
86 _CurrentElement = NULL;
87 _CurrentNode = NULL;
88 _PushBegin = false;
89 _AttribPresent = false;
90 _TryBinaryMode = tryBinaryMode;
91 _BinaryStream = NULL;
94 // ***************************************************************************
96 CIXml::~CIXml ()
98 // Release
99 release ();
102 // ***************************************************************************
104 void CIXml::release ()
106 // Release the parser
107 if (_Parser)
109 // Free it
110 xmlClearParserCtxt (_Parser);
111 xmlFreeParserCtxt (_Parser);
113 _Parser = NULL;
116 // Not initialized
117 _CurrentElement = NULL;
118 _CurrentNode = NULL;
119 _PushBegin = false;
120 _AttribPresent = false;
121 _ErrorString.clear();
123 resetPtrTable();
126 // ***************************************************************************
128 void xmlGenericErrorFuncRead (void *ctx, const char *msg, ...)
130 // Get the error string
131 string str;
132 NLMISC_CONVERT_VARGS (str, msg, NLMISC::MaxCStringSize);
133 CIXml::_ErrorString += str;
136 // ***************************************************************************
138 bool CIXml::init (IStream &stream)
140 // Release
141 release ();
143 initLibXml();
145 // Default : XML mode
146 _BinaryStream = NULL;
148 // Input stream ?
149 if (stream.isReading())
151 // Set XML mode
152 setXMLMode (true);
154 // Get current position
155 sint32 pos = stream.getPos ();
157 // Go to end
158 bool seekGood = stream.seek (0, end);
159 nlassert (seekGood);
161 // Get input stream length
162 sint32 length = stream.getPos () - pos;
164 // Go to start
165 stream.seek (pos, begin);
167 // The read buffer
168 char buffer[NLMISC_READ_BUFFER_SIZE];
170 // Fill the buffer
171 stream.serialBuffer ((uint8*)buffer, 4);
172 length -= 4;
174 // Try binary mode
175 if (_TryBinaryMode)
177 char header[5];
178 header[0] = buffer[0];
179 header[1] = buffer[1];
180 header[2] = buffer[2];
181 header[3] = buffer[3];
182 header[4] = '\0';
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);
194 // Done
195 return true;
199 _ErrorString.clear();
201 // The parser context
202 _Parser = xmlCreatePushParserCtxt(NULL, NULL, buffer, 4, NULL);
203 nlassert (_Parser);
205 // For all the file
206 while (length>=NLMISC_READ_BUFFER_SIZE)
208 // Fill the buffer
209 stream.serialBuffer ((uint8*)buffer, NLMISC_READ_BUFFER_SIZE);
211 // Read a buffer
212 int res = xmlParseChunk(_Parser, buffer, NLMISC_READ_BUFFER_SIZE, 0);
214 // Error code ?
215 if (res)
217 throw EXmlParsingError (_ErrorString);
220 // Length left
221 length -= NLMISC_READ_BUFFER_SIZE;
224 // Fill the buffer
225 stream.serialBuffer ((uint8*)buffer, length);
227 // Parse the last buffer
228 int res = xmlParseChunk(_Parser, buffer, length, 1);
230 // Error code ?
231 if (res)
233 throw EXmlParsingError (_ErrorString);
236 // Ok
237 return true;
239 else
241 nlwarning ("XML: The stream is not an input stream.");
244 // Error
245 return false;
248 // ***************************************************************************
250 void CIXml::serialSeparatedBufferIn ( string &value, bool checkSeparator )
252 nlassert( isReading() );
254 // Output stream has been initialized ?
255 if ( _Parser )
257 // Current node presents ?
258 if (_CurrentElement)
260 // Write a push attribute ?
261 if (_PushBegin)
263 // Current attrib is set ?
264 if (_AttribPresent)
266 // Should have a current element
267 nlassert (_CurrentElement);
269 // Get the attribute
270 xmlChar *attributeValue = xmlGetProp (_CurrentElement, (const xmlChar*)_AttribName.c_str());
272 // Attribute is here ?
273 if (attributeValue)
275 // Copy the value
276 value = (const char*)attributeValue;
278 // Delete the value
279 xmlFree ((void*)attributeValue);
281 else
283 // Node name must not be NULL
284 nlassert (_CurrentElement->name);
286 // Make an error message
287 char tmp[512];
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;
296 else
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" );
305 else
307 // Content length
308 uint length = (uint)_ContentString.length();
310 // String empty ?
311 if (length==0)
313 // Try to open the node
316 // If no more node, empty string
317 if (_CurrentNode == NULL)
319 value.clear();
320 _ContentStringIndex = 0;
321 _ContentString.erase ();
322 return;
325 // Node with the good name
326 if (_CurrentNode->type == XML_TEXT_NODE)
328 // Stop searching
329 break;
331 else
332 // Get next
333 _CurrentNode = _CurrentNode->next;
335 while (_CurrentNode);
337 // Not found ?
338 if (_CurrentNode != NULL)
340 // Read the content
341 const char *content = (const char*)xmlNodeGetContent (_CurrentNode);
342 if (content)
344 _ContentString = content;
346 // Delete the value
347 xmlFree ((void*)content);
349 else
350 _ContentString.erase ();
352 // Set the current index
353 _ContentStringIndex = 0;
355 // New length
356 length = (uint)_ContentString.length();
360 // Keyword in the buffer ?
361 if (_ContentStringIndex < length)
363 // First index
364 uint first = _ContentStringIndex;
366 // Have to take care of separators ?
367 if (checkSeparator)
369 // Scan to the end
370 while (_ContentStringIndex < length)
372 // Not a separator ?
373 if ( (_ContentString[_ContentStringIndex]==SEPARATOR) || (_ContentString[_ContentStringIndex]=='\n') )
375 _ContentStringIndex++;
376 break;
379 // Next char
380 _ContentStringIndex++;
383 else
385 // Copy all the string
386 _ContentStringIndex = length;
389 // Make a string
390 value.assign (_ContentString, first, _ContentStringIndex-first);
392 else
394 // Should have a name
395 nlassert (_CurrentElement->name);
397 // Make an error message
398 char tmp[512];
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);
405 else
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" );
412 else
414 nlerror ( "Output stream has not been initialized" );
418 // ***************************************************************************
420 void CIXml::serial(uint8 &b)
422 if (_BinaryStream)
424 _BinaryStream->serial(b);
426 else
428 // Read the number
429 readnumber( b, 3 );
433 // ***************************************************************************
435 void CIXml::serial(sint8 &b)
437 if (_BinaryStream)
439 _BinaryStream->serial(b);
441 else
443 readnumber( b, 4 );
447 // ***************************************************************************
449 void CIXml::serial(uint16 &b)
451 if (_BinaryStream)
453 _BinaryStream->serial(b);
455 else
457 readnumber( b, 5 );
461 // ***************************************************************************
463 void CIXml::serial(sint16 &b)
465 if (_BinaryStream)
467 _BinaryStream->serial(b);
469 else
471 readnumber( b, 6 );
475 // ***************************************************************************
477 inline uint32 atoui( const char *ident)
479 return (uint32) strtoul (ident, NULL, 10);
482 void CIXml::serial(uint32 &b)
484 if (_BinaryStream)
486 _BinaryStream->serial(b);
488 else
490 readnumber( b, 10 );
494 // ***************************************************************************
496 void CIXml::serial(sint32 &b)
498 if (_BinaryStream)
500 _BinaryStream->serial(b);
502 else
504 readnumber( b, 11 );
508 // ***************************************************************************
510 void CIXml::serial(uint64 &b)
512 if (_BinaryStream)
514 _BinaryStream->serial(b);
516 else
518 readnumber( b, 20 );
522 // ***************************************************************************
524 void CIXml::serial(sint64 &b)
526 if (_BinaryStream)
528 _BinaryStream->serial(b);
530 else
532 readnumber( b, 20 );
536 // ***************************************************************************
538 void CIXml::serial(float &b)
540 if (_BinaryStream)
542 _BinaryStream->serial(b);
544 else
546 readnumber( b, 128 );
550 // ***************************************************************************
552 void CIXml::serial(double &b)
554 if (_BinaryStream)
556 _BinaryStream->serial(b);
558 else
560 readnumber( b, 128 );
564 // ***************************************************************************
566 void CIXml::serial(bool &b)
568 if (_BinaryStream)
570 _BinaryStream->serial(b);
572 else
574 serialBit(b);
578 // ***************************************************************************
580 void CIXml::serialBit(bool &bit)
582 if (_BinaryStream)
584 _BinaryStream->serialBit(bit);
586 else
588 uint8 u;
589 serial (u);
590 bit = (u!=0);
594 // ***************************************************************************
596 #ifndef NL_OS_CYGWIN
597 void CIXml::serial(char &b)
599 if (_BinaryStream)
601 _BinaryStream->serial(b);
603 else
605 string toto;
606 serialSeparatedBufferIn ( toto );
608 // Good value ?
609 if (toto.length()!=1)
611 // Protect error
612 if (_Parser)
614 // Should have a name
615 nlassert (_CurrentElement->name);
617 // Make an error message
618 char tmp[512];
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);
623 else
625 nlerror ( "Output stream has not been initialized" );
628 else
629 b=toto[0];
632 #endif // NL_OS_CYGWIN
634 // ***************************************************************************
636 void CIXml::serial(std::string &b)
638 nlassert( isReading() );
640 if (_BinaryStream)
642 _BinaryStream->serial(b);
644 else
646 // Attibute ?
647 if (_PushBegin)
649 // Only serial the string
650 serialSeparatedBufferIn ( b, false );
652 else
654 // Open a string node
655 xmlPush ("S");
657 // Serial the string
658 serialSeparatedBufferIn ( b, false );
660 // Close the node
661 xmlPop ();
666 // ***************************************************************************
668 void CIXml::serial(ucstring &b)
670 nlassert( isReading() );
672 if (_BinaryStream)
674 _BinaryStream->serial(b);
676 else
678 // Serial a simple string
679 string tmp;
681 // Serial this string
682 serial (tmp);
684 // Return a ucstring
685 b.fromUtf8(tmp);
689 // ***************************************************************************
691 void CIXml::serialBuffer(uint8 *buf, uint len)
693 if (_BinaryStream)
695 _BinaryStream->serialBuffer(buf, len);
697 else
699 // Open a node
700 xmlPush ("BUFFER");
702 // Serialize the buffer
703 for (uint i=0; i<len; i++)
705 xmlPush ("ELM");
707 serial (buf[i]);
709 xmlPop ();
712 // Close the node
713 xmlPop ();
717 // ***************************************************************************
719 bool CIXml::xmlPushBeginInternal (const std::string &nodeName)
721 nlassert( isReading() );
723 if (_BinaryStream)
725 return true;
727 else
729 // Check _Parser
730 if ( _Parser )
732 // Can make a xmlPushBegin ?
733 if ( ! _PushBegin )
735 // Current node exist ?
736 if (_CurrentNode==NULL)
738 // Get the first node
739 _CurrentNode = xmlDocGetRootElement (_Parser->myDoc);
741 // Has a root node ?
742 if (_CurrentNode)
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
751 char tmp[512];
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);
757 else
759 // Make an error message
760 char tmp[512];
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;
778 // Stop searching
779 break;
781 else
782 // Get next
783 _CurrentNode = _CurrentNode->next;
785 while (_CurrentNode);
787 // Not found ?
788 if (_CurrentNode == NULL)
790 // Make an error message
791 char tmp[512];
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);
797 // Get first child
798 _CurrentNode = _CurrentNode->children;
800 // Push begun
801 _PushBegin = true;
803 // Flush current string
804 flushContentString ();
806 else
808 nlerror ( "You must close your xmlPushBegin - xmlPushEnd before calling a new xmlPushBegin.");
809 return false;
812 else
814 nlerror ( "Output stream has not been initialized.");
815 return false;
818 // Ok
819 return true;
823 // ***************************************************************************
825 bool CIXml::xmlPushEndInternal ()
827 nlassert( isReading() );
829 if (_BinaryStream)
831 return true;
833 else
835 // Check _Parser
836 if ( _Parser )
838 // Can make a xmlPushEnd ?
839 if ( _PushBegin )
841 // Push begun
842 _PushBegin = false;
844 else
846 nlerror ( "You must call xmlPushBegin before calling xmlPushEnd.");
847 return false;
850 else
852 nlerror ( "Output stream has not been initialized.");
853 return false;
856 // Ok
857 return true;
861 // ***************************************************************************
863 bool CIXml::xmlPopInternal ()
865 nlassert( isReading() );
867 if (_BinaryStream)
869 return true;
871 else
873 // Check _Parser
874 if ( _Parser )
876 // Not in the push mode ?
877 if ( ! _PushBegin )
879 // Some content to write ?
880 flushContentString ();
882 // Get parents
883 _CurrentNode = _CurrentElement;
884 _CurrentElement = _CurrentElement->parent;
885 _CurrentNode = _CurrentNode->next;
887 else
889 nlerror ( "You must call xmlPop after xmlPushEnd.");
890 return false;
893 else
895 nlerror ( "Output stream has not been initialized.");
896 return false;
899 // Ok
900 return true;
904 // ***************************************************************************
906 bool CIXml::xmlSetAttribInternal (const std::string &attribName)
908 nlassert( isReading() );
910 if (_BinaryStream)
912 return true;
914 else
916 // Check _Parser
917 if ( _Parser )
919 // Can make a xmlPushEnd ?
920 if ( _PushBegin )
922 // Set attribute name
923 _AttribName = attribName;
925 // Attribute name is present
926 _AttribPresent = true;
928 else
930 nlerror ( "You must call xmlSetAttrib between xmlPushBegin and xmlPushEnd calls.");
931 return false;
934 else
936 nlerror ( "Output stream has not been initialized.");
937 return false;
940 // Ok
941 return true;
945 // ***************************************************************************
947 bool CIXml::xmlBreakLineInternal ()
949 // Ok
950 return true;
953 // ***************************************************************************
955 bool CIXml::xmlCommentInternal (const std::string &/* comment */)
957 // Ok
958 return true;
961 // ***************************************************************************
963 xmlNodePtr CIXml::getFirstChildNode (xmlNodePtr parent, const std::string &childName)
965 xmlNodePtr child = parent->children;
966 while (child)
968 if (childName == (const char*)child->name)
969 return child;
970 child = child->next;
972 return NULL;
975 // ***************************************************************************
977 xmlNodePtr CIXml::getNextChildNode (xmlNodePtr last, const std::string &childName)
979 last = last->next;
980 while (last)
982 if (childName == (const char*)last->name)
983 return last;
984 last = last->next;
986 return NULL;
989 // ***************************************************************************
991 xmlNodePtr CIXml::getFirstChildNode (xmlNodePtr parent, sint /* xmlElementType */ type)
993 xmlNodePtr child = parent->children;
994 while (child)
996 if (child->type == (xmlElementType)type)
997 return child;
998 child = child->next;
1000 return NULL;
1003 // ***************************************************************************
1005 xmlNodePtr CIXml::getNextChildNode (xmlNodePtr last, sint /* xmlElementType */ type)
1007 last = last->next;
1008 while (last)
1010 if (last->type == (xmlElementType)type)
1011 return last;
1012 last = last->next;
1014 return NULL;
1017 // ***************************************************************************
1019 uint CIXml::countChildren (xmlNodePtr node, const std::string &childName)
1021 uint count=0;
1022 xmlNodePtr child = getFirstChildNode (node, childName);
1023 while (child)
1025 count++;
1026 child = getNextChildNode (child, childName);
1028 return count;
1031 // ***************************************************************************
1033 uint CIXml::countChildren (xmlNodePtr node, sint /* xmlElementType */ type)
1035 uint count=0;
1036 xmlNodePtr child = getFirstChildNode (node, type);
1037 while (child)
1039 count++;
1040 child = getNextChildNode (child, type);
1042 return count;
1045 // ***************************************************************************
1047 xmlNodePtr CIXml::getRootNode () const
1049 if (_Parser)
1050 if (_Parser->myDoc)
1051 return xmlDocGetRootElement (_Parser->myDoc);
1052 return NULL;
1055 // ***************************************************************************
1057 bool CIXml::getPropertyString (std::string &result, xmlNodePtr node, const std::string &property)
1059 // Get the value
1060 const char *value = (const char*)xmlGetProp (node, (xmlChar*)property.c_str());
1061 if (value)
1063 // Active value
1064 result = value;
1066 // Delete the value
1067 xmlFree ((void*)value);
1069 // Found
1070 return true;
1073 return false;
1076 // ***************************************************************************
1078 int CIXml::getIntProperty(xmlNodePtr node, const std::string &property, int defaultValue)
1080 std::string s;
1082 bool b = getPropertyString(s, node, property);
1084 if (!b)
1085 return defaultValue;
1087 // remove leading and trailing spaces
1088 s = trim(s);
1090 sint val;
1092 if (!fromString(s, val) || (val == 0 && s != "0"))
1094 nlwarning("Bad integer value: %s",s.c_str());
1095 return defaultValue;
1098 return val;
1101 // ***************************************************************************
1103 double CIXml::getFloatProperty(xmlNodePtr node, const std::string &property, float defaultValue)
1105 std::string s;
1107 bool b = getPropertyString(s, node, property);
1109 if (!b)
1110 return defaultValue;
1112 // remove leading and trailing spaces
1113 s = trim(s);
1115 float val;
1117 if (!fromString(s, val))
1119 nlwarning("Bad float value: %s", s.c_str());
1120 return defaultValue;
1123 return val;
1126 // ***************************************************************************
1128 std::string CIXml::getStringProperty(xmlNodePtr node, const std::string &property, const std::string& defaultValue)
1130 std::string s;
1132 bool b = getPropertyString(s, node, property);
1134 if (!b)
1135 return defaultValue;
1137 return s;
1140 // ***************************************************************************
1142 bool CIXml::getContentString (std::string &result, xmlNodePtr node)
1144 const char *valueText = (const char*)xmlNodeGetContent (node);
1145 if (valueText)
1147 result = valueText;
1149 // Delete the value
1150 xmlFree ((void*)valueText);
1152 // Found
1153 return true;
1155 return false;
1158 // ***************************************************************************
1160 void CIXml::initLibXml()
1162 if (_LibXmlIntialized) return;
1164 _ErrorString.clear();
1166 // Set error handler
1167 xmlSetGenericErrorFunc (NULL, xmlGenericErrorFuncRead);
1169 LIBXML_TEST_VERSION
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;
1189 xmlCleanupParser();
1191 _LibXmlIntialized = false;
1194 std::string CIXml::getErrorString()
1196 return _ErrorString;
1199 } // NLMISC
1201 #endif // NL_DONT_USE_EXTERNAL_CODE