gwenhywfar 4.0.3
|
00001 /*************************************************************************** 00002 $RCSfile$ 00003 ------------------- 00004 cvs : $Id: xsd.c 656 2004-12-22 17:02:05Z aquamaniac $ 00005 begin : Sat Jun 28 2003 00006 copyright : (C) 2003 by Martin Preuss 00007 email : martin@libchipcard.de 00008 00009 *************************************************************************** 00010 * * 00011 * This library is free software; you can redistribute it and/or * 00012 * modify it under the terms of the GNU Lesser General Public * 00013 * License as published by the Free Software Foundation; either * 00014 * version 2.1 of the License, or (at your option) any later version. * 00015 * * 00016 * This library is distributed in the hope that it will be useful, * 00017 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 00018 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * 00019 * Lesser General Public License for more details. * 00020 * * 00021 * You should have received a copy of the GNU Lesser General Public * 00022 * License along with this library; if not, write to the Free Software * 00023 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * 00024 * MA 02111-1307 USA * 00025 * * 00026 ***************************************************************************/ 00027 00028 #ifdef HAVE_CONFIG_H 00029 # include <config.h> 00030 #endif 00031 00032 00033 #include "xmlctx_p.h" 00034 #include "gwenhywfar/debug.h" 00035 #include "gwenhywfar/misc.h" 00036 #include "gwenhywfar/text.h" 00037 #include "gwenhywfar/path.h" 00038 #include "i18n_l.h" 00039 00040 #include <stdlib.h> 00041 #include <assert.h> 00042 #include <string.h> 00043 #include <ctype.h> 00044 00045 00046 00047 GWEN_INHERIT_FUNCTIONS(GWEN_XML_CONTEXT) 00048 00049 00050 00051 00052 GWEN_XML_CONTEXT *GWEN_XmlCtx_new(uint32_t flags) { 00053 GWEN_XML_CONTEXT *ctx; 00054 00055 GWEN_NEW_OBJECT(GWEN_XML_CONTEXT, ctx); 00056 ctx->_refCount=1; 00057 GWEN_INHERIT_INIT(GWEN_XML_CONTEXT, ctx); 00058 00059 ctx->flags=flags; 00060 00061 return ctx; 00062 } 00063 00064 00065 00066 void GWEN_XmlCtx_free(GWEN_XML_CONTEXT *ctx) { 00067 if (ctx) { 00068 assert(ctx->_refCount); 00069 if (ctx->_refCount==1) { 00070 GWEN_INHERIT_FINI(GWEN_XML_CONTEXT, ctx); 00071 ctx->_refCount=0; 00072 GWEN_FREE_OBJECT(ctx); 00073 } 00074 else 00075 ctx->_refCount--; 00076 } 00077 } 00078 00079 00080 00081 void GWEN_XmlCtx_Attach(GWEN_XML_CONTEXT *ctx) { 00082 assert(ctx); 00083 assert(ctx->_refCount); 00084 ctx->_refCount++; 00085 } 00086 00087 00088 00089 uint32_t GWEN_XmlCtx_GetFlags(const GWEN_XML_CONTEXT *ctx) { 00090 assert(ctx); 00091 return ctx->flags; 00092 } 00093 00094 00095 00096 void GWEN_XmlCtx_SetFlags(GWEN_XML_CONTEXT *ctx, uint32_t f) { 00097 assert(ctx); 00098 ctx->flags=f; 00099 } 00100 00101 00102 00103 int GWEN_XmlCtx_GetDepth(const GWEN_XML_CONTEXT *ctx) { 00104 assert(ctx); 00105 return ctx->depth; 00106 } 00107 00108 00109 00110 void GWEN_XmlCtx_SetDepth(GWEN_XML_CONTEXT *ctx, int i) { 00111 assert(ctx); 00112 ctx->depth=i; 00113 } 00114 00115 00116 00117 void GWEN_XmlCtx_IncDepth(GWEN_XML_CONTEXT *ctx) { 00118 assert(ctx); 00119 ctx->depth++; 00120 } 00121 00122 00123 00124 int GWEN_XmlCtx_DecDepth(GWEN_XML_CONTEXT *ctx) { 00125 assert(ctx); 00126 if (ctx->depth<1) 00127 return -1; 00128 ctx->depth--; 00129 return 0; 00130 } 00131 00132 00133 00134 uint32_t GWEN_XmlCtx_GetFinishedElement(const GWEN_XML_CONTEXT *ctx) { 00135 assert(ctx); 00136 return ctx->finishedElements; 00137 } 00138 00139 00140 00141 void GWEN_XmlCtx_IncFinishedElement(GWEN_XML_CONTEXT *ctx) { 00142 assert(ctx); 00143 ctx->finishedElements++; 00144 } 00145 00146 00147 00148 void GWEN_XmlCtx_ResetFinishedElement(GWEN_XML_CONTEXT *ctx) { 00149 assert(ctx); 00150 ctx->finishedElements=0; 00151 } 00152 00153 00154 00155 void GWEN_XmlCtx_SetCurrentNode(GWEN_XML_CONTEXT *ctx, GWEN_XMLNODE *n) { 00156 assert(ctx); 00157 ctx->currentNode=n; 00158 } 00159 00160 00161 00162 GWEN_XMLNODE *GWEN_XmlCtx_GetCurrentNode(const GWEN_XML_CONTEXT *ctx) { 00163 assert(ctx); 00164 return ctx->currentNode; 00165 } 00166 00167 00168 00169 void GWEN_XmlCtx_SetCurrentHeader(GWEN_XML_CONTEXT *ctx, GWEN_XMLNODE *n) { 00170 assert(ctx); 00171 ctx->currentHeader=n; 00172 } 00173 00174 00175 00176 GWEN_XMLNODE *GWEN_XmlCtx_GetCurrentHeader(const GWEN_XML_CONTEXT *ctx) { 00177 assert(ctx); 00178 return ctx->currentHeader; 00179 } 00180 00181 00182 00183 GWEN_XMLCTX_STARTTAG_FN GWEN_XmlCtx_SetStartTagFn(GWEN_XML_CONTEXT *ctx, 00184 GWEN_XMLCTX_STARTTAG_FN f){ 00185 GWEN_XMLCTX_STARTTAG_FN of; 00186 00187 assert(ctx); 00188 of=ctx->startTagFn; 00189 ctx->startTagFn=f; 00190 return of; 00191 } 00192 00193 00194 00195 GWEN_XMLCTX_ENDTAG_FN GWEN_XmlCtx_SetEndTagFn(GWEN_XML_CONTEXT *ctx, 00196 GWEN_XMLCTX_ENDTAG_FN f) { 00197 GWEN_XMLCTX_ENDTAG_FN of; 00198 00199 assert(ctx); 00200 of=ctx->endTagFn; 00201 ctx->endTagFn=f; 00202 return of; 00203 } 00204 00205 00206 00207 GWEN_XMLCTX_ADDDATA_FN GWEN_XmlCtx_SetAddDataFn(GWEN_XML_CONTEXT *ctx, 00208 GWEN_XMLCTX_ADDDATA_FN f) { 00209 GWEN_XMLCTX_ADDDATA_FN of; 00210 00211 assert(ctx); 00212 of=ctx->addDataFn; 00213 ctx->addDataFn=f; 00214 return of; 00215 } 00216 00217 00218 00219 GWEN_XMLCTX_ADDATTR_FN GWEN_XmlCtx_SetAddAttrFn(GWEN_XML_CONTEXT *ctx, 00220 GWEN_XMLCTX_ADDATTR_FN f) { 00221 GWEN_XMLCTX_ADDATTR_FN of; 00222 00223 assert(ctx); 00224 of=ctx->addAttrFn; 00225 ctx->addAttrFn=f; 00226 return of; 00227 } 00228 00229 00230 00231 GWEN_XMLCTX_ADDCOMMENT_FN 00232 GWEN_XmlCtx_SetAddCommentFn(GWEN_XML_CONTEXT *ctx, 00233 GWEN_XMLCTX_ADDCOMMENT_FN f) { 00234 GWEN_XMLCTX_ADDCOMMENT_FN of; 00235 00236 assert(ctx); 00237 of=ctx->addCommentFn; 00238 ctx->addCommentFn=f; 00239 return of; 00240 } 00241 00242 00243 00244 00245 int GWEN_XmlCtx_StartTag(GWEN_XML_CONTEXT *ctx, const char *tagName) { 00246 assert(ctx); 00247 00248 if (ctx->startTagFn) 00249 return ctx->startTagFn(ctx, tagName); 00250 else { 00251 DBG_INFO(GWEN_LOGDOMAIN, "Starting tag: [%s]", tagName); 00252 return 0; 00253 } 00254 } 00255 00256 00257 00258 int GWEN_XmlCtx_EndTag(GWEN_XML_CONTEXT *ctx, int closing) { 00259 assert(ctx); 00260 00261 if (ctx->endTagFn) 00262 return ctx->endTagFn(ctx, closing); 00263 else { 00264 DBG_INFO(GWEN_LOGDOMAIN, "Ending tag (%s)", closing?"closing":"not closing"); 00265 return 0; 00266 } 00267 } 00268 00269 00270 00271 int GWEN_XmlCtx_AddData(GWEN_XML_CONTEXT *ctx, const char *data) { 00272 assert(ctx); 00273 00274 if (ctx->addDataFn) 00275 return ctx->addDataFn(ctx, data); 00276 else { 00277 DBG_INFO(GWEN_LOGDOMAIN, "Adding data: [%s]", data); 00278 return 0; 00279 } 00280 } 00281 00282 00283 00284 int GWEN_XmlCtx_AddComment(GWEN_XML_CONTEXT *ctx, const char *data) { 00285 assert(ctx); 00286 00287 if (ctx->addCommentFn) 00288 return ctx->addCommentFn(ctx, data); 00289 else { 00290 DBG_INFO(GWEN_LOGDOMAIN, "Adding comment: [%s]", data); 00291 return 0; 00292 } 00293 } 00294 00295 00296 00297 int GWEN_XmlCtx_AddAttr(GWEN_XML_CONTEXT *ctx, 00298 const char *attrName, 00299 const char *attrData) { 00300 assert(ctx); 00301 00302 if (ctx->addAttrFn) 00303 return ctx->addAttrFn(ctx, attrName, attrData); 00304 else { 00305 DBG_INFO(GWEN_LOGDOMAIN, "Adding attribute: [%s]=[%s]", 00306 attrName, attrData); 00307 return 0; 00308 } 00309 } 00310 00311 00312 00313 00314 00315 00316 00317 00318 GWEN_XML_CONTEXT *GWEN_XmlCtxStore_new(GWEN_XMLNODE *n, uint32_t flags) { 00319 GWEN_XML_CONTEXT *ctx; 00320 00321 ctx=GWEN_XmlCtx_new(flags); 00322 assert(ctx); 00323 00324 GWEN_XmlCtx_SetCurrentNode(ctx, n); 00325 00326 GWEN_XmlCtx_SetStartTagFn(ctx, GWEN_XmlCtxStore_StartTag); 00327 GWEN_XmlCtx_SetEndTagFn(ctx, GWEN_XmlCtxStore_EndTag); 00328 GWEN_XmlCtx_SetAddDataFn(ctx, GWEN_XmlCtxStore_AddData); 00329 GWEN_XmlCtx_SetAddCommentFn(ctx, GWEN_XmlCtxStore_AddComment); 00330 GWEN_XmlCtx_SetAddAttrFn(ctx, GWEN_XmlCtxStore_AddAttr); 00331 00332 return ctx; 00333 } 00334 00335 00336 00337 int GWEN_XmlCtxStore_StartTag(GWEN_XML_CONTEXT *ctx, const char *tagName) { 00338 GWEN_XMLNODE *currNode; 00339 GWEN_XMLNODE *newNode; 00340 00341 currNode=GWEN_XmlCtx_GetCurrentNode(ctx); 00342 if (currNode==NULL) 00343 return GWEN_ERROR_INVALID; 00344 00345 if (*tagName=='?' && (GWEN_XmlCtx_GetFlags(ctx) & GWEN_XML_FLAGS_HANDLE_HEADERS)) { 00346 newNode=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, tagName); 00347 assert(newNode); 00348 DBG_VERBOUS(GWEN_LOGDOMAIN, "Adding header [%s] to [%s]", 00349 GWEN_XMLNode_GetData(newNode), 00350 GWEN_XMLNode_GetData(currNode)); 00351 GWEN_XMLNode_AddHeader(currNode, newNode); 00352 GWEN_XmlCtx_SetCurrentHeader(ctx, newNode); 00353 } 00354 else if (strcasecmp(tagName, "!DOCTYPE")==0) { 00355 newNode=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, tagName); 00356 assert(newNode); 00357 DBG_VERBOUS(GWEN_LOGDOMAIN, "Adding header [%s] to [%s]", 00358 GWEN_XMLNode_GetData(newNode), 00359 GWEN_XMLNode_GetData(currNode)); 00360 GWEN_XMLNode_AddHeader(currNode, newNode); 00361 GWEN_XmlCtx_SetCurrentHeader(ctx, newNode); 00362 } 00363 else if (*tagName=='/') { 00364 const char *s; 00365 00366 tagName++; 00367 DBG_VERBOUS(GWEN_LOGDOMAIN, "Finishing tag [%s]", tagName); 00368 s=GWEN_XMLNode_GetData(currNode); 00369 if (s==NULL) { 00370 DBG_INFO(GWEN_LOGDOMAIN, "Current node tag has no name"); 00371 return GWEN_ERROR_BAD_DATA; 00372 } 00373 00374 if (strcasecmp(s, tagName)!=0) { 00375 if (!(GWEN_XmlCtx_GetFlags(ctx) & GWEN_XML_FLAGS_TOLERANT_ENDTAGS)) { 00376 DBG_INFO(GWEN_LOGDOMAIN, 00377 "Endtag does not match curent tag (%s != %s)", s, tagName); 00378 return GWEN_ERROR_BAD_DATA; 00379 } 00380 else { 00381 newNode=currNode; 00382 00383 while( (newNode=GWEN_XMLNode_GetParent(newNode)) ) { 00384 GWEN_XmlCtx_DecDepth(ctx); 00385 s=GWEN_XMLNode_GetData(newNode); 00386 if (strcasecmp(s, tagName)==0) 00387 break; 00388 } 00389 if (newNode) 00390 newNode=GWEN_XMLNode_GetParent(newNode); 00391 if (newNode) { 00392 GWEN_XmlCtx_SetCurrentNode(ctx, newNode); 00393 GWEN_XmlCtx_DecDepth(ctx); 00394 } 00395 else { 00396 DBG_INFO(GWEN_LOGDOMAIN, "No matching parent node for [%s]", 00397 tagName); 00398 return GWEN_ERROR_BAD_DATA; 00399 } 00400 } 00401 } 00402 else { 00403 newNode=GWEN_XMLNode_GetParent(currNode); 00404 if (newNode==NULL) { 00405 DBG_INFO(GWEN_LOGDOMAIN, "No parent node at [%s]", tagName); 00406 return GWEN_ERROR_BAD_DATA; 00407 } 00408 GWEN_XmlCtx_SetCurrentNode(ctx, newNode); 00409 GWEN_XmlCtx_DecDepth(ctx); 00410 } 00411 /* one more element finished */ 00412 GWEN_XmlCtx_IncFinishedElement(ctx); 00413 } 00414 else { 00415 newNode=GWEN_XMLNode_new(GWEN_XMLNodeTypeTag, tagName); 00416 assert(newNode); 00417 GWEN_XMLNode_AddChild(currNode, newNode); 00418 GWEN_XmlCtx_SetCurrentNode(ctx, newNode); 00419 GWEN_XmlCtx_IncDepth(ctx); 00420 DBG_VERBOUS(GWEN_LOGDOMAIN, "Starting tag [%s]", tagName); 00421 } 00422 00423 return 0; 00424 } 00425 00426 00427 00428 int GWEN_XmlCtxStore_EndTag(GWEN_XML_CONTEXT *ctx, int closing) { 00429 GWEN_XMLNODE *currNode; 00430 00431 currNode=GWEN_XmlCtx_GetCurrentHeader(ctx); 00432 if (currNode) { 00433 DBG_VERBOUS(GWEN_LOGDOMAIN, "Ending header [%s]", GWEN_XMLNode_GetData(currNode)); 00434 GWEN_XmlCtx_SetCurrentHeader(ctx, NULL); 00435 } 00436 else { 00437 currNode=GWEN_XmlCtx_GetCurrentNode(ctx); 00438 if (currNode==NULL) 00439 return GWEN_ERROR_INVALID; 00440 DBG_VERBOUS(GWEN_LOGDOMAIN, "Ending tag [%s] (%s)", 00441 GWEN_XMLNode_GetData(currNode), 00442 closing?"closing":"not closing"); 00443 00444 if (closing) { 00445 GWEN_XMLNODE *newNode; 00446 00447 newNode=GWEN_XMLNode_GetParent(currNode); 00448 if (newNode==NULL) { 00449 DBG_INFO(GWEN_LOGDOMAIN, "No parent node at [%s]", GWEN_XMLNode_GetData(currNode)); 00450 return GWEN_ERROR_BAD_DATA; 00451 } 00452 GWEN_XmlCtx_SetCurrentNode(ctx, newNode); 00453 /* one more element finished */ 00454 GWEN_XmlCtx_DecDepth(ctx); 00455 GWEN_XmlCtx_IncFinishedElement(ctx); 00456 } 00457 } 00458 00459 return 0; 00460 } 00461 00462 00463 00464 int GWEN_XmlCtxStore_AddData(GWEN_XML_CONTEXT *ctx, const char *data) { 00465 GWEN_XMLNODE *currNode; 00466 GWEN_BUFFER *buf; 00467 uint32_t flags; 00468 00469 flags=GWEN_XmlCtx_GetFlags(ctx); 00470 currNode=GWEN_XmlCtx_GetCurrentNode(ctx); 00471 if (currNode==NULL) 00472 return GWEN_ERROR_INVALID; 00473 00474 buf=GWEN_Buffer_new(0, 64, 0, 1); 00475 if (GWEN_Text_UnescapeXmlToBuffer(data, buf)) { 00476 GWEN_Buffer_free(buf); 00477 DBG_INFO(GWEN_LOGDOMAIN, "here"); 00478 return GWEN_ERROR_BAD_DATA; 00479 } 00480 00481 if (!(flags & GWEN_XML_FLAGS_NO_CONDENSE) || 00482 (flags & GWEN_XML_FLAGS_KEEP_CNTRL) || 00483 (flags & GWEN_XML_FLAGS_KEEP_BLANKS)) { 00484 const uint8_t *p; 00485 uint8_t *dst; 00486 uint8_t *src; 00487 unsigned int size; 00488 unsigned int i; 00489 int lastWasBlank; 00490 uint8_t *lastBlankPos; 00491 uint32_t bStart=0; 00492 00493 dst=(uint8_t*)GWEN_Buffer_GetStart(buf); 00494 src=dst; 00495 if (!(flags & GWEN_XML_FLAGS_KEEP_BLANKS)) { 00496 if (flags & GWEN_XML_FLAGS_KEEP_CNTRL) { 00497 while(*src && (*src==32 || *src==9)) 00498 src++; 00499 } 00500 else { 00501 while(*src && *src<33) 00502 src++; 00503 } 00504 } 00505 00506 p=src; 00507 bStart=src-((uint8_t*)GWEN_Buffer_GetStart(buf)); 00508 size=GWEN_Buffer_GetUsedBytes(buf)-bStart; 00509 lastWasBlank=0; 00510 lastBlankPos=0; 00511 00512 for (i=0; i<size; i++) { 00513 uint8_t c; 00514 00515 c=*p; 00516 if (!(flags & GWEN_XML_FLAGS_KEEP_CNTRL) && c<32) 00517 c=32; 00518 00519 /* remember next loop whether this char was a blank */ 00520 if (!(flags & GWEN_XML_FLAGS_NO_CONDENSE) && c==32) { 00521 if (!lastWasBlank) { 00522 /* store only one blank */ 00523 lastWasBlank=1; 00524 lastBlankPos=dst; 00525 *(dst++)=c; 00526 } 00527 } 00528 else { 00529 lastWasBlank=0; 00530 lastBlankPos=0; 00531 *(dst++)=c; 00532 } 00533 p++; 00534 } 00535 00536 /* remove trailing blanks */ 00537 if (lastBlankPos!=0) 00538 dst=lastBlankPos; 00539 00540 size=dst-(uint8_t*)GWEN_Buffer_GetStart(buf); 00541 GWEN_Buffer_Crop(buf, 0, size); 00542 } 00543 00544 if (GWEN_Buffer_GetUsedBytes(buf)) { 00545 GWEN_XMLNODE *newNode; 00546 00547 newNode=GWEN_XMLNode_new(GWEN_XMLNodeTypeData, GWEN_Buffer_GetStart(buf)); 00548 assert(newNode); 00549 GWEN_XMLNode_AddChild(currNode, newNode); 00550 DBG_VERBOUS(GWEN_LOGDOMAIN, "Setting this data: [%s]", GWEN_Buffer_GetStart(buf)); 00551 } 00552 GWEN_Buffer_free(buf); 00553 00554 return 0; 00555 } 00556 00557 00558 00559 int GWEN_XmlCtxStore_AddComment(GWEN_UNUSED GWEN_XML_CONTEXT *ctx, GWEN_UNUSED const char *data) { 00560 return 0; 00561 } 00562 00563 00564 00565 int GWEN_XmlCtxStore_AddAttr(GWEN_XML_CONTEXT *ctx, 00566 const char *attrName, 00567 const char *attrData) { 00568 GWEN_XMLNODE *currNode; 00569 00570 currNode=GWEN_XmlCtx_GetCurrentHeader(ctx); 00571 if (currNode) { 00572 DBG_VERBOUS(GWEN_LOGDOMAIN, "Setting attribute of header [%s]: [%s]=[%s]", 00573 GWEN_XMLNode_GetData(currNode), attrName, attrData); 00574 GWEN_XMLNode_SetProperty(currNode, attrName, attrData); 00575 } 00576 else { 00577 int isNormalProperty=1; 00578 00579 currNode=GWEN_XmlCtx_GetCurrentNode(ctx); 00580 if (currNode==NULL) 00581 return GWEN_ERROR_INVALID; 00582 if (attrData==NULL) 00583 attrData=""; 00584 00585 if (ctx->flags & GWEN_XML_FLAGS_HANDLE_NAMESPACES) { 00586 if (strcasecmp(attrName, "xmlns")==0) { 00587 GWEN_XMLNODE_NAMESPACE *ns; 00588 00589 DBG_VERBOUS(GWEN_LOGDOMAIN, "Adding namespace [%s] to node [%s]", 00590 attrData, GWEN_XMLNode_GetData(currNode)); 00591 ns=GWEN_XMLNode_NameSpace_new("", attrData); 00592 GWEN_XMLNode_AddNameSpace(currNode, ns); 00593 GWEN_XMLNode_NameSpace_free(ns); 00594 isNormalProperty=0; 00595 } 00596 else if (strncasecmp(attrName, "xmlns:", 6)==0) { 00597 const char *name; 00598 00599 name=strchr(attrName, ':'); 00600 if (name) { 00601 name++; 00602 if (*name) { 00603 GWEN_XMLNODE_NAMESPACE *ns; 00604 00605 DBG_VERBOUS(GWEN_LOGDOMAIN, "Adding namespace [%s]=[%s]", 00606 name, attrData); 00607 ns=GWEN_XMLNode_NameSpace_new(name, attrData); 00608 GWEN_XMLNode_AddNameSpace(currNode, ns); 00609 GWEN_XMLNode_NameSpace_free(ns); 00610 isNormalProperty=0; 00611 } 00612 } 00613 } 00614 } 00615 00616 if (isNormalProperty) { 00617 GWEN_BUFFER *buf; 00618 00619 DBG_VERBOUS(GWEN_LOGDOMAIN, "Setting attribute of tag [%s]: [%s]=[%s]", 00620 GWEN_XMLNode_GetData(currNode), attrName, attrData); 00621 buf=GWEN_Buffer_new(0, 64, 0, 1); 00622 if (GWEN_Text_UnescapeXmlToBuffer(attrData, buf)) { 00623 GWEN_Buffer_free(buf); 00624 DBG_INFO(GWEN_LOGDOMAIN, "here"); 00625 return GWEN_ERROR_BAD_DATA; 00626 } 00627 GWEN_XMLNode_SetProperty(currNode, attrName, GWEN_Buffer_GetStart(buf)); 00628 GWEN_Buffer_free(buf); 00629 } 00630 } 00631 00632 return 0; 00633 } 00634 00635 00636 00637 00638 00639