00001 /********************************************************************** 00002 * $Id: cpl_minixml_cpp-source.html,v 1.5 2002/12/21 19:13:12 warmerda Exp $ 00003 * 00004 * Project: CPL - Common Portability Library 00005 * Purpose: Implementation of MiniXML Parser and handling. 00006 * Author: Frank Warmerdam, warmerdam@pobox.com 00007 * 00008 ********************************************************************** 00009 * Copyright (c) 2001, Frank Warmerdam 00010 * 00011 * Permission is hereby granted, free of charge, to any person obtaining a 00012 * copy of this software and associated documentation files (the "Software"), 00013 * to deal in the Software without restriction, including without limitation 00014 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 00015 * and/or sell copies of the Software, and to permit persons to whom the 00016 * Software is furnished to do so, subject to the following conditions: 00017 * 00018 * The above copyright notice and this permission notice shall be included 00019 * in all copies or substantial portions of the Software. 00020 * 00021 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 00022 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 00023 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 00024 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 00025 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 00026 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 00027 * DEALINGS IN THE SOFTWARE. 00028 ********************************************************************** 00029 * 00030 * $Log: cpl_minixml_cpp-source.html,v $ 00030 * Revision 1.5 2002/12/21 19:13:12 warmerda 00030 * updated 00030 * 00031 * Revision 1.16 2002/11/16 20:42:40 warmerda 00032 * improved inline comments 00033 * 00034 * Revision 1.15 2002/11/16 20:38:34 warmerda 00035 * added support for literals like DOCTYPE 00036 * 00037 * Revision 1.14 2002/07/16 15:06:26 warmerda 00038 * ensure that attributes are serialized properly regardless of their order 00039 * 00040 * Revision 1.13 2002/07/09 20:25:25 warmerda 00041 * expand tabs 00042 * 00043 * Revision 1.12 2002/05/28 18:54:05 warmerda 00044 * added escaping/unescaping support 00045 * 00046 * Revision 1.11 2002/05/24 04:09:10 warmerda 00047 * added clone and SetXMLValue functions 00048 * 00049 * Revision 1.10 2002/04/01 16:08:21 warmerda 00050 * allow periods in tokens 00051 * 00052 * Revision 1.9 2002/03/07 22:19:20 warmerda 00053 * don't do operations within CPLAssert(), in UnreadChar() 00054 * 00055 * Revision 1.8 2002/03/05 14:26:57 warmerda 00056 * expanded tabs 00057 * 00058 * Revision 1.7 2002/01/23 20:45:05 warmerda 00059 * handle <?...?> and comment elements 00060 * 00061 * Revision 1.6 2002/01/22 18:54:48 warmerda 00062 * ensure text is property initialized when serializing 00063 * 00064 * Revision 1.5 2002/01/16 03:58:51 warmerda 00065 * support single quotes as well as double quotes 00066 * 00067 * Revision 1.4 2001/12/06 18:13:49 warmerda 00068 * added CPLAddXMLChild and CPLCreateElmentAndValue 00069 * 00070 * Revision 1.3 2001/11/16 21:20:16 warmerda 00071 * fixed typo 00072 * 00073 * Revision 1.2 2001/11/16 20:29:58 warmerda 00074 * fixed lost char in normal CString tokens 00075 * 00076 * Revision 1.1 2001/11/16 15:39:48 warmerda 00077 * New 00078 */ 00079 00080 #include <ctype.h> 00081 #include "cpl_minixml.h" 00082 #include "cpl_error.h" 00083 #include "cpl_conv.h" 00084 #include "cpl_string.h" 00085 00086 CPL_CVSID("$Id: cpl_minixml_cpp-source.html,v 1.5 2002/12/21 19:13:12 warmerda Exp $"); 00087 00088 typedef enum { 00089 TNone, 00090 TString, 00091 TOpen, 00092 TClose, 00093 TEqual, 00094 TToken, 00095 TSlashClose, 00096 TQuestionClose, 00097 TComment, 00098 TLiteral 00099 } TokenType; 00100 00101 typedef struct { 00102 const char *pszInput; 00103 int nInputOffset; 00104 int nInputLine; 00105 00106 int bInElement; 00107 TokenType eTokenType; 00108 char *pszToken; 00109 int nTokenMaxSize; 00110 int nTokenSize; 00111 00112 int nStackMaxSize; 00113 int nStackSize; 00114 CPLXMLNode **papsStack; 00115 00116 CPLXMLNode *psFirstNode; 00117 } ParseContext; 00118 00119 /************************************************************************/ 00120 /* ReadChar() */ 00121 /************************************************************************/ 00122 00123 static char ReadChar( ParseContext *psContext ) 00124 00125 { 00126 char chReturn; 00127 00128 chReturn = psContext->pszInput[psContext->nInputOffset++]; 00129 00130 if( chReturn == '\0' ) 00131 psContext->nInputOffset--; 00132 else if( chReturn == 10 ) 00133 psContext->nInputLine++; 00134 00135 return chReturn; 00136 } 00137 00138 /************************************************************************/ 00139 /* UnreadChar() */ 00140 /************************************************************************/ 00141 00142 static void UnreadChar( ParseContext *psContext, char chToUnread ) 00143 00144 { 00145 if( chToUnread == '\0' ) 00146 { 00147 /* do nothing */ 00148 } 00149 else 00150 { 00151 CPLAssert( chToUnread 00152 == psContext->pszInput[psContext->nInputOffset-1] ); 00153 00154 psContext->nInputOffset--; 00155 00156 if( chToUnread == 10 ) 00157 psContext->nInputLine--; 00158 } 00159 } 00160 00161 /************************************************************************/ 00162 /* AddToToken() */ 00163 /************************************************************************/ 00164 00165 static void AddToToken( ParseContext *psContext, char chNewChar ) 00166 00167 { 00168 if( psContext->pszToken == NULL ) 00169 { 00170 psContext->nTokenMaxSize = 10; 00171 psContext->pszToken = (char *) CPLMalloc(psContext->nTokenMaxSize); 00172 } 00173 else if( psContext->nTokenSize >= psContext->nTokenMaxSize - 2 ) 00174 { 00175 psContext->nTokenMaxSize *= 2; 00176 psContext->pszToken = (char *) 00177 CPLRealloc(psContext->pszToken,psContext->nTokenMaxSize); 00178 } 00179 00180 psContext->pszToken[psContext->nTokenSize++] = chNewChar; 00181 psContext->pszToken[psContext->nTokenSize] = '\0'; 00182 } 00183 00184 /************************************************************************/ 00185 /* ReadToken() */ 00186 /************************************************************************/ 00187 00188 static TokenType ReadToken( ParseContext *psContext ) 00189 00190 { 00191 char chNext; 00192 00193 psContext->nTokenSize = 0; 00194 psContext->pszToken[0] = '\0'; 00195 00196 chNext = ReadChar( psContext ); 00197 while( isspace(chNext) ) 00198 chNext = ReadChar( psContext ); 00199 00200 /* -------------------------------------------------------------------- */ 00201 /* Handle comments. */ 00202 /* -------------------------------------------------------------------- */ 00203 if( chNext == '<' 00204 && EQUALN(psContext->pszInput+psContext->nInputOffset,"!--",3) ) 00205 { 00206 psContext->eTokenType = TComment; 00207 00208 // Skip "!--" characters 00209 ReadChar(psContext); 00210 ReadChar(psContext); 00211 ReadChar(psContext); 00212 00213 while( !EQUALN(psContext->pszInput+psContext->nInputOffset,"-->",3) 00214 && (chNext = ReadChar(psContext)) != '\0' ) 00215 AddToToken( psContext, chNext ); 00216 00217 // Skip "-->" characters 00218 ReadChar(psContext); 00219 ReadChar(psContext); 00220 ReadChar(psContext); 00221 } 00222 /* -------------------------------------------------------------------- */ 00223 /* Handle DOCTYPE or other literals. */ 00224 /* -------------------------------------------------------------------- */ 00225 else if( chNext == '<' 00226 && EQUALN(psContext->pszInput+psContext->nInputOffset,"!DOCTYPE",8) ) 00227 { 00228 int bInQuotes = FALSE; 00229 psContext->eTokenType = TLiteral; 00230 00231 AddToToken( psContext, '<' ); 00232 do { 00233 chNext = ReadChar(psContext); 00234 if( chNext == '\0' ) 00235 { 00236 CPLError( CE_Failure, CPLE_AppDefined, 00237 "Parse error in DOCTYPE on or before line %d, reached end of file without '>'.", 00238 psContext->nInputLine ); 00239 00240 break; 00241 } 00242 00243 if( chNext == '\"' ) 00244 bInQuotes = !bInQuotes; 00245 00246 if( chNext == '>' && !bInQuotes ) 00247 { 00248 AddToToken( psContext, '>' ); 00249 break; 00250 } 00251 00252 AddToToken( psContext, chNext ); 00253 } while( TRUE ); 00254 } 00255 /* -------------------------------------------------------------------- */ 00256 /* Simple single tokens of interest. */ 00257 /* -------------------------------------------------------------------- */ 00258 else if( chNext == '<' && !psContext->bInElement ) 00259 { 00260 psContext->eTokenType = TOpen; 00261 psContext->bInElement = TRUE; 00262 } 00263 else if( chNext == '>' && psContext->bInElement ) 00264 { 00265 psContext->eTokenType = TClose; 00266 psContext->bInElement = FALSE; 00267 } 00268 else if( chNext == '=' && psContext->bInElement ) 00269 { 00270 psContext->eTokenType = TEqual; 00271 } 00272 else if( chNext == '\0' ) 00273 { 00274 psContext->eTokenType = TNone; 00275 } 00276 /* -------------------------------------------------------------------- */ 00277 /* Handle the /> token terminator. */ 00278 /* -------------------------------------------------------------------- */ 00279 else if( chNext == '/' && psContext->bInElement 00280 && psContext->pszInput[psContext->nInputOffset] == '>' ) 00281 { 00282 chNext = ReadChar( psContext ); 00283 if( chNext != '>' ) 00284 { 00285 psContext->eTokenType = TNone; 00286 CPLError( CE_Failure, CPLE_AppDefined, 00287 "Parse error at '/' on line %d, expected '>'.", 00288 psContext->nInputLine ); 00289 } 00290 else 00291 { 00292 psContext->eTokenType = TSlashClose; 00293 psContext->bInElement = FALSE; 00294 } 00295 } 00296 /* -------------------------------------------------------------------- */ 00297 /* Handle the ?> token terminator. */ 00298 /* -------------------------------------------------------------------- */ 00299 else if( chNext == '?' && psContext->bInElement 00300 && psContext->pszInput[psContext->nInputOffset] == '>' ) 00301 { 00302 chNext = ReadChar( psContext ); 00303 if( chNext != '>' ) 00304 { 00305 psContext->eTokenType = TNone; 00306 CPLError( CE_Failure, CPLE_AppDefined, 00307 "Parse error at '?' on line %d, expected '>'.", 00308 psContext->nInputLine ); 00309 } 00310 else 00311 { 00312 psContext->eTokenType = TQuestionClose; 00313 psContext->bInElement = FALSE; 00314 } 00315 } 00316 00317 /* -------------------------------------------------------------------- */ 00318 /* Collect a quoted string. */ 00319 /* -------------------------------------------------------------------- */ 00320 else if( psContext->bInElement && chNext == '"' ) 00321 { 00322 psContext->eTokenType = TString; 00323 00324 while( (chNext = ReadChar(psContext)) != '"' 00325 && chNext != '\0' ) 00326 AddToToken( psContext, chNext ); 00327 00328 if( chNext != '"' ) 00329 { 00330 psContext->eTokenType = TNone; 00331 CPLError( CE_Failure, CPLE_AppDefined, 00332 "Parse error on line %d, reached EOF before closing quote.", 00333 psContext->nInputLine ); 00334 } 00335 00336 /* Do we need to unescape it? */ 00337 if( strchr(psContext->pszToken,'&') != NULL ) 00338 { 00339 int nLength; 00340 char *pszUnescaped = CPLUnescapeString( psContext->pszToken, 00341 &nLength, CPLES_XML ); 00342 strcpy( psContext->pszToken, pszUnescaped ); 00343 CPLFree( pszUnescaped ); 00344 psContext->nTokenSize = strlen(psContext->pszToken ); 00345 } 00346 } 00347 00348 else if( psContext->bInElement && chNext == '\'' ) 00349 { 00350 psContext->eTokenType = TString; 00351 00352 while( (chNext = ReadChar(psContext)) != '\'' 00353 && chNext != '\0' ) 00354 AddToToken( psContext, chNext ); 00355 00356 if( chNext != '\'' ) 00357 { 00358 psContext->eTokenType = TNone; 00359 CPLError( CE_Failure, CPLE_AppDefined, 00360 "Parse error on line %d, reached EOF before closing quote.", 00361 psContext->nInputLine ); 00362 } 00363 } 00364 00365 /* -------------------------------------------------------------------- */ 00366 /* Collect an unquoted string, terminated by a open angle */ 00367 /* bracket. */ 00368 /* -------------------------------------------------------------------- */ 00369 else if( !psContext->bInElement ) 00370 { 00371 psContext->eTokenType = TString; 00372 00373 AddToToken( psContext, chNext ); 00374 while( (chNext = ReadChar(psContext)) != '<' 00375 && chNext != '\0' ) 00376 AddToToken( psContext, chNext ); 00377 UnreadChar( psContext, chNext ); 00378 00379 /* Do we need to unescape it? */ 00380 if( strchr(psContext->pszToken,'&') != NULL ) 00381 { 00382 int nLength; 00383 char *pszUnescaped = CPLUnescapeString( psContext->pszToken, 00384 &nLength, CPLES_XML ); 00385 strcpy( psContext->pszToken, pszUnescaped ); 00386 CPLFree( pszUnescaped ); 00387 psContext->nTokenSize = strlen(psContext->pszToken ); 00388 } 00389 } 00390 00391 /* -------------------------------------------------------------------- */ 00392 /* Collect a regular token terminated by white space, or */ 00393 /* special character(s) like an equal sign. */ 00394 /* -------------------------------------------------------------------- */ 00395 else 00396 { 00397 psContext->eTokenType = TToken; 00398 00399 /* add the first character to the token regardless of what it is */ 00400 AddToToken( psContext, chNext ); 00401 00402 for( chNext = ReadChar(psContext); 00403 (chNext >= 'A' && chNext <= 'Z') 00404 || (chNext >= 'a' && chNext <= 'z') 00405 || chNext == '_' 00406 || chNext == '.' 00407 || chNext == ':' 00408 || (chNext >= '0' && chNext <= '9'); 00409 chNext = ReadChar(psContext) ) 00410 { 00411 AddToToken( psContext, chNext ); 00412 } 00413 00414 UnreadChar(psContext, chNext); 00415 } 00416 00417 return psContext->eTokenType; 00418 } 00419 00420 /************************************************************************/ 00421 /* PushNode() */ 00422 /************************************************************************/ 00423 00424 static void PushNode( ParseContext *psContext, CPLXMLNode *psNode ) 00425 00426 { 00427 if( psContext->nStackMaxSize <= psContext->nStackSize ) 00428 { 00429 psContext->nStackMaxSize += 10; 00430 psContext->papsStack = (CPLXMLNode **) 00431 CPLRealloc(psContext->papsStack, 00432 sizeof(CPLXMLNode*) * psContext->nStackMaxSize); 00433 } 00434 00435 psContext->papsStack[psContext->nStackSize++] = psNode; 00436 } 00437 00438 /************************************************************************/ 00439 /* AttachNode() */ 00440 /* */ 00441 /* Attach the passed node as a child of the current node. */ 00442 /* Special handling exists for adding siblings to psFirst if */ 00443 /* there is nothing on the stack. */ 00444 /************************************************************************/ 00445 00446 static void AttachNode( ParseContext *psContext, CPLXMLNode *psNode ) 00447 00448 { 00449 if( psContext->psFirstNode == NULL ) 00450 psContext->psFirstNode = psNode; 00451 else if( psContext->nStackSize == 0 ) 00452 { 00453 CPLXMLNode *psSibling; 00454 00455 psSibling = psContext->psFirstNode; 00456 while( psSibling->psNext != NULL ) 00457 psSibling = psSibling->psNext; 00458 psSibling->psNext = psNode; 00459 } 00460 else if( psContext->papsStack[psContext->nStackSize-1]->psChild == NULL ) 00461 { 00462 psContext->papsStack[psContext->nStackSize-1]->psChild = psNode; 00463 } 00464 else 00465 { 00466 CPLXMLNode *psSibling; 00467 00468 psSibling = psContext->papsStack[psContext->nStackSize-1]->psChild; 00469 while( psSibling->psNext != NULL ) 00470 psSibling = psSibling->psNext; 00471 psSibling->psNext = psNode; 00472 } 00473 } 00474 00475 /************************************************************************/ 00476 /* CPLParseXMLString() */ 00477 /************************************************************************/ 00478 00479 CPLXMLNode *CPLParseXMLString( const char *pszString ) 00480 00481 { 00482 ParseContext sContext; 00483 00484 CPLErrorReset(); 00485 00486 /* -------------------------------------------------------------------- */ 00487 /* Initialize parse context. */ 00488 /* -------------------------------------------------------------------- */ 00489 sContext.pszInput = pszString; 00490 sContext.nInputOffset = 0; 00491 sContext.nInputLine = 0; 00492 sContext.bInElement = FALSE; 00493 sContext.pszToken = NULL; 00494 sContext.nTokenMaxSize = 0; 00495 sContext.nTokenSize = 0; 00496 sContext.eTokenType = TNone; 00497 sContext.nStackMaxSize = 0; 00498 sContext.nStackSize = 0; 00499 sContext.papsStack = NULL; 00500 sContext.psFirstNode = NULL; 00501 00502 /* ensure token is initialized */ 00503 AddToToken( &sContext, ' ' ); 00504 00505 /* ==================================================================== */ 00506 /* Loop reading tokens. */ 00507 /* ==================================================================== */ 00508 while( ReadToken( &sContext ) != TNone ) 00509 { 00510 /* -------------------------------------------------------------------- */ 00511 /* Create a new element. */ 00512 /* -------------------------------------------------------------------- */ 00513 if( sContext.eTokenType == TOpen ) 00514 { 00515 CPLXMLNode *psElement; 00516 00517 if( ReadToken(&sContext) != TToken ) 00518 { 00519 CPLError( CE_Failure, CPLE_AppDefined, 00520 "Line %d: Didn't find element token after open angle bracket.", 00521 sContext.nInputLine ); 00522 break; 00523 } 00524 00525 if( sContext.pszToken[0] != '/' ) 00526 { 00527 psElement = CPLCreateXMLNode( NULL, CXT_Element, 00528 sContext.pszToken ); 00529 AttachNode( &sContext, psElement ); 00530 PushNode( &sContext, psElement ); 00531 } 00532 else 00533 { 00534 if( sContext.nStackSize == 0 00535 || !EQUAL(sContext.pszToken+1, 00536 sContext.papsStack[sContext.nStackSize-1]->pszValue) ) 00537 { 00538 CPLError( CE_Failure, CPLE_AppDefined, 00539 "Line %d: <%s> doesn't have matching <%s>.", 00540 sContext.nInputLine, 00541 sContext.pszToken, sContext.pszToken+1 ); 00542 break; 00543 } 00544 else 00545 { 00546 if( ReadToken(&sContext) != TClose ) 00547 { 00548 CPLError( CE_Failure, CPLE_AppDefined, 00549 "Line %d: Missing close angle bracket after <%s.", 00550 sContext.nInputLine, 00551 sContext.pszToken ); 00552 break; 00553 } 00554 00555 /* pop element off stack */ 00556 sContext.nStackSize--; 00557 } 00558 } 00559 } 00560 00561 /* -------------------------------------------------------------------- */ 00562 /* Add an attribute to a token. */ 00563 /* -------------------------------------------------------------------- */ 00564 else if( sContext.eTokenType == TToken ) 00565 { 00566 CPLXMLNode *psAttr; 00567 00568 psAttr = CPLCreateXMLNode(NULL, CXT_Attribute, sContext.pszToken); 00569 AttachNode( &sContext, psAttr ); 00570 00571 if( ReadToken(&sContext) != TEqual ) 00572 { 00573 CPLError( CE_Failure, CPLE_AppDefined, 00574 "Line %d: Didn't find expected '=' for value of attribute '%s'.", 00575 sContext.nInputLine, psAttr->pszValue ); 00576 break; 00577 } 00578 00579 if( ReadToken(&sContext) != TString 00580 && sContext.eTokenType != TToken ) 00581 { 00582 CPLError( CE_Failure, CPLE_AppDefined, 00583 "Line %d: Didn't find expected attribute value.", 00584 sContext.nInputLine ); 00585 break; 00586 } 00587 00588 CPLCreateXMLNode( psAttr, CXT_Text, sContext.pszToken ); 00589 } 00590 00591 /* -------------------------------------------------------------------- */ 00592 /* Close the start section of an element. */ 00593 /* -------------------------------------------------------------------- */ 00594 else if( sContext.eTokenType == TClose ) 00595 { 00596 if( sContext.nStackSize == 0 ) 00597 { 00598 CPLError( CE_Failure, CPLE_AppDefined, 00599 "Line %d: Found unbalanced '>'.", 00600 sContext.nInputLine ); 00601 break; 00602 } 00603 } 00604 00605 /* -------------------------------------------------------------------- */ 00606 /* Close the start section of an element, and pop it */ 00607 /* immediately. */ 00608 /* -------------------------------------------------------------------- */ 00609 else if( sContext.eTokenType == TSlashClose ) 00610 { 00611 if( sContext.nStackSize == 0 ) 00612 { 00613 CPLError( CE_Failure, CPLE_AppDefined, 00614 "Line %d: Found unbalanced '/>'.", 00615 sContext.nInputLine ); 00616 break; 00617 } 00618 00619 sContext.nStackSize--; 00620 } 00621 00622 /* -------------------------------------------------------------------- */ 00623 /* Close the start section of a <?...?> element, and pop it */ 00624 /* immediately. */ 00625 /* -------------------------------------------------------------------- */ 00626 else if( sContext.eTokenType == TQuestionClose ) 00627 { 00628 if( sContext.nStackSize == 0 ) 00629 { 00630 CPLError( CE_Failure, CPLE_AppDefined, 00631 "Line %d: Found unbalanced '?>'.", 00632 sContext.nInputLine ); 00633 break; 00634 } 00635 else if( sContext.papsStack[sContext.nStackSize-1]->pszValue[0] != '?' ) 00636 { 00637 CPLError( CE_Failure, CPLE_AppDefined, 00638 "Line %d: Found '?>' without matching '<?'.", 00639 sContext.nInputLine ); 00640 break; 00641 } 00642 00643 sContext.nStackSize--; 00644 } 00645 00646 /* -------------------------------------------------------------------- */ 00647 /* Handle comments. They are returned as a whole token with the */ 00648 /* prefix and postfix omitted. No processing of white space */ 00649 /* will be done. */ 00650 /* -------------------------------------------------------------------- */ 00651 else if( sContext.eTokenType == TComment ) 00652 { 00653 CPLXMLNode *psValue; 00654 00655 psValue = CPLCreateXMLNode(NULL, CXT_Comment, sContext.pszToken); 00656 AttachNode( &sContext, psValue ); 00657 } 00658 00659 /* -------------------------------------------------------------------- */ 00660 /* Handle literals. They are returned without processing. */ 00661 /* -------------------------------------------------------------------- */ 00662 else if( sContext.eTokenType == TLiteral ) 00663 { 00664 CPLXMLNode *psValue; 00665 00666 psValue = CPLCreateXMLNode(NULL, CXT_Literal, sContext.pszToken); 00667 AttachNode( &sContext, psValue ); 00668 } 00669 00670 /* -------------------------------------------------------------------- */ 00671 /* Add a text value node as a child of the current element. */ 00672 /* -------------------------------------------------------------------- */ 00673 else if( sContext.eTokenType == TString && !sContext.bInElement ) 00674 { 00675 CPLXMLNode *psValue; 00676 00677 psValue = CPLCreateXMLNode(NULL, CXT_Text, sContext.pszToken); 00678 AttachNode( &sContext, psValue ); 00679 } 00680 /* -------------------------------------------------------------------- */ 00681 /* Anything else is an error. */ 00682 /* -------------------------------------------------------------------- */ 00683 else 00684 { 00685 CPLError( CE_Failure, CPLE_AppDefined, 00686 "Parse error at line %d, unexpected token:%s\n", 00687 sContext.nInputLine, sContext.pszToken ); 00688 break; 00689 } 00690 } 00691 00692 /* -------------------------------------------------------------------- */ 00693 /* Did we pop all the way out of our stack? */ 00694 /* -------------------------------------------------------------------- */ 00695 if( CPLGetLastErrorType() == CE_None && sContext.nStackSize != 0 ) 00696 { 00697 CPLError( CE_Failure, CPLE_AppDefined, 00698 "Parse error at EOF, not all elements have been closed,\n" 00699 "starting with %s\n", 00700 sContext.papsStack[sContext.nStackSize-1]->pszValue ); 00701 } 00702 00703 /* -------------------------------------------------------------------- */ 00704 /* Cleanup */ 00705 /* -------------------------------------------------------------------- */ 00706 CPLFree( sContext.pszToken ); 00707 if( sContext.papsStack != NULL ) 00708 CPLFree( sContext.papsStack ); 00709 00710 if( CPLGetLastErrorType() != CE_None ) 00711 { 00712 CPLDestroyXMLNode( sContext.psFirstNode ); 00713 sContext.psFirstNode = NULL; 00714 } 00715 00716 return sContext.psFirstNode; 00717 } 00718 00719 /************************************************************************/ 00720 /* _GrowBuffer() */ 00721 /************************************************************************/ 00722 00723 static void _GrowBuffer( unsigned int nNeeded, 00724 char **ppszText, unsigned int *pnMaxLength ) 00725 00726 { 00727 if( nNeeded+1 >= *pnMaxLength ) 00728 { 00729 *pnMaxLength = MAX(*pnMaxLength * 2,nNeeded+1); 00730 *ppszText = (char *) CPLRealloc(*ppszText, *pnMaxLength); 00731 } 00732 } 00733 00734 /************************************************************************/ 00735 /* CPLSerializeXMLNode() */ 00736 /************************************************************************/ 00737 00738 static void 00739 CPLSerializeXMLNode( CPLXMLNode *psNode, int nIndent, 00740 char **ppszText, unsigned int *pnLength, 00741 unsigned int *pnMaxLength ) 00742 00743 { 00744 if( psNode == NULL ) 00745 return; 00746 00747 /* -------------------------------------------------------------------- */ 00748 /* Ensure the buffer is plenty large to hold this additional */ 00749 /* string. */ 00750 /* -------------------------------------------------------------------- */ 00751 *pnLength += strlen(*ppszText + *pnLength); 00752 if(strlen(psNode->pszValue) + *pnLength + 40 + nIndent > *pnMaxLength) 00753 _GrowBuffer( strlen(psNode->pszValue) + *pnLength + 40 + nIndent, 00754 ppszText, pnMaxLength ); 00755 00756 /* -------------------------------------------------------------------- */ 00757 /* Text is just directly emitted. */ 00758 /* -------------------------------------------------------------------- */ 00759 if( psNode->eType == CXT_Text ) 00760 { 00761 char *pszEscaped = CPLEscapeString( psNode->pszValue, -1, CPLES_XML ); 00762 00763 CPLAssert( psNode->psChild == NULL ); 00764 00765 strcat( *ppszText + *pnLength, pszEscaped ); 00766 00767 CPLFree( pszEscaped ); 00768 } 00769 00770 /* -------------------------------------------------------------------- */ 00771 /* Attributes require a little formatting. */ 00772 /* -------------------------------------------------------------------- */ 00773 else if( psNode->eType == CXT_Attribute ) 00774 { 00775 CPLAssert( psNode->psChild != NULL 00776 && psNode->psChild->eType == CXT_Text ); 00777 00778 sprintf( *ppszText + *pnLength, " %s=\"", psNode->pszValue ); 00779 CPLSerializeXMLNode( psNode->psChild, 0, ppszText, 00780 pnLength, pnMaxLength ); 00781 strcat( *ppszText + *pnLength, "\"" ); 00782 } 00783 00784 /* -------------------------------------------------------------------- */ 00785 /* Handle comment output. */ 00786 /* -------------------------------------------------------------------- */ 00787 else if( psNode->eType == CXT_Comment ) 00788 { 00789 int i; 00790 00791 CPLAssert( psNode->psChild == NULL ); 00792 00793 for( i = 0; i < nIndent; i++ ) 00794 (*ppszText)[(*pnLength)++] = ' '; 00795 00796 sprintf( *ppszText + *pnLength, "<!--%s-->\n", 00797 psNode->pszValue ); 00798 } 00799 00800 /* -------------------------------------------------------------------- */ 00801 /* Handle literal output (like <!DOCTYPE...>) */ 00802 /* -------------------------------------------------------------------- */ 00803 else if( psNode->eType == CXT_Literal ) 00804 { 00805 int i; 00806 00807 CPLAssert( psNode->psChild == NULL ); 00808 00809 for( i = 0; i < nIndent; i++ ) 00810 (*ppszText)[(*pnLength)++] = ' '; 00811 00812 strcpy( *ppszText + *pnLength, psNode->pszValue ); 00813 strcat( *ppszText + *pnLength, "\n" ); 00814 } 00815 00816 /* -------------------------------------------------------------------- */ 00817 /* Elements actually have to deal with general children, and */ 00818 /* various formatting issues. */ 00819 /* -------------------------------------------------------------------- */ 00820 else if( psNode->eType == CXT_Element ) 00821 { 00822 int bHasNonAttributeChildren = FALSE; 00823 CPLXMLNode *psChild; 00824 char *pszIndent; 00825 00826 pszIndent = (char *) CPLCalloc(nIndent + 1,1); 00827 memset(pszIndent, ' ', nIndent ); 00828 00829 strcat( *ppszText + *pnLength, pszIndent ); 00830 *pnLength += nIndent; 00831 sprintf( *ppszText + *pnLength, "<%s", psNode->pszValue ); 00832 00833 /* Serialize *all* the attribute children, regardless of order */ 00834 for( psChild = psNode->psChild; 00835 psChild != NULL; 00836 psChild = psChild->psNext ) 00837 { 00838 if( psChild->eType == CXT_Attribute ) 00839 CPLSerializeXMLNode( psChild, 0, ppszText, pnLength, 00840 pnMaxLength ); 00841 else 00842 bHasNonAttributeChildren = TRUE; 00843 } 00844 00845 if( !bHasNonAttributeChildren ) 00846 { 00847 if( psNode->pszValue[0] == '?' ) 00848 strcat( *ppszText + *pnLength, "?>\n" ); 00849 else 00850 strcat( *ppszText + *pnLength, "/>\n" ); 00851 } 00852 else 00853 { 00854 int bJustText = TRUE; 00855 00856 strcat( *ppszText + *pnLength, ">" ); 00857 00858 for( psChild = psNode->psChild; 00859 psChild != NULL; 00860 psChild = psChild->psNext ) 00861 { 00862 if( psChild->eType == CXT_Attribute ) 00863 continue; 00864 00865 if( psChild->eType != CXT_Text && bJustText ) 00866 { 00867 bJustText = FALSE; 00868 strcat( *ppszText + *pnLength, "\n" ); 00869 } 00870 00871 CPLSerializeXMLNode( psChild, nIndent + 2, ppszText, pnLength, 00872 pnMaxLength ); 00873 } 00874 00875 if( strlen(psNode->pszValue)+*pnLength+40+nIndent > *pnMaxLength) 00876 _GrowBuffer( strlen(psNode->pszValue)+*pnLength+40+nIndent, 00877 ppszText, pnMaxLength ); 00878 00879 if( !bJustText ) 00880 strcat( *ppszText + *pnLength, pszIndent ); 00881 00882 *pnLength += strlen(*ppszText + *pnLength); 00883 sprintf( *ppszText + *pnLength, "</%s>\n", psNode->pszValue ); 00884 } 00885 00886 CPLFree( pszIndent ); 00887 } 00888 } 00889 00890 /************************************************************************/ 00891 /* CPLSerializeXMLTree() */ 00892 /************************************************************************/ 00893 00894 char *CPLSerializeXMLTree( CPLXMLNode *psNode ) 00895 00896 { 00897 unsigned int nMaxLength = 10000, nLength = 0; 00898 char *pszText = NULL; 00899 CPLXMLNode *psThis; 00900 00901 pszText = (char *) CPLMalloc(nMaxLength); 00902 pszText[0] = '\0'; 00903 00904 for( psThis = psNode; psThis != NULL; psThis = psThis->psNext ) 00905 CPLSerializeXMLNode( psThis, 0, &pszText, &nLength, &nMaxLength ); 00906 00907 return pszText; 00908 } 00909 00910 /************************************************************************/ 00911 /* CPLCreateXMLNode() */ 00912 /************************************************************************/ 00913 00914 CPLXMLNode *CPLCreateXMLNode( CPLXMLNode *poParent, CPLXMLNodeType eType, 00915 const char *pszText ) 00916 00917 { 00918 CPLXMLNode *psNode; 00919 00920 /* -------------------------------------------------------------------- */ 00921 /* Create new node. */ 00922 /* -------------------------------------------------------------------- */ 00923 psNode = (CPLXMLNode *) CPLCalloc(sizeof(CPLXMLNode),1); 00924 00925 psNode->eType = eType; 00926 psNode->pszValue = CPLStrdup( pszText ); 00927 00928 /* -------------------------------------------------------------------- */ 00929 /* Attach to parent, if provided. */ 00930 /* -------------------------------------------------------------------- */ 00931 if( poParent != NULL ) 00932 { 00933 if( poParent->psChild == NULL ) 00934 poParent->psChild = psNode; 00935 else 00936 { 00937 CPLXMLNode *psLink = poParent->psChild; 00938 00939 while( psLink->psNext != NULL ) 00940 psLink = psLink->psNext; 00941 00942 psLink->psNext = psNode; 00943 } 00944 } 00945 00946 return psNode; 00947 } 00948 00949 /************************************************************************/ 00950 /* CPLDestroyXMLNode() */ 00951 /************************************************************************/ 00952 00953 void CPLDestroyXMLNode( CPLXMLNode *psNode ) 00954 00955 { 00956 if( psNode->psChild != NULL ) 00957 CPLDestroyXMLNode( psNode->psChild ); 00958 00959 if( psNode->psNext != NULL ) 00960 CPLDestroyXMLNode( psNode->psNext ); 00961 00962 CPLFree( psNode->pszValue ); 00963 CPLFree( psNode ); 00964 } 00965 00966 /************************************************************************/ 00967 /* CPLGetXMLNode() */ 00968 /************************************************************************/ 00969 00970 CPLXMLNode *CPLGetXMLNode( CPLXMLNode *poRoot, const char *pszPath ) 00971 00972 { 00973 char **papszTokens; 00974 int iToken = 0; 00975 00976 papszTokens = CSLTokenizeStringComplex( pszPath, ".", FALSE, FALSE ); 00977 00978 while( papszTokens[iToken] != NULL && poRoot != NULL ) 00979 { 00980 CPLXMLNode *psChild; 00981 00982 for( psChild = poRoot->psChild; psChild != NULL; 00983 psChild = psChild->psNext ) 00984 { 00985 if( psChild->eType != CXT_Text 00986 && EQUAL(papszTokens[iToken],psChild->pszValue) ) 00987 break; 00988 } 00989 00990 if( psChild == NULL ) 00991 { 00992 poRoot = NULL; 00993 break; 00994 } 00995 00996 poRoot = psChild; 00997 iToken++; 00998 } 00999 01000 CSLDestroy( papszTokens ); 01001 return poRoot; 01002 } 01003 01004 /************************************************************************/ 01005 /* CPLGetXMLValue() */ 01006 /************************************************************************/ 01007 01008 const char *CPLGetXMLValue( CPLXMLNode *poRoot, const char *pszPath, 01009 const char *pszDefault ) 01010 01011 { 01012 CPLXMLNode *psTarget; 01013 01014 psTarget = CPLGetXMLNode( poRoot, pszPath ); 01015 if( psTarget == NULL ) 01016 return pszDefault; 01017 01018 if( psTarget->eType == CXT_Attribute ) 01019 { 01020 CPLAssert( psTarget->psChild != NULL 01021 && psTarget->psChild->eType == CXT_Text ); 01022 01023 return psTarget->psChild->pszValue; 01024 } 01025 01026 if( psTarget->eType == CXT_Element 01027 && psTarget->psChild != NULL 01028 && psTarget->psChild->eType == CXT_Text 01029 && psTarget->psChild->psNext == NULL ) 01030 { 01031 return psTarget->psChild->pszValue; 01032 } 01033 01034 return pszDefault; 01035 } 01036 01037 /************************************************************************/ 01038 /* CPLAddXMLChild() */ 01039 /* */ 01040 /* Add a node as a child of another. */ 01041 /************************************************************************/ 01042 01043 void CPLAddXMLChild( CPLXMLNode *psParent, CPLXMLNode *psChild ) 01044 01045 { 01046 CPLXMLNode *psSib; 01047 01048 CPLAssert( psChild->psNext == NULL ); 01049 psChild->psNext = NULL; 01050 01051 if( psParent->psChild == NULL ) 01052 { 01053 psParent->psChild = psChild; 01054 return; 01055 } 01056 01057 for( psSib = psParent->psChild; 01058 psSib->psNext != NULL; 01059 psSib = psSib->psNext ) {} 01060 01061 psSib->psNext = psChild; 01062 } 01063 01064 /************************************************************************/ 01065 /* CPLCreateXMLElementAndValue() */ 01066 /************************************************************************/ 01067 01068 CPLXMLNode *CPLCreateXMLElementAndValue( CPLXMLNode *psParent, 01069 const char *pszName, 01070 const char *pszValue ) 01071 01072 { 01073 return CPLCreateXMLNode( 01074 CPLCreateXMLNode( psParent, CXT_Element, pszName ), 01075 CXT_Text, pszValue ); 01076 } 01077 01078 /************************************************************************/ 01079 /* CPLCloneXMLTree() */ 01080 /* */ 01081 /* Clone an XML Tree. We use recursion to handle children, but */ 01082 /* we do siblings by looping. This means we can handle very */ 01083 /* long lists of elements, but great depth may cause stack */ 01084 /* overflow problems on some systems. */ 01085 /************************************************************************/ 01086 01087 CPLXMLNode *CPLCloneXMLTree( CPLXMLNode *psTree ) 01088 01089 { 01090 CPLXMLNode *psPrevious = NULL; 01091 CPLXMLNode *psReturn = NULL; 01092 01093 while( psTree != NULL ) 01094 { 01095 CPLXMLNode *psCopy; 01096 01097 psCopy = CPLCreateXMLNode( NULL, psTree->eType, psTree->pszValue ); 01098 if( psReturn == NULL ) 01099 psReturn = psCopy; 01100 if( psPrevious != NULL ) 01101 psPrevious->psNext = psCopy; 01102 01103 if( psTree->psChild != NULL ) 01104 psCopy->psChild = CPLCloneXMLTree( psTree->psChild ); 01105 01106 psPrevious = psCopy; 01107 psTree = psTree->psNext; 01108 } 01109 01110 return psReturn; 01111 } 01112 01113 /************************************************************************/ 01114 /* CPLSetXMLValue() */ 01115 /* */ 01116 /* Set the text value of an XML element to the suggested */ 01117 /* value. Intermediate element nodes are created if */ 01118 /* an existing component is missing. */ 01119 /************************************************************************/ 01120 01121 int CPLSetXMLValue( CPLXMLNode *psRoot, const char *pszPath, 01122 const char *pszValue ) 01123 01124 { 01125 char **papszTokens; 01126 int iToken = 0; 01127 01128 papszTokens = CSLTokenizeStringComplex( pszPath, ".", FALSE, FALSE ); 01129 01130 while( papszTokens[iToken] != NULL && psRoot != NULL ) 01131 { 01132 CPLXMLNode *psChild; 01133 int bIsAttribute = FALSE; 01134 const char *pszName = papszTokens[iToken]; 01135 01136 if( pszName[0] == '#' ) 01137 { 01138 bIsAttribute = TRUE; 01139 pszName++; 01140 } 01141 01142 if( psRoot->eType != CXT_Element ) 01143 return FALSE; 01144 01145 for( psChild = psRoot->psChild; psChild != NULL; 01146 psChild = psChild->psNext ) 01147 { 01148 if( psChild->eType != CXT_Text 01149 && EQUAL(pszName,psChild->pszValue) ) 01150 break; 01151 } 01152 01153 if( psChild == NULL ) 01154 { 01155 if( bIsAttribute ) 01156 psChild = CPLCreateXMLNode( psRoot, CXT_Attribute, pszName ); 01157 else 01158 psChild = CPLCreateXMLNode( psRoot, CXT_Element, pszName ); 01159 } 01160 01161 psRoot = psChild; 01162 iToken++; 01163 } 01164 01165 CSLDestroy( papszTokens ); 01166 01167 /* -------------------------------------------------------------------- */ 01168 /* Now set a value node under this node. */ 01169 /* -------------------------------------------------------------------- */ 01170 if( psRoot->psChild == NULL ) 01171 CPLCreateXMLNode( psRoot, CXT_Text, pszValue ); 01172 else if( psRoot->psChild->eType != CXT_Text ) 01173 return FALSE; 01174 else 01175 { 01176 CPLFree( psRoot->psChild->pszValue ); 01177 psRoot->psChild->pszValue = CPLStrdup( pszValue ); 01178 } 01179 01180 return TRUE; 01181 } 01182