home *** CD-ROM | disk | FTP | other *** search
/ Computer Shopper 217 / DPCS0306DVD.ISO / Toolkit / Internet / FileZilla / Server / FileZilla_Server-0.9.11.exe / source / interface / misc / MarkupSTL.cpp < prev    next >
Encoding:
C/C++ Source or Header  |  2004-12-03  |  38.1 KB  |  1,445 lines

  1. // Markup.cpp: implementation of the CMarkupSTL class.
  2. //
  3. // Markup Release 6.3
  4. // Copyright (C) 1999-2002 First Objective Software, Inc. All rights reserved
  5. // Go to www.firstobject.com for the latest CMarkupSTL and EDOM documentation
  6. // Use in commercial applications requires written permission
  7. // This software is provided "as is", with no warranty.
  8.  
  9. #include "stdafx.h"
  10. #include "afxconv.h"
  11. #include "MarkupSTL.h"
  12.  
  13. #if defined(_DEBUG) && !defined(MMGR)
  14. #undef THIS_FILE
  15. static char THIS_FILE[]=__FILE__;
  16. #define new DEBUG_NEW
  17. #endif
  18.  
  19. #ifdef _MBCS
  20. #pragma message( "Note: MBCS build (not UTF-8)" )
  21. // For UTF-8, remove _MBCS from project settings C/C++ preprocessor definitions
  22. #endif
  23.  
  24. // Defines for Windows CE
  25. #ifndef _tclen
  26. #define _tclen(p) 1
  27. #define _tccpy(p1,p2) *(p1)=*(p2)
  28. #endif
  29.  
  30.  
  31. void CMarkupSTL::operator=( const CMarkupSTL& markup )
  32. {
  33.     m_iPosParent = markup.m_iPosParent;
  34.     m_iPos = markup.m_iPos;
  35.     m_iPosChild = markup.m_iPosChild;
  36.     m_iPosFree = markup.m_iPosFree;
  37.     m_nNodeType = markup.m_nNodeType;
  38.     m_aPos.clear();
  39.     m_aPos= markup.m_aPos;
  40.     m_csDoc = markup.m_csDoc;
  41.     MARKUP_SETDEBUGSTATE;
  42. }
  43.  
  44. bool CMarkupSTL::SetDoc( LPCTSTR szDoc )
  45. {
  46.     // Reset indexes
  47.     m_iPosFree = 1;
  48.     ResetPos();
  49.     m_mapSavedPos.clear();
  50.  
  51.     // Set document text
  52.     if ( szDoc )
  53.         m_csDoc = szDoc;
  54.     else
  55.         m_csDoc.Empty();
  56.  
  57.     // Starting size of position array: 1 element per 64 bytes of document
  58.     // Tight fit when parsing small doc, only 0 to 2 reallocs when parsing large doc
  59.     // Start at 8 when creating new document
  60.     UINT nStartSize = m_csDoc.GetLength() / 64 + 8;
  61.     if ( m_aPos.size() < nStartSize )
  62.         m_aPos.resize( nStartSize );
  63.  
  64.     // Parse document
  65.     bool bWellFormed = false;
  66.     if ( m_csDoc.GetLength() )
  67.     {
  68.         m_aPos[0].Clear();
  69.         int iPos = x_ParseElem( 0 );
  70.         if ( iPos > 0 )
  71.         {
  72.             m_aPos[0].iElemChild = iPos;
  73.             bWellFormed = true;
  74.         }
  75.     }
  76.  
  77.     // Clear indexes if parse failed or empty document
  78.     if ( ! bWellFormed )
  79.     {
  80.         m_aPos[0].Clear();
  81.         m_iPosFree = 1;
  82.     }
  83.  
  84.     ResetPos();
  85.     return bWellFormed;
  86. };
  87.  
  88. bool CMarkupSTL::IsWellFormed()
  89. {
  90.     if ( m_aPos.size() && m_aPos[0].iElemChild )
  91.         return true;
  92.     return false;
  93. }
  94.  
  95. bool CMarkupSTL::Load( LPCTSTR szFileName )
  96. {
  97.     CString csDoc;
  98.     CFile file;
  99.     if ( ! file.Open(szFileName,CFile::modeRead) )
  100.         return false;
  101.     //The following will not work for files larger than 2GB
  102.     int nLength = static_cast<int>(file.GetLength());
  103.  
  104. #if defined(_UNICODE)
  105.     // Allocate Buffer for UTF-8 file data
  106.     unsigned char* pBuffer = new unsigned char[nLength + 1];
  107.     nLength = file.Read( pBuffer, nLength );
  108.     pBuffer[nLength] = '\0';
  109.  
  110.     // Convert file from UTF-8 to Windows UNICODE (AKA UCS-2)
  111.     int nWideLength = MultiByteToWideChar(CP_UTF8,0,(const char*)pBuffer,nLength,NULL,0);
  112.     nLength = MultiByteToWideChar(CP_UTF8,0,(const char*)pBuffer,nLength,
  113.         csDoc.GetBuffer(nWideLength),nWideLength);
  114.     ASSERT( nLength == nWideLength );
  115.     delete [] pBuffer;
  116. #else
  117.     nLength = file.Read( csDoc.GetBuffer(nLength), nLength );
  118. #endif
  119.     csDoc.ReleaseBuffer(nLength);
  120.     file.Close();
  121.  
  122.     return SetDoc( csDoc );
  123. }
  124.  
  125. bool CMarkupSTL::Save( LPCTSTR szFileName )
  126. {
  127.     int nLength = m_csDoc.GetLength();
  128.     CFile file;
  129.     if ( ! file.Open(szFileName,CFile::modeWrite|CFile::modeCreate) )
  130.         return false;
  131. #if defined( _UNICODE )
  132.     int nUTF8Len = WideCharToMultiByte(CP_UTF8,0,m_csDoc,nLength,NULL,0,NULL,NULL);
  133.     char* pBuffer = new char[nUTF8Len+1];
  134.     nLength = WideCharToMultiByte(CP_UTF8,0,m_csDoc,nLength,pBuffer,nUTF8Len+1,NULL,NULL);
  135.     file.Write( pBuffer, nLength );
  136.     delete pBuffer;
  137. #else
  138.     file.Write( (LPCTSTR)m_csDoc, nLength );
  139. #endif
  140.     file.Close();
  141.     return true;
  142. }
  143.  
  144. bool CMarkupSTL::FindElem( LPCTSTR szName )
  145. {
  146.     // Change current position only if found
  147.     //
  148.     if ( m_aPos.size() )
  149.     {
  150.         int iPos = x_FindElem( m_iPosParent, m_iPos, szName );
  151.         if ( iPos )
  152.         {
  153.             // Assign new position
  154.             x_SetPos( m_aPos[iPos].iElemParent, iPos, 0 );
  155.             return true;
  156.         }
  157.     }
  158.     return false;
  159. }
  160.  
  161. bool CMarkupSTL::FindChildElem( LPCTSTR szName )
  162. {
  163.     // Change current child position only if found
  164.     //
  165.     // Shorthand: call this with no current main position
  166.     // means find child under root element
  167.     if ( ! m_iPos )
  168.         FindElem();
  169.  
  170.     int iPosChild = x_FindElem( m_iPos, m_iPosChild, szName );
  171.     if ( iPosChild )
  172.     {
  173.         // Assign new position
  174.         int iPos = m_aPos[iPosChild].iElemParent;
  175.         x_SetPos( m_aPos[iPos].iElemParent, iPos, iPosChild );
  176.         return true;
  177.     }
  178.  
  179.     return false;
  180. }
  181.  
  182. CString CMarkupSTL::GetTagName() const
  183. {
  184.     // Return the tag name at the current main position
  185.     CString csTagName;
  186.  
  187.     if ( m_iPos )
  188.         csTagName = x_GetTagName( m_iPos );
  189.     return csTagName;
  190. }
  191.  
  192. bool CMarkupSTL::IntoElem()
  193. {
  194.     // If there is no child position and IntoElem is called it will succeed in release 6.3
  195.     // (A subsequent call to FindElem will find the first element)
  196.     // The following short-hand behavior was never part of EDOM and was misleading
  197.     // It would find a child element if there was no current child element position and go into it
  198.     // It is removed in release 6.3, this change is NOT backwards compatible!
  199.     // if ( ! m_iPosChild )
  200.     //    FindChildElem();
  201.  
  202.     if ( m_iPos && m_nNodeType == MNT_ELEMENT )
  203.     {
  204.         x_SetPos( m_iPos, m_iPosChild, 0 );
  205.         return true;
  206.     }
  207.     return false;
  208. }
  209.  
  210. bool CMarkupSTL::OutOfElem()
  211. {
  212.     // Go to parent element
  213.     if ( m_iPosParent )
  214.     {
  215.         x_SetPos( m_aPos[m_iPosParent].iElemParent, m_iPosParent, m_iPos );
  216.         return true;
  217.     }
  218.     return false;
  219. }
  220.  
  221. CString CMarkupSTL::GetAttribName( int n ) const
  222. {
  223.     // Return nth attribute name of main position
  224.     if ( ! m_iPos || m_nNodeType != MNT_ELEMENT )
  225.         return _T("");
  226.  
  227.     TokenPos token( m_csDoc );
  228.     token.nNext = m_aPos[m_iPos].nStartL + 1;
  229.     for ( int nAttrib=0; nAttrib<=n; ++nAttrib )
  230.         if ( ! x_FindAttrib(token) )
  231.             return _T("");
  232.  
  233.     // Return substring of document
  234.     return x_GetToken( token );
  235. }
  236.  
  237. bool CMarkupSTL::SavePos( LPCTSTR szPosName )
  238. {
  239.     // Save current element position in saved position map
  240.     if ( szPosName )
  241.     {
  242.         SavedPos savedpos;
  243.         savedpos.iPosParent = m_iPosParent;
  244.         savedpos.iPos = m_iPos;
  245.         savedpos.iPosChild = m_iPosChild;
  246.         m_mapSavedPos[szPosName]=savedpos;
  247.         return true;
  248.     }
  249.     return false;
  250. }
  251.  
  252. bool CMarkupSTL::RestorePos( LPCTSTR szPosName )
  253. {
  254.     // Restore element position if found in saved position map
  255.     if (!szPosName)
  256.         return false;
  257.  
  258.     std::map<CString, SavedPos>::iterator iter=m_mapSavedPos.find( szPosName );
  259.  
  260.     if ( iter!=m_mapSavedPos.end() )
  261.     {
  262.         x_SetPos( iter->second.iPosParent, iter->second.iPos, iter->second.iPosChild );
  263.         return true;
  264.     }
  265.     return false;
  266. }
  267.  
  268. bool CMarkupSTL::GetOffsets( int& nStart, int& nEnd ) const
  269. {
  270.     // Return document offsets of current main position element
  271.     // This is not part of EDOM but is used by the Markup project
  272.     if ( m_iPos )
  273.     {
  274.         nStart = m_aPos[m_iPos].nStartL;
  275.         nEnd = m_aPos[m_iPos].nEndR;
  276.         return true;
  277.     }
  278.     return false;
  279. }
  280.  
  281. CString CMarkupSTL::GetChildSubDoc() const
  282. {
  283.     if ( m_iPosChild )
  284.     {
  285.         int nL = m_aPos[m_iPosChild].nStartL;
  286.         int nR = m_aPos[m_iPosChild].nEndR + 1;
  287.         TokenPos token( m_csDoc );
  288.         token.nNext = nR;
  289.         if ( ! x_FindToken(token) || m_csDoc[token.nL] == _T('<') )
  290.             nR = token.nL;
  291.         return m_csDoc.Mid( nL, nR - nL );
  292.     }
  293.     return _T("");
  294. }
  295.  
  296. bool CMarkupSTL::RemoveElem()
  297. {
  298.     // Remove current main position element
  299.     if ( m_iPos && m_nNodeType == MNT_ELEMENT )
  300.     {
  301.         int iPos = x_RemoveElem( m_iPos );
  302.         x_SetPos( m_iPosParent, iPos, 0 );
  303.         return true;
  304.     }
  305.     return false;
  306. }
  307.  
  308. bool CMarkupSTL::RemoveChildElem()
  309. {
  310.     // Remove current child position element
  311.     if ( m_iPosChild )
  312.     {
  313.         int iPosChild = x_RemoveElem( m_iPosChild );
  314.         x_SetPos( m_iPosParent, m_iPos, iPosChild );
  315.         return true;
  316.     }
  317.     return false;
  318. }
  319.  
  320. //////////////////////////////////////////////////////////////////////
  321. // Private Methods
  322. //////////////////////////////////////////////////////////////////////
  323.  
  324. int CMarkupSTL::x_GetFreePos()
  325. {
  326.     //
  327.     // This returns the index of the next unused ElemPos in the array
  328.     //
  329.     if ( m_iPosFree == m_aPos.size() )
  330.         m_aPos.resize( m_iPosFree + m_iPosFree / 2 );
  331.     ++m_iPosFree;
  332.     return m_iPosFree - 1;
  333. }
  334.  
  335. int CMarkupSTL::x_ReleasePos()
  336. {
  337.     //
  338.     // This decrements the index of the next unused ElemPos in the array
  339.     // allowing the element index returned by GetFreePos() to be reused
  340.     //
  341.     --m_iPosFree;
  342.     return 0;
  343. }
  344.  
  345. int CMarkupSTL::x_ParseError( LPCTSTR szError, LPCTSTR szName )
  346. {
  347.     if ( szName )
  348.         m_csError.Format( szError, szName );
  349.     else
  350.         m_csError = szError;
  351.     x_ReleasePos();
  352.     return -1;
  353. }
  354.  
  355. int CMarkupSTL::x_ParseElem( int iPosParent )
  356. {
  357.     // This is either called by SetDoc, x_AddSubDoc, or itself recursively
  358.     // m_aPos[iPosParent].nEndL is where to start parsing for the child element
  359.     // This returns the new position if a tag is found, otherwise zero
  360.     // In all cases we need to get a new ElemPos, but release it if unused
  361.     //
  362.     int iPos = x_GetFreePos();
  363.     m_aPos[iPos].nStartL = m_aPos[iPosParent].nEndL;
  364.     m_aPos[iPos].iElemParent = iPosParent;
  365.     m_aPos[iPos].iElemChild = 0;
  366.     m_aPos[iPos].iElemNext = 0;
  367.  
  368.     // Start Tag
  369.     // A loop is used to ignore all remarks tags and special tags
  370.     // i.e. <?xml version="1.0"?>, and <!-- comment here -->
  371.     // So any tag beginning with ? or ! is ignored
  372.     // Loop past ignored tags
  373.     TokenPos token( m_csDoc );
  374.     token.nNext = m_aPos[iPosParent].nEndL;
  375.     CString csName;
  376.     while ( csName.IsEmpty() )
  377.     {
  378.         // Look for left angle bracket of start tag
  379.         m_aPos[iPos].nStartL = token.nNext;
  380.         if ( ! x_FindChar( token.szDoc, m_aPos[iPos].nStartL, _T('<') ) )
  381.             return x_ParseError( _T("Element tag not found") );
  382.  
  383.         // Set parent's End tag to start looking from here (or later)
  384.         m_aPos[iPosParent].nEndL = m_aPos[iPos].nStartL;
  385.  
  386.         // Determine whether this is an element, or bypass other type of node
  387.         token.nNext = m_aPos[iPos].nStartL + 1;
  388.         if ( x_FindToken( token ) )
  389.         {
  390.             if ( token.bIsString )
  391.                 return x_ParseError( _T("Tag starts with quote") );
  392.             _TCHAR cFirstChar = m_csDoc[token.nL];
  393.             if ( cFirstChar == _T('?') || cFirstChar == _T('!') )
  394.             {
  395.                 token.nNext = m_aPos[iPos].nStartL;
  396.                 if ( ! x_ParseNode(token) )
  397.                     return x_ParseError( _T("Invalid node") );
  398.             }
  399.             else if ( cFirstChar != _T('/') )
  400.             {
  401.                 csName = x_GetToken( token );
  402.                 // Look for end of tag
  403.                 if ( ! x_FindChar(token.szDoc, token.nNext, _T('>')) )
  404.                     return x_ParseError( _T("End of tag not found") );
  405.             }
  406.             else
  407.                 return x_ReleasePos(); // probably end tag of parent
  408.         }
  409.         else
  410.             return x_ParseError( _T("Abrupt end within tag") );
  411.     }
  412.     m_aPos[iPos].nStartR = token.nNext;
  413.  
  414.     // Is ending mark within start tag, i.e. empty element?
  415.     if ( m_csDoc[m_aPos[iPos].nStartR-1] == _T('/') )
  416.     {
  417.         // Empty element
  418.         // Close tag left is set to ending mark, and right to open tag right
  419.         m_aPos[iPos].nEndL = m_aPos[iPos].nStartR-1;
  420.         m_aPos[iPos].nEndR = m_aPos[iPos].nStartR;
  421.     }
  422.     else // look for end tag
  423.     {
  424.         // Element probably has contents
  425.         // Determine where to start looking for left angle bracket of end tag
  426.         // This is done by recursively parsing the contents of this element
  427.         int iInner, iInnerPrev = 0;
  428.         m_aPos[iPos].nEndL = m_aPos[iPos].nStartR + 1;
  429.         while ( (iInner = x_ParseElem( iPos )) > 0 )
  430.         {
  431.             // Set links to iInner
  432.             if ( iInnerPrev )
  433.                 m_aPos[iInnerPrev].iElemNext = iInner;
  434.             else
  435.                 m_aPos[iPos].iElemChild = iInner;
  436.             iInnerPrev = iInner;
  437.  
  438.             // Set offset to reflect child
  439.             m_aPos[iPos].nEndL = m_aPos[iInner].nEndR + 1;
  440.         }
  441.         if ( iInner == -1 )
  442.             return -1;
  443.  
  444.         // Look for left angle bracket of end tag
  445.         if ( ! x_FindChar( token.szDoc, m_aPos[iPos].nEndL, _T('<') ) )
  446.             return x_ParseError( _T("End tag of %s element not found"), csName );
  447.  
  448.         // Look through tokens of end tag
  449.         token.nNext = m_aPos[iPos].nEndL + 1;
  450.         int nTokenCount = 0;
  451.         while ( x_FindToken( token ) )
  452.         {
  453.             ++nTokenCount;
  454.             if ( ! token.bIsString )
  455.             {
  456.                 // Is first token not an end slash mark?
  457.                 if ( nTokenCount == 1 && m_csDoc[token.nL] != _T('/') )
  458.                     return x_ParseError( _T("Expecting end tag of element %s"), csName );
  459.  
  460.                 else if ( nTokenCount == 2 && ! token.Match(csName) )
  461.                     return x_ParseError( _T("End tag does not correspond to %s"), csName );
  462.  
  463.                 // Else is it a right angle bracket?
  464.                 else if ( m_csDoc[token.nL] == _T('>') )
  465.                     break;
  466.             }
  467.         }
  468.  
  469.         // Was a right angle bracket not found?
  470.         if ( ! token.szDoc[token.nL] || nTokenCount < 2 )
  471.             return x_ParseError( _T("End tag not completed for element %s"), csName );
  472.         m_aPos[iPos].nEndR = token.nL;
  473.     }
  474.  
  475.     // Successfully parsed element (and contained elements)
  476.     return iPos;
  477. }
  478.  
  479. bool CMarkupSTL::x_FindChar( LPCTSTR szDoc, int& nChar, _TCHAR c )
  480. {
  481.     // static function
  482.     LPCTSTR pChar = &szDoc[nChar];
  483.     while ( *pChar && *pChar != c )
  484.         pChar += _tclen( pChar );
  485.     nChar = pChar - szDoc;
  486.     if ( ! *pChar )
  487.         return false;
  488.     /*
  489.     while ( szDoc[nChar] && szDoc[nChar] != c )
  490.         nChar += _tclen( &szDoc[nChar] );
  491.     if ( ! szDoc[nChar] )
  492.         return false;
  493.     */
  494.     return true;
  495. }
  496.  
  497. bool CMarkupSTL::x_FindToken( CMarkupSTL::TokenPos& token )
  498. {
  499.     // Starting at token.nNext, bypass whitespace and find the next token
  500.     // returns true on success, members of token point to token
  501.     // returns false on end of document, members point to end of document
  502.     LPCTSTR szDoc = token.szDoc;
  503.     int nChar = token.nNext;
  504.     token.bIsString = false;
  505.  
  506.     // By-pass leading whitespace
  507.     while ( szDoc[nChar] && _tcschr(_T(" \t\n\r"),szDoc[nChar]) )
  508.         ++nChar;
  509.     if ( ! szDoc[nChar] )
  510.     {
  511.         // No token was found before end of document
  512.         token.nL = nChar;
  513.         token.nR = nChar;
  514.         token.nNext = nChar;
  515.         return false;
  516.     }
  517.  
  518.     // Is it an opening quote?
  519.     _TCHAR cFirstChar = szDoc[nChar];
  520.     if ( cFirstChar == _T('\"') || cFirstChar == _T('\'') )
  521.     {
  522.         token.bIsString = true;
  523.  
  524.         // Move past opening quote
  525.         ++nChar;
  526.         token.nL = nChar;
  527.  
  528.         // Look for closing quote
  529.         x_FindChar( token.szDoc, nChar, cFirstChar );
  530.  
  531.         // Set right to before closing quote
  532.         token.nR = nChar - 1;
  533.  
  534.         // Set nChar past closing quote unless at end of document
  535.         if ( szDoc[nChar] )
  536.             ++nChar;
  537.     }
  538.     else
  539.     {
  540.         // Go until special char or whitespace
  541.         token.nL = nChar;
  542.         while ( szDoc[nChar] && ! _tcschr(_T(" \t\n\r<>=\\/?!"),szDoc[nChar]) )
  543.             nChar += _tclen(&szDoc[nChar]);
  544.  
  545.         // Adjust end position if it is one special char
  546.         if ( nChar == token.nL )
  547.             ++nChar; // it is a special char
  548.         token.nR = nChar - 1;
  549.     }
  550.  
  551.     // nNext points to one past last char of token
  552.     token.nNext = nChar;
  553.     return true;
  554. }
  555.  
  556. CString CMarkupSTL::x_GetToken( const CMarkupSTL::TokenPos& token ) const
  557. {
  558.     // The token contains indexes into the document identifying a small substring
  559.     // Build the substring from those indexes and return it
  560.     if ( token.nL > token.nR )
  561.         return _T("");
  562.     return m_csDoc.Mid( token.nL,
  563.         token.nR - token.nL + ((token.nR<m_csDoc.GetLength())? 1:0) );
  564. }
  565.  
  566. int CMarkupSTL::x_FindElem( int iPosParent, int iPos, LPCTSTR szPath )
  567. {
  568.     // If szPath is NULL or empty, go to next sibling element
  569.     // Otherwise go to next sibling element with matching path
  570.     //
  571.     if ( iPos )
  572.         iPos = m_aPos[iPos].iElemNext;
  573.     else
  574.         iPos = m_aPos[iPosParent].iElemChild;
  575.  
  576.     // Finished here if szPath not specified
  577.     if ( szPath == NULL || !szPath[0] )
  578.         return iPos;
  579.  
  580.     // Search
  581.     TokenPos token( m_csDoc );
  582.     while ( iPos )
  583.     {
  584.         // Compare tag name
  585.         token.nNext = m_aPos[iPos].nStartL + 1;
  586.         x_FindToken( token ); // Locate tag name
  587.         if ( token.Match(szPath) )
  588.             return iPos;
  589.         iPos = m_aPos[iPos].iElemNext;
  590.     }
  591.     return 0;
  592. }
  593.  
  594. int CMarkupSTL::x_ParseNode( CMarkupSTL::TokenPos& token )
  595. {
  596.     // Call this with token.nNext set to the start of the node
  597.     // This returns the node type and token.nNext set to the char after the node
  598.     // If the node is not found or an element, token.nR is not determined
  599.     // White space between elements is a text node
  600.     int nTypeFound = 0;
  601.     LPCTSTR szDoc = token.szDoc;
  602.     token.nL = token.nNext;
  603.     if ( szDoc[token.nL] == '<' )
  604.     {
  605.         // Started with <, could be:
  606.         // <!--...--> comment
  607.         // <!DOCTYPE ...> dtd
  608.         // <?target ...?> processing instruction
  609.         // <![CDATA[...]]> cdata section
  610.         // <NAME ...> element
  611.         //
  612.         if ( ! szDoc[token.nL+1] || ! szDoc[token.nL+2] )
  613.             return 0;
  614.         _TCHAR cFirstChar = szDoc[token.nL+1];
  615.         LPCTSTR szEndOfNode = NULL;
  616.         if ( cFirstChar == _T('?') )
  617.         {
  618.             nTypeFound = MNT_PROCESSING_INSTRUCTION; // processing instruction
  619.             szEndOfNode = _T("?>");
  620.         }
  621.         else if ( cFirstChar == _T('!') )
  622.         {
  623.             _TCHAR cSecondChar = szDoc[token.nL+2];
  624.             if ( cSecondChar == _T('[') )
  625.             {
  626.                 nTypeFound = MNT_CDATA_SECTION;
  627.                 szEndOfNode = _T("]]>");
  628.             }
  629.             else if ( cSecondChar == _T('-') )
  630.             {
  631.                 nTypeFound = MNT_COMMENT;
  632.                 szEndOfNode = _T("-->");
  633.             }
  634.             else
  635.             {
  636.                 // Document type requires tokenizing because of strings and brackets
  637.                 nTypeFound = 0;
  638.                 int nBrackets = 0;
  639.                 while ( x_FindToken(token) )
  640.                 {
  641.                     if ( ! token.bIsString )
  642.                     {
  643.                         _TCHAR cChar = szDoc[token.nL];
  644.                         if ( cChar == _T('[') )
  645.                             ++nBrackets;
  646.                         else if ( cChar == _T(']') )
  647.                             --nBrackets;
  648.                         else if ( nBrackets == 0 && cChar == _T('>') )
  649.                         {
  650.                             nTypeFound = MNT_DOCUMENT_TYPE;
  651.                             break;
  652.                         }
  653.                     }
  654.                 }
  655.                 if ( ! nTypeFound )
  656.                     return 0;
  657.             }
  658.         }
  659.         else if ( cFirstChar == _T('/') )
  660.         {
  661.             // End tag means no node found within parent element
  662.             return 0;
  663.         }
  664.         else
  665.         {
  666.             nTypeFound = MNT_ELEMENT;
  667.         }
  668.  
  669.         // Search for end of node if not found yet
  670.         if ( szEndOfNode )
  671.         {
  672.             LPCTSTR pEnd = _tcsstr( &szDoc[token.nNext], szEndOfNode );
  673.             if ( ! pEnd )
  674.                 return 0; // not well-formed
  675.             token.nNext = (pEnd - szDoc) + _tcslen(szEndOfNode);
  676.         }
  677.     }
  678.     else if ( szDoc[token.nL] )
  679.     {
  680.         // It is text or whitespace because it did not start with <
  681.         nTypeFound = MNT_WHITESPACE;
  682.         if ( x_FindToken(token) )
  683.         {
  684.             if ( szDoc[token.nL] == _T('<') )
  685.                 token.nNext = token.nL;
  686.             else
  687.             {
  688.                 nTypeFound = MNT_TEXT;
  689.                 x_FindChar( token.szDoc, token.nNext, _T('<') );
  690.             }
  691.         }
  692.     }
  693.     return nTypeFound;
  694. }
  695.  
  696. CString CMarkupSTL::x_GetTagName( int iPos ) const
  697. {
  698.     // Return the tag name at specified element
  699.     TokenPos token( m_csDoc );
  700.     token.nNext = m_aPos[iPos].nStartL + 1;
  701.     if ( ! iPos || ! x_FindToken( token ) )
  702.         return _T("");
  703.  
  704.     // Return substring of document
  705.     return x_GetToken( token );
  706. }
  707.  
  708. bool CMarkupSTL::x_FindAttrib( CMarkupSTL::TokenPos& token, LPCTSTR szAttrib ) const
  709. {
  710.     // If szAttrib is NULL find next attrib, otherwise find named attrib
  711.     // Return true if found
  712.     int nAttrib = 0;
  713.     for ( int nCount = 0; x_FindToken(token); ++nCount )
  714.     {
  715.         if ( ! token.bIsString )
  716.         {
  717.             // Is it the right angle bracket?
  718.             if ( m_csDoc[token.nL] == _T('>') || m_csDoc[token.nL] == _T('/') )
  719.                 break; // attrib not found
  720.  
  721.             // Equal sign
  722.             if ( m_csDoc[token.nL] == _T('=') )
  723.                 continue;
  724.  
  725.             // Potential attribute
  726.             if ( ! nAttrib && nCount )
  727.             {
  728.                 // Attribute name search?
  729.                 if ( ! szAttrib || ! szAttrib[0] )
  730.                     return true; // return with token at attrib name
  731.  
  732.                 // Compare szAttrib
  733.                 if ( token.Match(szAttrib) )
  734.                     nAttrib = nCount;
  735.             }
  736.         }
  737.         else if ( nAttrib && nCount == nAttrib + 2 )
  738.         {
  739.             return true;
  740.         }
  741.     }
  742.  
  743.     // Not found
  744.     return false;
  745. }
  746.  
  747. CString CMarkupSTL::x_GetAttrib( int iPos, LPCTSTR szAttrib ) const
  748. {
  749.     // Return the value of the attrib at specified element
  750.     if ( ! iPos || m_nNodeType != MNT_ELEMENT )
  751.         return _T("");
  752.  
  753.     TokenPos token( m_csDoc );
  754.     token.nNext = m_aPos[iPos].nStartL + 1;
  755.     if ( szAttrib && x_FindAttrib( token, szAttrib ) )
  756.         return x_TextFromDoc( token.nL, token.nR - ((token.nR<m_csDoc.GetLength())?0:1) );
  757.     return _T("");
  758. }
  759.  
  760. bool CMarkupSTL::x_SetAttrib( int iPos, LPCTSTR szAttrib, int nValue )
  761. {
  762.     // Convert integer to string and call SetChildAttrib
  763.     _TCHAR szVal[25];
  764.     _stprintf( szVal, _T("%d"), nValue );
  765.     return x_SetAttrib( iPos, szAttrib, szVal );
  766. }
  767.  
  768. bool CMarkupSTL::x_SetAttrib( int iPos, LPCTSTR szAttrib, __int64 nValue )
  769. {
  770.     // Convert integer to string and call SetChildAttrib
  771.     _TCHAR szVal[25];
  772.     _stprintf( szVal, _T("%I64d"), nValue );
  773.     return x_SetAttrib( iPos, szAttrib, szVal );
  774. }
  775.  
  776. bool CMarkupSTL::x_SetAttrib( int iPos, LPCTSTR szAttrib, LPCTSTR szValue )
  777. {
  778.     // Set attribute in iPos element
  779.     if ( ! iPos || m_nNodeType != MNT_ELEMENT )
  780.         return false;
  781.  
  782.     TokenPos token( m_csDoc );
  783.     token.nNext = m_aPos[iPos].nStartL + 1;
  784.     int nInsertAt, nReplace = 0;
  785.     CString csInsert;
  786.     if ( x_FindAttrib( token, szAttrib ) )
  787.     {
  788.         // Decision: for empty value leaving attrib="" instead of removing attrib
  789.         // Replace value only
  790.         csInsert = x_TextToDoc( szValue, true );
  791.         nInsertAt = token.nL;
  792.         nReplace = token.nR-token.nL+1;
  793.     }
  794.     else
  795.     {
  796.         // Insert string name value pair
  797.         CString csFormat;
  798.         csFormat = _T(" ");
  799.         csFormat += szAttrib;
  800.         csFormat += _T("=\"");
  801.         csFormat += x_TextToDoc( szValue, true );
  802.         csFormat += _T("\"");
  803.         csInsert = csFormat;
  804.  
  805.         // take into account whether it is an empty element
  806.         nInsertAt = m_aPos[iPos].nStartR - (m_aPos[iPos].IsEmptyElement()?1:0);
  807.     }
  808.  
  809.     x_DocChange( nInsertAt, nReplace, csInsert );
  810.     int nAdjust = csInsert.GetLength() - nReplace;
  811.     m_aPos[iPos].nStartR += nAdjust;
  812.     m_aPos[iPos].AdjustEnd( nAdjust );
  813.     x_Adjust( iPos, nAdjust );
  814.     MARKUP_SETDEBUGSTATE;
  815.     return true;
  816. }
  817.  
  818. bool CMarkupSTL::x_CreateNode( CString& csNode, int nNodeType, LPCTSTR szText )
  819. {
  820.     // Set csNode based on nNodeType and szData
  821.     // Return false if szData would jeopardize well-formed document
  822.     //
  823.     switch ( nNodeType )
  824.     {
  825.     case MNT_CDATA_SECTION:
  826.         if ( _tcsstr(szText,_T("]]>")) != NULL )
  827.             return false;
  828.         csNode = "<![CDATA[";
  829.         csNode += szText;
  830.         csNode += "]]>";
  831.         break;
  832.     }
  833.     return true;
  834. }
  835.  
  836. bool CMarkupSTL::x_SetData( int iPos, LPCTSTR szData, int nCDATA )
  837. {
  838.     // Set data at specified position
  839.     // if nCDATA==1, set content of element to a CDATA Section
  840.     CString csInsert;
  841.  
  842.     // Set data in iPos element
  843.     if ( ! iPos || m_aPos[iPos].iElemChild )
  844.         return false;
  845.  
  846.     // Build csInsert from szData based on nCDATA
  847.     // If CDATA section not valid, use parsed text (PCDATA) instead
  848.     if ( nCDATA != 0 )
  849.         if ( ! x_CreateNode(csInsert, MNT_CDATA_SECTION, szData) )
  850.             nCDATA = 0;
  851.     if ( nCDATA == 0 )
  852.         csInsert = x_TextToDoc( szData );
  853.  
  854.     // Decide where to insert
  855.     int nInsertAt, nReplace;
  856.     if ( m_aPos[iPos].IsEmptyElement() )
  857.     {
  858.         nInsertAt = m_aPos[iPos].nEndL;
  859.         nReplace = 1;
  860.  
  861.         // Pre-adjust since <NAME/> becomes <NAME>data</NAME>
  862.         CString csTagName = x_GetTagName( iPos );
  863.         m_aPos[iPos].nStartR -= 1;
  864.         m_aPos[iPos].nEndL -= (1 + csTagName.GetLength());
  865.         CString csFormat;
  866.         csFormat = _T(">");
  867.         csFormat += csInsert;
  868.         csFormat += _T("</");
  869.         csFormat += csTagName;
  870.         csInsert = csFormat;
  871.     }
  872.     else
  873.     {
  874.         nInsertAt = m_aPos[iPos].nStartR+1;
  875.         nReplace = m_aPos[iPos].nEndL - m_aPos[iPos].nStartR - 1;
  876.     }
  877.     x_DocChange( nInsertAt, nReplace, csInsert );
  878.     int nAdjust = csInsert.GetLength() - nReplace;
  879.     x_Adjust( iPos, nAdjust );
  880.     m_aPos[iPos].AdjustEnd( nAdjust );
  881.     MARKUP_SETDEBUGSTATE;
  882.     return true;
  883. }
  884.  
  885. CString CMarkupSTL::x_GetData( int iPos ) const
  886. {
  887.     // Return a string representing data between start and end tag
  888.     // Return empty string if there are any children elements
  889.     if ( ! m_aPos[iPos].iElemChild && ! m_aPos[iPos].IsEmptyElement() )
  890.     {
  891.         // See if it is a CDATA section
  892.         TokenPos token( m_csDoc );
  893.         token.nNext = m_aPos[iPos].nStartR+1;
  894.         if ( x_FindToken( token ) && m_csDoc[token.nL] == _T('<')
  895.                 && token.nL + 11 < m_aPos[iPos].nEndL
  896.                 && _tcsncmp( &token.szDoc[token.nL+1], _T("![CDATA["), 8 ) == 0 )
  897.         {
  898.             int nEndCDATA = m_csDoc.Find( _T("]]>"), token.nNext );
  899.             if ( nEndCDATA != -1 && nEndCDATA < m_aPos[iPos].nEndL )
  900.             {
  901.                 return m_csDoc.Mid( token.nL+9, nEndCDATA-token.nL-9 );
  902.             }
  903.         }
  904.         return x_TextFromDoc( m_aPos[iPos].nStartR+1, m_aPos[iPos].nEndL-1 );
  905.     }
  906.     return _T("");
  907. }
  908.  
  909. CString CMarkupSTL::x_TextToDoc( LPCTSTR szText, bool bAttrib ) const
  910. {
  911.     // Convert text as seen outside XML document to XML friendly
  912.     // replacing special characters with ampersand escape codes
  913.     // E.g. convert "6>7" to "6>7"
  914.     //
  915.     // <   less than
  916.     // &  ampersand
  917.     // >   greater than
  918.     //
  919.     // and for attributes:
  920.     //
  921.     // ' apostrophe or single quote
  922.     // " double quote
  923.     //
  924.     static _TCHAR* szaReplace[] = { _T("<"),_T("&"),_T(">"),_T("'"),_T(""") };
  925.     const _TCHAR* pFind = bAttrib?_T("<&>\'\""):_T("<&>");
  926.     CString csText;
  927.     const _TCHAR* pSource = szText;
  928.     int nDestSize = _tcslen(pSource);
  929.     nDestSize += nDestSize / 10 + 7;
  930.     _TCHAR* pDest = csText.GetBuffer(nDestSize);
  931.     int nLen = 0;
  932.     _TCHAR cSource = *pSource;
  933.     _TCHAR* pFound;
  934.     while ( cSource )
  935.     {
  936.         if ( nLen > nDestSize - 6 )
  937.         {
  938.             csText.ReleaseBuffer(nLen);
  939.             nDestSize *= 2;
  940.             pDest = csText.GetBuffer(nDestSize);
  941.         }
  942.         if ( (pFound=(_TCHAR *)_tcschr(pFind,cSource)) != NULL )
  943.         {
  944.             pFound = szaReplace[pFound-pFind];
  945.             _tcscpy(&pDest[nLen],pFound);
  946.             nLen += _tcslen(pFound);
  947.         }
  948.         else
  949.         {
  950.             _tccpy( &pDest[nLen], pSource );
  951.             ++nLen;
  952.         }
  953.         pSource += _tclen( pSource );
  954.         cSource = *pSource;
  955.     }
  956.     csText.ReleaseBuffer(nLen);
  957.     return csText;
  958. }
  959.  
  960. CString CMarkupSTL::x_TextFromDoc( int nLeft, int nRight ) const
  961. {
  962.     // Convert XML friendly text to text as seen outside XML document
  963.     // replacing ampersand escape codes with special characters
  964.     // E.g. convert "6>7" to "6>7"
  965.     //
  966.     // Conveniently the result is always the same or shorter in length
  967.     //
  968.     static _TCHAR* szaCode[] = { _T("lt;"),_T("amp;"),_T("gt;"),_T("apos;"),_T("quot;") };
  969.     static int anCodeLen[] = { 3,4,3,5,5 };
  970.     static _TCHAR* szSymbol = _T("<&>\'\"");
  971.     CString csText;
  972.     const _TCHAR* pSource = m_csDoc;
  973.     int nDestSize = nRight - nLeft + 1;
  974.     _TCHAR* pDest = csText.GetBuffer(nDestSize);
  975.     int nLen = 0;
  976.     int nCharLen;
  977.     int nChar = nLeft;
  978.     while ( nChar <= nRight )
  979.     {
  980.         if ( pSource[nChar] == _T('&') )
  981.         {
  982.             // Look for matching &code;
  983.             int nMatch;
  984.             for ( nMatch = 0; nMatch < 5; ++nMatch )
  985.             {
  986.                 if ( nChar <= nRight - anCodeLen[nMatch]
  987.                     && _tcsncmp(szaCode[nMatch],&pSource[nChar+1],anCodeLen[nMatch]) == 0 )
  988.                 {
  989.                     pDest[nLen++] = szSymbol[nMatch];
  990.                     nChar += anCodeLen[nMatch] + 1;
  991.                     break;
  992.                 }
  993.             }
  994.  
  995.             // If no match is found it means XML doc is invalid
  996.             // no devastating harm done, ampersand code will just be left in result
  997.             if ( nMatch == 5 )
  998.             {
  999.                 pDest[nLen++] = _T('&');
  1000.                 ++nChar;
  1001.             }
  1002.         }
  1003.         else
  1004.         {
  1005.             nCharLen = _tclen(&pSource[nChar]);
  1006.             _tccpy( &pDest[nLen], &pSource[nChar] );
  1007.             nLen += nCharLen;
  1008.             nChar += nCharLen;
  1009.         }
  1010.     }
  1011.     csText.ReleaseBuffer(nLen);
  1012.     return csText;
  1013. }
  1014.  
  1015. void CMarkupSTL::x_DocChange( int nLeft, int nReplace, const CString& csInsert )
  1016. {
  1017.     // Insert csInsert int m_csDoc at nLeft replacing nReplace chars
  1018.     // Do this with only one buffer reallocation if it grows
  1019.     //
  1020.     int nDocLength = m_csDoc.GetLength();
  1021.     int nInsLength = csInsert.GetLength();
  1022.  
  1023.     // Make sure nLeft and nReplace are within bounds
  1024.     nLeft = max( 0, min( nLeft, nDocLength ) );
  1025.     nReplace = max( 0, min( nReplace, nDocLength-nLeft ) );
  1026.  
  1027.     // Get pointer to buffer with enough room
  1028.     int nNewLength = nInsLength + nDocLength - nReplace;
  1029.     int nBufferLen = nNewLength;
  1030.     _TCHAR* pDoc = m_csDoc.GetBuffer( nBufferLen );
  1031.  
  1032.     // Move part of old doc that goes after insert
  1033.     if ( nLeft+nReplace < nDocLength )
  1034.         memmove( &pDoc[nLeft+nInsLength], &pDoc[nLeft+nReplace], (nDocLength-nLeft-nReplace)*sizeof(_TCHAR) );
  1035.  
  1036.     // Copy insert
  1037.     memcpy( &pDoc[nLeft], csInsert, nInsLength*sizeof(_TCHAR) );
  1038.  
  1039.     // Release
  1040.     m_csDoc.ReleaseBuffer( nNewLength );
  1041. }
  1042.  
  1043. void CMarkupSTL::x_Adjust( int iPos, int nShift, bool bAfterPos )
  1044. {
  1045.     // Loop through affected elements and adjust indexes
  1046.     // Algorithm:
  1047.     // 1. update children unless bAfterPos
  1048.     //    (if no children or bAfterPos is true, end tag of iPos not affected)
  1049.     // 2. update next siblings and their children
  1050.     // 3. go up until there is a next sibling of a parent and update end tags
  1051.     // 4. step 2
  1052.     int iPosTop = m_aPos[iPos].iElemParent;
  1053.     bool bPosFirst = bAfterPos; // mark as first to skip its children
  1054.     while ( iPos )
  1055.     {
  1056.         // Were we at containing parent of affected position?
  1057.         bool bPosTop = false;
  1058.         if ( iPos == iPosTop )
  1059.         {
  1060.             // Move iPosTop up one towards root
  1061.             iPosTop = m_aPos[iPos].iElemParent;
  1062.             bPosTop = true;
  1063.         }
  1064.  
  1065.         // Traverse to the next update position
  1066.         if ( ! bPosTop && ! bPosFirst && m_aPos[iPos].iElemChild )
  1067.         {
  1068.             // Depth first
  1069.             iPos = m_aPos[iPos].iElemChild;
  1070.         }
  1071.         else if ( m_aPos[iPos].iElemNext )
  1072.         {
  1073.             iPos = m_aPos[iPos].iElemNext;
  1074.         }
  1075.         else
  1076.         {
  1077.             // Look for next sibling of a parent of iPos
  1078.             // When going back up, parents have already been done except iPosTop
  1079.             while ( (iPos=m_aPos[iPos].iElemParent) != 0 && iPos != iPosTop )
  1080.                 if ( m_aPos[iPos].iElemNext )
  1081.                 {
  1082.                     iPos = m_aPos[iPos].iElemNext;
  1083.                     break;
  1084.                 }
  1085.         }
  1086.         bPosFirst = false;
  1087.  
  1088.         // Shift indexes at iPos
  1089.         if ( iPos != iPosTop )
  1090.             m_aPos[iPos].AdjustStart( nShift );
  1091.         m_aPos[iPos].AdjustEnd( nShift );
  1092.     }
  1093. }
  1094.  
  1095. void CMarkupSTL::x_LocateNew( int iPosParent, int& iPosRel, int& nOffset, int nLength, int nFlags )
  1096. {
  1097.     // Determine where to insert new element or node
  1098.     //
  1099.     bool bInsert = (nFlags&1)?true:false;
  1100.     bool bHonorWhitespace = (nFlags&2)?true:false;
  1101.  
  1102.     int nStartL;
  1103.     if ( nLength )
  1104.     {
  1105.         // Located at a non-element node
  1106.         if ( bInsert )
  1107.             nStartL = nOffset;
  1108.         else
  1109.             nStartL = nOffset + nLength;
  1110.     }
  1111.     else if ( iPosRel )
  1112.     {
  1113.         // Located at an element
  1114.         if ( bInsert ) // precede iPosRel
  1115.             nStartL = m_aPos[iPosRel].nStartL;
  1116.         else // follow iPosRel
  1117.             nStartL = m_aPos[iPosRel].nEndR + 1;
  1118.     }
  1119.     else if ( m_aPos[iPosParent].IsEmptyElement() )
  1120.     {
  1121.         // Parent has no separate end tag, so split empty element
  1122.         nStartL = m_aPos[iPosParent].nStartR;
  1123.     }
  1124.     else
  1125.     {
  1126.         if ( bInsert ) // after start tag
  1127.             nStartL = m_aPos[iPosParent].nStartR + 1;
  1128.         else // before end tag
  1129.             nStartL = m_aPos[iPosParent].nEndL;
  1130.     }
  1131.  
  1132.     // Go up to start of next node, unless its splitting an empty element
  1133.     if ( ! bHonorWhitespace && ! m_aPos[iPosParent].IsEmptyElement() )
  1134.     {
  1135.         TokenPos token( m_csDoc );
  1136.         token.nNext = nStartL;
  1137.         if ( ! x_FindToken(token) || m_csDoc[token.nL] == _T('<') )
  1138.             nStartL = token.nL;
  1139.     }
  1140.  
  1141.     // Determine iPosBefore
  1142.     int iPosBefore = 0;
  1143.     if ( iPosRel )
  1144.     {
  1145.         if ( bInsert )
  1146.         {
  1147.             // Is iPosRel past first sibling?
  1148.             int iPosPrev = m_aPos[iPosParent].iElemChild;
  1149.             if ( iPosPrev != iPosRel )
  1150.             {
  1151.                 // Find previous sibling of iPosRel
  1152.                 while ( m_aPos[iPosPrev].iElemNext != iPosRel )
  1153.                     iPosPrev = m_aPos[iPosPrev].iElemNext;
  1154.                 iPosBefore = iPosPrev;
  1155.             }
  1156.         }
  1157.         else
  1158.         {
  1159.             iPosBefore = iPosRel;
  1160.         }
  1161.     }
  1162.     else if ( m_aPos[iPosParent].iElemChild )
  1163.     {
  1164.         if ( ! bInsert )
  1165.         {
  1166.             // Find last element under iPosParent
  1167.             int iPosLast = m_aPos[iPosParent].iElemChild;
  1168.             int iPosNext = iPosLast;
  1169.             while ( iPosNext )
  1170.             {
  1171.                 iPosLast = iPosNext;
  1172.                 iPosNext = m_aPos[iPosNext].iElemNext;
  1173.             }
  1174.             iPosBefore = iPosLast;
  1175.         }
  1176.     }
  1177.  
  1178.     nOffset = nStartL;
  1179.     iPosRel = iPosBefore;
  1180. }
  1181.  
  1182. bool CMarkupSTL::x_AddElem( LPCTSTR szName, LPCTSTR szValue, bool bInsert, bool bAddChild )
  1183. {
  1184.     if ( bAddChild )
  1185.     {
  1186.         // Adding a child element under main position
  1187.         if ( ! m_iPos )
  1188.             return false;
  1189.     }
  1190.     else if ( m_iPosParent == 0 )
  1191.     {
  1192.         // Adding root element
  1193.         if ( IsWellFormed() )
  1194.             return false;
  1195.  
  1196.         // Locate after any version and DTD
  1197.         m_aPos[0].nEndL = m_csDoc.GetLength();
  1198.     }
  1199.  
  1200.     // Locate where to add element relative to current node
  1201.     int iPosParent, iPosBefore, nOffset = 0, nLength = 0;
  1202.     if ( bAddChild )
  1203.     {
  1204.         iPosParent = m_iPos;
  1205.         iPosBefore = m_iPosChild;
  1206.     }
  1207.     else
  1208.     {
  1209.         iPosParent = m_iPosParent;
  1210.         iPosBefore = m_iPos;
  1211.     }
  1212.     int nFlags = bInsert?1:0;
  1213.     x_LocateNew( iPosParent, iPosBefore, nOffset, nLength, nFlags );
  1214.     bool bEmptyParent = m_aPos[iPosParent].IsEmptyElement();
  1215.     if ( bEmptyParent )
  1216.         nOffset += 2; // include CRLF
  1217.  
  1218.     // Create element and modify positions of affected elements
  1219.     // If no szValue is specified, an empty element is created
  1220.     // i.e. either <NAME>value</NAME> or <NAME/>
  1221.     //
  1222.     int iPos = x_GetFreePos();
  1223.     m_aPos[iPos].nStartL = nOffset;
  1224.  
  1225.     // Set links
  1226.     m_aPos[iPos].iElemParent = iPosParent;
  1227.     m_aPos[iPos].iElemChild = 0;
  1228.     m_aPos[iPos].iElemNext = 0;
  1229.     if ( iPosBefore )
  1230.     {
  1231.         // Link in after iPosBefore
  1232.         m_aPos[iPos].iElemNext = m_aPos[iPosBefore].iElemNext;
  1233.         m_aPos[iPosBefore].iElemNext = iPos;
  1234.     }
  1235.     else
  1236.     {
  1237.         // First child
  1238.         m_aPos[iPos].iElemNext = m_aPos[iPosParent].iElemChild;
  1239.         m_aPos[iPosParent].iElemChild = iPos;
  1240.     }
  1241.  
  1242.     // Create string for insert
  1243.     CString csInsert;
  1244.     int nLenName = _tcslen(szName);
  1245.     int nLenValue = szValue? _tcslen(szValue) : 0;
  1246.     if ( ! nLenValue )
  1247.     {
  1248.         // <NAME/> empty element
  1249.         csInsert = _T("<");
  1250.         csInsert += szName;
  1251.         csInsert += _T("/>\r\n");
  1252.         m_aPos[iPos].nStartR = m_aPos[iPos].nStartL + nLenName + 2;
  1253.         m_aPos[iPos].nEndL = m_aPos[iPos].nStartR - 1;
  1254.         m_aPos[iPos].nEndR = m_aPos[iPos].nEndL + 1;
  1255.     }
  1256.     else
  1257.     {
  1258.         // <NAME>value</NAME>
  1259.         CString csValue = x_TextToDoc( szValue );
  1260.         nLenValue = csValue.GetLength();
  1261.         csInsert = _T("<");
  1262.         csInsert += szName;
  1263.         csInsert += _T(">");
  1264.         csInsert += csValue;
  1265.         csInsert += _T("</");
  1266.         csInsert += szName;
  1267.         csInsert += _T(">\r\n");
  1268.         m_aPos[iPos].nStartR = m_aPos[iPos].nStartL + nLenName + 1;
  1269.         m_aPos[iPos].nEndL = m_aPos[iPos].nStartR + nLenValue + 1;
  1270.         m_aPos[iPos].nEndR = m_aPos[iPos].nEndL + nLenName + 2;
  1271.     }
  1272.  
  1273.     // Insert
  1274.     int nReplace = 0, nLeft = m_aPos[iPos].nStartL;
  1275.     if ( bEmptyParent )
  1276.     {
  1277.         CString csParentTagName = x_GetTagName(iPosParent);
  1278.         CString csFormat;
  1279.         csFormat = _T(">\r\n");
  1280.         csFormat += csInsert;
  1281.         csFormat += _T("</");
  1282.         csFormat += csParentTagName;
  1283.         csInsert = csFormat;
  1284.         nLeft -= 3;
  1285.         nReplace = 1;
  1286.         // x_Adjust is going to update all affected indexes by one amount
  1287.         // This will satisfy all except the empty parent
  1288.         // Here we pre-adjust for the empty parent
  1289.         // The empty tag slash is removed
  1290.         m_aPos[iPosParent].nStartR -= 1;
  1291.         // For the newly created end tag, see the following example:
  1292.         // <A/> (len 4) becomes <A><B/></A> (len 11)
  1293.         // In x_Adjust everything will be adjusted 11 - 4 = 7
  1294.         // But the nEndL of element A should only be adjusted 5
  1295.         m_aPos[iPosParent].nEndL -= (csParentTagName.GetLength() + 1);
  1296.     }
  1297.     x_DocChange( nLeft, nReplace, csInsert );
  1298.     x_Adjust( iPos, csInsert.GetLength() - nReplace );
  1299.  
  1300.     if ( bAddChild )
  1301.         x_SetPos( m_iPosParent, iPosParent, iPos );
  1302.     else
  1303.         x_SetPos( iPosParent, iPos, 0 );
  1304.     return true;
  1305. }
  1306.  
  1307. bool CMarkupSTL::x_AddSubDoc( LPCTSTR szSubDoc, bool bInsert, bool bAddChild )
  1308. {
  1309.     // Add subdocument, parse, and modify positions of affected elements
  1310.     //
  1311.     int nOffset = 0, iPosParent, iPosBefore;
  1312.     if ( bAddChild )
  1313.     {
  1314.         // Add a subdocument under main position, after current child position
  1315.         if ( ! m_iPos )
  1316.             return false;
  1317.         iPosParent = m_iPos;
  1318.         iPosBefore = m_iPosChild;
  1319.     }
  1320.     else
  1321.     {
  1322.         iPosParent = m_iPosParent;
  1323.         iPosBefore = m_iPos;
  1324.     }
  1325.     int nFlags = bInsert?1:0;
  1326.     x_LocateNew( iPosParent, iPosBefore, nOffset, 0, nFlags );
  1327.     bool bEmptyParent = m_aPos[iPosParent].IsEmptyElement();
  1328.     if ( bEmptyParent )
  1329.         nOffset += 2; // include CRLF
  1330.  
  1331.     // if iPosBefore is NULL, insert as first element under parent
  1332.     int nParentEndLBeforeAdd = m_aPos[iPosParent].nEndL;
  1333.     int iPosFreeBeforeAdd = m_iPosFree;
  1334.  
  1335.     // Skip version tag or DTD at start of subdocument
  1336.     TokenPos token( szSubDoc );
  1337.     int nNodeType = x_ParseNode( token );
  1338.     while ( nNodeType && nNodeType != MNT_ELEMENT )
  1339.     {
  1340.         token.szDoc = &szSubDoc[token.nNext];
  1341.         token.nNext = 0;
  1342.         nNodeType = x_ParseNode( token );
  1343.     }
  1344.     CString csInsert = token.szDoc;
  1345.  
  1346.     // Insert subdocument
  1347.     m_aPos[iPosParent].nEndL = nOffset;
  1348.     int nReplace = 0, nLeft = nOffset;
  1349.     CString csParentTagName;
  1350.     if ( bEmptyParent )
  1351.     {
  1352.         csParentTagName = x_GetTagName(iPosParent);
  1353.         CString csFormat;
  1354.         csFormat = _T(">\r\n");
  1355.         csFormat += csInsert;
  1356.         csFormat += _T("</");
  1357.         csFormat += csParentTagName;
  1358.         csInsert = csFormat;
  1359.         m_aPos[iPosParent].nEndL = m_aPos[iPosParent].nStartR + 2;
  1360.         nLeft = m_aPos[iPosParent].nStartR - 1;
  1361.         nReplace = 1;
  1362.     }
  1363.     x_DocChange( nLeft, nReplace, csInsert );
  1364.  
  1365.     // Parse subdocument
  1366.     int iPos = x_ParseElem(iPosParent);
  1367.     m_aPos[iPosParent].nEndL = nParentEndLBeforeAdd;
  1368.     if ( iPos <= 0 )
  1369.     {
  1370.         // Abort because not well-formed
  1371.         CString csRevert = bEmptyParent?_T("/"):_T("");
  1372.         x_DocChange( nLeft, csInsert.GetLength(), csRevert );
  1373.         m_iPosFree = iPosFreeBeforeAdd;
  1374.         return false;
  1375.     }
  1376.     else
  1377.     {
  1378.         // Link in parent and siblings
  1379.         m_aPos[iPos].iElemParent = iPosParent;
  1380.         if ( iPosBefore )
  1381.         {
  1382.             m_aPos[iPos].iElemNext = m_aPos[iPosBefore].iElemNext;
  1383.             m_aPos[iPosBefore].iElemNext = iPos;
  1384.         }
  1385.         else
  1386.         {
  1387.             m_aPos[iPos].iElemNext = m_aPos[iPosParent].iElemChild;
  1388.             m_aPos[iPosParent].iElemChild = iPos;
  1389.         }
  1390.  
  1391.         // Make empty parent pre-adjustment
  1392.         if ( bEmptyParent )
  1393.         {
  1394.             m_aPos[iPosParent].nStartR -= 1;
  1395.             m_aPos[iPosParent].nEndL -= (csParentTagName.GetLength() + 1);
  1396.         }
  1397.  
  1398.         // Adjust, but don't adjust children of iPos (bAfterPos=true)
  1399.         x_Adjust( iPos, csInsert.GetLength() - nReplace, true );
  1400.     }
  1401.  
  1402.     // Set position to top element of subdocument
  1403.     if ( bAddChild )
  1404.         x_SetPos( m_iPosParent, iPosParent, iPos );
  1405.     else // Main
  1406.         x_SetPos( m_iPosParent, iPos, 0 );
  1407.     return true;
  1408. }
  1409.  
  1410. int CMarkupSTL::x_RemoveElem( int iPos )
  1411. {
  1412.     // Remove element and all contained elements
  1413.     // Return new position
  1414.     //
  1415.     int iPosParent = m_aPos[iPos].iElemParent;
  1416.  
  1417.     // Find previous sibling and bypass removed element
  1418.     // This leaves orphan positions in m_aPos array
  1419.     int iPosLook = m_aPos[iPosParent].iElemChild;
  1420.     int iPosPrev = 0;
  1421.     while ( iPosLook != iPos )
  1422.     {
  1423.         iPosPrev = iPosLook;
  1424.         iPosLook = m_aPos[iPosLook].iElemNext;
  1425.     }
  1426.     if ( iPosPrev )
  1427.         m_aPos[iPosPrev].iElemNext = m_aPos[iPos].iElemNext;
  1428.     else
  1429.         m_aPos[iPosParent].iElemChild = m_aPos[iPos].iElemNext;
  1430.  
  1431.     // Remove from document
  1432.     // Links have been changed to go around removed element
  1433.     // But element position and links are still valid
  1434.     int nAfterEnd = m_aPos[iPos].nEndR + 1;
  1435.     TokenPos token( m_csDoc );
  1436.     token.nNext = nAfterEnd;
  1437.     if ( ! x_FindToken(token) || token.szDoc[token.nL] == _T('<') )
  1438.         nAfterEnd = token.nL;
  1439.     int nLen = nAfterEnd - m_aPos[iPos].nStartL;
  1440.     x_DocChange( m_aPos[iPos].nStartL, nLen, CString() );
  1441.     x_Adjust( iPos, - nLen, true );
  1442.     return iPosPrev;
  1443. }
  1444.  
  1445.