TMGXINST ;TMG/kst/XML Configuration Scripting System ;03/25/06
         ;;1.0;TMG-LIB;**1**;07/12/04

 ;" XML Configuration Scripting System
 ;"
 ;" K. Toppenberg, MD
 ;" 7-12-04
 ;"
 ;"Purpose: Intrepret a specially-prepaired XML file, designed
 ;"         for configuring VistA

 ;"Dependancy: Requires TMGXDLG.m, TMGSTUTL.m, TMGDEBUG.m

 ;"-------------------------------------------------------------
 ;"CHANGE LOG
 ;"10-17-04: Got WP fields to upload properly.  Created FormatArray function.
 ;"10-15-04: Forgot to log several days.  Created <FileUtility>.  Ensured data substitution
 ;"        more widely implemented.  Worked more on script. Tracked down modal dialog
 ;"        box bug (conflicting globals in two different modules).
 ;"10-5-04: Learned that WP fields must be treated differently, so worked on support.
 ;"        Had trouble with a locked record after a crash.  Learn about GTM lke utility.
 ;"10-4-04: Tracked down apparent bug in FILE^DIE that doesn't allow upload to a word
 ;"        processor field.  Also allowed redirection of debug output to a file or to
 ;"        an X graphic tail box.
 ;"10-2-05: Changed record node divider character from "/" to "|" because I could not
 ;"        ever remember to protect the / as // and I'm sure others wouldn't remember
 ;"        either.  Fixed bug that caused crash when showing error box before XML
 ;"        parse was complete, and datanode contained valid data. Changed UploadFile
 ;"        to UploadRecord with <Record></Record> syntax
 ;"10-1-04: Fixed bug with line wrapping disordering in dialog boxes.  Fixed bug
 ;"        preventing non-modal dialog boxes ("&"-->" &") NOTE: ??working?
 ;"9-30-04: Allowed data substitution {{...}} to be used in Show and message boxes.
 ;"        Fixed bug to allow multiple data substitutions on one line.
 ;"9-27-04:
 ;"        Ran a test menu upload and got Adam and TMG Text menu to upload
 ;"        Cleaned up error reporting. Discovered that including the ` character
 ;"        in upload data causes an error... haven't tracked down reason yet.
 ;"9-26-04:
 ;"        Started this change log
 ;"        Change parameter system so that unlimited number of params allowed
 ;"        Cleaned up command execution and passing of parameters
 ;"        Got X graphic dialogs working -- can call from XML script.
 ;"        Added options for a variety of user interfaces: GUI,CHUI,Roll
 ;"        Changed log in process so that user #1 is used (MGR,IRM on my system)
 ;"2/9/2008: Moved some functions out into TMGXMLT for reuse by other code.


 ;"-------------------------------------------------------------
 ;"Public Functions

 ;"Run(DispMode,DebugMode,UserPath,UserFName)

 ;"-------------------------------------------------------------
 ;"Private Functions
 ;"
 ;"ShowWelcome()
 ;"GetFName(Path,Filename)
 ;"LoadFile(Path,Filename)
 ;"ShutDown
 ;"InitVars()
 ;"CMDProcess(Command,Params)
 ;"DoComment(Params)
 ;"DoShow(Params)
 ;"DoM(Params)
 ;"DoMenu(Params)
 ;"DoLookup(Params) -- take data from XML file, and look up if it is already in database
 ;"DoValueLookup(Params) -- look for a value of a given value in a given record in given file.
 ;"DoFileUtility(Params)
 ;"DoSearchRec(Params)
 ;"DoUpload(Params)
 ;"GetRInfo(ID,Data) -- get record info from the <DATA> section and store it in the Data variable.
 ;"ProcessRNode(DataP,Field,Text,EntryNumber,FileNumber,DoingSubNodes,Flags) -- Allow for recursive calling when doing GetRInfo
 ;"WPHandle(DataP,EntryNumber,FieldNumber,Text) -- process word-processing fields for ProcessRNode()
 ;"CheckArraySubst(TextArray)
 ;"ParamSubstitute(Params)
 ;"CheckSubstituteData(Text)
 ;"DoJump(Params)
 ;"GetLabelNode(Label)
 ;"GetData(Ref)
 ;"ParseSeg(Ref,ID)
 ;"GetDescIDNode(ParentNode,Name,ID)
 ;"GetCMDLine(ExecNode,Command,Params)
 ;"GetNextCMD(ExecNode)
 ;"RunScript(ExecNode)
 ;"GetDispMode()
 ;"DoMsgBox(Params)
 ;"=================================================================
 ;"=================================================================


