Chapter 17: Advanced SRF


NOW THAT WE VE COVERED the basics of ATL Server SRF files and request handlers, we can examine more advanced areas; in particular, extending the stencil syntax for custom tags. Familiarity with the ATL Server stencil extensibility mechanisms will provide you with the tools to work around many of the issues and limitations you may encounter in writing your Web applications.

Extending Stencil Syntax

In this section we examine how to extend the ATL Server stencil syntax by adding custom tags and modifying the way existing tags work. We show you how to add several new control-flow tokens to demonstrate how you can override and extend the stencil syntax. You ll add the following control-flow tags:

  • if_and : Same as the if tag, but the if block is entered only if all the statements in the conditional are true (hence and )

  • if_or : Same as the if tag, but the if block is entered if any of the statements in the conditional are true (hence or )

  • if_not : Same as the if tag, but the if block is entered only if the statement in the conditional is not true

Additionally, you ll add support to the if_and and if_or tags for not statements using the ! syntax. That is, {{if_and !tag1 tag2}} evaluates to true only if tag1 is not true and tag2 is true . You ll also extend the else and endif tags to support the new conditional tags.

The ATL Server framework breaks up stencil processing into two parts : parsing the stencil and rendering the stencil. As we explained earlier, this parse-once model avoids the costs of parsing the stencil on every page hit. The top-level parsing method is CStencil::ParseReplacements , which in turn calls CStencil::ParseToken for each stencil tag that s encountered . You ll override ParseToken to handle your new tags and extend the else and endif tags. You ll also need to override CStencil::FinishParseReplacements to check for unclosed if blocks and other errors in the same way as the base CStencil class does.

The rendering of the stencil is handled in the CStencil::Render function, which in turn calls CStencil::RenderToken for each stencil token. You ll override RenderToken to handle the rendering of your new tags.

The first thing you need to do is define constants to represent your new tags:

 #define STENCIL_ANDSTART         (STENCIL_USER_TOKEN_BASE + 0x00000001)  #define STENCIL_ANDPART          (STENCIL_USER_TOKEN_BASE + 0x00000002)  #define STENCIL_ORSTART          (STENCIL_USER_TOKEN_BASE + 0x00000003)  #define STENCIL_ORPART           (STENCIL_USER_TOKEN_BASE + 0x00000004)  #define STENCIL_IFNOT            (STENCIL_USER_TOKEN_BASE + 0x00000005) 

Note that all the tags are based from STENCIL_USER_TOKEN_BASE , which is defined by the ATL Server framework and is meant to be used as a base for custom tags.

