00001
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024 #include "stdmisc.h"
00025
00026 #include "nel/misc/i_xml.h"
00027 #include "nel/misc/sstring.h"
00028
00029 #ifndef NL_DONT_USE_EXTERNAL_CODE
00030
00031
00032 #include <libxml/xmlerror.h>
00033
00034 using namespace std;
00035
00036 #define NLMISC_READ_BUFFER_SIZE 1024
00037
00038 namespace NLMISC
00039 {
00040
00041
00042
00043 const char SEPARATOR = ' ';
00044
00045
00046
00047 #define readnumber(dest,thetype,digits,convfunc) \
00048 string number_as_string; \
00049 serialSeparatedBufferIn( number_as_string ); \
00050 dest = (thetype)convfunc( number_as_string.c_str() );
00051
00052
00053
00054 inline void CIXml::flushContentString ()
00055 {
00056
00057 _ContentString.erase ();
00058
00059
00060 _ContentStringIndex = 0;
00061 }
00062
00063
00064
00065 CIXml::CIXml () : IStream (true )
00066 {
00067
00068 _Parser = NULL;
00069 _CurrentElement = NULL;
00070 _CurrentNode = NULL;
00071 _PushBegin = false;
00072 _AttribPresent = false;
00073 _ErrorString = "";
00074 _TryBinaryMode = false;
00075 _BinaryStream = NULL;
00076 }
00077
00078
00079
00080 CIXml::CIXml (bool tryBinaryMode) : IStream (true )
00081 {
00082
00083 _Parser = NULL;
00084 _CurrentElement = NULL;
00085 _CurrentNode = NULL;
00086 _PushBegin = false;
00087 _AttribPresent = false;
00088 _ErrorString = "";
00089 _TryBinaryMode = tryBinaryMode;
00090 _BinaryStream = NULL;
00091 }
00092
00093
00094
00095 CIXml::~CIXml ()
00096 {
00097
00098 release ();
00099 }
00100
00101
00102
00103 void CIXml::release ()
00104 {
00105
00106 if (_Parser)
00107 {
00108
00109 xmlClearParserCtxt (_Parser);
00110 xmlFreeParserCtxt (_Parser);
00111 xmlCleanupParser ();
00112
00113 _Parser = NULL;
00114 }
00115
00116
00117 _Parser = NULL;
00118 _CurrentElement = NULL;
00119 _CurrentNode = NULL;
00120 _PushBegin = false;
00121 _AttribPresent = false;
00122 _ErrorString = "";
00123
00124 resetPtrTable();
00125 }
00126
00127
00128
00129 void xmlGenericErrorFuncRead (void *ctx, const char *msg, ...)
00130 {
00131
00132 string str;
00133 NLMISC_CONVERT_VARGS (str, msg, NLMISC::MaxCStringSize);
00134 ((CIXml*)ctx)->_ErrorString += str;
00135 }
00136
00137
00138
00139 bool CIXml::init (IStream &stream)
00140 {
00141
00142 release ();
00143
00144 xmlInitParser();
00145
00146
00147 _BinaryStream = NULL;
00148
00149
00150 if (stream.isReading())
00151 {
00152
00153 setXMLMode (true);
00154
00155
00156 sint32 pos = stream.getPos ();
00157
00158
00159 bool seekGood = stream.seek (0, end);
00160 nlassert (seekGood);
00161
00162
00163 sint32 length = stream.getPos () - pos;
00164
00165
00166 stream.seek (pos, begin);
00167
00168
00169 char buffer[NLMISC_READ_BUFFER_SIZE];
00170
00171
00172 stream.serialBuffer ((uint8*)buffer, 4);
00173 length -= 4;
00174
00175
00176 if (_TryBinaryMode)
00177 {
00178 string header;
00179 header.resize(4);
00180 header[0] = buffer[0];
00181 header[1] = buffer[1];
00182 header[2] = buffer[2];
00183 header[3] = buffer[3];
00184 toLower(header);
00185
00186
00187 if (header != "<?xm")
00188 {
00189
00190 _BinaryStream = &stream;
00191
00192
00193 stream.seek (pos, begin);
00194
00195
00196 return true;
00197 }
00198 }
00199
00200
00201 _ErrorString = "";
00202 xmlSetGenericErrorFunc (this, xmlGenericErrorFuncRead);
00203
00204
00205 xmlLineNumbersDefault(1);
00206
00207
00208 _Parser = xmlCreatePushParserCtxt(NULL, NULL, buffer, 4, NULL);
00209 nlassert (_Parser);
00210
00211
00212 while (length>=NLMISC_READ_BUFFER_SIZE)
00213 {
00214
00215 stream.serialBuffer ((uint8*)buffer, NLMISC_READ_BUFFER_SIZE);
00216
00217
00218 int res = xmlParseChunk(_Parser, buffer, NLMISC_READ_BUFFER_SIZE, 0);
00219
00220
00221 if (res)
00222 {
00223 throw EXmlParsingError (_ErrorString);
00224 }
00225
00226
00227 length -= NLMISC_READ_BUFFER_SIZE;
00228 }
00229
00230
00231 stream.serialBuffer ((uint8*)buffer, length);
00232
00233
00234 int res = xmlParseChunk(_Parser, buffer, length, 1);
00235
00236
00237 if (res)
00238 {
00239 throw EXmlParsingError (_ErrorString);
00240 }
00241
00242
00243 return true;
00244 }
00245 else
00246 {
00247 nlwarning ("XML: The stream is not an input stream.");
00248 }
00249
00250
00251 return false;
00252 }
00253
00254
00255
00256 void CIXml::serialSeparatedBufferIn ( string &value, bool checkSeparator )
00257 {
00258 nlassert( isReading() );
00259
00260
00261 if ( _Parser )
00262 {
00263
00264 if (_CurrentElement)
00265 {
00266
00267 if (_PushBegin)
00268 {
00269
00270 if (_AttribPresent)
00271 {
00272
00273 nlassert (_CurrentElement);
00274
00275
00276 xmlChar *attributeValue = xmlGetProp (_CurrentElement, (const xmlChar*)_AttribName.c_str());
00277
00278
00279 if (attributeValue)
00280 {
00281
00282 value = (const char*)attributeValue;
00283
00284
00285 xmlFree ((void*)attributeValue);
00286 }
00287 else
00288 {
00289
00290 nlassert (_CurrentElement->name);
00291
00292
00293 char tmp[512];
00294 smprintf (tmp, 512, "NeL XML Syntax error in block line %d\nAttribute \"%s\" is missing in node \"%s\"",
00295 (int)_CurrentElement->line, _AttribName.c_str(), _CurrentElement->name);
00296 throw EXmlParsingError (tmp);
00297 }
00298
00299
00300 _AttribPresent = false;
00301 }
00302 else
00303 {
00304
00305
00306
00307
00308 nlerror ( "Error, the stream don't use XML streaming properly" );
00309 }
00310 }
00311 else
00312 {
00313
00314 uint length = _ContentString.length();
00315
00316
00317 if (length==0)
00318 {
00319
00320 do
00321 {
00322
00323 if (_CurrentNode == NULL)
00324 {
00325 value = "";
00326 _ContentStringIndex = 0;
00327 _ContentString.erase ();
00328 return;
00329 }
00330
00331
00332 if (_CurrentNode->type == XML_TEXT_NODE)
00333 {
00334
00335 break;
00336 }
00337 else
00338
00339 _CurrentNode = _CurrentNode->next;
00340 }
00341 while (_CurrentNode);
00342
00343
00344 if (_CurrentNode != NULL)
00345 {
00346
00347 const char *content = (const char*)xmlNodeGetContent (_CurrentNode);
00348 if (content)
00349 {
00350 _ContentString = content;
00351
00352
00353 xmlFree ((void*)content);
00354 }
00355 else
00356 _ContentString.erase ();
00357
00358
00359 _ContentStringIndex = 0;
00360
00361
00362 length = _ContentString.length();
00363 }
00364 }
00365
00366
00367 if (_ContentStringIndex < length)
00368 {
00369
00370 uint first = _ContentStringIndex;
00371
00372
00373 if (checkSeparator)
00374 {
00375
00376 while (_ContentStringIndex < length)
00377 {
00378
00379 if ( (_ContentString[_ContentStringIndex]==SEPARATOR) || (_ContentString[_ContentStringIndex]=='\n') )
00380 {
00381 _ContentStringIndex++;
00382 break;
00383 }
00384
00385
00386 _ContentStringIndex++;
00387 }
00388 }
00389 else
00390 {
00391
00392 _ContentStringIndex = length;
00393 }
00394
00395
00396 value.assign (_ContentString, first, _ContentStringIndex-first);
00397 }
00398 else
00399 {
00400
00401 nlassert (_CurrentElement->name);
00402
00403
00404 char tmp[512];
00405 smprintf (tmp, 512, "NeL XML Syntax error in block line %d \nMissing keywords in text child node in the node %s",
00406 (int)_CurrentElement->line, _CurrentElement->name);
00407 throw EXmlParsingError (tmp);
00408 }
00409 }
00410 }
00411 else
00412 {
00413
00414
00415 nlerror ( "Error, the stream don't use XML streaming properly" );
00416 }
00417 }
00418 else
00419 {
00420 nlerror ( "Output stream has not been initialized" );
00421 }
00422 }
00423
00424
00425
00426 void CIXml::serial(uint8 &b)
00427 {
00428 if (_BinaryStream)
00429 {
00430 _BinaryStream->serial(b);
00431 }
00432 else
00433 {
00434
00435 readnumber( b, uint8, 3, atoi );
00436 }
00437 }
00438
00439
00440
00441 void CIXml::serial(sint8 &b)
00442 {
00443 if (_BinaryStream)
00444 {
00445 _BinaryStream->serial(b);
00446 }
00447 else
00448 {
00449 readnumber( b, sint8, 4, atoi );
00450 }
00451 }
00452
00453
00454
00455 void CIXml::serial(uint16 &b)
00456 {
00457 if (_BinaryStream)
00458 {
00459 _BinaryStream->serial(b);
00460 }
00461 else
00462 {
00463 readnumber( b, uint16, 5, atoi );
00464 }
00465 }
00466
00467
00468
00469 void CIXml::serial(sint16 &b)
00470 {
00471 if (_BinaryStream)
00472 {
00473 _BinaryStream->serial(b);
00474 }
00475 else
00476 {
00477 readnumber( b, sint16, 6, atoi );
00478 }
00479 }
00480
00481
00482
00483 inline uint32 atoui( const char *ident)
00484 {
00485 return (uint32) strtoul (ident, NULL, 10);
00486 }
00487
00488 void CIXml::serial(uint32 &b)
00489 {
00490 if (_BinaryStream)
00491 {
00492 _BinaryStream->serial(b);
00493 }
00494 else
00495 {
00496 readnumber( b, uint32, 10, atoui );
00497 }
00498 }
00499
00500
00501
00502 void CIXml::serial(sint32 &b)
00503 {
00504 if (_BinaryStream)
00505 {
00506 _BinaryStream->serial(b);
00507 }
00508 else
00509 {
00510 readnumber( b, sint32, 11, atoi );
00511 }
00512 }
00513
00514
00515
00516 void CIXml::serial(uint64 &b)
00517 {
00518 if (_BinaryStream)
00519 {
00520 _BinaryStream->serial(b);
00521 }
00522 else
00523 {
00524 readnumber( b, uint64, 20, atoiInt64 );
00525 }
00526 }
00527
00528
00529
00530 void CIXml::serial(sint64 &b)
00531 {
00532 if (_BinaryStream)
00533 {
00534 _BinaryStream->serial(b);
00535 }
00536 else
00537 {
00538 readnumber( b, sint64, 20, atoiInt64 );
00539 }
00540 }
00541
00542
00543
00544 void CIXml::serial(float &b)
00545 {
00546 if (_BinaryStream)
00547 {
00548 _BinaryStream->serial(b);
00549 }
00550 else
00551 {
00552 readnumber( b, float, 128, atof );
00553 }
00554 }
00555
00556
00557
00558 void CIXml::serial(double &b)
00559 {
00560 if (_BinaryStream)
00561 {
00562 _BinaryStream->serial(b);
00563 }
00564 else
00565 {
00566 readnumber( b, double, 128, atof );
00567 }
00568 }
00569
00570
00571
00572 void CIXml::serial(bool &b)
00573 {
00574 if (_BinaryStream)
00575 {
00576 _BinaryStream->serial(b);
00577 }
00578 else
00579 {
00580 serialBit(b);
00581 }
00582 }
00583
00584
00585
00586 void CIXml::serialBit(bool &bit)
00587 {
00588 if (_BinaryStream)
00589 {
00590 _BinaryStream->serialBit(bit);
00591 }
00592 else
00593 {
00594 uint8 u;
00595 serial (u);
00596 bit = (u!=0);
00597 }
00598 }
00599
00600
00601
00602 #ifndef NL_OS_CYGWIN
00603 void CIXml::serial(char &b)
00604 {
00605 if (_BinaryStream)
00606 {
00607 _BinaryStream->serial(b);
00608 }
00609 else
00610 {
00611 string toto;
00612 serialSeparatedBufferIn ( toto );
00613
00614
00615 if (toto.length()!=1)
00616 {
00617
00618 if (_Parser)
00619 {
00620
00621 nlassert (_CurrentElement->name);
00622
00623
00624 char tmp[512];
00625 smprintf (tmp, 512, "NeL XML Syntax error in block line %d \nValue is not a char in the node named %s",
00626 (int)_CurrentElement->line, _CurrentElement->name);
00627 throw EXmlParsingError (tmp);
00628 }
00629 else
00630 {
00631 nlerror ( "Output stream has not been initialized" );
00632 }
00633 }
00634 else
00635 b=toto[0];
00636 }
00637 }
00638 #endif // NL_OS_CYGWIN
00639
00640
00641
00642 void CIXml::serial(std::string &b)
00643 {
00644 nlassert( isReading() );
00645
00646 if (_BinaryStream)
00647 {
00648 _BinaryStream->serial(b);
00649 }
00650 else
00651 {
00652
00653 if (_PushBegin)
00654 {
00655
00656 serialSeparatedBufferIn ( b, false );
00657 }
00658 else
00659 {
00660
00661 xmlPush ("S");
00662
00663
00664 serialSeparatedBufferIn ( b, false );
00665
00666
00667 xmlPop ();
00668 }
00669 }
00670 }
00671
00672
00673
00674 void CIXml::serial(ucstring &b)
00675 {
00676 nlassert( isReading() );
00677
00678 if (_BinaryStream)
00679 {
00680 _BinaryStream->serial(b);
00681 }
00682 else
00683 {
00684
00685 string tmp;
00686
00687
00688 serial (tmp);
00689
00690
00691 b.fromUtf8(tmp);
00692 }
00693 }
00694
00695
00696
00697 void CIXml::serialBuffer(uint8 *buf, uint len)
00698 {
00699 if (_BinaryStream)
00700 {
00701 _BinaryStream->serialBuffer(buf, len);
00702 }
00703 else
00704 {
00705
00706 xmlPush ("BUFFER");
00707
00708
00709 for (uint i=0; i<len; i++)
00710 {
00711 xmlPush ("ELM");
00712
00713 serial (buf[i]);
00714
00715 xmlPop ();
00716 }
00717
00718
00719 xmlPop ();
00720 }
00721 }
00722
00723
00724
00725 bool CIXml::xmlPushBeginInternal (const char *nodeName)
00726 {
00727 nlassert( isReading() );
00728
00729 if (_BinaryStream)
00730 {
00731 return true;
00732 }
00733 else
00734 {
00735
00736 if ( _Parser )
00737 {
00738
00739 if ( ! _PushBegin )
00740 {
00741
00742 if (_CurrentNode==NULL)
00743 {
00744
00745 _CurrentNode = xmlDocGetRootElement (_Parser->myDoc);
00746
00747
00748 if (_CurrentNode)
00749 {
00750
00751 nlassert (_CurrentNode->name);
00752
00753
00754 if ( (_CurrentNode->type != XML_ELEMENT_NODE) || ( (const char*)_CurrentNode->name != string(nodeName)) )
00755 {
00756
00757 char tmp[512];
00758 smprintf (tmp, 512, "NeL XML Syntax error : root node has the wrong name : \"%s\" should have \"%s\"",
00759 _CurrentNode->name, nodeName);
00760 throw EXmlParsingError (tmp);
00761 }
00762 }
00763 else
00764 {
00765
00766 char tmp[512];
00767 smprintf (tmp, 512, "NeL XML Syntax error : no root node found.");
00768 throw EXmlParsingError (tmp);
00769 }
00770 }
00771
00772
00773 do
00774 {
00775
00776 nlassert (_CurrentNode->name);
00777
00778
00779 if ( (_CurrentNode->type == XML_ELEMENT_NODE) && ( (const char*)_CurrentNode->name == string(nodeName)) )
00780 {
00781
00782 _CurrentElement = _CurrentNode;
00783
00784
00785 break;
00786 }
00787 else
00788
00789 _CurrentNode = _CurrentNode->next;
00790 }
00791 while (_CurrentNode);
00792
00793
00794 if (_CurrentNode == NULL)
00795 {
00796
00797 char tmp[512];
00798 smprintf (tmp, 512, "NeL XML Syntax error in block line %d \nCan't open the node named %s in node named %s",
00799 (int)_CurrentElement->line, nodeName, _CurrentElement->name);
00800 throw EXmlParsingError (tmp);
00801 }
00802
00803
00804 _CurrentNode = _CurrentNode->children;
00805
00806
00807 _PushBegin = true;
00808
00809
00810 flushContentString ();
00811 }
00812 else
00813 {
00814 nlerror ( "You must close your xmlPushBegin - xmlPushEnd before calling a new xmlPushBegin.");
00815 return false;
00816 }
00817 }
00818 else
00819 {
00820 nlerror ( "Output stream has not been initialized.");
00821 return false;
00822 }
00823
00824
00825 return true;
00826 }
00827 }
00828
00829
00830
00831 bool CIXml::xmlPushEndInternal ()
00832 {
00833 nlassert( isReading() );
00834
00835 if (_BinaryStream)
00836 {
00837 return true;
00838 }
00839 else
00840 {
00841
00842 if ( _Parser )
00843 {
00844
00845 if ( _PushBegin )
00846 {
00847
00848 _PushBegin = false;
00849 }
00850 else
00851 {
00852 nlerror ( "You must call xmlPushBegin before calling xmlPushEnd.");
00853 return false;
00854 }
00855 }
00856 else
00857 {
00858 nlerror ( "Output stream has not been initialized.");
00859 return false;
00860 }
00861
00862
00863 return true;
00864 }
00865 }
00866
00867
00868
00869 bool CIXml::xmlPopInternal ()
00870 {
00871 nlassert( isReading() );
00872
00873 if (_BinaryStream)
00874 {
00875 return true;
00876 }
00877 else
00878 {
00879
00880 if ( _Parser )
00881 {
00882
00883 if ( ! _PushBegin )
00884 {
00885
00886 flushContentString ();
00887
00888
00889 _CurrentNode = _CurrentElement;
00890 _CurrentElement = _CurrentElement->parent;
00891 _CurrentNode = _CurrentNode->next;
00892 }
00893 else
00894 {
00895 nlerror ( "You must call xmlPop after xmlPushEnd.");
00896 return false;
00897 }
00898 }
00899 else
00900 {
00901 nlerror ( "Output stream has not been initialized.");
00902 return false;
00903 }
00904
00905
00906 return true;
00907 }
00908 }
00909
00910
00911
00912 bool CIXml::xmlSetAttribInternal (const char *attribName)
00913 {
00914 nlassert( isReading() );
00915
00916 if (_BinaryStream)
00917 {
00918 return true;
00919 }
00920 else
00921 {
00922
00923 if ( _Parser )
00924 {
00925
00926 if ( _PushBegin )
00927 {
00928
00929 _AttribName = attribName;
00930
00931
00932 _AttribPresent = true;
00933 }
00934 else
00935 {
00936 nlerror ( "You must call xmlSetAttrib between xmlPushBegin and xmlPushEnd calls.");
00937 return false;
00938 }
00939 }
00940 else
00941 {
00942 nlerror ( "Output stream has not been initialized.");
00943 return false;
00944 }
00945
00946
00947 return true;
00948 }
00949 }
00950
00951
00952
00953 bool CIXml::xmlBreakLineInternal ()
00954 {
00955
00956 return true;
00957 }
00958
00959
00960
00961 bool CIXml::xmlCommentInternal (const char * )
00962 {
00963
00964 return true;
00965 }
00966
00967
00968
00969 xmlNodePtr CIXml::getFirstChildNode (xmlNodePtr parent, const char *childName)
00970 {
00971 xmlNodePtr child = parent->children;
00972 while (child)
00973 {
00974 if (strcmp ((const char*)child->name, childName) == 0)
00975 return child;
00976 child = child->next;
00977 }
00978 return NULL;
00979 }
00980
00981
00982
00983 xmlNodePtr CIXml::getNextChildNode (xmlNodePtr last, const char *childName)
00984 {
00985 last = last->next;
00986 while (last)
00987 {
00988 if (strcmp ((const char*)last->name, childName) == 0)
00989 return last;
00990 last = last->next;
00991 }
00992 return NULL;
00993 }
00994
00995
00996
00997 xmlNodePtr CIXml::getFirstChildNode (xmlNodePtr parent, xmlElementType type)
00998 {
00999 xmlNodePtr child = parent->children;
01000 while (child)
01001 {
01002 if (child->type == type)
01003 return child;
01004 child = child->next;
01005 }
01006 return NULL;
01007 }
01008
01009
01010
01011 xmlNodePtr CIXml::getNextChildNode (xmlNodePtr last, xmlElementType type)
01012 {
01013 last = last->next;
01014 while (last)
01015 {
01016 if (last->type == type)
01017 return last;
01018 last = last->next;
01019 }
01020 return NULL;
01021 }
01022
01023
01024
01025 uint CIXml::countChildren (xmlNodePtr node, const char *childName)
01026 {
01027 uint count=0;
01028 xmlNodePtr child = getFirstChildNode (node, childName);
01029 while (child)
01030 {
01031 count++;
01032 child = getNextChildNode (child, childName);
01033 }
01034 return count;
01035 }
01036
01037
01038
01039 uint CIXml::countChildren (xmlNodePtr node, xmlElementType type)
01040 {
01041 uint count=0;
01042 xmlNodePtr child = getFirstChildNode (node, type);
01043 while (child)
01044 {
01045 count++;
01046 child = getNextChildNode (child, type);
01047 }
01048 return count;
01049 }
01050
01051
01052
01053 xmlNodePtr CIXml::getRootNode () const
01054 {
01055 if (_Parser)
01056 if (_Parser->myDoc)
01057 return xmlDocGetRootElement (_Parser->myDoc);
01058 return NULL;
01059 }
01060
01061
01062
01063 bool CIXml::getPropertyString (std::string &result, xmlNodePtr node, const char *property)
01064 {
01065
01066 const char *value = (const char*)xmlGetProp (node, (xmlChar*)property);
01067 if (value)
01068 {
01069
01070 result = value;
01071
01072
01073 xmlFree ((void*)value);
01074
01075
01076 return true;
01077 }
01078 return false;
01079 }
01080
01081
01082
01083 int CIXml::getIntProperty(xmlNodePtr node, const char *property, int defaultValue)
01084 {
01085 CSString s;
01086 bool b;
01087
01088 b=getPropertyString(s,node,property);
01089 if (b==false)
01090 return defaultValue;
01091
01092 s=s.strip();
01093 int val=s.atoi();
01094 if (val==0 && s!="0")
01095 {
01096 nlwarning("bad integer value: %s",s.c_str());
01097 return defaultValue;
01098 }
01099
01100 return val;
01101 }
01102
01103
01104
01105 double CIXml::getFloatProperty(xmlNodePtr node, const char *property, float defaultValue)
01106 {
01107 CSString s;
01108 bool b;
01109
01110 b=getPropertyString(s,node,property);
01111 if (b==false)
01112 return defaultValue;
01113
01114 return s.strip().atof();
01115 }
01116
01117
01118
01119 std::string CIXml::getStringProperty(xmlNodePtr node, const char *property, const std::string& defaultValue)
01120 {
01121 std::string s;
01122 bool b;
01123
01124 b=getPropertyString(s,node,property);
01125 if (b==false)
01126 return defaultValue;
01127
01128 return s;
01129 }
01130
01131
01132
01133 bool CIXml::getContentString (std::string &result, xmlNodePtr node)
01134 {
01135 const char *valueText = (const char*)xmlNodeGetContent (node);
01136 if (valueText)
01137 {
01138 result = valueText;
01139
01140
01141 xmlFree ((void*)valueText);
01142
01143
01144 return true;
01145 }
01146 return false;
01147 }
01148
01149
01150
01151 }
01152
01153 #endif // NL_DONT_USE_EXTERNAL_CODE