Run(DispMode,DebugMode,UserPath,UserFName)
        ;"Purpose: To use given XML filename to process
        ;"Input:
        ;"  DispMode: OPTIONAL -- If not given, will ask user.  Should be
        ;"        1 for GUI
        ;"        2 for CHUI
        ;"        3 for Roll-n-Scroll
        ;"  DebugMode: OPTIONAL -- If not given, will ask user.  Should be:
        ;"        0 for none,
        ;"        1 for To Screen
        ;"        2 for To File
        ;"        3 for To Tail (only valid if DispMode="GUI")
        ;"  UserPath: OPTIONAL --Directory to load from
        ;"  UserFName: OPTIONAL --the full filename.  If not given, will ask user

        ;"Set up some global variables.

        new TMGDEBUG set TMGDEBUG=0  ;"Note: user could change this at runtime...
        new DBIndent set DBIndent=0
        new PriorErrorFound set PriorErrorFound=0
        ;"new DispMode
        new cGUI set cGUI="GUI"
        new cCHUI set cCHUI="CHUI"
        new cRoll set cRoll="Roll-n-Scroll"
        new DModes
        new cDialog set cDialog="UseDialog"
        set DModes(0)="x"
        set DModes(1)=cGUI
        set DModes(2)=cCHUI
        set DModes(3)=cRoll
        set DModes(4)="x"

        new ExecNode    ;"This is the execution point
        new DataNode        ;"A handle to <Data> node
        new ScriptNode        ;"A handle to <Script> node
        new TopNode        ;"A handle to top level node <CONFIG_SCRIPT>
        new XMLHandle        ;"Handle referring to current XML document

        new cNodeDiv set cNodeDiv="|"
        new c2NodeDiv set c2NodeDiv=cNodeDiv_cNodeDiv

        new cProtect set cProtect="~~"
        new cDataOpen set cDataOpen="{{"
        new cDataClose set cDataClose="}}"
        new cNewLn set cNewLn="\n"
        new cEntries set cEntries="Entries"
        new cGlobal set cGlobal="GLOBAL"
        new cOpen set cOpen="OPEN"
        new cParentIENS set cParentIENS="ParentIENS"
        new cTrue set cTrue=1
        new cFalse set cFalse=0
        new cdbNone set cdbNone=0
        new cdbToScrn set cdbToScrn=1  ;"was 2
        new cdbToFile set cdbToFile=2  ;"was 3
        new cdbToTail set cdbToTail=3  ;"was 4
        new cdbAbort set cdbAbort=-1
        new cOKToCont set cOKToCont=1
        new cAbort set cAbort=0

        new cScript set cScript="SCRIPT"                        ;"Script"
        new cData set cData="DATA"                                ;"Data"
        new cMVar set cMVar="MVAR"                                ;"MVar"
        new cOption set cOption="OPTION"                        ;"option"
        new cCondition set cCondition="CONDITION"                  ;"condition"
        new cMatchThis set cMatchThis="MATCHTHIS"                  ;"MatchThis"
        new cMatchValue set cMatchValue="MATCHVALUE"                ;"MatchValue
        new cField set cField="FIELD"                                ;"Field"
        new cFile set cFile="FILE"                                ;"File"
        new cRecNum set cRecNum="RECNUM"                        ;"RecNum
        new cRecord set cRecord="RECORD"                        ;"Record"
        new cId set cId="ID"                                    ;"id"
        new cOutput set cOutput="OUTVAR"                        ;"OutVar"
        new cInput set cInput="INVAR"                                ;"InVar
        new cShow set cShow="SHOW"                                  ;"Show"
        new cM set cM="M"                                        ;"M"
        new cMenu set cMenu="DOMENUOPTION"                           ;"DoMenuOption"
        new cUpload set cUpload="UPLOADRECORD"                  ;"UploadRecord"
        new cLookup set cLookup="LOOKUPFILEINFO"                ;"LookupFileInfo"
        new cValueLookup set cValueLookup="LOOKUPFIELDVALUE"        ;"LookupFieldValue"
        new cSearchRec set cSearchRec="SEARCHREC"                ;"SearchRec
        new cFileUtility set cFileUtility="FILEUTILITY"                ;"FileUtility
        new cMsgBox set cMsgBox="MSGBOX"                        ;"MsgBox
        new cHeader set cHeader="HEADER"                        ;"Header
        new cText set cText="TEXT"                                ;"Text
        new cJump set cJump="JUMP"                                   ;"Jump"
        new cRemark set cRemark="REM"                           ;"Rem"
        new cLabel set cLabel="LABEL"                                  ;"Label"
        new cFlags set cFlags="FLAGS"                                ;"Flags"
        new cWidth set cWidth="WIDTH"                                ;"Width
        new cModal set cModal="MODAL"                                ;"Modal"
        new cFn set cFn="FN"                                        ;"Fn
        new cInfo set cInfo="INFO"                                ;"Info
        new cDelete set cDelete="DELETE"                        ;"Delete
        new cNextRec set cNextRec="NEXTREC"
        new cPrev set cPrev="PREV"
        new cNumRecs set cNumRecs="NUMRECS"
        new cFirstRec set cFirstRec="FIRSTREC"
        new cLastRec set cLastRec="LASTREC"
        new cRef set cRef="Ref"
        new cNonModal set cNonModal="0"
        new cModalMode set cModalMode="1"
        ;"Field flags
        new cHack set cHack="H"
        new cNoOverwrite set cNoOverwrite="N"
        new cEncrypt set cEncrypt="E"
        ;"----------
        new cUpperCase set cUpperCase="UpperCase"
        new cName set cName="Name"
        new cValue set cValue="VALUE"
        new cSet set cSet="SET"
        new cNull set cNull="(none)"
        new cMaxNode set cMaxNode="Max Node Num"
        new Filename
        new DebugFPath
        new DebugFName
        new DebugFile

        new result
        new FileSpec

        new ProcTable
        set ProcTable(cRemark)="DoComment"        ;"a do-nothing function
        set ProcTable(cLabel)="DoComment"        ;"a do-nothing function
        set ProcTable(cShow)="DoShow"
        set ProcTable(cM)="DoM"
        set ProcTable(cMenu)="DoMenu"
        set ProcTable(cUpload)="DoUpload"
        set ProcTable(cJump)="DoJump"
        set ProcTable(cLookup)="DoLookup"
        set ProcTable(cMsgBox)="DoMsgBox"
        set ProcTable(cValueLookup)="DoValueLookup"
        set ProcTable(cFileUtility)="DoFileUtility"
        set ProcTable(cSearchRec)="DoSearchRec"

        if TMGDEBUG>0 do DebugEntry^TMGDEBUG(.DBIndent,"Main Run")

        if $get(WelcomeShown)'=1 do ShowWelcome()

        ;"A local code login function.
        if $$XUP^TMGXUP()=0 do  goto RunDone
        . do ShowError^TMGDEBUG(.PriorErrorFound,"Error setting up a user privilages for configuration.")

        if ($data(DispMode)#10=0)!($get(DispMode)>3)!($get(DispMode)<1) do
        . set DispMode=$$GetDispMode()
        set DispMode=DModes(DispMode)
        if DispMode="x" goto RunDone
        set DispMode(cDialog)=(DispMode'=cRoll)

        if ($data(DebugMode)#10=0)!($get(DebugMode)<0)!($get(DebugMode)>3)!(($get(DebugMode)=1)&(DispMode'=cGUI)) do
        . set TMGDEBUG=$$GetDebugMode^TMGDEBUG(2)  ;"2=default to File output
        else  set TMGDEBUG=DebugMode
        if TMGDEBUG=cdbAbort goto RunDone

        do
        . new DefPath set DefPath="/tmp/"
        . new DefName set DefName="XMLInst_DebugLog.tmp"
        . new DefFName set DefFName=DefPath_DefName
        . do OpenLogFile^TMGDEBUG(DefPath,DefName)
        . if TMGDEBUG=cdbToTail do
        . . set result=$$Tail^TMGXDLG(DefFName,0,0,0)

        if ($data(UserPath)#10=0)!($data(UserFName)#10=0) do
        .
        . set result=$$GetFName(.UserPath,.UserFName)
        . if result=cAbort do PopupBox^TMGUSRIF("<!> No script file selected.","Come back again soon!")
        else  set result=cOKToCont
        if (result=cAbort)!($data(UserPath)=0)!($data(UserFName)=0) goto RunDone

        set Filename=UserPath_UserFName

        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Let's go! Cranking up system!...")

        kill ^TMP("TMG",$J)
        set XML1Ref=$name(^TMP("TMG",$J,1))  ;"I have to use this to load file
        set XMLRef=$name(^TMP("TMG",$J))     ;"I have to pass this to XML parser

        set XMLHandle=$$LoadFile(UserPath,UserFName)

        if XMLHandle=0 do  goto RunDone
        . do ShowError^TMGDEBUG(.PriorErrorFound,"Unable to load/parse file")

        if '$$InitVars do  goto RunDone
        . do ShowError^TMGDEBUG(.PriorErrorFound,"Error setting up script system (InitVars procedure).")

        if TMGDEBUG do
        . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Calling ArrayDump")
        . do ArrayDump^TMGDEBUG("^TMP(""TMG"")",$J)

        new Text
        set Text(0)="[*] XML Script"
        set Text(1)="Beginning execution of user XML script:"
        set Text(2)=Filename
        set Text(2)=" "
        set Text(3)="This could be the beginning of "
        set Text(4)="something wonderful..."
        do PopupArray^TMGUSRIF(5,45,.Text)

        new RunResult
        set RunResult=$$RunScript(.ExecNode)

        new Text
        set Text(0)="[*] XML Script"
        set Text(1)="Done with execution of user XML script."
        set Text(2)=" "
        set Text(3)="See you later..."
        if RunResult=cAbort do
        . set Text(4)="Note: Script was not completed."
        do PopupArray^TMGUSRIF(5,45,.Text)

RunDone
        do ShutDown

        write "Clean shutdown completed. Goodbye.",!,!

        if TMGDEBUG>0 do DebugExit^TMGDEBUG(.DBIndent,"Main Run")

        quit


 ;"=================================================================
 ;" Subroutines
 ;"=================================================================

ShowWelcome()
        ;"Purpose: To show a splash for program

        write !,!

        new Text
        set Text(0)="XML Configurator for VistA on GT.M"
        set Text(1)=" "
        set Text(2)="WELCOME..."
        set Text(3)=" "
        set Text(4)="Interpreter created by: Kevin Toppenberg, MD"
        set Text(5)="GNU General Public License, 7/2004"
        set Text(6)=" "
        do PopupArray^TMGUSRIF(5,55,.Text)

        quit


GetFName(Path,Filename)
        ;"Purpose: Interact with user to get path and filename
        ;"Input: Path--should be passed by reference, used to pass back result
        ;"       Filename--should be passed by reference, used to pass back result
        ;"Output: Results passed in Path and Filename
        ;"        Function will result in 0 if user 'cancelled', 1 otherwise

        new result set result=cAbort
        new FullNamePath
        new PathNode
        set Path="/"
        set Filename=""

        if TMGDEBUG>0 do DebugEntry^TMGDEBUG(.DBIndent,"GetFName")

        if DispMode=cRoll goto GFNRoll

        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Calling $$FileSel()")
        set FullNamePath=$$FileSel^TMGXDLG("Please select script to process . . .","~/XMLScript")
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Results=",FullNamePath)
        if FullNamePath="" goto GFNDone  ;"result=cAbort still --> cancelled.

        ;"Separate path from filename
GFNL1
        if '(FullNamePath["/") set Filename=FullNamePath goto GFNL2
        set PathNode=$piece(FullNamePath,"/",1)
        set Path=Path_PathNode_"/"
        set $piece(FullNamePath,"/",1)=""
        set FullNamePath=$extract(FullNamePath,2,255)
        goto GFNL1
GFNL2
        set result=cOKToCont
        goto GFNDone

GFNRoll
        new DefFName set DefFName="XMLScript"
        new DefPath set DefPath="/home/kdtop/OpenVistA_UserData/r"
        new Msg set Msg="Select script file:"
        new tempName

        ;"if $get(TMGDEBUG)>0 do DebugMsg^TMGDEBUG(DBIndent,"Will new file picker work?")

        set tempName=$$GetFName^TMGIOUTL(.Msg,.DefPath,.DefFName,"/",.Path,.Filename)
        write "Path=",$get(Path)," and Filename=",$get(Filename),!
        if tempName'="" set result=cOKToCont
        goto GFNDone

        ;"write !,"------------------------------------------",!
        write !
        write "Enter script filename with path:",!
        write "    ['^'] = Abort",!
        write "  [Enter] = '",DefPath,"/",DefFName,"'",!
        write "> "
        read Filename:240
        write !
        if Filename="^" goto GFNDone
        if Filename="" do
        . set Filename=DefFName
        . set Path=DefPath
        . write "Using default: ",Path,"/",Filename,!,!,!
        set result=cOKToCont

GFNDone
        if TMGDEBUG>0 do DebugExit^TMGDEBUG(.DBIndent,"GetFName")
        quit result



LoadFile(Path,Filename)
        ;"Purpose: To load the file and check for XML validity
        ;"           Also check for DOCTYPE = 'CONFIG_SCRIPT' and other
        ;"         possible validity tests.
        ;"Input: FullFile: full filename with path, ready to pass to Host file system.
        ;"NOTE: uses XML1Ref and XMLRef vars with global scope
        ;"Returns: 0 if fails, otherwise XML file handle.

        new FileHandle
        set XMLHandle=0

        if TMGDEBUG>0 do DebugEntry^TMGDEBUG(.DBIndent,"LoadFile")
        set FileHandle=$$FTG^%ZISH(Path,Filename,XML1Ref,3)
        if FileHandle=0 do  goto QLoad
        . do ShowError^TMGDEBUG(.PriorErrorFound,"Error opening file. Path="_Path_", Filename="_Filename)
        else  do
        . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"File Loaded... Handle#="_FileHandle)

        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Calling EN^MXMLDOM")
        write "Parsing XML File.  Please wait . . .",!
        set XMLHandle=$$EN^MXMLDOM(XMLRef,"")
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Back from calling EN^MXMLDOM. XMLHandle="_XMLHandle)
        if XMLHandle=0 do
        . new ErrMsg
        . set ErrMsg="Error parsing XML document.\n\n"
        . set ErrMsg=ErrMsg_"Now analyzing XML file to determine problem...\n"
        . do ShowError^TMGDEBUG(.PriorErrorFound,ErrMsg)
        . do DetailParse^TMGXMLP()

QLoad
        if TMGDEBUG>0 do DebugExit^TMGDEBUG(.DBIndent,"LoadFile")
        quit XMLHandle

ShutDown
        ;"Purpose: to do any cleanup needed to exit system cleanly

        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Freeing vars...")

        if $get(XMLHandle) do DELETE^MXMLDOM(XMLHandle)
        kill ^TMP("TMG",$J)

        ;"Kill a few variables.  The others should be automatically freed
        ;"  when they go out of scope as the program exits.
        kill TMGDEBUG
        kill LoggedUsr
        kill SubMarkNum

        if $data(DebugFile) close DebugFile

        write "Exiting XML Scripter.",!,!

        quit


InitVars()
        ;"Purpose: Initialize variables
        ;"Input: None:
        ;"Output: Global (program-wide) variables are set up.
        ;"        Return value is 0 if an error occurs.

        new result
        set result=cAbort
        set TopNode=1

        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Entry InitVars()",1)

        set ScriptNode=$$GetDescNode^TMGXMLT(XMLHandle,TopNode,cScript)
        if ScriptNode=0 do  goto QInitVar
        . do ShowError^TMGDEBUG(.PriorErrorFound,"Unable to find node: '"_cScript_"'.")

        set ExecNode=$$CHILD^MXMLDOM(XMLHandle,ScriptNode)
        if ExecNode=0 do  goto QInitVar
        . do ShowError^TMGDEBUG(.PriorErrorFound,"Error finding first child of ScriptNode (#"_ScriptNode_").")

        set DataNode=$$GetDescNode^TMGXMLT(XMLHandle,TopNode,cData)
        if DataNode=0 do  goto QInitVar
        . do ShowError^TMGDEBUG(.PriorErrorFound,"Unable to find node: '"_cData_"'")

        set result=cOKToCont

QInitVar
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Exit InitVars()",1)
        quit result


CMDProcess(Command,Params)
        ;"Purpose: Take allowed command, and carry out appropriate action
        ;"Input: Command:  One of following allowed commands:
        ;"                    Show,M,DoMenuOption,UploadRecord,Jump
        ;"         Params: An array holding parameters.  See GetParams() for format.
        ;"                Note: if node had no parameters, this array will be undefined.
        ;"Note: Not all commands will have valid data for all attribs.
        ;"Returns: If should continue execution:  1=OK to continue.  0=abort.

        new OKToCont set OKToCont=1

        if TMGDEBUG>0 do DebugEntry^TMGDEBUG(.DBIndent,"CMDProcess")

        if $data(ProcTable(Command)) do
        . new Cmd set Cmd=ProcTable(Command)
        . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"CMD=",Cmd)
        . set @("OKToCont=$$"_Cmd_"(.Params)")

        goto CMDQuit

CMDQuit
        if TMGDEBUG>0 do DebugExit^TMGDEBUG(.DBIndent,"CMDProcess")
        quit OKToCont


DoComment(Params)
        ;"Purpose: To provide a function for doing nothing.... for comments in the code.
        quit 1

DoShow(Params)
        ;"Purpose: execute Show command
        ;"Syntax: e.g. <Show>This is a test script system.</Show>
        ;"Input: Params -- an array that holds all parameters (or is undefined if there were none)
        ;"          if there is text to be show, it should be in
        ;"          Params(cText)
        ;"Input: TextArray: a reference to global array, holding the text found between tags
        ;"Returns: If should continue execution:  1=OK to continue.  0=abort.

        if TMGDEBUG>0 do DebugEntry^TMGDEBUG(.DBIndent,"DoShow")

        new done
        new lineI
        new OneLine
        new result set result=cOKToCont

        new TextArray

        if $data(Params(cText))=0 do  goto DSDone
        . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Asked to show text, but none found!")
        . ;"if TMGDEBUG do ArrayDump^TMGDEBUG("Params")   ;"zwr Params(*)
        merge TextArray=Params(cText)
        if TMGDEBUG do ArrayDump^TMGDEBUG("TextArray") ;"zwr TextArray(*)

        set result=$$CheckArraySubst(.TextArray)

        set lineI=$Order(TextArray(""))
        for  do  quit:(lineI="")!(result=cAbort)
        . set OneLine=TextArray(lineI)
        . write OneLine,!
        . set lineI=$Order(TextArray(lineI))

DSDone
        if TMGDEBUG>0 do DebugExit^TMGDEBUG(.DBIndent,"DoShow")

        quit result

DoM(Params)
        ;"Purpose: execute a single line of M code
        ;"Syntax: e.g. <M>write "This is a test of M code"</M>
        ;"          e.g. <M>set XMLData={{Data.Site.Office[Kevin].Field[Doctor]}}</M>
        ;"Input: Params -- an array that holds all parameters (or is undefined if there were none)
        ;"          if there is code to be executed, it should be in
        ;"          Params(cText,1)
        ;"Note: If a {{...}} pair is found, then the contents between the braces will
        ;"      be interpreted as a data reference, and the value will be looked up.
        ;"      The references are read-only.  Attempts to write to them will only
        ;"      create an unused variable by the name of the data result.  Will likely
        ;"      cause an error.
        ;"        Note: This code could be anything.  Script execution will only continue
        ;"              after M code execution is complete.
        ;"Returns: If should continue execution:  1=OK to continue.  0=abort.

        new RefB
        new Abort
        new result set result=cOKToCont
        new OrigCode

        if TMGDEBUG>0 do DebugEntry^TMGDEBUG(.DBIndent,"DoM")

        new Code set Code=$get(Params(cText,1))
        if Code="" do  goto DMDone
        . do ShowError^TMGDEBUG(.PriorErrorFound,"No M code found to execute!")

        ;"Check if Code contains a data reference.  Replace with data if found
        set OrigCode=Code
        set result=$$CheckSubstituteData(.Code)
        if result=cAbort do  goto DMDone
        . do ShowError^TMGDEBUG(.PriorErrorFound,"UNABLE to execute this code: "_OrigCode)
        . do ShowError^TMGDEBUG(.PriorErrorFound,"After lookup, code was:"_Code)

        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"execute:> "_Code)

        ;"Note: Here I trap execution errors, and return 0 if error encountered
        do
        . new $etrap set $etrap="do DoMErrTrap^TMGXINST"
        . set ^TMP("TMG",$J,"trap")=cOKToCont
        . xecute Code
        . set result=^TMP("TMG",$J,"trap")
        . if result=cAbort do
        . . do ShowError^TMGDEBUG(.PriorErrorFound,"Error executing code: \n"_Code)

DMDone
        if TMGDEBUG>0 do DebugExit^TMGDEBUG(.DBIndent,"DoM")
        quit result


        ;"=========================================================
        ;"DoM Error trap routine
        ;"=========================================================
DoMErrTrap
        set $etrap=""
        set $ecode=""
        set ^TMP("TMG",$J,"trap")=cAbort
        quit
        ;"=========================================================
        ;"DoM End of Error trap routine
        ;"=========================================================


DoMenu(Params)
        ;"Purpose: To execute a menu option inside the VistA system
        ;"Syntax: e.g. <DoMenuOption option="DIUSER"></DoMenuOption>
        ;"Input: Params -- an array that holds all parameters (or is undefined if there were none)
        ;"          if there is code to be executed, it should be in
        ;"          Params(cOption)
        ;"       This should be a valid VistA menu option name.
        ;"Returns: If should continue execution:  1=OK to continue.  0=abort.

        if TMGDEBUG>0 do DebugEntry^TMGDEBUG(.DBIndent,"DoMenu")

        set result=$$DoShow(.Params)  ;"Show any associated text as a message

        new MenuOption
        set MenuOption=$get(Params(cOption))    ;"note use of attrib value with case UN-MODIFIED
        if MenuOption="" do  goto DoMenuQ
        . do ShowError^TMGDEBUG(.PriorErrorFound,"No menu option found to execute!")

        new Text
        set Text(0)="<!> Notice:"
        set Text(1)=" "
        set Text(2)="Temporarily leaving XML Script Configurator"
        set Text(3)="to run VistA menu option system...."
        set Text(4)="This script will return to this point when"
        set Text(5)="VistA menu option exited."
        set Text(6)=" "
        do PopupArray^TMGUSRIF(5,55,.Text)

        new result
        set result=cOKToCont

        set DIC=19 ;"File 19 is the OPTION file
        set DIC(0)="M"  ;"M=Multiple index lookup allowed
        set X=MenuOption
        do ^DIC  ;"Do lookup for variable X.  Result returns in Y
        if Y<0 do  quit
        . do ShowError^TMGDEBUG(.PriorErrorFound,"Menu option '"_MenuOption_"' wasn't found.\nTry specifying a more specific name, or check spelling.")

        ;"Note: DIC is already set to 19
        set X=$piece(Y,"^",1)  ;"X=Menu option IEN to execute

        ;Note: If the OPTION is a run routine, then this won't work.  I could
        ;        Get the run routine my self, but I would also need to do the
        ;        entry and exit points etc. etc., so I am not now going to.

        do EN^XQOR  ;"call standard entry point to run menu/option X

        new Text
        set Text(0)="<!> Notice:"
        set Text(1)=" "
        set Text(2)="Re-entering XML Script Configurator"
        set Text(3)="(Back from VistA menu option system)"
        set Text(4)="Script continuing..."
        set Text(5)=" "
        do PopupArray^TMGUSRIF(5,55,.Text)


        ;"Note: Here I could do some error checking, and return
        ;"      result=cAbort if we need to abort.
DoMenuQ
        if TMGDEBUG>0 do DebugExit^TMGDEBUG(.DBIndent,"DoMenu")
        quit result


DoLookup(Params)
        ;"Purpose: To take data from XML file, and look if it is already in database
        ;"           -- if so, then put RecNum-IEN of record into variable pointed to by OutVarP
        ;"Syntax: e.g. <LookupFileInfo id="Kevin" OutVar="MyVar"></LookupFileInfo>
        ;"Input: Params -- an array loaded with expected parameters.  I.e.:
        ;"                Params(cId): the ID of the <Record> data entry.
        ;"                        Params(cId)="Kevin" in our example
        ;"                Params(cOutput)=the NAME of a variable to put RecNum-IEN into.
        ;"                        Params(cOutput)="MyVar" in example
        ;"Output: OutVarP is loaded with data, i.e.:
        ;"                @OutVarP@(cRecNum)=81
        ;"                @OutVarP@(cFile)=200
        ;"                @OutVarP@(cGlobal)="^VA(200)"
        ;"                @OutVarP@(cGlobal,cOpen)="^VA(200,"
        ;"Returns: If should continue execution:  1=OK to continue.  0=abort.
        ;"Note: Even if <Record> specifies a RecNum="2", this function will STILL do a
        ;"        search and return THAT value, not the "2" in this example.

        if TMGDEBUG>0 do DebugEntry^TMGDEBUG(.DBIndent,"DoLookup")

        new Data
        new RecNumIEN
        new result set result=cOKToCont
        new ID set ID=$get(Params(cId))
        new OutVarP set OutVarP=$get(Params(cOutput))

        set result=$$DoShow(.Params)  ;"Show any associated text as a message

        if OutVarP="" goto LkDone

        ;"Parse XML data into a usable form.  Verification is done.
        if '$$GetRInfo(ID,.Data) do  goto LkDone
        . do ShowError^TMGDEBUG(.PriorErrorFound,"Unable to process <Record> section with id='"_ID_"'.")
        . set result=cAbort ;"0=Abort

        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Parsed data.")
        set @OutVarP@(cFile)=$get(Data(0,cFile))
        set @OutVarP@(cGlobal)=$get(Data(0,cFile,cGlobal))
        set @OutVarP@(cGlobal,cOpen)=$get(Data(0,cFile,cGlobal,cOpen))

        set result=$$GetRecMatch^TMGDBAPI(.Data,.RecNumIEN)  ;"if no prior record, returns 0
        ;"set RecNumIEN=$$GetRecMatch^TMGDBAPI(.Data)  ;"if no prior record, returns 0
        set @OutVarP@(cRecNum)=RecNumIEN

LkDone
        set result=(+result>0) ;"Change RecNum-IEN into boolean 1 or 0
        if result=cAbort do ShowError^TMGDEBUG(.PriorErrorFound,"Lookup command failed.")
        if TMGDEBUG>0 do DebugExit^TMGDEBUG(.DBIndent,"DoLookup")
        quit result


DoValueLookup(Params)
        ;"Purpose: To look for a value of a given value in a given record in given file.
        ;"Syntax: e.g. <LookupFieldValue File="NEW PERSON" RecNum="1" Field=".01" OutVar="MyVar">
        ;"Input: Params -- an array loaded with expected parameters.  I.e.:
        ;"                Params(cFile)="NEW PERSON" in our example
        ;"                Params(cRecNum)="1" in example
        ;"                Params(cField)=".01" in our example (could be Name of field)
        ;"                Params(cOutput)="MyVar"
        ;"Output: MyVar is loaded with data, i.e.:
        ;"                     MyVar(cFile)=200
        ;"                     MyVar(cGlobal)="^VA(200)"
        ;"                     MyVar(cGlobal,cOpen)="^VA(200,"
        ;"                   MyVar(cRecNum)=1
        ;"                     MyVar(cField)=.01
        ;"                     MyVar(cValue)=xxx <-- the looked-up value
        ;"Returns: If should continue execution:  1=OK to continue.  0=unsuccessful lookup
        ;"Note: I am getting values by directly looking into database, rather than use
        ;"        the usual lookup commands. I am doing this so that there will be no
        ;"        'hidden' data, based on security etc.

        new result

        if TMGDEBUG>0 do DebugEntry^TMGDEBUG(.DBIndent,"DoValueLookup")

        set result=$$ParamSubstitute(.Params)
        if result=cAbort goto DVLDone

        set result=$$ValueLookup^TMGDBAPI(.Params)

DVLDone
        if TMGDEBUG>0 do DebugExit^TMGDEBUG(.DBIndent,"DoValueLookup")
        quit result


DoFileUtility(Params)
        ;"Purpose: To provide file access/manipulation utilities to script user
        ;"syntax:
        ;"   <FileUtility File="NEW PERSON" Fn="xxx" RecNum="1" Field=".01" OutVar"MyOutVar" Value="xx" >
        ;"        File: The name of the file to act upon.
        ;"                File may have subnodes (i.e. "NEW PERSON|ALIAS|TITLE")
        ;"                **BUT**, any deletion or set values will only work on top level (i.e. "NEW PERSON")
        ;"        Fn can be on of the following [OPTIONAL].  (Data substitution is allowed)
        ;"          Fn="delete"  If Field is not specified:
        ;"                                  Will cause record RecNum to be deleted.
        ;"                                  MyOutVar("DELETED")=RecNum of deleted record, or
        ;"                                0 if not found.
        ;"                        If Field IS specified:
        ;"                                Will delete the value in field, in record RecNum
        ;"                        Note: delete is intended only for the highest-level records
        ;"                                (i.e. not subfiels, or multiple fields)
        ;"                   Note: delete method uses ^DIK to delete the record
        ;"          Fn="info"  Will just fill in info below.
        ;"                If Fn not specified, this is default
        ;"          Fn="set"  Will put Value into Field, in RecNum, in File (all required)
        ;"        RecNum: [OPTIONAL] Specifies which record to act on.  If not
        ;"                specified, then just file info is returned.  Data substitution is allowed
        ;"        Field: [OPTIONAL] Specifies which field to act on. Data substitution is allowed
        ;"        OutVar: Needed to get information back from function (but still Optional)
        ;"                Gives name of variable to put info into.
        ;"                Data substitution is allowed.
        ;"Input: Params -- an array loaded with expected parameters.  I.e.:
        ;"                Params(cFile)="NEW PERSON" in our example
        ;"                Params(cFn)="info" or "delete", or "set"
        ;"                Params(cRecNum)="1" in example
        ;"                Params(cField)=".01" in our example (could be Name of field)
        ;"                Params(cOutput)="MyVar"
        ;"Output: MyVar is loaded with data, i.e.
        ;"        i.e. MyOutVar("FILE")=Filenumber
        ;"             MyOutVar("FILE","FILE")=SubFilenumber <-- only if subnodes input in File name (e.g."ALIAS")
        ;"             MyOutVar("FILE","FILE","FILE")=SubSubFilenumber <-- only if subnodes input in File name (e.g."TITLE")
        ;"             MyOutVar("GLOBAL")="^VA(200)"
        ;"             MyOutVar("GLOBAL, OPEN")="^VA(200,"
        ;"             MyOutVar("RECNUM")=record number
        ;"             MyOutVar("FIELD")=Filenumber
        ;"             MyOutVar("VALUE")=xxxx <=== value of field (PRIOR TO deletion, if deleted)
        ;"             MyOutVar("NEXTREC")=record number after RecNum, or "" if none
        ;"             MyOutVar("PREVREC")=record number before RecNum, or "" if none
        ;"             MyOutVar("FN")=the function executed
        ;"             MyOutVar("NUMRECS")=Number of records in file PRIOR to any deletions
        ;"             MyOutVar("FIRSTREC")=Rec number of first record in file
        ;"             MyOutVar("LASTREC")=Rec number of last record in file
        ;"Returns: If should continue execution:  1=OK to continue.  0=abort
        ;"Note: I am getting values by directly looking into database, rather than use
        ;"        the usual lookup commands. I am doing this so that there will be no
        ;"        'hidden' data, based on security etc.

        if TMGDEBUG>0 do DebugEntry^TMGDEBUG(.DBIndent,"DoFileUtility")

        new result

        set result=$$ParamSubstitute(.Params)
        if result=cAbort goto DFUTDone

        set result=$$FileUtility^TMGDBAPI(.Params)

DFUTDone
        if TMGDEBUG>0 do DebugExit^TMGDEBUG(.DBIndent,"DoFileUtility")
        quit result


DoSearchRec(Params)
        ;"Purpose: To allow the user to search for a specif record number
        ;"Syntax: <SearchRec File="PERSON CLASS" InVar="MyInput" OutVar="MyOutput"></SearchRec>
        ;"        File: The name of the file to act upon.
        ;"        InVar: the name of a variable with global scope that will hold lookup info
        ;"        OutVar: the name of variable to receive results
        ;"Input: Params -- an array loaded with expected parameters.  I.e.:
        ;"                Params(cFile)="NEW PERSON" in our example
        ;"                Params(cOutput)="MyOutput"
        ;"                Params(cInput)="MyInput"
        ;"Note: The format of the input params variable (e.g. 'MyInput') should be as follows:
        ;"                MyInput(FieldNum)=ValueToSearchFor
        ;"                MyInput(FieldNum)=ValueToSearchFor
        ;"                MyInput(FieldNum)=ValueToSearchFor
        ;"                ... etc.
        ;"Output: MyVar is loaded with data, i.e.
        ;"             MyOutVar("RECNUM")=record number, or 0 if not found
        ;"Returns: If should continue execution:  1=OK to continue.  0=abort

        if TMGDEBUG>0 do DebugEntry^TMGDEBUG(.DBIndent,"DoSearchRec")

        new result set result=cAbort
        new SrchParams,RecNum,OutVar
        new MyInput,MyOutput

        if $$DoShow(.Params)=0 goto DSRDone  ;"Show any associated text as a message

        if TMGDEBUG>0 do ArrayDump^TMGDEBUG("Params")   ;"zwr Params(*)

        set MyInput=$get(Params(cInput))
        set MyOutput=$get(Params(cOutput))
        if (MyOutput="")!(MyInput="") goto DSRDone  ;"result=cAbort be default

        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"MyInput=",MyInput)
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"MyOutput=",MyOutput)
        merge SrchParams=@MyInput
        set SrchParams(0,cFile)=$get(Params(cFile))
        set RecNum=$$RecFind^TMGDBAPI(.SrchParams)
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Found record number: ",RecNum)
        set @MyOutput@(cRecNum)=RecNum
        if RecNum=0 goto DSRDone
        set result=cOKToCont

DSRDone
        if TMGDEBUG>0 do DebugExit^TMGDEBUG(.DBIndent,"DoSearchRec")
        quit result


DoUpload(Params)
        ;"Purpose: To take data from XML file, and get it up into the VistA database
        ;"Syntax: e.g. <UploadRecord id="Kevin"></UploadRecord>
        ;"Note:   ***See documentation in GetRInfo for expected formats
        ;"Input:  Params -- an array that holds all parameters (or is undefined if there were none)
        ;"          Params(cId,cUpperCase) -- the ID ofthe data to upload
        ;"                  Expected ID -- the ID of the <Record> data entry. e.g. "Kevin" in our example
        ;"          Params(cOutput)=the NAME of a variable to put RecNum-IEN into. (Optional)
        ;"                        i.g. Params(cOutput)="MyVar" will cause MyVar=IEN
        ;"Returns: If should continue execution:  1=OK to continue.  0=abort.

        new Data
        new result set result=cOKToCont
        new RecNumIEN

        new OutVarP set OutVarP=$get(Params(cOutput))

        if TMGDEBUG>0 do DebugEntry^TMGDEBUG(.DBIndent,"DoUpload")

        set result=$$DoShow(.Params)  ;"Show any associated text as a message

        new ID
        set ID=$get(Params(cId,cUpperCase))
        if ID="" do  goto ULDone
        . do ShowError^TMGDEBUG(.PriorErrorFound,"Unable to get ID of file to upload!")

        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Uploading file info -- id="_ID)

        ;"Parse XML data into a usable form.  Verification is done.
        if '$$GetRInfo(ID,.Data) do  goto ULDone
        . do ShowError^TMGDEBUG(.PriorErrorFound,"Unable to process <Record> section with id='"_ID_"'.")
        . set result=cAbort ;"0=Abort

        set RecNumIEN=$get(Data(0,cRecNum),0) ;"Get user-specified Record Num(IEN), or null
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"User-requested recordnum is (0=not requested): ",RecNumIEN)
        set result=$$UploadData^TMGDBAPI(.Data,.RecNumIEN)
        if OutVarP'="" do
        . set @OutVarP@(cRecNum)=RecNumIEN

ULDone
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Result = ",result)
        if result=cAbort do ShowError^TMGDEBUG(.PriorErrorFound,"Error uploading data.")
        if TMGDEBUG>0 do DebugExit^TMGDEBUG(.DBIndent,"DoUpload")
        quit result



GetRInfo(ID,Data)
        ;"Purpose: To get record info from the <DATA> section of the XML file,
        ;"         and to store it in the Data variable.
        ;"Input: ID: The name of the record info to get.
        ;"                e.g. to get the info for this entry:
        ;"                  <Record id="Kevin" File="1234.1">
        ;"                Then ID should = "Kevin" (no extra quotes)
        ;"        Data: This is to be an array that is passed by reference
        ;"                Any preexisting contents will be deleted
        ;"                See output below.
        ;"Note: The syntax of the <Record> block is as follows.  Note, <Record>
        ;"        should be a child (i.e. not a grandchild) of the <DATA> block.
        ;"          example:
        ;"            <Record id="InstFile" File="1234.1">
        ;"         or <Record id="InstFile" File="NEW PERSON">
        ;"         or <Record id="InstFile" File="NEW PERSON" RecNum="1">
        ;"                <Field id=".01" MatchThis="true">MyData1</Field>
        ;"                <Field id=".02" MatchValue="John">Bill</Field>
        ;"                <Field id=".03">MyData3</Field>
        ;"                <Field id=".04">MyData4</Field>
        ;"                <Field id="NAME">MyData5</Field>
        ;"                <Field id="ITEM/.01">SubEntry1</Field>
        ;"                <Field id="ITEM/SYNONYM">SE1</Field>   ;"Note: SYNONYM here is field .02
        ;"                <Field id="ITEM/INFO">'Some Info'</Field> ;"Note: INFO here is field .03
        ;"                <Field id="ITEM/MENU">SubEntry2</Field> <-- start of 2nd subfile entry
        ;"                <Field id="ITEM/TEXT/INITS">JD</Field>  ;"TEXT=.4;  INITS=.1
        ;"                <Field id="ITEM/TEXT/CREATOR">Doe,John</Field>  ;"CREATOR is field .2
        ;"            </Record>
        ;"
        ;"          'id': specifies a name that is used in <UploadRecord> command
        ;"          'File': specifies the filenumber or formal file name to put info into
        ;"          'RecNum': an OPTIONAL parameter.  If specified, data will be forced into the
        ;"                  specified record number.  If not specified, then data matching is used
        ;"                to determine where to put record.  Data substitution is allowed.
        ;"                A value of 0 will be treated as if no value specified.
        ;"
        ;"          At least one (and likely many) <Field> entries must exist in the <Record> block
        ;"          Syntax:
        ;"                  <Field id=".01">MyDataplacing</Field>
        ;"                  <Field id="NAME" MatchThis="true">MyDataplacing</Field>
        ;"                <Field id="ITEM/SYNONYM">M1</Field>
        ;"
        ;"                'id' gives the field number or name
        ;"                      Multiple field names/numbers may be included here.
        ;"                        "ITEM/SYNONYM" means that SYNONYM is a field within
        ;"                        the ITEM subfile (a field with multiple entries).  Thus
        ;"                        field ITEM would be located, then SYNONYM subfield.
        ;"                        To have a '/' character as part of the field name, and not
        ;"                        to be interpreted as a node divider, then use '//', this will
        ;"                        be replaced with '/'.
        ;"                      Note: When a field allows multiple entries (like "ITEM" above),
        ;"                        then there must be a way to determine group of the data into
        ;"                        one entry or another. The field ".01" (or a name that resolves
        ;"                        to ".01" will serve this purpose. For example:
        ;"                                ITEM|.01     <---- the beginning of one entry
        ;"                                ITEM|SYNONYM
        ;"                                ITEM|INFO
        ;"                                ITEM|MENU    <---- beginning of the next entry. (MENU=.01)
        ;"                                ITEM|TEXT|INITS
        ;"                                ITEM|TEXT|CREATOR
        ;"              'MatchThis': if value="true", then this entry will be used to
        ;"                        search for preexisting record in file.  Should only be
        ;"                        used for highest levels, i.e. match in subfields not supported
        ;"                'MatchValue': if specified, then value of entry will be used to
        ;"                        search for preexisting record in file.  Should only be
        ;"                        used for highest levels, i.e. match in subfields not supported
        ;"
        ;"                The data is found between the <Field></Field> tags.
        ;"                Note: The data values may contain lookup codes.  For example
        ;"                        <Field id="ITEM|CREATOR">{{Data.Site.Office[EastSide].Field[Doctor]}}</Field>
        ;"                        would cause the {{..}} value to be looked up in the corresponding
        ;"                        section in the XML file and replaced.  Thus the line would be converted to:
        ;"                        <Field id="ITEM|CREATOR">Kevin</Field>
        ;"
        ;"Output: The Data array will be filed with data. Thus for above example:
        ;"                Data(0,cFile)="1234.1" <-- "NEW PERSON" Note conversion
        ;"                Data(0,cFile,cGlobal)="^DIC(200)"  <-- note, NOT "^DIC(200,"
        ;"                Data(0,cRecNum)=2  <-- only if user-specified.
        ;"                Data(0,cEntries)=1
        ;"                Data(1,".01")="MyData1"
        ;"                Data(1,".01",cMatchValue)="MyData1"
        ;"                Data(1,".02")="Bill"
        ;"                Data(1,".02",cMatchValue)="John"
        ;"                Data(1,".03")="MyData3"
        ;"                Data(1,".04")="MyData4"
        ;"                Data(1,".06")="MyData5"  <-- note "NAME" was converted to ".06"
        ;"                Data(1,".07",0,cEntries)=2    <-- "ITEM" converted to ".07"
        ;"                Data(1,".07",1,".01")="SubEntry1"
        ;"                Data(1,".07",1,".02")="SE1"
        ;"                Data(1,".07",1,".03")="'Some Info'"
        ;"                Data(1,".07",2,".01")="SubEntry2"
        ;"                Data(1,".07",2,".02")="SE2"
        ;"                Data(1,".07",2,".04",0,cEntries)=1    ;"TEXT converted to .04
        ;"                Data(1,".07",2,".04",1,".01")="JD"
        ;"                Data(1,".07",2,".04",1,".02")="DOE,JOHN"
        ;"                ADDENDUM
        ;"                Data(1,".01",cFlags)=any flags specified for given field.
        ;"                        only present if user specified.


        ;"        Note: The output is somewhat validated, in that if file NAME is given
        ;"                instead of a number, the name will be converted.  The same applies
        ;"                for field NUMBERS.  This ensures that the file exists, and
        ;"                puts the global reference in the array
        ;"Result: 1 if valid data in Data, 0 if data invalid

        new result
        new ChildNode
        new FileNode
        new Text,TextArray
        new NodeName,AtrN,AtrVal
        new AtrMatch,MatchValue
        new MatchThis
        new Entries set Entries=0
        new Field,FieldNumber
        new RecNum
        new Flags
        set result=cOKToCont
        set ChildNode=0
        set Entries=0
        new FileNumber,FileName,File
        new index
        new DataP set DataP="Data"
        new EntryNumber set EntryNumber=0  ;"was 1

        new InitDebug set InitDebug=TMGDEBUG
        ;"set TMGDEBUG=0  ;"Force this function to not put out TMGDEBUG info.

        if TMGDEBUG>0 do DebugEntry^TMGDEBUG(.DBIndent,"GetRInfo")

        if $data(ID)'=0 do
        . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"var ID=",ID)
        else  do
        . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"var ID=(empty)")

        if $data(ID)'=0 do
        . set FileNode=$$GetDescIDNode(DataNode,cRecord,ID)
        . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Node with ",cRecord,"=",FileNode)
        else  do
        . set FileNode=0
        if FileNode=0 do  goto GInfPast
        . set result=cAbort
        . do ShowError^TMGDEBUG(.PriorErrorFound,"File entry named '"_ID_"' not found.")

        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Looking for user-specified record number in node: ",FileNode)
        set RecNum=$$GetAtrVal^TMGXMLT(XMLHandle,FileNode,cRecNum) ;"get user-specified RecNum IEN (optional)
        set result=$$CheckSubstituteData(.RecNum)
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Specified RecNum='",RecNum,"'")
        if +RecNum>0 set Data(0,cRecNum)=RecNum

        set File=$$GetAtrVal^TMGXMLT(XMLHandle,FileNode,cFile)
        set Data(0,cFile)=File
        set result=$$SetupFileNum^TMGDBAPI(.Data)
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Setup file number result=",result)
        if result=cAbort do  goto GInfPast
        . do ShowError^TMGDEBUG(.PriorErrorFound,"Unable to set up file '"_File_"'.")
        set FileNumber=$get(Data(0,cFile),-1)
        if FileNumber=-1 do  goto GInfQuit
        . set result=cAbort