The next thing you need to do is derive a class from CStencil to handle your new tags. Because by default ATL Server request handlers derive from CHtmlStencil (which in turn derives from CStencil ), you ll derive from CHtmlStencil . You ll call your new class CCustomStencil .

 class CCustomStencil : public CHtmlStencil  {  public:      CCustomStencil(IAtlMemMgr *pMemMgr) throw()          :CHtmlStencil(pMemMgr)      {      }         // omitted   }; 

Then you need to override ParseToken to understand your new tags and override the behavior of the endif and else tags. You ll use the ATL Server support functions SkipSpace and CheckTag to verify your tags, as shown in Listing 17-1. If the token doesn t match one of your new tags or the tags you need to override, you ll delegate to the base class.

Listing 17.1: Overridden ParseToken Method
start example
 virtual PARSE_TOKEN_RESULT ParseToken(LPSTR szTokenStart,          LPSTR szTokenEnd, DWORD *pBlockStack, DWORD *pdwTop) throw()      {          LPCSTR pStart = szTokenStart;          pStart += 2; //skip curlies          pStart = SkipSpace(pStart, GetCodePage());          DWORD dwLen = (DWORD)(szTokenEnd - szTokenStart);          int nIndex = -1;          if (CheckTag("if_and", sizeof("if_and")-1, pStart, dwLen))              nIndex = ParseAnd(szTokenStart, szTokenEnd, pBlockStack, pdwTop);          else if (CheckTag("if_or", sizeof("if_or")-1, pStart, dwLen))              nIndex = ParseOr(szTokenStart, szTokenEnd, pBlockStack, pdwTop);          else if (CheckTag("endif", sizeof("endif")-1, pStart, dwLen))              nIndex = ParseEndEx(szTokenStart, szTokenEnd, pBlockStack, pdwTop);          else if (CheckTag("else", sizeof("else")-1, pStart, dwLen))              nIndex = ParseElseEx(szTokenStart, szTokenEnd, pBlockStack, pdwTop);          else if (CheckTag("if_not", sizeof("if_not")-1, pStart, dwLen))              nIndex = ParseIfNot(szTokenStart, szTokenEnd, pBlockStack, pdwTop);          else          {              // delegate to base class              return __super::ParseToken(szTokenStart, szTokenEnd,                  pBlockStack, pdwTop);          }          if (nIndex < 0)              return INVALID_TOKEN;          return RESERVED_TOKEN;      } 
end example
 

Now let s look at what you need to do implement one of these new tags in more detail. You ll take the simplest example first: if_not . All you need to do in this case is delegate to the ATL Server support function ParseReplacement with your custom token type STENCIL_IFNOT , as shown in Listing 17-2.

Listing 17.2: Overridden FinishParseReplacements Method
start example
 bool FinishParseReplacements() throw()      {          DWORD dwSize = GetTokenCount();          // iterate over the tokens          for (DWORD dwIndex = 0; dwIndex < dwSize; dwIndex++)          {              StencilToken& token = *(GetToken(dwIndex));              // only modify the new tags              bool bModify = (token.type == STENCIL_ANDPART                   token.type == STENCIL_ORPART                   token.type == STENCIL_IFNOT                    token.type == STENCIL_ANDSTART                    token.type == STENCIL_ORSTART);              // unclosed blocks are conditional start              // tags with the dwLoopIndex == STENCIL_INVALIDOFFSET              bool bUnclosedBlock =                  ((token.type == STENCIL_ANDSTART                   token.type == STENCIL_ORSTART                   token.type == STENCIL_IFNOT)                  && token.dwLoopIndex == STENCIL_INVALIDOFFSET);              if (bModify && token.szMethodName[0] &&                  (token.dwFnOffset == STENCIL_INVALIDOFFSET  bUnclosedBlock))              {                  // if it is an unclosed block or if we cannot resolve the                  // replacement method, convert to text tags                  if (bUnclosedBlock                       m_pReplacer->FindReplacementOffset(token.szMethodName, &token.dwFnOffset,                          token.szHandlerName, &token.dwObjOffset,                          &token.dwMap,                          (void **)&token.dwData, m_pMemMgr) != HTTP_SUCCESS)                  {                      DWORD dwStartIndex = dwIndex;                      // if the token is STENCIL_ANDPART or STENCIL_ORPART,                      // find the start of the block                      if (token.type == STENCIL_ANDPART                           token.type == STENCIL_ORPART)                      {                          DWORD dwStartType =                              (token.type == STENCIL_ANDPART ?                              STENCIL_ANDSTART : STENCIL_ORSTART);                          DWORD dwStartIndex = dwIndex-1;                          while (IsValidIndex(dwStartIndex) &&                              GetToken(dwStartIndex)->type != dwStartType)                          {                              --dwStartIndex;                          }                          if (GetToken(dwStartIndex)->type != dwStartType)                              dwStartIndex = dwIndex;                      }                      DWORD dwLoopIndex = GetToken(dwStartIndex)->dwLoopIndex;                      // if STENCIL_ANDSTART or STENCIL_ORSTART,                      // convert component tokens to STENCIL_CONDITIONALEND,                      // so they are skipped during rendering and set                      // szMethodName to an empty string, so it is ignored by                      // the base class's FinishParseReplacements                      if (GetToken(dwStartIndex)->type == STENCIL_ANDSTART                           GetToken(dwStartIndex)->type == STENCIL_ORSTART)                      {                          DWORD dwNextToken = dwIndex+1;                          while (dwNextToken != dwLoopIndex &&                              dwNextToken != STENCIL_INVALIDINDEX)                          {                              if (GetToken(dwNextToken)->type                                      == STENCIL_ANDPART                                   GetToken(dwNextToken)->type                                      == STENCIL_ORPART)                              {                                  GetToken(dwNextToken)->type =                                      STENCIL_CONDITIONALEND;                                  GetToken(dwNextToken)->szMethodName[0] = 0;                              }                              dwNextToken++;                          }                      }                      // unresolved replacement, convert it to a text token                      GetToken(dwStartIndex)->type = STENCIL_TEXTTAG;                      GetToken(dwStartIndex)->szMethodName[0] = 0;                      // convert all linked tokens to text tokens as well                      // this includes: endif, else, endwhile                      while (dwLoopIndex != dwIndex &&                          dwLoopIndex != STENCIL_INVALIDINDEX)                      {                          GetToken(dwLoopIndex)->type = STENCIL_TEXTTAG;                          GetToken(dwLoopIndex)->szMethodName[0] = 0;                          dwLoopIndex = GetToken(dwLoopIndex)->dwLoopIndex;                      }                  }              }          }          // call the base FinishParseReplacements to handle other tags          return __super::FinishParseReplacements();      } 
end example
 

To render the new tags, override the RenderToken function, as shown in Listing 17-3. This function will check the token type and then delegate to RenderAnd , RenderOr , and RenderIfNot . If the token type isn t one of your new tokens, delegate to the base class implementation.

Listing 17.3: Overridden RenderToken Method
start example
 DWORD RenderToken(DWORD dwIndex,          ITagReplacer* pReplacer,          IWriteStream *pWriteStream,          DWORD *pdwErrorCode,          void* pState = NULL) const throw()      {          DWORD dwNextToken = STENCIL_INVALIDINDEX;          DWORD dwErrorCode = HTTP_SUCCESS;          const StencilToken* ptoken = GetToken(dwIndex);          if (ptoken)          {              switch(ptoken->type)              {              case STENCIL_ANDSTART:                  // render the {{if_and ...}} tag                  dwNextToken = RenderAnd(dwIndex, pReplacer, &dwErrorCode);                  break;              case STENCIL_ORSTART:                  // render the {{if_or ...}} tag                  dwNextToken = RenderOr(dwIndex, pReplacer, &dwErrorCode);                  break;              case STENCIL_IFNOT:                  // render the {{if_not ...}} tag                  dwNextToken = RenderIfNot(dwIndex, pReplacer, &dwErrorCode);                  break;              default:                  // delegate to the base class                  dwNextToken = __super::RenderToken(dwIndex, pReplacer,                          pWriteStream, &dwErrorCode, (CStencilState *)pState);                  break;              }          }          if (pdwErrorCode)              *pdwErrorCode = dwErrorCode;          return dwNextToken;      } 
end example
 

Note that even though you override the parsing of the endif and else tags, you don t need to override their runtime behavior.

The RenderIfNot function is effectively the same as rendering normal if blocks with the exception of the evaluation of the condition that determines if the block is entered (see Listing 17-4).

Listing 17.4: Rendering the if_not Tag
start example
 DWORD RenderIfNot(DWORD dwIndex,          ITagReplacer *pReplacer,          DWORD *pdwErrorCode) const throw()      {          const StencilToken* ptoken = GetToken(dwIndex);          DWORD dwNextToken = STENCIL_INVALIDINDEX;          DWORD dwErrorCode = HTTP_SUCCESS;          if (!ptoken)              return STENCIL_INVALIDINDEX;          // ensure the function is valid          if (ptoken->type == STENCIL_IFNOT &&              ptoken->dwFnOffset == STENCIL_INVALIDINDEX)          {              dwErrorCode =                  HTTP_ERROR(500, ISE_SUBERR_STENCIL_INVALIDFUNCOFFSET);          }          else          {              // ensure that we end with an {{endif}} or an {{else}}              if (ptoken->dwLoopIndex == STENCIL_INVALIDINDEX)              {                  dwErrorCode =                      HTTP_ERROR(500, ISE_SUBERR_STENCIL_MISMATCHIF);              }              else              {                  // points to the end of the loop                  DWORD dwLoopIndex = ptoken->dwLoopIndex;                  // Call the replacement method.                  // If it returns HTTP_S_FALSE, we render everything up to                  //  the end of the conditional.                  // if it returns HTTP_SUCCESS, the condition is not met                  // and we render the else part if it exists or jump past                  // the endif otherwise                  DWORD dwErr = pReplacer->RenderReplacement(ptoken->dwFnOffset,                      ptoken->dwObjOffset, ptoken->dwMap, (void *)ptoken->dwData);                  if (dwErr == HTTP_S_FALSE)                  {                      // if the method returns false, execute the block                      dwNextToken = dwIndex+1;                      dwErrorCode = HTTP_SUCCESS;                  }                  else if (dwErr == HTTP_SUCCESS)                  {                      // if the method returns true,                      // jump to the end of the block                      dwNextToken = dwLoopIndex+1;                      dwErrorCode = HTTP_SUCCESS;                  }                  else                  {                      // otherwise, an error occurred                      dwNextToken = STENCIL_INVALIDINDEX;                      dwErrorCode = dwErr;                  }              }          }          if (pdwErrorCode)              *pdwErrorCode = dwErrorCode;          return dwNextToken;      } 
end example
 

For rendering if_and and if_or , you need to handle the multiple statements in the conditional as well as potential ! (not) conditions in the statement. You simply loop over the statements and call the replacement methods , as shown in Listing 17-5. Depending on whether it s an and or an or , you short-circuit on first success or first failure.

Listing 17.5: Rendering the if_and Tag
start example
 DWORD RenderAnd(DWORD dwIndex,          ITagReplacer *pReplacer,          DWORD *pdwErrorCode) const throw()      {          const StencilToken* ptoken = GetToken(dwIndex);          DWORD dwNextToken = STENCIL_INVALIDINDEX;          DWORD dwErrorCode = HTTP_SUCCESS;          if (!ptoken)              return STENCIL_INVALIDINDEX;          // ensure that the function offset is valid          if (ptoken->type == STENCIL_ANDSTART &&              ptoken->dwFnOffset == STENCIL_INVALIDINDEX)          {              dwErrorCode =                  HTTP_ERROR(500, ISE_SUBERR_STENCIL_INVALIDFUNCOFFSET);          }          else          {              // ensure that we end with and {{endif}} or and {{else}}              if (ptoken->dwLoopIndex == STENCIL_INVALIDINDEX)              {                  dwErrorCode = HTTP_ERROR(500, ISE_SUBERR_STENCIL_MISMATCHIF);              }              else              {                  // points to the end of the loop                  DWORD dwLoopIndex = ptoken->dwLoopIndex;                  DWORD dwErr;                  // loop over the components of the if_and                  do                  {                      // call the replacement method                      dwErr = pReplacer->RenderReplacement(ptoken->dwFnOffset,                          ptoken->dwObjOffset,                          ptoken->dwMap,                          (void *)ptoken->dwData);                      // check if it is '! <method_name>' call                      if (ptoken->dwData)                      {                          // invert the return code                          if (dwErr == HTTP_S_FALSE)                              dwErr = HTTP_SUCCESS;                          else if (dwErr == HTTP_SUCCESS)                              dwErr = HTTP_S_FALSE;                      }                      // short-circuit on failure                      if (dwErr)                          break;                      // go to the next token                      dwIndex++;                      // ensure that the token is valid                      if (STENCIL_SUCCESS == (dwErrorCode = IsValidIndex(dwIndex)))                      {                          ptoken = GetToken(dwIndex);                      }                      else                      {                          dwErr = dwErrorCode;                          break;                      }                      // loop until we reach the end of the {{if_and ...}} methods                  } while (ptoken->type == STENCIL_ANDPART);                  if (!dwErr)                  {                      // if successful, dwIndex is the index of the                      // first token inside the {{if_and ...}} block                      dwNextToken = dwIndex;                      dwErrorCode = HTTP_SUCCESS;                  }                  else if (dwErr == HTTP_S_FALSE)                  {                      // if we failed, jump to the end of the block                      // ({{else}} or {{endif}})                      // and increment by one                      dwNextToken = dwLoopIndex+1;                      dwErrorCode = HTTP_SUCCESS;                  }                  else                  {                      // an error has occurred                      dwNextToken = STENCIL_INVALIDINDEX;                      dwErrorCode = dwErr;                  }              }          }          if (pdwErrorCode)              *pdwErrorCode = dwErrorCode;          return dwNextToken;      } 
end example
 

You have now successfully added all the code necessary to parse and render your new tags. To use the new tags in an ATL Server request handler, you need to specify the stencil type, as shown in Listing 17-6.

Listing 17.6: Overriding the StencilType Template Parameter
start example
 class CMyHandler :      public CRequestHandlerT<          CMyHandler,          CComSingleThreadModel,  CHtmlTagReplacer<CMyHandler, CCustomStencil>  >  {  // normal request handler code   };  // class CMyHandler 
end example
 

Now CMyHandler can use the new tags in all SRF files that refer to it.




ATL Server. High Performance C++ on. NET
Observing the User Experience: A Practitioners Guide to User Research
ISBN: B006Z372QQ
EAN: 2147483647
Year: 2002
Pages: 181

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net