
SET PROCEDURE TO wwScripting ADDITIVE
*SET PROCEDURE TO wwEVAL ADDITIVE

*************************************************************
DEFINE CLASS wwScripting AS Custom
*************************************************************
*: Author: Rick Strahl
*:         (c) West Wind Technologies, 2004
*:Contact: http://www.west-wind.com
*:Created: 09/24/2004
*************************************************************
#IF .F.
*:Help Documentation
*:Topic:
Class wwScripting 

*:Description:
This class provides end user applications with the ability to
dynamically evaluate and execute templates. It provides both
a TextMerge expression evaluator and a fully capable script
parser using Visual FoxPro language code. Both mechanisms
are based on the ASP <%=  %> script tag expressions.

*:Remarks
This class requires VFP 8 or later

*:ENDHELP
#ENDIF
************************************************************************

*** Error Message if a failure occurs
cErrorMsg = ""

*** Error flag that can and should be checked after running a script or template
lError = .F.

*** A VFP 8/9 Exception object
oException = null

*** If .t. doesn't delete the generate VFP source code after compiling
lSaveVfpSourceCode = .F.

*** If .T. pops up an editor window for editing the error in the code
lEditErrors = .f.

*** Flag used to cause parser to stop on the line of code causing error in VFP
*** This flag should be used only as a debugging feature.
lStopOnError = .F.

*** The HTML page header (including <html> and HTML text) that is displayed over
*** the table of error information. This property is used to override the display
cErrorHeader = ""

*** Path where our compiled FXP and WCT files are stored
cCompiledPath = ""

PROTECTED cCurrentTemplate
*** Internal property that holds the currently processing template
cCurrentTemplate = ""


*** Not used at this time
lUnloadAfterRunning = .F.

*** Not used at this time
lNoVersionCheck = .f.


************************************************************************
FUNCTION AspTextMerge(lcAspCode,llIsFile)
****************************************
#IF .F.
*:Help Documentation
*:Topic:
wwScripting :: AspTextMerge

*:Description:
This method is a wrapper around the VFP TextMerge function
to provide template expansion of <%= %> syntax. It also
provides proper error handling.


*:Parameters:
<<b>>lcASPCode<</b>>
The code that is to be textmerged. This code should be
TEXT with embedded <%= %> tags for expressions TO be
evaluated. Note that any expressions must be in scope
(ie. vars need to be PRIVATE or PUBLIC).

You can also pass a filename here if llIsFile is set to .t.

<<b>>llIsFile<</b>>
IF .t. specifies that the first parameter indicates a filename.

*:Returns:
Expanded text. Check lError for failures.

*:Remarks:

*:SeeAlso:

*:Keywords:

*:ENDHELP
#ENDIF
************************************************************************
LOCAL lcASPCode, lcResult

this.lError = .f.

IF llIsFile
   lcASPCode = FILETOSTR(lcAspCode)
ENDIF

*** Must convert to 2 characters from <%=
lcASPCode = STRTRAN(lcAspCode,"<%=","<%")

lcResult = ""
loException = ""
TRY 
  lcResult = TEXTMERGE(lcASPCode,.f.,"<%","%>") 
CATCH TO loException
   this.oException = loException
   this.cErrorMsg = this.oException.Message
   this.lError = .t.
ENDTRY

RETURN lcResult
ENDFUNC

************************************************************************
* wwEval :: MergeText
*********************
***  Function: This function provides an evaluation engine for FoxPro
***            expressions and Codeblocks that is tuned for Active
***            Server syntax. It works with any delimiters however. This
***            parsing engine is faster than TEXTMERGE and provides
***            extensive error checking and the ability to run
***            dynamically in the VFP runtime (ie uncompiled). Embed any
***            valid FoxPro expressions using
***
***               <%= Expression%>
***
***            and any FoxPro code with
***
***               <% CodeBlock %>
***
***            Expressions ideally should be character for optimal
***            speed, but other values are converted to string
***            automatically. Although optimized for the delimiters
***            above you may specify your own. Make sure to set the
***            llNoAspSyntax parameter to .t. to disallow the = check
***            for expressions vs code. If you use your own parameters
***            you can only evaluate expressions OR you must use ASP
***            syntax and follow your deleimiters with an = for
***            expressions.
***   Assume:  Delimiter is not used in regular text of the text.
***     Uses:  wwEval class (wwEval.prg)
***            Codeblock Class (wwEval.prg)
***     Pass:  tcString    -    String to Merge
***            tcDelimeter -    Delimiter used to embed expressions
***                             Default is "<%"
***            tcDelimeter2-    Delimiter used to embed expressions
***                             Default is "%>"
***            llNoAspSytnax    Don't interpret = following first
***                             parm as expression. Everything is
***                             evaluated as expressions.
***
***  Example:  loHTML.MergeText(HTMLDocs.MemField,"##")
*************************************************************************
FUNCTION MergeText
LPARAMETER tcString,tlIsFile
LOCAL lnLoc1,lnLoc2,lnIndex, lcEvalText, lcExtractText, lcOldError, ;
   lnErrCount, lcType,lcResult