GInfLoop
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Starting GInfLoop")
        set ChildNode=$$CHILD^MXMLDOM(XMLHandle,FileNode,ChildNode)
        if ChildNode=0 goto GInfPast
        set NodeName=$$GetNName^TMGXMLT(XMLHandle,ChildNode)
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Name="_NodeName)
        if NodeName'=cField goto GInfLoop
        set Text=$$Get1NText^TMGXMLT(XMLHandle,ChildNode,.TextArray)
        if $data(TextArray(2)) do
        . merge Text(cText)=TextArray

        if $$UP^XLFSTR($$GetAtrVal^TMGXMLT(XMLHandle,ChildNode,cMatchThis))=cTrue do
        . set MatchValue=Text
        set MatchValue=$get(MatchValue)
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Attrib Match value='",MatchValue,"'")
        set Entries=Entries+1
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Entries='",Entries,"'")

        set Field=$$GetAtrVal^TMGXMLT(XMLHandle,ChildNode,cId)  ;"May get either a name or a number
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Field='",Field,"'")

        ;"Protect any //'s by converting to ~~'s
        set Field=$$Substitute^TMGSTUTL(.Field,c2NodeDiv,cProtect)
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"After substitution, Field '",cId,"'=",Field)

        set Flags=$$GetAtrVal^TMGXMLT(XMLHandle,ChildNode,cFlags)  ;"Get any flags that might exist.
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Flags for node #",ChildNode," = '",Flags,"'")

        ;"Allow recursive calls via ProcessRNode
        set result=$$ProcessRNode(DataP,Field,.Text,.EntryNumber,FileNumber,0,MatchValue,Flags)
        if result=cAbort goto GInfQuit
        ;"temp ... set EntryNumber=EntryNumber+1
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"EntryNumber=",EntryNumber)

        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Completed loop cycle (maybe there will be more to come)")
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"------------")
        goto GInfLoop

