//------------------------------------------------------------------------------ // Desc: Sample application // // Tabs: 3 // // Copyright (c) 2002-2005 Novell, Inc. All Rights Reserved. // // This program is free software; you can redistribute it and/or // modify it under the terms of version 2.1 of the GNU Lesser General Public // License as published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with this program; if not, contact Novell, Inc. // // To contact Novell about this file by physical or electronic mail, // you may find current contact information at www.novell.com // // $Id: sample.cpp 3102 2006-01-10 10:15:17 -0700 (Tue, 10 Jan 2006) ahodgkinson $ //------------------------------------------------------------------------------ #include "xflaim.h" #ifdef FLM_WIN #define UI64FormatStr "I64u" #define I64FormatStr "I64d" #include #elif defined( FLM_SOLARIS) #define UI64FormatStr "llu" #define I64FormatStr "lld" #else #define UI64FormatStr "Lu" #define I64FormatStr "Ld" #endif #include #include FLMBOOL gv_bShutdown = FALSE; #define DB_NAME_STR "tst.db" #define BACKUP_NAME_STR "tst.bak" #define NEW_NAME_STR "new.db" void printMessage( const char * pszMessage, FLMUINT uiLevel = 0); RCODE printDocument( IF_Db * pDb, IF_DOMNode * pRootNode); RCODE processAttributes( IF_Db * pDb, IF_DOMNode * pNode, FLMBYTE ** ppszLine); /*************************************************************************** Desc: Program entry point (main) ****************************************************************************/ int main( int, // iArgC, char ** // ppucArgV ) { RCODE rc = NE_XFLM_OK; FLMBYTE ucMsgBuf[ 200]; XFLM_CREATE_OPTS createOpts; IF_DbSystem * pDbSystem = NULL; IF_Db * pDb = NULL; IF_Backup * pBackup = NULL; IF_DOMNode * pTmpNode = NULL; FLMUINT uiMusicElementDef=0; FLMUINT uiCdElementDef=0; FLMUINT uiTitleElementDef=0; FLMUINT uiArtistElementDef=0; FLMUINT uiTracksElementDef=0; FLMUINT uiNoAttrDef=0; FLMUINT uiTitleAttrDef=0; FLMUINT uiCollectionDef=0; FLMBOOL bTranStarted = FALSE; IF_DOMNode * pCdChild = NULL; IF_DOMNode * pCdElement = NULL; IF_DOMNode * pMusicElement = NULL; IF_DOMNode * pCdAttr = NULL; IF_DOMNode * pTrackNode = NULL; IF_Query * pQuery = NULL; IF_PosIStream * pPosIStream = NULL; IF_PosIStream * pIStream = NULL; FLMUINT uiIndex = 1; FLMBYTE ucUntilKeyBuf[ XFLM_MAX_KEY_SIZE]; FLMUINT uiUntilKeyLen; FLMBYTE ucCurrentKeyBuf[ XFLM_MAX_KEY_SIZE]; FLMUINT uiCurrentKeyLen; FLMUINT64 ui64DocId; char ucTitle[ 200]; FLMUINT uiTitleLen; char * ppszPath[] = { "7001e10c.xml", "70028663.xml", "70037c08.xml", "70040b08.xml", "70044808.xml", "70045109.xml", "70045e09.xml", "70046709.xml", "7004920a.xml", "" }; FLMUINT uiLoop; IF_DataVector * pFromKeyV = NULL; IF_DataVector * pUntilKeyV = NULL; const char * pszQueryString = "/music/cd/tracks[@title~=\"we our in luv\"]"; const char * pszIndex = "" "" ""; // Allocate a DbSystem object if( RC_BAD( rc = FlmAllocDbSystem( &pDbSystem))) { goto Exit; } // Initialize the database system if( RC_BAD( rc = pDbSystem->init())) { goto Exit; } // Create the database. This code will remove the database first if it // exists. Once removed, it will try to create it. If that fails for // any reason other than the database already exists, it will exit. RetryCreate: memset( &createOpts, 0, sizeof( XFLM_CREATE_OPTS)); if( RC_BAD( rc = pDbSystem->dbCreate( DB_NAME_STR, NULL, NULL, NULL, NULL, &createOpts, &pDb))) { if( RC_BAD( rc == NE_XFLM_FILE_EXISTS)) { if( RC_BAD( rc = pDbSystem->dbRemove( DB_NAME_STR, NULL, NULL, TRUE))) { goto Exit; } goto RetryCreate; } else { goto Exit; } } // Start an update transaction. All access to the database should // be done within the confines of a transaction. When changes are being // made to the database, an UPDATE transaction is required. It is best to // keep transactions small if possible. if( RC_BAD( rc = pDb->transBegin( XFLM_UPDATE_TRANS, XFLM_NO_TIMEOUT))) { goto Exit; } bTranStarted = TRUE; // Let's create a document to demonstrate how we use the DOM... // Our document will catalog a music CD. It will look like: // // // We Are In Love // Harry Connick Jr. // // // // // To accomplish this, we must define the elements of the document and a new // collection to store the document in. // Create some element definitions for our document. // Create the "music" element definition. if( RC_BAD( rc = pDb->createElementDef( NULL, "music", XFLM_NODATA_TYPE, &uiMusicElementDef))) { goto Exit; } // Create the "cd" element definition. if( RC_BAD( rc = pDb->createElementDef( NULL, "cd", XFLM_NODATA_TYPE, &uiCdElementDef))) { goto Exit; } // Create the "title" element definition. if( RC_BAD( rc = pDb->createElementDef( NULL, "title", XFLM_TEXT_TYPE, &uiTitleElementDef))) { goto Exit; } // Create the "artist" element definition. if( RC_BAD( rc = pDb->createElementDef( NULL, "artist", XFLM_TEXT_TYPE, &uiArtistElementDef))) { goto Exit; } // Create the "tracks" element definition. if( RC_BAD( rc = pDb->createElementDef( NULL, "tracks", XFLM_NODATA_TYPE, &uiTracksElementDef))) { goto Exit; } // Create the "no" attribute definition. if( RC_BAD( rc = pDb->createAttributeDef( NULL, "no", XFLM_NUMBER_TYPE, &uiNoAttrDef))) { goto Exit; } // Create the "title" attribute definition. if( RC_BAD( rc = pDb->createAttributeDef( NULL, "title", XFLM_TEXT_TYPE, &uiTitleAttrDef))) { goto Exit; } // Create our special music collection for storing music stuff. if( RC_BAD( rc = pDb->createCollectionDef( "Music Collection", &uiCollectionDef))) { goto Exit; } // We now have all of the definitions we need to build our document. // Lets first create a new document, followed by its members... // Create the "music" root element if( RC_BAD( rc = pDb->createRootElement( uiCollectionDef, uiMusicElementDef, &pMusicElement))) { goto Exit; } // Create the "cd" element if( RC_BAD( rc = pMusicElement->createNode( pDb, ELEMENT_NODE, uiCdElementDef, XFLM_FIRST_CHILD, &pCdElement))) { goto Exit; } // Create the "title" element if( RC_BAD( rc = pCdElement->createNode( pDb, ELEMENT_NODE, uiTitleElementDef, XFLM_FIRST_CHILD, &pCdChild))) { goto Exit; } // Set the value for the title. if (RC_BAD( rc = pCdChild->setUTF8( pDb, (FLMBYTE *)"We Are In Love"))) { goto Exit; } // Create the "artist" element if( RC_BAD( rc = pCdElement->createNode( pDb, ELEMENT_NODE, uiArtistElementDef, XFLM_LAST_CHILD, &pCdChild))) { goto Exit; } // Set the value for the title. if (RC_BAD( rc = pCdChild->setUTF8( pDb, (FLMBYTE *)"Harry Connick Jr."))) { goto Exit; } // Create the first "tracks" element if( RC_BAD( rc = pCdElement->createNode( pDb, ELEMENT_NODE, uiTracksElementDef, XFLM_LAST_CHILD, &pCdChild))) { goto Exit; } // Create the "no." attribute, then the "title" attribute. if (RC_BAD( rc = pCdChild->createAttribute( pDb, uiNoAttrDef, &pCdAttr))) { goto Exit; } if (RC_BAD( rc = pCdAttr->setUINT( pDb, (FLMUINT)1))) { goto Exit; } if (RC_BAD( rc = pCdChild->createAttribute( pDb, uiTitleAttrDef, &pCdAttr))) { goto Exit; } if (RC_BAD( rc = pCdAttr->setUTF8( pDb, (FLMBYTE *)"We Are In Love"))) { goto Exit; } // Create the next "tracks" element if( RC_BAD( rc = pCdElement->createNode( pDb, ELEMENT_NODE, uiTracksElementDef, XFLM_LAST_CHILD, &pCdChild))) { goto Exit; } // An alternate way to create the attributes and set their values is to use // the parent element to set the attribute values. // Create the two attributes. if (RC_BAD( rc = pCdChild->createAttribute( pDb, uiNoAttrDef, &pCdAttr))) { goto Exit; } if (RC_BAD( rc = pCdChild->createAttribute( pDb, uiTitleAttrDef, &pCdAttr))) { goto Exit; } // Using the parent of the attributes, set their values by specifying // which attribute to set. if (RC_BAD( rc = pCdChild->setAttributeValueUINT( pDb, uiNoAttrDef, (FLMUINT)2))) { goto Exit; } if (RC_BAD( rc = pCdChild->setAttributeValueUTF8( pDb, uiTitleAttrDef, (FLMBYTE *)"Only 'Cause I Don't Have You"))) { goto Exit; } // It is always good practice to call documentDone whenever updates to a // document are completed. if (RC_BAD( rc = pDb->documentDone( pMusicElement))) { goto Exit; } // Commit the transaction if( RC_BAD( rc = pDb->transCommit())) { goto Exit; } bTranStarted= FALSE; // Now we want to read back our document, and display it for all // the world to see...:^) // Start a read transaction if( RC_BAD( rc = pDb->transBegin( XFLM_READ_TRANS, 0))) { goto Exit; } bTranStarted = TRUE; // Read the nodes. Start with the document node. if( RC_BAD( rc = pDb->getFirstDocument( uiCollectionDef, &pTmpNode))) { goto Exit; } // printDocument is a simple function that walks through the document // and displays it, node by node as an XML document. It assumes the // node passed in is the root node. If it is not, then the display will // look a bit odd, as you will see later in this sample program. if (RC_BAD( rc = printDocument( pDb, pTmpNode))) { goto Exit; } // Commit the transaction if( RC_BAD( rc = pDb->transCommit())) { goto Exit; } bTranStarted = FALSE; // Start an update transaction if( RC_BAD( rc = pDb->transBegin( XFLM_READ_TRANS, 0))) { goto Exit; } bTranStarted = TRUE; // Let's do a query on this document... Our query (above) is // deliberately miss-spelling some of the words. It is looking for // the music "tracks" with the title "We Are In Love". // The syntax ~= means approximately equals, and is a "sounds like" // search. if (RC_BAD( rc = pDbSystem->createIFQuery( &pQuery))) { goto Exit; } if (RC_BAD( rc = pQuery->setCollection( uiCollectionDef))) { goto Exit; } if (RC_BAD( rc = pQuery->setupQueryExpr( pDb, pszQueryString))) { goto Exit; } if (RC_BAD( rc = pQuery->getFirst( pDb, &pTrackNode))) { goto Exit; } printMessage( "Query:"); printMessage( pszQueryString); // This will pring the tracks node, followed by all of the closing parent // nodes. if (RC_BAD( rc = printDocument( pDb, pTrackNode))) { goto Exit; } pQuery->Release(); pQuery = NULL; // Commit the transaction if( RC_BAD( rc = pDb->transCommit())) { goto Exit; } bTranStarted = FALSE; // Let's create an index to make searching easier. An index is essentially // just another XML document to XFlaim, however it is created in the dictionary // collection rather than in a data collection. There are two ways to create // a document in XFlaim. One way (demonstrated above) is to create each node // of the document, one at a time. The other is to import the document. // Creating the index will demonstrate importing the document. Our // index definition is shown as follows: // // // " // For our purposes, we will create a local variable (pszIndex) which // holds the index document. We will import that using the importDocument // method of the pDb object. // Import the index... // We first need to create a BufferIStream object to stream the document // from... // Start an update transaction if( RC_BAD( rc = pDb->transBegin( XFLM_UPDATE_TRANS, XFLM_NO_TIMEOUT))) { goto Exit; } bTranStarted = TRUE; if ( RC_BAD( rc = pDbSystem->openBufferIStream( pszIndex, strlen( pszIndex), &pPosIStream))) { goto Exit; } if ( RC_BAD( rc = pDb->import( pPosIStream, XFLM_DICT_COLLECTION))) { goto Exit; } // Commit the transaction if( RC_BAD( rc = pDb->transCommit())) { goto Exit; } bTranStarted = FALSE; // Now, let's get some additional documents in so we can do some more // interesting stuff using a IF_DataVector. The documents we are // interested in searching are the CD music documents. // They have the following format: // // // 00097210 // 2420 // Frank Sinatra / Blue skies // cddb/jazz // blue skies // . // . // . // if( RC_BAD( rc = pDb->transBegin( XFLM_UPDATE_TRANS, XFLM_NO_TIMEOUT))) { goto Exit; } bTranStarted = TRUE; // We will first need an input file stream. uiLoop = 0; while( strlen( ppszPath[ uiLoop])) { if( RC_BAD( rc = pDbSystem->openFileIStream( ppszPath[ uiLoop], &pIStream))) { goto Exit; } if( RC_BAD( rc = pDb->import( pIStream, XFLM_DATA_COLLECTION))) { goto Exit; } uiLoop++; } // Commit the transaction if( RC_BAD( rc = pDb->transCommit())) { goto Exit; } bTranStarted = FALSE; // Let's get some IF_DataVectors to work with. if( RC_BAD( rc = pDb->transBegin( XFLM_READ_TRANS, 0))) { goto Exit; } bTranStarted = TRUE; if (RC_BAD( rc = pDbSystem->createIFDataVector( &pFromKeyV))) { goto Exit; } if (RC_BAD( rc = pDbSystem->createIFDataVector( &pUntilKeyV))) { goto Exit; } // We need to get the index. // Now to search the index above for all document titles in the index and // display the document Id and title for each node. Note that the index // number is known to be 1. if (RC_BAD( rc = pDb->keyRetrieve( uiIndex, NULL, XFLM_FIRST, pFromKeyV))) { goto Exit; } if (RC_BAD( rc = pDb->keyRetrieve( uiIndex, NULL, XFLM_LAST, pUntilKeyV))) { goto Exit; } // Save the UntilKey value to compare with later. if (RC_BAD( rc = pUntilKeyV->outputKey( pDb, uiIndex, FALSE, &ucUntilKeyBuf[0], XFLM_MAX_KEY_SIZE, &uiUntilKeyLen))) { goto Exit; } for (;;) { // Display the current Document Id and the title. ui64DocId = pFromKeyV->getDocumentID(); uiTitleLen = sizeof(ucTitle); if (RC_BAD( rc = pFromKeyV->getUTF8( 0, (FLMBYTE *)&ucTitle[0], &uiTitleLen))) { goto Exit; } sprintf( (char *)&ucMsgBuf[0], "DocId: %"UI64FormatStr"\n%s\n", ui64DocId, ucTitle); printMessage( (char *)&ucMsgBuf[0]); // Check to see if this key matches the last or UntilKeyV value. if (RC_BAD( rc = pFromKeyV->outputKey( pDb, uiIndex, FALSE, &ucCurrentKeyBuf[0], XFLM_MAX_KEY_SIZE, &uiCurrentKeyLen))) { goto Exit; } if (uiCurrentKeyLen == uiUntilKeyLen) { if (memcmp( ucCurrentKeyBuf, ucUntilKeyBuf, uiCurrentKeyLen) == 0) { // We are done! break; } } // Get the next key. if (RC_BAD( rc = pDb->keyRetrieve( uiIndex, pFromKeyV, XFLM_EXCL, pFromKeyV))) { goto Exit; } } if( RC_BAD( rc = pDb->transCommit())) { goto Exit; } bTranStarted = FALSE; // Close the database pDb->Release(); pDb = NULL; // Close all unused files if( RC_BAD( rc = pDbSystem->closeUnusedFiles( 0))) { goto Exit; } // Re-open the database if( RC_BAD( rc = pDbSystem->dbOpen( DB_NAME_STR, NULL, NULL, NULL, FALSE, &pDb))) { goto Exit; } // Backup the database if( RC_BAD( rc = pDb->backupBegin( XFLM_FULL_BACKUP, XFLM_READ_TRANS, 0, &pBackup))) { goto Exit; } if( RC_BAD( rc = pBackup->backup( BACKUP_NAME_STR, NULL, NULL, NULL, NULL))) { goto Exit; } if (RC_BAD( rc = pBackup->endBackup())) { goto Exit; } pBackup->Release(); pBackup = NULL; // Close the database again pDb->Release(); pDb = NULL; if( RC_BAD( rc = pDbSystem->closeUnusedFiles( 0))) { goto Exit; } // Remove the database if( RC_BAD( rc = pDbSystem->dbRemove( DB_NAME_STR, NULL, NULL, TRUE))) { goto Exit; } // Restore the database if( RC_BAD( rc = pDbSystem->dbRestore( DB_NAME_STR, NULL, NULL, BACKUP_NAME_STR, NULL, NULL, NULL))) { goto Exit; } // Rename the database if( RC_BAD( rc = pDbSystem->dbRename( DB_NAME_STR, NULL, NULL, NEW_NAME_STR, TRUE, NULL))) { goto Exit; } // Copy the database if( RC_BAD( rc = pDbSystem->dbCopy( NEW_NAME_STR, NULL, NULL, DB_NAME_STR, NULL, NULL, NULL))) { goto Exit; } // Remove the new database if( RC_BAD( rc = pDbSystem->dbRemove( NEW_NAME_STR, NULL, NULL, TRUE))) { goto Exit; } Exit: if (bTranStarted && pDb) { (void)pDb->transAbort(); } if (pCdChild) { pCdChild->Release(); } if (pCdElement) { pCdElement->Release(); } if( pMusicElement) { pMusicElement->Release(); } if (pCdAttr) { pCdAttr->Release(); } if (pTrackNode) { pTrackNode->Release(); } if( pTmpNode) { pTmpNode->Release(); } if (pQuery) { pQuery->Release(); } if( pPosIStream) { pPosIStream->Release(); } if (pIStream) { pIStream->Release(); } if (pFromKeyV) { pFromKeyV->Release(); } if (pUntilKeyV) { pUntilKeyV->Release(); } if( pBackup) { pBackup->Release(); } // Close the database object if( pDb) { pDb->Release(); } if( RC_BAD( rc)) { sprintf( (char *)ucMsgBuf, "Error %04X -- %s\n", (unsigned)rc, (char *)pDbSystem->errorString( rc)); printMessage( (char *)ucMsgBuf); } // Shut down the XFlaim database engine. This call must be made // even if the init call failed. No more XFlaim calls should be made // by the application. pDbSystem->exit(); pDbSystem->Release(); return( 0); } /*************************************************************************** Desc: Prints a string to stdout ****************************************************************************/ void printMessage( const char * pszMessage, FLMUINT uiLevel) { unsigned int uiLoop; for (uiLoop = 0; uiLoop < uiLevel; uiLoop++) { printf( " "); } printf( "%s\n", pszMessage); } /*============================================================================= Desc: Simple routine to display the contents of a document. =============================================================================*/ RCODE printDocument( IF_Db * pDb, IF_DOMNode * pRootNode) { RCODE rc = NE_XFLM_OK; FLMBYTE ucLine[ 200]; FLMBYTE * pszLine; FLMBOOL bHasChildren = FALSE; FLMBOOL bHadAttributes; FLMUINT uiDataType; char szName[ 50]; FLMUINT uiNameLen; IF_DOMNode * pNode = pRootNode; char szTemp[ 200]; FLMUINT uiTemp; FLMBOOL bUnwinding = FALSE; FLMBOOL bHadValue = FALSE; FLMUINT uiThisLevel = 0; FLMUINT uiNextLevel = 0; eDomNodeType eNodeType; pNode->AddRef(); pszLine = &ucLine[0]; bHadAttributes = FALSE; while (pNode) { bHadValue = FALSE; // Write out the current line. if (pszLine != &ucLine[0]) { printMessage( (char *)ucLine, uiThisLevel); pszLine = &ucLine[0]; } // Adjust to the next level uiThisLevel = uiNextLevel; eNodeType = pNode->getNodeType(); if( eNodeType == DOCUMENT_NODE) { if (!bUnwinding) { sprintf( (char *)pszLine, ""); } pszLine += strlen( (char *)pszLine); } else if( eNodeType == ELEMENT_NODE) { if (RC_BAD( rc = pNode->getQualifiedName( pDb, szName, sizeof(szName), &uiNameLen))) { goto Exit; } if (!bUnwinding) { sprintf( (char *)pszLine, "<%s", szName); } else { sprintf( (char *)pszLine, "", szName); } pszLine += strlen( (char *)pszLine); } if (!bUnwinding) { if (RC_BAD( rc = processAttributes( pDb, pNode, &pszLine))) { goto Exit; } // We need to know if this node has children to // know how to close the line. if (RC_BAD( rc = pNode->hasChildren( pDb, &bHasChildren))) { goto Exit; } if( eNodeType == DATA_NODE || eNodeType == COMMENT_NODE) { if (RC_BAD( rc = pNode->getDataType( pDb, &uiDataType))) { goto Exit; } if( eNodeType == COMMENT_NODE) { sprintf( (char *)pszLine, ""); pszLine += 3; } } else { if ( !bHasChildren) { sprintf( (char *)pszLine, "/>"); pszLine += 2; } else { sprintf( (char *)pszLine, ">"); pszLine++; } } // Children if (bHasChildren) { // Get the child node. if (RC_BAD( rc = pNode->getFirstChild( pDb, &pNode))) { goto Exit; } uiNextLevel++; continue; } } bUnwinding = FALSE; if (RC_BAD( rc = pNode->getNextSibling( pDb, &pNode))) { if (rc == NE_XFLM_DOM_NODE_NOT_FOUND) { rc = NE_XFLM_OK; } else { goto Exit; } } else { continue; } // Get the parent if there is one. Otherwise we are done. if (uiNextLevel) { --uiNextLevel; } if (RC_BAD( rc = pNode->getParentNode( pDb, &pNode))) { if (rc != NE_XFLM_DOM_NODE_NOT_FOUND) { goto Exit; } rc = NE_XFLM_OK; pNode->Release(); pNode = NULL; } else { bUnwinding = TRUE; } } printMessage( (char *)ucLine); Exit: return( rc); } /*============================================================================= Desc: =============================================================================*/ RCODE processAttributes( IF_Db * pDb, IF_DOMNode * pNode, FLMBYTE ** ppszLine) { RCODE rc = NE_XFLM_OK; IF_DOMNode * pAttrNode = NULL; FLMBOOL bHasAttrs; char szName[ 50]; FLMUINT uiNameLen; FLMBYTE * pszLine = *ppszLine; FLMUINT uiDataType; FLMBYTE szTemp[ 200]; FLMUINT uiTemp; if (RC_BAD( rc = pNode->hasAttributes( pDb, &bHasAttrs))) { goto Exit; } if( !bHasAttrs) { goto Exit; } // We have attributes. Let's get them and add them to the line. if (RC_BAD( rc = pNode->getFirstAttribute( pDb, &pAttrNode))) { goto Exit; } for (;;) { if (RC_BAD( rc = pAttrNode->getQualifiedName( pDb, szName, sizeof(szName), &uiNameLen))) { goto Exit; } sprintf( (char *)pszLine, " %s", szName); pszLine += (uiNameLen + 1); if (RC_BAD( rc = pAttrNode->getDataType( pDb, &uiDataType))) { goto Exit; } switch (uiDataType) { case XFLM_TEXT_TYPE: { if (RC_BAD( rc = pAttrNode->getUTF8( pDb, szTemp, sizeof( szTemp), 0, 200, &uiTemp))) { goto Exit; } sprintf( (char *)pszLine, "=\"%s\"", szTemp); pszLine += (uiTemp + 3); break; } case XFLM_NUMBER_TYPE: { if (RC_BAD( rc = pAttrNode->getUINT( pDb, &uiTemp))) { goto Exit; } sprintf( (char *)szTemp, "%u", (unsigned)uiTemp); sprintf( (char *)pszLine, "=\"%s\"", szTemp); pszLine += (strlen( (char *)szTemp) + 3); break; } // Could also get binary data, but we won't handle it here. } // Get the next attribute, if any if (RC_BAD( rc = pAttrNode->getNextSibling( pDb, &pAttrNode))) { if (rc == NE_XFLM_DOM_NODE_NOT_FOUND) { rc = NE_XFLM_OK; break; } goto Exit; } } // Update the line pointer on the way out. *ppszLine = pszLine; Exit: if (pAttrNode) { pAttrNode->Release(); } return( rc); }