PRIVATE plEvalError

plEvalError=.F.   && Debug Error Variable

IF tlIsFile
   tcString = FILETOSTR(tcString)
ENDIF

tcDelimiter="<%"
tcDelimiter2="%>"

*** Occurance flag for second delim AT()
IF tcDelimiter # tcDelimiter2
   lnDifferent = 1
ELSE
   lnDifferent = 2
ENDIF

lnLoc1=1
lnLoc2=1
lnIndex=0

lnErrCount=0

*** Loop through all occurances of embedding
DO WHILE lnLoc1 > 0 AND lnLoc2>0
   *** Find instance
   lnLoc1=AT(tcDelimiter,tcString,1)

   IF lnLoc1>0
      *** Now check for the ending delimiter
      lnLoc2=AT(tcDelimiter2,tcString,lnDifferent)
      IF lnLoc2 = 0
         LOOP
      ENDIF

      IF lnLoc2>lnLoc1
         *** Strip out the delimiter to get embedded expression
         lcExtractText=SUBSTR(tcString,lnLoc1+LEN(tcDelimiter),;
            lnLoc2-lnLoc1-LEN(tcDelimiter)  )

         llError = .F.
            *** ASP Syntax allows for <%= Expression %> <% CodeBlock %>
            IF  lcExtractText = "="
               llError = .f.

               TRY 
                  lcResult = EVALUATE(SUBSTR(lcExtractText,2))
               CATCH TO loException
                  llError = .t.
               ENDTRY
               IF !THIS.lError
                     IF VARTYPE(lcResult) # "C"
                        lcResult = TRANSFORM(lcResult)
                     ENDIF
               ENDIF
            ELSE
               TRY
                  lcResult = EXECSCRIPT(lcExtractText)
               CATCH TO loException   
                  llError = .T.
               ENDTRY
               
               IF VARTYPE(lcResult) # "C"
                  lcResult = ""
               ENDIF
            ENDIF

            IF !llError AND !plEvalError
               *** Now translate and evaluate the expression
               *** NOTE: Any delimiters contained in the evaluated
               ***       string are discarded!!! Otherwise we could end
               ***       up in an endless loop...
               tcString= STRTRAN(tcString,tcDelimiter+lcExtractText+tcDelimiter2,;
                  TRIM(lcResult))
            ELSE

               *** Check for EVAL error
               *         IF loEval.lError or plEvalError
               plEvalError=.F.

               *** Bail-Out Hack in case invalid bracket code
               *** causes recursive lockup
               lnErrCount=lnErrCount+1
               IF lnErrCount>150
                  EXIT
               ENDIF

               *** Error - embed an error string instead
               tcString=STRTRAN(tcString,;
                  tcDelimiter+lcExtractText+tcDelimiter2,;
                  "< % ERROR: "+STRTRAN(STRTRAN(lcExtractText,tcDelimiter,""),tcDelimiter2,"")+ " % >")
            ENDIF

         ELSE
            tcString = STUFF(tcString,lnLoc2,LEN(tcDelimiter2),SPACE(LEN(tcDelimiter2)) )
            LOOP
         ENDIF  && lnLoc2>=lnLoc1
      ENDIF     && lnLoc2>0
   ENDDO

 RETURN tcString
 ENDFUNC
 *EOF MergeText



************************************************************************
FUNCTION RenderAspScript(lcTemplate)
****************************************
#IF .F.
*:Help Documentation
*:Topic:
wwScripting :: RenderASPScript