GInfPast
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Done with loop")
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"------------")

        if $data(Data(0,cEntries))=0 do  goto GInfQuit
        . set result=cAbort

        ;"Ensure that there is at least a .01 field.  Required for every record
        ;"Note: I think that other files have multiple KEY fields.... I am not checking
        ;"     for this (perhaps I should later??)
        new bFound set bFound=0
        for index=1:1:Data(0,cEntries) do  quit:bFound
        . ;"if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Data(",index,",.01)='",$get(Data(index,".01")),"'")
        . if $data(Data(index,".01")) set bFound=1

        if bFound=0 do  goto GInfQuit
        . do ShowError^TMGDEBUG(.PriorErrorFound,"Data entry did not specify any entry for field .01")
        . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Here is data:")
        . if TMGDEBUG do ArrayDump^TMGDEBUG("Data")  ;"zwr Data(*)
        . set result=cAbort

GInfQuit
        if TMGDEBUG>0 do DebugExit^TMGDEBUG(.DBIndent,"GetRInfo")
        set TMGDEBUG=InitDebug
        quit result


ProcessRNode(DataP,Field,Text,EntryNumber,FileNumber,DoingSubNodes,MatchValue,Flags)
        ;"Purpose: Allow for recursive calling when doing GetRInfo
        ;"         This takes one entry and processes it.
        ;"Input: DataP: The 'name' of the data array -- like this: "Data(1)"
        ;"         Field: a field name with 0..n subnodes
        ;"           i.e. "ITEM", OR "ITEM|NUMBER", OR "ITEM|NUMBER|ID"
        ;"         Text: the value that should be put into field.  should be passed by REFERENCE
        ;"                Text will have the following format:
        ;"                        Text="First line of text"
        ;"                        Text(cText,1)="First line of text" <-- only present if multiple
        ;"                        Text(cText,2)="Second line of text"    lines of text present.
        ;"       EntryNumber: The current entry number.  Should be passed by REFERENCE
        ;"         FileNumber: the current file number, or sub-filenumber. DON'T PASS BY REFERENCE
        ;"                The first node (i.e. "ITEM") should be field in FileNumber
        ;"         DoingSubNodes: 1 if true (changes behavior or entry numbering for subnodes), 0 otherwise
        ;"         //AtrMatch: if this field should be matched for during DB lookup
        ;"         MatchValue: Value to looking in database when finding matching record.
        ;"         Flags: any user specified flags for field
        ;"Result: Returns success 1=OK to continue.  0=Abort

        ;"Note: This entry--><Field id="ITEM|TEXT|CREATOR">Doe,John</Field>
        ;"Should result it--> Data(6,".07",2,".04",1,".02")="DOE,JOHN"
        ;"See data format description in GetRInfo

        new PartA,PartB
        new tempA,tempB
        new result set result=cOKToCont
        new cFieldNumber set cFieldNumber="Field Number"

        if TMGDEBUG>0 do DebugEntry^TMGDEBUG(.DBIndent,"ProcessRNode")

        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"DataP='",$get(DataP),"'")
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"File number=",$get(FileNumber))
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Field=",$get(Field))
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"EntryNumber=",$get(EntryNumber))
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"MatchValue='",$get(MatchValue),"'")
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Flags='",$get(Flags),"'")
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"DoingSubNodes=",$get(DoingSubNodes))

        new SpliceArray
        new temp

        if Field[cNodeDiv do           ;"Parse 'ITEM|NUMBER|ID'  into 'ITEM', 'NUMBER', 'ID'
        . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Multiple nodes found for field.  Processing...")
        . do CleaveStr^TMGSTUTL(.Field,cNodeDiv,.PartB)
        . set FieldNumber=$$GetNumField^TMGDBAPI(FileNumber,Field)
        . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Parsed off first part of Field.  Looking at only '",Field,"'")
        . ;"Note: this does NOT handle processesing of more than 2 nodes.
        . if PartB'=cNull do    ;"If PartB has data, then PartB(cFieldNumber) will also have data
        . . if PartB=".01" do
        . . . set PartB(cFieldNumber)=".01"
        . . else  do
        . . . new BFileNumber
        . . . set BFileNumber=$$GetSubFileNumber^TMGDBAPI(FileNumber,FieldNumber)  ;"get 'file number' of subfile
        . . . if BFileNumber'=0 do
        . . . . set PartB(cFieldNumber)=$$GetNumField^TMGDBAPI(BFileNumber,PartB)
        . . . else  set PartB(cFieldNumber)=0
        . . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Will deal with '",PartB,"' later")

        set Field=$$Substitute^TMGSTUTL(.Field,cProtect,cNodeDiv)  ;"convert protected ||'s back from }}'s to single |
        if $data(PartB) set PartB=$$Substitute^TMGSTUTL(.PartB,cProtect,cNodeDiv)

        set FieldNumber=+Field
        if FieldNumber=0 do
        . set FieldNumber=$$GetNumField^TMGDBAPI(FileNumber,Field)
        . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Converted '",Field,"' to field number ",FieldNumber)
        else  if $$VFIELD^DILFD(FileNumber,Field)=0 do  goto PFNDone
        . do ShowError^TMGDEBUG(.PriorErrorFound,Field_" is not a valid field number in file "_FileNumber)
        . set result=cAbort
        if FieldNumber=0 do  goto PFNDone
        . do ShowError^TMGDEBUG(.PriorErrorFound,"Unable to convert field '"_Field_"' to a field number. (Hint: If this name is supposed to contain multiple nodes, did you use '"_cNodeDiv_"' as a divider?)")
        . set result=cAbort

        if FieldNumber=.01 do
        . set EntryNumber=EntryNumber+1    ;"Test this!!
        . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Found .01 field.  Incrementing EntryNumber to "_EntryNumber)


        if $data(PartB) do
        . ;"If there are subnodes, then search if current entry should be under a prior entry
        . if $data(@DataP@(EntryNumber-1,FieldNumber,0,cEntries)) do
        . . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"EntryNumber=",EntryNumber)
        . . ;"set EntryNumber=EntryNumber-1
        . . set DoingSubNodes=1
        . . ;"if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Attaching current data as a subnode of prior entry.")
        . . ;"if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Changing EntryNumber to ",EntryNumber)

        if DoingSubNodes=0 goto PFNPast
        if (EntryNumber=0) do  goto PFNDone
        . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"No '.01' field found yet, so skipping processing.")

