TMGSRCH ;TMG/kst/Search API ; 6/4/10
        ;;1.0;TMG-LIB;**1**;05/19/10
        ;
 ;"TMG FILEMAN SEARCH API
 ;
 ;"Copyright Kevin Toppenberg MD 5/19/10
 ;"Released under GNU General Public License (GPL)
 ;"
 ;"NOTE: this function depends on new version of LIST^DIC, from G. Timpson Patch
 ;"=======================================================================
 ;" RPC -- Public Functions.
 ;"=======================================================================
 ;"SRCH(OUT,FILENUM,STR) --A search function, to support calls by RPC from CPRS
 ;"BKSRCH(FILENUM,STR)  -- designed to be called via JOB --> separate job thread   
 ;"FMSRCH(OUT,FILENUM,COMPEXPR) --A wrapper for Fileman search call
 ;"=======================================================================
 ;"PRIVATE API FUNCTIONS
 ;"=======================================================================
 ;"PARSESTR(FILENUM,STR,ARRAY,FNUMPTR) -- Parse user input into formatted array
 ;"PARSE1(FILENUM,STR,FNUMPTR,ARRAY) --Parse a simple search term 
 ;"BKPGFN(MSG,PCT) -- Callable progress function code for background thread.
 ;"DOSRCH(PTMGOUT,FILENUM,STR,PGFN) --Common search codes 
 ;"=======================================================================
 ;"=======================================================================
 ;"Dependencies:
 ;" TMGDBAPI, DIE, XLFSTR, TMGSRCH0, TMGSRCH1, TMGSTUTL
 ;"=======================================================================
 ;"=======================================================================
 ;
 ;
 ;"=======================================================================
 ;"  SEARCH STRING DOCUMENTATION
 ;"=======================================================================
 ;"Search string examples:
 ;"  8925:.02(.01="SMITH,JOHN")
 ;"  1234:.01(.03in"32..55")   <-- this is a range test
 ;"  1234:.99((.01="SMITH,JOHN") OR (.01="SMITH,BILL")) AND 4567:.01(.02'="4/2/10") NOT (1["HAPPY")
 ;"  8925:(REPORT TEXT[DM-2)!(REPORT TEXT[HTN) AND 120.5:((VITAL TYPE=PULSE)&(RATE>70)) Targetfile=2
 ;"
 ;"SYNTAX:
 ;"  -- File specifier.  To specify searching in a file OTHER THAN target filenumber, an optional
 ;"         FILENUM:FLD[:FLD[:FLD...]] may be specified.  However, ultimately, this must point back
 ;"         to the target filenumber.  E.g. Search in file 8925, but for each entry found, use the IEN
 ;"         specified by FLD (or FLDA:FLDB or FLDA:FLDB:FLDC:...).  NOTE: If just FILENUM is provided
 ;"         without specifying FLD(s) to point to target filenumber, then the code will find a path
 ;"         (if possible), using first one found.
 ;"       FILENUM:(...)
 ;"         The logic is read from left to right, honoring parentheses.  If a filenumber
 ;"         is not specified, then the last specified filenumber is used.
 ;"         E.g. 1234:.01( LogicA ) OR 234:.99( LogicB )  AND ( LogicC )
 ;"              LogicA fields refer to file 1234:.01.
 ;"              LogicB fields refer to file 234:.99
 ;"              LogicA fields refer to file 234:.99 (last specified file number)
 ;"         E.g. 5678:.01( (LogicA1) OR 5432:.88(LogicA2) NOT (LogicA3) ) or (LogicB)
 ;"              LogicA1 fields refer to file  5678:.01
 ;"              LogicA2 fields refer to file 5432:.88
 ;"              LogicA3 fields refer to file 5432:.88 (last specified file number inside parentheses)
 ;"              LogicB fields refer to file 5678 (last specified file number at same parentheses level)        
 ;"  -- Each individual search term must be enclosed in parentheses, and may contain sub-terms
 ;"     enclosed in nested parentheses
 ;"  -- Each individual search term is comprised of:
 ;"         FIELD then COMPARATOR then VALUE
 ;"          1. FIELDS -- can be name or number.  This is for currently active file (see below)
 ;"                       may also be FIELDA:FIELDB:... when FIELDA is a pointer, then FIELDB
 ;"                       is taken from the pointed-to file. If FIELDB is not provided, and FIELDA
 ;"                       is a pointer, then the .01 field of pointed-to-file.  Individual field
 ;"                       names may be inclosed in quotes
 ;"          2. COMPARATOR -- can be:
 ;"                "="                -- means exact match
 ;"                "'=", "<>",        -- any of these means Does-not-equal
 ;"                ">=", "'<"         -- means greater-than-or-equal-to (same as not-less-than)
 ;"                "<=", "'>"         -- means less-than-or-equal-to (same sa not-greater-than)
 ;"                "in","IN","In","{" -- means field is in specified rage (see Value below)
 ;"                                      When using IN, if field name is provided by NAME (not number),
 ;"                                      then field name should be inclosed in quotes to separate the
 ;"                                      letters of the field name from the letters of 'IN'.
 ;"                "["                -- means 'contains'.  Interpreted as follows:
 ;"                         -- For Word processor (WP) fields, this means that any line in the entire field
 ;"                            can contain search term, to be matched positive.
 ;"                         -- For free text field, then just text of field is searched.
 ;"          3. VALUE -- The search term to search for.  Should be in quotes.
 ;"                      Note: if comparator is "IN", then syntax is "Value1..Value2"
 ;"                      There should be a ".." between the two values.
 ;"  -- Logical combiners of separate search terms allowed are:
 ;"            "OR" or "|" or "||" or "!"
 ;"            "AND" or "&" or "&&"
 ;"            "NOT" or "'" or "ANDNOT"
 ;"=======================================================================
 ;"=======================================================================
 ;
 ;
TEST ;
        NEW STR,OUT
        ;"SET STR="8925:(STATUS=COMPLETED)&((PATIENT[CUTSHALL)!(PATIENT[CUTSHAW))"
        SET STR="8925:(REPORT TEXT[DM-2)!(REPORT TEXT[HTN) AND 120.5:((VITAL TYPE=PULSE)&(RATE>70))"
        ;"SET STR="8925:(REPORT TEXT[DM-2) AND 120.5:((VITAL TYPE=PULSE)&(RATE>70))"
        ;"SET STR="8925:(REPORT TEXT[HTN) AND 120.5:((VITAL TYPE=PULSE)&(RATE{70..75))"
        ;"SET STR="8925:(REPORT TEXT[DM-2)!(REPORT TEXT[HTN)"
        ;"WRITE STR,!
        ;"DO SRCH(.OUT,2,STR)        
        ;"NEW CT SET CT=+$GET(OUT("COUNT"))        
        ;"WRITE "Found ",CT," total matches.",!
        ;
        DO BKSRCH(2,STR)
        NEW STATUS,PCT
        NEW REF SET REF=$NAME(^TMP("TMG","TMGSRCH",$J))        
        FOR  DO  QUIT:(STATUS["#DONE#")
        . HANG 1
        . SET STATUS=$GET(@REF@("MSG"))
        . WRITE "STATUS: ",STATUS,!
        ;"IF $DATA(@REF) ZWR @REF
        QUIT
 ;
SRCH(OUT,FILENUM,STR) ;
        ;"Purpose: A search function, to support calls by RPC from CPRS
        ;"Input:  OUT-- Pass by reference.  AN OUT PARAMETER.
        ;"        FILENUM -- The target file number that resulting IENs will be in
        ;"        STR -- This is a logic string for searching.  See details above.
        ;"Results:  OUT is filled in.  Format:
        ;"             OUT(0)=1    for success, or -1^Error Message
        ;"             OUT(IEN)=""
        ;"             OUT(IEN)=""
        ;"             OUT("COUNT")=Count of number of found records.
        ;"Results: None
        ;"
        DO DOSRCH("OUT",.FILENUM,.STR) ;
SRCHDN  QUIT
        ;
        ;
BKSRCH(FILENUM,STR) ; 
        ;"Purpose: this function is designed to be called via JOB, to setup separate job thread  
        ;"         E.g. JOB BKSRCH^TMGTMGSRCH(FILENUM,STR) NEW MSGJOB SET MSGJOB=$ZJOB
        ;"         NOTE: When job, output MSG will be "#DONE#" (see below)
        ;"Input: Filenum: This this is the target file of the search. 
        ;"       STR -- This is the logic string for searching.  Format as per SRCH() docs
        ;"Output: Output will go into ^TMP("TMG","TMGSRCH",$J,"OUT")
        ;"        Messages will go into ^TMP("TMG","TMGSRCH",$J,"MSG") 
        ;"        % Done  will go into ^TMP("TMG","TMGSRCH",$J,"PCT") 
        ;"Results: none
        NEW PGFN SET PGFN="DO BKPGFN^TMGSRCH(.TMGSTAT,.TMGPCT)"
        NEW POUT SET POUT=$NAME(^TMP("TMG","TMGSRCH",$J,"OUT"))
        KILL @POUT
        DO DOSRCH(POUT,.FILENUM,.STR,PGFN) ;
        DO BKPGFN("#DONE#",100)
        QUIT ;"This should terminate thread (if called by JOB as above)
        ;
BKPGFN(MSG,PCT) ;
        ;"Callable progress function code for background thread.
        SET ^TMP("TMG","TMGSRCH",$J,"MSG")=$GET(MSG)
        SET ^TMP("TMG","TMGSRCH",$J,"PCT")=$GET(PCT)
        QUIT
        ;
        ;
DOSRCH(PTMGOUT,FILENUM,STR,PGFN) ;
        ;"Common entry endpoint for search entry tags.  See docs in SRCH()
        ;"Input: PTMGOUT -- Pass by NAME.  The name of the output array
        ;"       FILENUM -- See SRCH()
        ;"       STR -- See SRCH()
        ;"       TMGPGFN -- OPTIONAL. Mumps code that will be called periodically
        ;"                            to allow display of progress of slow searches.
        ;"                            Code may depend on the following variables:
        ;"                            TMGSTAT -- The most recent status text
        ;"                            TMGPCT -- a very gross estimate of % done (0-100%)
        ;"Results -- None.
        NEW TMGARRAY,RESULT,CT
        SET RESULT=$$PARSESTR(.FILENUM,STR,.TMGARRAY)
        ;
        ;"MERGE ^TMG("TMP","RPC","TMGRPCSR","TMGARRAY")=TMGARRAY  ;"TEMP!!!
        ;
        IF +RESULT=-1 SET @PTMGOUT@(0)=RESULT GOTO DSRCHDN
        SET CT=$$ARRYSRCH^TMGSRCH0(FILENUM,PTMGOUT,.TMGARRAY,.PGFN)        
        SET @PTMGOUT@("COUNT")=CT
        SET @PTMGOUT@("FILENUM")=FILENUM
        IF $GET(@PTMGOUT@(0))="" SET @PTMGOUT@(0)=1  ;"Success
DSRCHDN QUIT
        ;
        ;
PARSESTR(FILENUM,STR,ARRAY,FNUMPTR) ;
        ;"Purpose: To take user input, validate it, and parse into an formatted array
        ;"Input: FILENUM -- The file number that is the target of the search. 
        ;"       STR: This is the user input string.  Format as documented in SRCH() above.
        ;"       ARRAY -- PASS BY REFERENCE.  An OUT PARAMETER.  Format as follows.
        ;"              ARRAY(1,"FNUMPTR")= FNUM:FLDA[:FLDB[:FLDC...]] FNUM is filenumber that 
        ;"                                  contain search field, and then fields used to point 
        ;"                                  back to *TARGET* FILENUM for entire search
        ;"              ARRAY(1,"FLD")=Fieldnumber to search
        ;"              ARRAY(1,"COMP")=Comparator, will be "=", "'=", "'<", or "'>", "[", "{", "IN"
        ;"              ARRAY(1,"SRCH")=The value of to be used in search.
        ;"              ARRAY(1,"WP")=1 if field is a WP field
        ;"              ARRAY(2,...)  The second search term.
        ;"              ARRAY(2,"LOGIC")=#^Combiner
        ;"                          # means the set so far.
        ;"                          Combiner will be "AND", "OR", or "NOT"
        ;"              ARRAY(3,...)  The third search term (which is comprised of sub terms)
        ;"              ARRAY(3,1,...  The first subterm (same format as higher level)
        ;"              ARRAY(3,2,...  The second subterm (same format as higher level)
        ;"              ARRAY(n,...)  The N'th search term.
        ;"    removed-> ARRAY("SETCOMP",i)= NumA^Combiner^NumB
        ;"                          NumA and NumB refer to seach term number (e.g. 1, 2, ... n above)
        ;"                          If NumA="#", then it means 'the resulting set of results so far'
        ;"                          Combiner will be "AND", "OR", or "NOT"
        ;"                          i is the index variable, and logic should be evaluated in numerical order
        ;"       FNUMPTR: Will be used when calling self reiteratively.  Leave blank in first call.
        ;"                DON'T pass by reference.  This is 'FileNum:FLD[:FLD[:FLD...]] specifier
        ;"Results: 1 if OK, or -1^Message if error during processing.
        ;
        NEW SUBSTRA,SUBSTRB,POS
        NEW RESULT SET RESULT=1 ;"default to success
        NEW TERMNUM SET TERMNUM=0
        SET FILENUM=+$GET(FILENUM)
        IF FILENUM'>0 DO  GOTO PSDN
        . SET RESULT="-1^Target file number not provided."
        SET FNUMPTR=$GET(FNUMPTR,FILENUM)
        SET ARRAY("FILE")=FILENUM        
        NEW LOGICNUM SET LOGICNUM=0
        NEW DONE SET DONE=0
        FOR  DO  QUIT:(DONE=1)!(+RESULT=-1)
        . NEW TEMPARRAY
        . SET TERMNUM=TERMNUM+1
        . ;"--- Get file number, if any
        . SET STR=$$TRIM^XLFSTR(STR)
        . IF +$PIECE(STR,"(",1)>0 DO  QUIT:(+RESULT=-1)
        . . SET FNUMPTR=$PIECE(STR,"(",1)  ;"Convert 1234:.01:.02:(...) --> 1234:.01:.02:
        . . IF $EXTRACT(FNUMPTR,$LENGTH(FNUMPTR))=":" SET FNUMPTR=$EXTRACT(FNUMPTR,1,$LENGTH(FNUMPTR)-1)
        . . IF ($PIECE(FNUMPTR,":",2)="")&(+FNUMPTR'=FILENUM) DO  QUIT:(+RESULT=-1)
        . . . NEW SAVPTR SET SAVPTR=FNUMPTR
        . . . SET FNUMPTR=$PIECE($$PATHTO^TMGSRCH1(+FNUMPTR,FILENUM),"^",1)
        . . . IF FNUMPTR="" SET RESULT="-1^Unable to find path to file #"_FILENUM_" from "_SAVPTR
        . . ELSE  IF $$FNPTR^TMGSRCH1(FNUMPTR)'=FILENUM DO  QUIT
        . . . SET RESULT="-1^'"_FNUMPTR_"' points to file #"_$$FNPTR^TMGSRCH1(FNUMPTR)_", not file #"_FILENUM_" as expected"
        . ;"Split STR --> SUBSTRA + SUBSTRB
        . SET SUBSTRA=$$MATCHXTR^TMGSTUTL(STR,"(",,,"(")
        . IF SUBSTRA="" SET DONE=1 QUIT
        . SET POS=$FIND(STR,SUBSTRA)  ;"Return pos of following character
        . SET SUBSTRB=$EXTRACT(STR,POS+1,9999) ;"Should be " [LOGICTERM] [SearchTerm]..."
        . ;"Process SUBSTRA, either directly if single term, or recursively if compound term.
        . IF $$HNQTSUB^TMGSTUTL(SUBSTRA,"(") DO
        . . SET RESULT=$$PARSESTR(FILENUM,SUBSTRA,.TEMPARRAY,FNUMPTR)
        . . SET ARRAY(TERMNUM,"SUBTERMS")=1
        . ELSE  DO
        . . SET RESULT=$$PARSE1(FILENUM,SUBSTRA,FNUMPTR,.TEMPARRAY)
        . IF +RESULT=-1 QUIT
        . SET SUBSTRA=""
        . MERGE ARRAY(TERMNUM)=TEMPARRAY
        . ;"Now get Logic term connecting this to next term (if any)
        . SET SUBSTRB=$$TRIM^XLFSTR(SUBSTRB) ;"Remove opening (and closing) spaces
        . NEW LOGICTERM SET LOGICTERM=""
        . NEW P,CH
        . NEW DNCOMB SET DNCOMB=0
        . FOR P=1:1:$LENGTH(SUBSTRB) DO  QUIT:DNCOMB!(+RESULT=-1)
        . . SET CH=$$UP^XLFSTR($EXTRACT(SUBSTRB,P))
        . . IF ("&|'!ANDORNOT"'[CH) SET DNCOMB=1 QUIT
        . . SET LOGICTERM=LOGICTERM_CH
        . SET STR=$EXTRACT(SUBSTRB,$LENGTH(LOGICTERM)+1,9999),SUBSTRB=""
        . IF LOGICTERM="" QUIT
        . SET LOGICTERM=$$FIXCOMB^TMGSRCH1(LOGICTERM,.RESULT) QUIT:(+RESULT=-1)
        . NEW CURSET SET CURSET=$SELECT(TERMNUM=1:"1",1:"#")
        . SET LOGICNUM=LOGICNUM+1
        . ;"SET ARRAY("SETCOMP",LOGICNUM)=CURSET_"^"_LOGICTERM_"^"_(TERMNUM+1) ;"will check later that TERMNUM+1 is supplied
        . SET ARRAY(TERMNUM+1,"LOGIC")="#^"_LOGICTERM
PSDN	QUIT RESULT
        ;
        ;
PARSE1(FILENUM,STR,FNUMPTR,ARRAY) ;
        ;"Purpose: Parse a simple search term (e.g. .01="SMITH,JOHN"). Also validate that field exists in file.
        ;"Input: FILENUM -- The TARGET filenumber that the entire search is referencing.
        ;"       STR: This is part of the user input string to parse
        ;"       FNUMPTR: FNUM:FLDA[:FLDB[:FLDC...]] FNUM is filenumber that contain search field, and then 
        ;"                fields used to point back to *TARGET* FILENUM for entire search
        ;"       ARRAY -- PASS BY REFERENCE.  An OUT PARAMETER.  Format as follows.
        ;"              ARRAY("FNUMPTR")=Filenumber that contains field)
        ;"              ARRAY("FLD")=Fieldnumber to search
        ;"              ARRAY("COMP")=Comparator, will be "=", "'=", "'<", or "'>", "[","IN", "{"
        ;"              ARRAY("SRCH")=The value of to be used in search.
        ;"              ARRAY("WP")=1 if field is a WP field
        ;"NOTE:  If field specifies a DATE, then the search value will be converted to FileMan format
        ;"Results: 1 if OK, or -1^Message if error during processing.
        ;"
        NEW RESULT SET RESULT=1 ;"default to success
        NEW SAV SET SAV=STR
        SET STR=$$TRIM^XLFSTR($GET(STR))
        SET ARRAY("FNUMPTR")=FNUMPTR
        NEW FLD,FLDS SET FLDS=""
        NEW TMGTFILE SET TMGTFILE=+FNUMPTR
        FOR  QUIT:("'<>=[:({"[$EXTRACT(STR,1))!(STR="")  DO 
        . SET FLD=$$GETFLD^TMGSRCH1(.STR) ;
        . NEW SAVFIL SET SAVFIL=TMGTFILE
        . NEW ONEFLD SET ONEFLD=$$FLDNUM^TMGSRCH1(.TMGTFILE,.FLD)          
        . IF ONEFLD'>0 DO  QUIT
        . . SET RESULT="-1^Field ["_FLD_"] was not found in file ["_SAVFIL_"]"
        . IF FLDS'="" SET FLDS=FLDS_":"
        . SET FLDS=FLDS_ONEFLD
        IF +RESULT=-1 GOTO PS1DN
        SET ARRAY("FLD")=FLDS
        IF $$ISWPFLD^TMGDBAPI(+FNUMPTR,+FLDS) SET ARRAY("WP")=1
        NEW FLDTYPE SET FLDTYPE=$PIECE($GET(^DD(+FNUMPTR,+FLDS,0)),"^",2)      
        IF FLDTYPE["M" DO  GOTO PS1DN
        . SET RESULT="-1^Searches in fields that are MULTIPLES not supported"
        SET STR=$$TRIM^XLFSTR(STR)
        NEW COMP
        IF $$UP^XLFSTR($EXTRACT(STR,1,3))="'IN" SET COMP="'IN"
        ELSE  IF $$UP^XLFSTR($EXTRACT(STR,1,2))="IN" SET COMP="IN"
        ELSE  DO
        . SET COMP="" NEW P,CH
        . FOR P=1:1:$LENGTH(STR) SET CH=$EXTRACT(STR,P) QUIT:("'!<>=[{"'[CH)  SET COMP=COMP_CH
        SET STR=$EXTRACT(STR,$LENGTH(COMP)+1,9999)
        SET COMP=$$FIXCOMP^TMGSRCH1(COMP,.RESULT)
        IF +RESULT=-1 GOTO PS1DN
        SET ARRAY("COMP")=COMP
        SET STR=$$TRIM^XLFSTR(STR) ;"Remove any spaces after comparator 
        NEW SRCH SET SRCH=$$TRIM^XLFSTR(STR,,"""") ;"Trim quotes, if any.
        IF FLDTYPE["D" DO  GOTO:(+RESULT=-1) PS1DN   ;"standardized dates
        . NEW ADATE SET ADATE=SRCH
        . NEW TEMPRSLT SET TEMPRSLT=""
        . FOR  QUIT:(ADATE="")!(+RESULT=-1)  DO
        . . IF TEMPRSLT'="" SET TEMPRSLT=TEMPRSLT_".."        
        . . SET TEMPRSLT=TEMPRSLT_$$STDDATE^TMGSRCH1($PIECE(ADATE,"..",1),.RESULT)
        . . IF +RESULT=-1 QUIT
        . . SET ADATE=$PIECE(SRCH,"..",2)
        . SET SRCH=TEMPRSLT
        ELSE  IF FLDTYPE["S" DO  ;"Convert FM SET type into internal format
        . NEW OUT,TMGMSG
        . DO VAL^DIE(+FNUMPTR,"+1,",FLD,"E",SRCH,.OUT,,"TMGMSG")
        . SET SRCH=$GET(OUT)
        IF SRCH'="" SET ARRAY("SRCH")=SRCH
        ELSE  DO  GOTO PS1DN
        . SET RESULT="-1^Search value is invalid"
        ;
PS1DN   IF +RESULT=-1 SET RESULT=RESULT_", found in ["_SAV_"]"
        QUIT RESULT     
        ;
        ;
FMSRCH(TMGFILE,TMGCOMPEXPR,TMGOUT,TMGOPTION)  ;
        QUIT $$FMSRCH^TMGSRCH0(.TMGFILE,.TMGCOMPEXPR,.TMGOUT,.TMGOPTION)