*:Description:
Renders an ASP style page as a compiled VFP program. This method
handles creation of the VFP source code from the script file,
compilation and checking for updates of the file.

Parsing works using ASP style syntax:

<%= Expression  %>

<%
VFP Code Block
%>

The script page gets passed a Response object which you can
USE to send output directly into the output stream. 
 
*:Parameters:
lcTemplate
Name of the file on disk to parse

*:Returns:
Evaluated result from the script code.
*:ENDHELP
#ENDIF
************************************************************************
LOCAL loException, lcOutFile, lcFile, lcFXPFile, lcWCTFile, lcVFPCode,;
      lcFileName, lcFXPFile, lcWCTFile, llNeedToCompile

IF EMPTY(THIS.cCompiledPath)
   this.cCompiledPath = JUSTPATH(lcTemplate) + "\"
ELSE
   this.cCompiledPath = ADDBS(THIS.cCompiledPath)   
ENDIF

this.cCurrentTemplate = lcTemplate

*** Figure out our filenames to use
lcFileName = JUSTFNAME(lcTemplate)
lcFXPFile =  FORCEEXT(THIS.cCompiledPath + lcFileName,"FXP")

this.lError = .f.

llNeedToCompile = .T.
TRY
   *** This catches both fxp file not existing and being out of date
   *** Slightly more efficient than checking file, and the dates
   llNeedToCompile = FDATE(lcFxpFile,1) < FDATE(lcTemplate,1)
CATCH
   llNeedToCompile = .t.
ENDTRY

*** Create and compile the file only if it doesn't exist
*** or the timestamp of the FXP is older than the source file
*** NOTE: Must use SYS(2000) instead of file so we don't look for files in the EXE!
IF  llNeedToCompile
   IF !this.ConvertAndCompileScript(lcTemplate)
      RETURN this.AspScriptErrorResponse()
   ENDIF
ENDIF

*** We're going to TextMerge to a temporary file
lcOutFile = this.cCompiledPath + "WCS"+Sys(2015)+ TRANSFORM(Application.ProcessId) + ".TMP"
Response = CREATEOBJECT("wwScriptingResponse",lcOutfile) 

loException = null
IF this.lStopOnError
   DO (lcFXPFile)
ELSE

   TRY 
      *** Just execute the PRG File
      DO (lcFxpFile)
   CATCH TO loException
      this.lError = .t.
      this.cErrormsg = loException.Message

      *IF EMPTY(loException.LineContents)
         lcFile = FILE2VAR( FORCEEXT(lcFxPFile,"prg") )
         IF !EMPTY(lcFile)
            LOCAL lnCodeLines
            lnCodeLines = ALINES(laLines,lcFile)
            IF lnCodeLines > 0 AND loException.LineNo <= lnCodeLines
               loException.LineContents = ALLTRIM(laLines[loException.Lineno])
            ENDIF
         ENDIF   
      *ENDIF
      this.oException = loException
   ENDTRY   
ENDIF

IF THIS.lError
   RETURN this.AspScriptErrorResponse()
ENDIF

RETURN Response.GetOutput()
ENDFUNC


************************************************************************
FUNCTION ConvertAndCompileScript( lcTemplate as String) 
****************************************
#IF .F.
*:Help Documentation
*:Topic:
wwScripting :: ConvertAndCompileScript

*:Description:
This method takes a script file and converts it to a VFP file and
compiles it.

This method allows your code to pre-compile pages if you choose.
*:Parameters:
lcTemplate
The template to convert.
*:Returns:
.T. of .f.
*:Remarks:

*:SeeAlso:

*:Keywords:

*:ENDHELP
#ENDIF
************************************************************************
LOCAL lcWCTFile, lcFXPFile, lcFileName

lcFileName = JUSTFNAME(lcTemplate)
lcFXPFile =  FORCEEXT(THIS.cCompiledPath + lcFileName,"FXP")
lcWCTFile =  FORCEEXT(THIS.cCompiledPath + lcFileName,"PRG")

*** Must create directory if it doesn't exist
IF !ISDIR(this.cCompiledPath)
   MD (this.cCompiledPath)
ENDIF   

IF !FILE(lcTemplate)
   this.cErrorMsg = "ASP Template doesn't exist"
   this.oException = null
   this.lError = .t.
   RETURN .F.
ENDIF      