PFNPast
        if $data(PartB)=0 do
        . set result=$$CheckSubstituteData(.Text)  ;"Do any data lookup needed
        . if result=cAbort do
        . . do ShowError^TMGDEBUG(.PriorErrorFound,"Unable to do data lookup: "_Text)
        . else  do
        . . ;"HERE IS WHERE WE PUT THE INFO INTO THE DATA ARRAY.
        . . set @DataP@(EntryNumber,FieldNumber)=Text
        . . set @DataP@(EntryNumber,FieldNumber,"FieldName")=$get(Field) ;"mainly for debugging.
        . . if Flags'=" " set @DataP@(EntryNumber,FieldNumber,cFlags)=Flags
        . . new FieldInfo
        . . do GetFieldInfo^TMGDBAPI(FileNumber,FieldNumber,"FieldInfo")
        . . if $get(FieldInfo("TYPE"))="WORD-PROCESSING" do
        . . . do WPHandle(DataP,EntryNumber,FieldNumber,.Text)
        . . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Setting ",DataP,"(",EntryNumber,",",FieldNumber,")=",Text)
        . . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Flags were: '",Flags,"'")
        else  do
        . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"DoingSubNodes=",DoingSubNodes,", PartB='",$get(PartB),"'")
        . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Doing subnodes, so did NOT set ",DataP,"(",EntryNumber,",",FieldNumber,")=",Text)

        if result=cAbort goto PFNDone

        if FieldNumber=.01 set MatchValue=Text

        if (MatchValue'="")!(FieldNumber=.01) do
        . ;"set @DataP@(EntryNumber,FieldNumber,cMatchThis)=1  ;"i.e. true
        . set @DataP@(EntryNumber,FieldNumber,cMatchValue)=MatchValue
        . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Setting ",DataP,"(",EntryNumber,",",FieldNumber,",",cMatchValue,")=",MatchValue)

        set @DataP@(0,cEntries)=EntryNumber
        set @DataP@(0,cFile)=FileNumber

        if $data(PartB) do   ;"I.e. we have subnodes. -- process
        . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Subnodes detected. Here is where we deal with that '",PartB,"'")
        . new SubEntryNumber
        . set SubEntryNumber=$get(@DataP@(EntryNumber,FieldNumber,0,cEntries),0)
        . if (PartB(cFieldNumber)=".01")!(SubEntryNumber=0) do
        . . ;"test ... set SubEntryNumber=SubEntryNumber+1
        . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"SubEntryNumber=",SubEntryNumber)
        . set FileNumber=$$GetSubFileNumber^TMGDBAPI(FileNumber,FieldNumber)  ;"get 'file number' of subfile
        . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"file number=",FileNumber)
        . if FileNumber=0 quit
        . set DataP=$name(@DataP@(EntryNumber,FieldNumber))
        . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Calling self recursively")
        . new SubFlags set SubFlags=Flags ;"SubFlags=" "
        . new SubMatchValue set SubMatchValue=""
        . set result=$$ProcessRNode(DataP,PartB,.Text,.SubEntryNumber,FileNumber,1,SubMatchValue,SubFlags)   ;"Call self recursively

PFNDone
        if TMGDEBUG>0 do DebugExit^TMGDEBUG(.DBIndent,"ProcessRNode")
        quit result

WPHandle(DataP,EntryNumber,FieldNumber,Text)
        ;"Purpose: to process word-processing fields for ProcessRNode()
        ;"        It will get text into form ready for use by FILE^DIE
        ;"Input: DataP: The 'name' of the data array -- like this: "Data(1)"
        ;"       EntryNumber: The current entry number.  Should be passed by REFERENCE
        ;"         FileNumber: the current file number, or sub-filenumber. DON'T PASS BY REFERENCE
        ;"                The first node (i.e. "ITEM") should be field in FileNumber
        ;"         Text: the value that should be put into field.  should be passed by REFERENCE
        ;"                Text will have the following format:
        ;"                        Text="First line of text"
        ;"                        Text(cText,1)="First line of text" <-- only present if multiple
        ;"                        Text(cText,2)="Second line of text"    lines of text present.
        ;"Result: none

        new Array,temp
        new result

        if TMGDEBUG>0 do DebugEntry^TMGDEBUG(.DBIndent,"WPHandle")

        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Here is Text to use to put into WP field:")
        if TMGDEBUG do ArrayDump^TMGDEBUG("Text")
        if $data(Text(cText)) do
        . set result=$$FormatArray^TMGSTUTL(.Text,.Array,"\n")
        else  do
        . do CleaveToArray^TMGSTUTL(Text,"\n",.Array,1)

        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Here is array after processing, ready to put into WP field:")
        if TMGDEBUG do ArrayDump^TMGDEBUG("Array")

        merge @DataP@(EntryNumber,FieldNumber,"WP")=Array
        set @DataP@(EntryNumber,FieldNumber)=$name(@DataP@(EntryNumber,FieldNumber,"WP"))
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Setting: ",DataP,"(",EntryNumber,",",FieldNumber,")=",$name(@DataP@(EntryNumber,FieldNumber,"WP")))

        if TMGDEBUG>0 do DebugExit^TMGDEBUG(.DBIndent,"WPHandle")
        quit


CheckArraySubst(TextArray)
        ;"Purpose: Accept a text array, and scan all lines for any needed data substitution
        ;"Input: TextArray -- should be passed by reference.
        ;"                any number scheme of lines may be used.
        ;"Output -- Text array is changed, if passed by reference
        ;"Result: 1=OK to continue, 0=Error (data requested, but not found)

        new lineI,Count
        new OneLine
        new result set result=cOKToCont

        if $data(TextArray)'=10 goto CKASq

        set lineI=$Order(TextArray(""))
        for  do  quit:(lineI="")!(result=cAbort)
        . set OneLine=TextArray(lineI)
        . set result=$$CheckSubstituteData(.OneLine)  ;"Do any data lookup needed
        . set TextArray(lineI)=OneLine
        . set lineI=$Order(TextArray(lineI))