lcVFPCode = this.ConvertAspToVfpCode( FILETOSTR(lcTemplate))
STRTOFILE( lcVFPCode,lcWCTFile )

loException = null
TRY
   COMPILE ( lcWctFile )

   *** Once compiled we don't need the source code any more
   IF !this.lSaveVfpSourceCode
      ERASE ( lcWCTFile) 
   ENDIF
CATCH TO loException
   this.lError = .t.
   this.oException = loException
   this.cErrorMsg = this.oException.Message
ENDTRY


RETURN .T.
ENDFUNC

************************************************************************
FUNCTION ConvertAspToVFPCode( lcFile AS String) AS String
****************************************
#IF .F.
*:Help Documentation
*:Topic:
wwScripting :: ConvertAspTemplateToVfpCode

*:Description:
Takes an input script file and converts the file into a string 
that contains a VFP program of that script page. The page is
parsed into a PRG that executes several blocks of TEXTMERGE command
to evaluate the code in the page.

*:Parameters:
lcScriptFile
The File to convert

*:Returns:
String containing the parsed source code.

*:Remarks:

*:ENDHELP
#ENDIF
************************************************************************
LOCAL lcFile, lcVFPCode, lcEvalReplace, lcOutFile, llReturnCode, lcEvalCode,;
      lcDelim,lcDelimLength,lcLDelim,lcRDelim

*** Figure out delimiters - note Delimiters must be equal in length!
lcDelims = SET("TEXTMERGE",1)
lnDelimLength = LEN(lcDelims) / 2
lcLDelim = LEFT(lcDelims,lnDelimLength)
lcRDelim = RIGHT(lcDelims,lnDelimLength)

*** Start by converting Expressions (<%= %>) 
*** to standard TextMerge characters (<< >>)
lcEvalCode = "x"
DO WHILE !EMPTY(lcEvalCode)
   lcEvalCode = Extract(lcFile,"<%=","%>")
   IF !EMPTY(lcEvalCode)
      lcEvalReplace = "<%=" + lcEvalCode + "%>"
      lcFile = STRTRAN(lcFile,lcEvalReplace,lcLDelim + lcEvalCode + lcRDelim)
   ENDIF
ENDDO

*** Now let's deal with 'script blocks' by putting TEXT/ENDTEXT
lcFile = STRTRAN(lcFile,"<%",CHR(13)+"ENDTEXT"+CHR(13))
lcVFPCode =;
 "TEXT" + CHR(13) +;
 STRTRAN(lcFile,"%>",CHR(13)+"TEXT"+CHR(13)) + ;
 CHR(13) + "ENDTEXT"  

RETURN lcVFPCode
ENDFUNC
* ConvertPage

************************************************************************
PROTECTED FUNCTION AspScriptErrorResponse
****************************************
#IF .F.
*:Help Documentation
*:Topic:
wwScripting :: ScripterrorResponse

*:Description:
Creates an Error Response page based on the oException object
that might have captured an exception in the script execution.

*:ENDHELP
#ENDIF
************************************************************************
LOCAL lcOutput, lnErrorLine, lnErrorPos, lnLoc, lcFileContent,lcCode, lcLineContents

lcOutput = ""

*** Figure out our filenames to use
lcFileName = JUSTFNAME(this.cCurrentTemplate)
lcFXPFile =  FORCEEXT(THIS.cCompiledPath + lcFileName,"FXP")
lcWCTFile =  FORCEEXT(THIS.cCompiledPath + lcFileName,"WCT")