CKASq
        quit result

ParamSubstitute(Params)
        ;"Purpose: To accept an array of parameters, and do data substitution on all entries
        ;"Input: Params: an array of parameters
        ;"Result: 1=OK to continue, 0=Error (data requested, but not found)

        if TMGDEBUG>0 do DebugEntry^TMGDEBUG(.DBIndent,"ParamSubstitute")

        new result set result=cAbort
        if $data(Params)=0 goto PStDone
        new index

        set index=$order(Params(""))
        for  do  quit:(index="")!(result=cAbort)
        . if index="" quit
        . new s
        . if $data(Params(index))#10'=0 do
        . . set s=Params(index)
        . . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Looking at Param(",index,")=",s)
        . . set result=$$CheckSubstituteData(.s)
        . . if result=cAbort quit
        . . set Params(index)=s
        . else  do
        . . new subindex
        . . set subindex=$order(Params(index,""))
        . . for  do  quit:(subindex="")!(result=cAbort)
        . . . set s=Params(index,subindex)
        . . . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Looking at Param("_index_","_subindex_")=",s)
        . . . set result=$$CheckSubstituteData(.s)
        . . . if result=cAbort quit
        . . . set Params(index)=s
        . . . set subindex=$order(Params(index,subindex))
        . set index=$order(Params(index))

PStDone
        if TMGDEBUG>0 do DebugExit^TMGDEBUG(.DBIndent,"ParamSubstitute")
        quit result

CheckSubstituteData(Text)
        ;"Purpose:  To look for data-substitution codes (i.e. {{...}}), and if
        ;"            found, to replace with data from XML file
        ;"Input: A line of text that may or may not have codes. ** Should be passed by reference
        ;"Output: Text is modified if passed by reference
        ;"Result: 1=OK to continue, 0=Error (data requested, but not found, or error occured)
        ;"Note: Nesting is allowed, and all instances of {{...}} will be substituted

        new PartA,PartB,PartC,RefB
        new result set result=cOKToCont

        new InitDebug set InitDebug=TMGDEBUG
        set TMGDEBUG=0  ;"Force this function to not put out TMGDEBUG info.

        if TMGDEBUG>0 do DebugEntry^TMGDEBUG(.DBIndent,"CheckSubstituteData")

CKSubL1        ;"Check if Code contains a data reference
        if $$NestSplit^TMGSTUTL(.Text,cDataOpen,cDataClose,.PartA,.PartB,.PartC)=0 goto CkSubDone
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Reference to data found... replacing now.")
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Initline: '",Text,"'")

        set RefB=$$GetData(PartB)
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Looked up data: '",RefB,"'")
        if RefB="" do  goto CkSubDone
        . do ShowError^TMGDEBUG(.PriorErrorFound,"Error.  Unable to find data reference: "_PartB)
        . set result=cAbort
        set Text=PartA_RefB_PartC ;"reassemble new code line
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"After replacement, line='",Text,"'")
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"---------------------")
        goto CKSubL1

CkSubDone
        if TMGDEBUG>0 do DebugExit^TMGDEBUG(.DBIndent,"CheckSubstituteData")
        set TMGDEBUG=InitDebug
        quit result


DoJump(Params)
        ;"Purpose: To allow limited program flow control
        ;"Syntax:  e.g. <Jump condition="if State=1" label="C"></Jump>
        ;"Input: Params -- an array containg parameters to run
        ;"          Params(cCondition): M code executed to determine whether to jump
        ;"                e.g. Params(cCondition)="if State=2"
        ;"          Params(cLabel): The name of the block to jump to.
        ;"                Params(cLabel)="TargetLabel"
        ;"Note: The expected syntax of the label is: <Label>B</Label>
        ;"        In this example, the label name is "B"
        ;"Returns: If should continue execution:  1=OK to continue.  0=abort.

        new result
        set result=cOKToCont
        new CondBool set CondBool=1

        if TMGDEBUG>0 do DebugEntry^TMGDEBUG(.DBIndent,"DoJump")

        new CondCode set CondCode=$get(Params(cCondition))
        set result=$$CheckSubstituteData(.CondCode)
        if result=cAbort goto DJDone
        new Label set Label=$get(Params(cLabel))
        set result=$$CheckSubstituteData(.Label)
        if result=cAbort goto DJDone

        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Condition code='"_CondCode_"'")
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Label="_Label)

        ;"Note: Here I trap errors that might be returned from xecute,
        ;"      and set result=cAbort to cause script abort
        if CondCode'="" do
        . new $etrap set $etrap="do DoJErrTrap^TMGXINST"
        . set ^TMP("TMG",$J,"trap")=cOKToCont
        . xecute CondCode
        . set CondBool=$TEST
        . set result=^TMP("TMG",$J,"trap")
        . if result=cAbort do
        . . do ShowError^TMGDEBUG(.PriorErrorFound,"Error executing Jump conditional code: \n"_CondCode)
        else  do
        . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"No condition code given, so should already have set bool")

        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"CondBool",CondBool)

        if (CondBool)&(Label'="")&(result=cOKToCont) do
        . set result=$$DoShow(.Params)  ;"Show any associated text as a message
        . new OldNode set OldNode=ExecNode
        . set ExecNode=$$GetLabelNode(Label)
        . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Changed point of execution from ",OldNode," to ",ExecNode)
        . if ExecNode=0 do
        . . do ShowError^TMGDEBUG(.PriorErrorFound,"In Jump instruction, label '"_Label_"' not found.")
        . . set result=cAbort  ;"i.e. abort
        else  do
        . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Jump not done.")

DJDone
        if TMGDEBUG>0 do DebugExit^TMGDEBUG(.DBIndent,"DoJump")
        if result=cAbort do ShowError^TMGDEBUG(.PriorErrorFound,"Jump command failed.")

        quit result


        ;"=========================================================
        ;"DoJump Error trap routine
        ;"=========================================================
DoJErrTrap
        set $etrap=""
        set $ecode=""
        set ^TMP("TMG",$J,"trap")=cAbort
        quit
        ;"=========================================================
        ;"DoJump End of Error trap routine
        ;"=========================================================



GetLabelNode(Label)
        ;"Purpose: Scan through <Script> section for a <Label> that matches
        ;"Input: Label: the name to search for (case insensitive)
        ;"Results: the handle of the node sought, or 0 if not found

        new ChildNode
        set ChildNode=0

GLNLoop set ChildNode=$$CHILD^MXMLDOM(XMLHandle,ScriptNode,ChildNode)
        if ChildNode=0 goto GLNQuit
        if $$UP^XLFSTR($$Get1NText^TMGXMLT(XMLHandle,ChildNode))=$$UP^XLFSTR(Label) goto GLNQuit
        goto GLNLoop

GLNQuit quit ChildNode


GetData(Ref)
        ;"Purpose: To get data from the <DATA> section of the XML file
        ;"Input: Ref: the refrence path.
        ;"                e.g. Data.Site.Office[EastSide].Field[OpenDate],
        ;"                when used with the following data section...
        ;"        <Data>
        ;"          <Site>
        ;"            <Office id="EastSide">
        ;"              <Field id="Doctor">Kevin</Field>
        ;"              <Field id="OpenDate">12/1/04</Field>
        ;"            </Office>
        ;"          </Site>
        ;"        </Data>
        ;"                will return the value of '12/1/04'
        ;"
        ;"        Alternative acceptible input:
        ;"                e.g. MVar.SomeVar
        ;"                This will retrieve the value of variable 'SomeVar'
        ;"                that is defined in the M language, i.e. a local variable
        ;"                that might have been set in some M code.
        ;"                The name for SomeVar is case-specific.
        ;"
        ;"Note: The first node must be 'Data' or 'MVar'
        ;"Returns: the value requested, or "" if not found.

        new result set result=""

        if TMGDEBUG>0 do DebugEntry^TMGDEBUG(.DBIndent,"GetData")
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Ref to search for="_Ref)

        if $data(Ref)=0 goto QGetDat

        new Segment
        new SegNode
        new ID

        set Segment=$$ParseSeg(.Ref,.ID)
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Segment="_Segment)
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"ID=["_ID_"]")
        if $$UP^XLFSTR(Segment)=cData goto GetData1
        if $$UP^XLFSTR(Segment)='cMVar goto QGetDat

        ;"Here we are dealing with {{MVar.SomeVar}} pattern
        ;"Get name of variable to access.
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Found request to access M variable: ",ID)
        set Segment=$$ParseSeg(.Ref,.ID) ;"ID to be ignored.
        set result=$get(@Segment)
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Requested variable: ",Segment,"= '",result,"'")
        goto QGetDat

GetData1
        if $data(DataNode)=0 goto QGetDat  ;"Occurs if error box occurs before full XML parse
        set SegNode=DataNode
GetData2
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Getting ready to parse segment....")
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Ref="_Ref)
        set Segment=$$ParseSeg(.Ref,.ID)
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Segment="_Segment)
        set SegNode=$$GetDescIDNode(SegNode,Segment,ID)
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"SegNode=#"_SegNode)
        if SegNode=0 goto QGetDat

        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"?ready to loop?  Ref='"_Ref_"'")
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Ref='' is "_Ref="")
        if Ref="" goto QGetDat1

        goto GetData2

QGetDat1
        ;"If we get here, must have found correct node
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Success. data node found. SegNode="_SegNode)
        set result=$$Get1NText^TMGXMLT(XMLHandle,SegNode)
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Result="_result)

QGetDat
        if TMGDEBUG>0 do DebugExit^TMGDEBUG(.DBIndent,"GetData")
        quit result


ParseSeg(Ref,ID)
        ;"Purpose: to parse a line in the following format
        ;"         Data.Site.Office[EastSide].Field[OpenDate]
        ;"           Function will return the next segment (divided
        ;"         by '.', left-to-right
        ;"Input: Ref:  Should be passed by reference .  text of line, as described above
        ;"       ID:  Should be passed by reference. An OUT parameter (not used for input)
        ;"Output: Ref is changed (shortened).  When all done, Ref will equal " "
        ;"        If an ID is found (i.e. 'EastSide' in above example), then ID will
        ;"        will be set, otherwise " "
        ;"Result: The leftmost section, or " " if none found

        new result
        set result=" "
        set ID=" "
        new PartA,PartB,PartC

        if TMGDEBUG>0 do DebugEntry^TMGDEBUG(.DBIndent,"ParseSeg")

        ;"If no more pieces, just return input
        if 'Ref["." do  goto Parse2
        . set result=Ref
        . set Ref=" "

        set result=$piece(Ref,".",1)
        set result=$get(result," ")
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Result="_result)
        set PartB=$piece(Ref,".",2,100)
        set PartB=$get(PartB," ")
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"PartB="_PartB)
        set Ref=PartB