IF !ISNULL(this.oException)
   lcLineContents = STRTRAN(this.oException.LineContents,"<<","<%=")
   lcLineContents = STRTRAN(lcLineContents,">>","%>")

   IF EMPTY(this.cErrorHeader)
      this.cErrorHeader = ;   
         [<html>] + CHR(13)+CHR(10) +;
         [<body topmargin=0 leftmargin=0 style="font:normal 10pt verdana">] + CHR(13) +;
         [<h2 style="text-align=center;background-color:#eeeeee;Color:navy">Scripting Error</h2>] + CHR(13) +;
         [<div style="margin-left:15pt;margin-right:25pt;">] +;
         [An error occurred during the processing of the script page <b>] + lcFileName + [</b>.] + CHR(13) +;
         [Most likely there's an invalid script tag inside of this script page.<p>] + CHR(13) +;
         [The following error information is available:<p></div>] + CHR(13)
   ENDIF      
     
     lcOutput = this.cErrorHeader + ;
      [<center><TABLE BGCOLOR=#EEEEEE BORDER=1 CELLPADDING=3 STYLE="Font:normal normal 10pt Verdana;border-collapse:collapse">] + CHR(13) + CHR(10) +;
      "<tr><th style='background-color:navy;color:white;' colspan=2>Error Information</th>" + CHR(13) +;
      "<TR><TD ALGIN=RIGHT>Script:<b> </TD><TD>" + lcFileName + "</TD></TR>" + CHR(13) +;
      "<TR><TD ALGIN=RIGHT>Error:<b> </TD><TD>" +FixHtmlForDisplay( this.oException.Message ) + "</TD></TR>" + CHR(13) +;
      "<TR><TD ALGIN=RIGHT>Code:</TD><TD>" + FixHtmlForDisplay(lcLineContents) + "</TD></TR>" + CHR(13) +;
      "<TR><TD ALGIN=RIGHT>Error Number:</TD><TD>"+ TRANSFORM(this.oException.ErrorNo) + "</TD></TR>" + CHR(13) +;
      "<TR><TD ALGIN=RIGHT>Line No:</TD><TD>" + TRANSFORM(this.oException.LineNo) + "</TD></TR>" + CHR(13) +;
      "</TABLE></center><p>"

      IF this.lEditErrors AND this.lSaveVfPSourceCode
           lcCode = this.oException.LineContents

           lcCode = STRTRAN(lcCode,"<<","<%=")
           lcCode = STRTRAN(lcCode,">>","%>")
           
           lcSourceFile = this.cCurrentTemplate
           IF FILE(  lcSourceFile )
              lcFileContent = FILETOSTR( lcSourceFile)
              lnLoc = ATC(lcCode,lcfileContent)
              IF ATC(lcCode,lcfileContent) = 0
                 MODIFY FILE (lcSourceFile) IN MACDESKTOP NOWAIT
              ELSE
                 MODIFY FILE (lcSourceFile) RANGE (lnLoc),(lnLoc + LEN(lcCode)) IN MACDESKTOP NOWAIT
              ENDIF
           ENDIF
      ENDIF
ELSE
      lcOutput = [<TABLE WIDTH=100% BGCOLOR="BLACK"><TD ALIGN=CENTER><b><FONT COLOR="WHITE" FACE="VERDANA" SIZE="4">Scripting Error</FONT></b></TD></TABLE><p>] + ;
      [<TABLE BGCOLOR=#EEEEEE BORDER=1 CELLPADDING=3 STYLE="Font:normal normal 10pt Verdana">] + ;
      "<TR><TD ALGIN=RIGHT>Error: </TD><TD>"+this.oException.cErrorMsg + "</TD</TR>" + ;
      "</TABLE><p>"
ENDIF

RETURN lcOutput
ENDFUNC
* wwVFPScript :: wwEvalErrorResponse

ENDDEFINE


*************************************************************
DEFINE CLASS wwExecute AS Relation
*************************************************************
*: Author: Rick Strahl
*:         (c) West Wind Technologies, 2005
*:Contact: http://www.west-wind.com
*:Created: 01/06/2005
*************************************************************
#IF .F.
*:Help Documentation
*:Topic:
Class wwExecute

*:Description:
Simplified Safe Execution class. Allows execution of a
single command or expression with error handling WITHOUT
requiring a TRY/CATCH block.

Used here to allow for execution of code AND provide runtime
ERROR information (line no AND procedure name) which is not
available with TRY/CATCH)

*:ENDHELP
#ENDIF


lError = .f.
ErrorNo = 0
LineNo = 0
Message = ""
Method = ""
LineContents = ""

************************************************************************
* wwExecute :: ExecuteCommand
****************************************
***  Function:
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION ExecuteCommand(lcCommand)
SET DEBUG ON
SET DEVELOPMENT ON
&lcCommand
RETURN

************************************************************************
* wwExecute :: EvaluateCommand
****************************************
***  Function:
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION EvaluateCommand(lcCommand)
RETURN EVAL( lcCommand )

FUNCTION Error(nError, cMethod, nLine)

this.LineNo = nLine
this.LineContents = Message(1)
this.Message = Message()
this.ErrorNo = nError
this.Method = cMethod
this.lError = .t.
RETURN

ENDDEFINE

*************************************************************
DEFINE CLASS wwScriptingResponse AS Custom
*************************************************************
***    Author: Rick Strahl
***            (c) West Wind Technologies, 1997
***   Contact: (541) 386-2087  / rstrahl@west-wind.com
***  Modified: 11/18/97
***
***  Function: Provides basic Response.Write() functionality
***            to inject output into the generated script
***            page.
*************************************************************

*** Custom Properties
cFileName = ""

PROTECTED lOpened
lOpened = .f.

*** Stock Properties

************************************************************************
* wwScriptResponse :: Init
*********************************
***  Function:
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION Init
LPARAMETERS lcOutputFile, loVFPScript

THIS.cFileName = lcOutputFile
this.Open()

ENDFUNC
* wwScriptResponse :: Init

************************************************************************
* wwScriptResponse :: Destroy
****************************************
***  Function:
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION Destroy()

IF this.lOpened
   this.GetOutput()  && Close the file and delete
ENDIF   

ENDFUNC
*  wwScriptResponse :: Destroy


************************************************************************
* wwScriptResponse :: Open
****************************************
***  Function:
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION Open()

SET TEXTMERGE ON
SET TEXTMERGE TO (this.cFileName) NOSHOW
this.lOpened = .t.

ENDFUNC
*  wwScriptResponse :: Open

************************************************************************
* wwScriptResponse :: Write
*********************************
***  Function: Basic Output Method for scripting. Used for direct
***            call or when using <%= <expression %> syntax.
************************************************************************
FUNCTION Write
LPARAMETER lvExpression

\\<<lvExpression>>

ENDFUNC
* Write

FUNCTION SEND
LPARAMETER lvExpression

\\<<lvExpression>>
ENDFUNC

************************************************************************
* wwScriptResponse :: Clear
*********************************
***  Function:
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION Clear

this.GetOutput()

ENDFUNC
* wwScriptResponse :: Clear

FUNCTION Rewind
THIS.Clear()
ENDFUNC

************************************************************************
* wwScriptResponse :: GetOutput
*********************************
***  Function:
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION GetOutput
LOCAL lcOutput

SET TEXTMERGE TO

*** Retrieve the output
lcOutput = file2Var(THIS.cFileName)

*** And immediately erase file
ERASE (THIS.cFileName)

this.lOpened = .f.

RETURN lcOutput 
ENDFUNC
* wwScriptResponse :: GetOutput


 
ENDDEFINE
*EOC wwScriptResponse


*************************************************************
DEFINE CLASS wwHelpScripting AS wwScripting
*************************************************************
*: Author: Rick Strahl
*:         (c) West Wind Technologies, 2005
*:Contact: http://www.west-wind.com
*:Created: 01/06/2005
*************************************************************
#IF .F.
*:Help Documentation
*:Topic:
Class wwHelpScripting

*:Description:
Subclass of wwScripting that provides a custom Error Message
header.

*:Example:

*:Remarks:

*:SeeAlso:


*:ENDHELP
#ENDIF

*** Custom Properties

*** Stock Properties

cErrorHeader = ;   
 [<html><head>] + ;
 [<LINK rel="stylesheet" type="text/css" href="templates/wwhelp.css">] +;
 [</head>] + CHR(13)+CHR(10) +;
 [<body topmargin=0 leftmargin=0>] + CHR(13) +;
 [<TABLE WIDTH=100% class="banner"><TD ALIGN=CENTER><b><font SIZE="4">Help Builder Scripting Error</FONT></b></TD></TABLE><p>]+;
 [<div style="margin-left:15pt;margin-right:25pt;"><img src="file:///] + SYS(5) + CURDIR() + [bmp\images\alerticon.gif] + ;
 [" align="left">An error occurred during the processing of the Help Builder script page.] + CHR(13) +;
 [Most likely there's an invalid script tag inside of this script page. Help Builder has opened]  + CHR(13) +;
 [the script for editing and has tried to locate the cursor on the source of the error.<p>] + CHR(13) +;
 [The following error information is available:<p></div>] + CHR(13)


ENDDEFINE
*EOC wwHelpScripting 