Parse2        ;"If Office[EastSide] pattern found, separate parts
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Here is result: "_result_"  Will now look for '['")
        if (result["[")&(result["]") do
        . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"... found.")
        . set PartA=$piece(result,"[",1)
        . set PartB=$piece(result,"[",2)
        . set PartC=$piece(PartB,"]",1)
        . set result=PartA
        . set ID=PartC
        . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Result now ="_result_" ID="_ID)

        if TMGDEBUG>0 do DebugExit^TMGDEBUG(.DBIndent,"ParseSeg")
        quit result


GetDescIDNode(ParentNode,Name,ID)
        ;"Purpose: get a descendant node that matches Name and ID
        ;"Input: ParentNode node handle of parent
        ;"         Name is name of node
        ;"       ID, the ID to match against.  ID is an attrib of "id"
        ;"e.g.   Look for <Field id="Doctor">Kevin</Field> type pattern.
        ;"       Here, Name='Field', ID='Doctor'
        ;"Note: only immediate children (not grandchildren) are searched.
        ;"Returns: the handle of the sought node, or 0 if not found.

        new ChildNode
        set ChildNode=0
        new NodeName,AtrVal

        new InitDebug set InitDebug=TMGDEBUG
        set TMGDEBUG=0  ;"Force this function to not put out TMGDEBUG info.

        if TMGDEBUG>0 do DebugEntry^TMGDEBUG(.DBIndent,"GetDescIDNode")

        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Looking for children of node="_ParentNode)
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"with name="_Name_" ID="_ID)
        ;"if ID=" " write "ID=space (null)",!
        ;"else  write "ID is something other than space. ",!

GDILoop set ChildNode=$$CHILD^MXMLDOM(XMLHandle,ParentNode,ChildNode)
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Looking at child node #"_ChildNode)
        ;"if TMGDEBUG>0 do ShowXMLNode(ChildNode)
        if ChildNode=0 goto GDIQuit
        set NodeName=$$GetNName^TMGXMLT(XMLHandle,ChildNode)   ;"Returns result in UPPERCASE
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Name="_NodeName)
        if NodeName'=$$UP^XLFSTR(Name) goto GDILoop
        if ID=" " goto GDIQuit  ;"if no ID specified, then match based on Name only.
        set AtrVal=$$GetAtrVal^TMGXMLT(XMLHandle,ChildNode,cId)
        set AtrVal=$$UP^XLFSTR(AtrVal)
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Value: ",AtrVal)
        if AtrVal'=$$UP^XLFSTR(ID) goto GDILoop
        ;"If we get here, we have a match

GDIQuit
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Success! Node: ",ChildNode)
        if TMGDEBUG>0 do DebugExit^TMGDEBUG(.DBIndent,"GetDescIDNode")

        set TMGDEBUG=InitDebug
        quit ChildNode


GetCMDLine(ExecNode,Command,Params)
        ;"Purpose: Load elements needed to execute line
        ;"Input: ExecNode, the node to be executed...
        ;"       Other parameters are OUT params... should be passed by reference
        ;"Output: Command -- the command of the line
        ;"          Params -- PASS BY REFERENCE-- to accept back the parameters
        ;"Results: 1=if valid info;  0=should NOT be executed (i.e. abort)

        new result set result=cOKToCont

        if TMGDEBUG>0 do DebugEntry^TMGDEBUG(.DBIndent,"GetCMDLine")

        set Command=$$GetNName^TMGXMLT(XMLHandle,ExecNode)
        set Command=$$UP^XLFSTR(Command)  ;"convert to uppercase

        if $data(ProcTable(Command)) goto GCOK
        else  do  goto GCDone
        . do ShowError^TMGDEBUG(.PriorErrorFound,"Command '"_Command_"' is invalid.")
        . set result=cAbort

GCOK
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"CMD Command=",Command)

        new TextArray,ValidText
        set ValidText=$$GetNText^TMGXMLT(XMLHandle,ExecNode,.TextArray)
        ;"if result=cAbort do  goto GCDone
        ;". do ShowError^TMGDEBUG(.PriorErrorFound,"Error retrieving text into array.")
        if ValidText merge Params(cText)=TextArray

        set result=$$GetParams^TMGXMLT(XMLHandle,ExecNode,.Params)
        if result=cAbort do
        . do ShowError^TMGDEBUG(.PriorErrorFound,"Error getting parameters")

GCDone
        if TMGDEBUG>0 do DebugExit^TMGDEBUG(.DBIndent,"GetCMDLine")
        quit result


GetNextCMD(ExecNode)
        ;"Purpose: Advance execution point
        ;"Input: ExecNode: the current execution point, should be passed by reference
        ;"Output: ExecNode is changed
        ;"        returns 0 if end of program, otherwise positive number (i.e. ExecNode)

        set ExecNode=$$CHILD^MXMLDOM(XMLHandle,ScriptNode,ExecNode)

        quit ExecNode


RunScript(ExecNode)
        ;"Purpose: To run the entire script
        ;"Input: ExecNode, should be passed by reference
        ;"Assumptions: That ExecNode points to first line of script.
        ;"Result: 1: quit normally.  0=error exit.

        new Command
        new Params
        new OKToCont set OKToCont=1 ;"1=OK to continue  0=should abort

        if TMGDEBUG>0 do DebugEntry^TMGDEBUG(.DBIndent,"RunScript")
RunLoop
        if ExecNode=0 goto RSDone

        ;"Get current command line information
        ;"if TMGDEBUG>0 do ShowXMLNode(ExecNode)
        kill Params

        set OKToCont=$$GetCMDLine(ExecNode,.Command,.Params)
        if OKToCont=0 do  goto RSDone  ;"If error, then quit execution.
        . do ShowError^TMGDEBUG(.PriorErrorFound,"Error parsing command line.")
        . if TMGDEBUG>0 do ShowXMLNode^TMGXMLT(ExecNode)

        set OKToCont=$$CMDProcess(Command,.Params)
        if OKToCont=0 do  goto RSDone  ;"If error, then quit execution.
        . do ShowError^TMGDEBUG(.PriorErrorFound,"Error executing command.")
        . if TMGDEBUG>0 do ShowXMLNode^TMGXMLT(ExecNode)

        ;"Look for ESC that will cause loop abort
        ;"write "#"
        read *CheckKey:0
        if CheckKey=27 do  goto RSDone
        . write !,!,"Escape key pressed.  Aborting script.",!,!

        ;"Advance to next command line
        set OKToCont=$$GetNextCMD(.ExecNode)
        if OKToCont'=0 goto RunLoop
        set OKToCont=1  ;"At this point, exit is normal.

RSDone
        if TMGDEBUG>0 do DebugExit^TMGDEBUG(.DBIndent,"RunScript")
        quit OKToCont

 ;"------------------------------------------------------------------------
 ;"========================================================================
 ;"------------------------------------------------------------------------
GetDispMode()
        ;"Purpose: To determine with form of input user wants
        ;"Results: 1=GUI,2=CHUI,3=RollNScroll,0=abort
        new Input
        new result set result=cAbort
        new Default set Default=3 ;"If changed, change(1) below

        if TMGDEBUG>0 do DebugEntry^TMGDEBUG(.DBIndent,"GetDispMode")

        write "Select interface option:",!
        write "    0. Quit. (Goodbye!)",!
        write "    1. Linux X graphics/ 'GUI' (Recommended)",!
        write "    2. Text graphics / 'CHUI' (Incomplete)",!
        write "    3. Line-by-Line / 'Roll-and-scroll'",!

        write "Enter option number ("_Default_"): "
        read Input,!
        if Input="" do
        . ;"write "Defaulting to: ",Default,!
        . set Input=Default
        else  if +Input>4 do
        . set Input=Default

        set result=+Input
        if (Input=1)!(Input=2) do
        . do SetupConsts^TMGXDLG()
        . do SetGUI^TMGXDLG(Input=1)
        ;"if Input=2 do  goto GIMDone
        ;". do SetupConsts^TMGXDLG()
        ;". do SetGUI^TMGXDLG(0)

GIMDone
        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Display mode set at: ",result)
        if TMGDEBUG>0 do DebugExit^TMGDEBUG(.DBIndent,"GetDispMode")
        quit result



DoMsgBox(Params)
        ;"Purpose: To provide a method for script users to
        ;"        show a message box
        ;"Input: Params -- an array loaded with needed values:
        ;"          Params(cHeader): Header text
        ;"          Params(cText,*): Array containing text
        ;"          i.e. Params(cText,1)="text of line 1"
        ;"          i.e. Params(cText,2)="text of line 2"
        ;"          i.e. Params(cText,3)="text of line 3"
        ;"          i.e. Params(cText,4)="text of line 4"
        ;"Result: 1=ok to continue,  0=abort

        if TMGDEBUG>0 do DebugEntry^TMGDEBUG(.DBIndent,"DoMsgBox")

        new Width
        new Text,S,PartB,PartB1
        new index,j
        new Modal
        new result set result=cOKToCont

        if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Here is a dump of the params")
        if TMGDEBUG do ArrayDump^TMGDEBUG("Params") ;"zwr Params(*)

        set Text(0)=$get(Params(cHeader),"Message:")
        set Width=$get(Params(cWidth,cUpperCase),0)
        set Modal=$get(Params(cModal),cModalMode)
        set index=$order(Params(cText,""))
        set j=1
DMSGLoop
        if index="" goto DMSGPast
        set S=$get(Params(cText,index))
        set result=$$CheckSubstituteData(.S)
        if result=cAbort goto DMSGQuit
DMSG2Loop ;"Load string up into Text array, to pass to PopupArray
        if S[cNewLn do
        . do CleaveStr^TMGSTUTL(.S,cNewLn,.PartB1)
        do SplitStr^TMGSTUTL(.S,(Width-4),.PartB)
        set PartB=PartB_PartB1 set PartB1=""
        set Text(j)=S
        set j=j+1
        if $length(PartB)>0 do  goto DMSG2Loop
        . set S=PartB
        . set PartB=""

        set index=$order(Params(cText,index))
        goto DMSGLoop

DMSGPast
        if TMGDEBUG do
        . if TMGDEBUG>0 do DebugMsg^TMGDEBUG(DBIndent,"Here is Text array to send to PopupArray:")
        . do ArrayDump^TMGDEBUG("Text") ;"zwr Text(*)

        do PopupArray^TMGUSRIF(2,Width,.Text,Modal)
DMSGQuit
        if TMGDEBUG>0 do DebugExit^TMGDEBUG(.DBIndent,"DoMsgBox")
        quit result


