// SORTMERGE.VDM -- Sort a block of lines/records based on key fields. // Asks user for key fields and options via Dialog_Input_1(). // Implements {Edit, Sort, Sort lines}. // // Originally by: Greenview Data, Inc. // Last change: 21-June-2004 // Last change: 26-Nov-2004 CZ // // Requires: VEDIT Plus 6.0 or later for Windows // // From VEDIT: Highlight a block and select {Edit, Sort, Sort lines). // This macro will prompt for keyfields and options. // (It is unnecessary to highlight when sorting the entire file). // // From OS: VPW -X SORTMERGE filename [-T rlen] // // This will prompt for the keyfields and options and then // sort the entire file. It will remain in VEDIT. // // Use "-T rlen" after the filename for sorting fixed-length // records. // // -OR- VPW -C SortMerge("keyfields",bpos,epos,options) fname [-T rlen] // // Keyfields: cb1,ce1;cb2,ce2;...cbn,cen // // Beginning and ending columns (inclusive) can be any // VEDIT numeric expression. Only one delimiter type is // needed. Also, delimiters other than the ones indicated // may be used, but not "-" which is treated as "minus" // not "dash". // // bpos=0, epos=File_Size (or FSize) sorts the entire file. // // Options: CASE Distinguishes cased characters. // REVERSE Sorts in descending order. // RESET Removes any block highlighting. // NORESTORE Leave edit position past sorted block. // NOCOLLATE Uses ASCII values as sort values; tabs // are not expanded. Otherwise, values // are sorted as specified by a collate // table. The collate table is loaded // by the SortLoad("table") command, q.v. // The default collate table COLLDEF.TBL // specifies the sort order. As distributed, // this is a copy of COLLANSI.TBL wherein // the sort order is specified as: // // shortlines, control chars, whitespace, // symbols, digits, lower case letters, upper // case letters and all high-bit characters // not already included among the letters. // // Shortlines are those whose end of line // delimiters are encountered before "cb1". // // Whitespace is , or . // Tabs are expanded according to the // current settings. // // Control chars are the remaining ASCII // codes (0x01 - 0x1f) plus (0x7f). // // See COLLATE.VDM and COLL*.TXT for creating collate tables. // // To sort digits after letters: // Copy COLLANSI.TXT to COLLANDI.TXT // Edit COLLANDI.TXT: change the internal table name // from "ANSI" to "ANS (hi digits)", perhaps; // move the "digits:..." line past "upper:...'Z'" // // run VPW -X COLLATE COLLANSD.TXT, generating // COLLANSD.TBL; finally, load the table with // SortLoad("COLLANSD.TBL"). Digits will now be // sorted after letters. To have the table loaded // automatically, copy COLLANSD.TBL to COLLDEF.TBL. // // SUPPRESS To allow '-' to be used as a delimiter. // However, this requires that columns be // expressed as simple numbers. // // Options may be combined with "+" or "|". // // Note: This macro restores the cursor position for blocks < 1 Meg but not // for larger blocks. // // Technical: Tag records are constructed for each source line/record as it is // encountered during the first sorting pass. These records consist // of the collated keyfields and source record position and length. // (Positions are used only when outputting the 1st source pass). // The keyfields are constructed based on the collate tables and // user-specied options (CASE,REVERSE,NOCOLLATE). The tag records are // sorted and merged. The source records just tag along for the ride. // // #104 = Beginning column for 1st keyfield. // #105 = Ending column for 1st keyfield. // #106 = Beginning file position for sorting. // #107 = Ending file position for sorting. // #108 = 'r' for Dialog_Input_1(). // // [0] = 1st part of title: "Sort [lines/records] using collate table " // [1] = Internal collate table name or "Default" if no table loaded yet // /////////////////////////////////////////////////////////////////////////////// // // Ensure this is the Windows/NT version and the version # >= 6.0 // #105 = 0 if (OS_Type==1 && Is_Win32_Version) { if (Version_Num >= 600) { #105=1 } } if (#105==0) { Message("\n****Can only be run by the 32-bit Windows version 6.0 or later.") Type_Newline() Break_Out(EXTRA+CONFIRM) } ////////////////////////////////////////////////// // // // Initialize for Dialog_Input_1() // // // ////////////////////////////////////////////////// // // Determine beginning and ending file positions #106 and #107. // Determine beginning and ending columns of first keyfield, #104 and #105. // Save_Pos() // Save current position // if (!Column_Mode) { // When no colum markers.... // // Determine block begin/end positions. // Set #106 = block_begin ? block_begin : 0. // if ((#106=Block_Begin)==-1) { // When no defined block markers... #106++ // Set #106 = 0 #107 = File_Size // Set #107 = File_Size // // Else at least one marker set. // Set #107 = block_end ? block_end : Cur_Pos. // } else { if ((#107=Block_End)==-1) { // When (#107 = block_end) is undefined... If (!At_BOL) { Line(1,NOERR) } #107 = Cur_Pos // Use edit position }} // // Determine begin/end columns (#104,#105) for primary sort key. // (SortMerge uses fixed length tag records for sorting). // #104=1 #105=Config(F_F_Type) if (Config(F_F_Type)==7) { #105 = Config(F_CUST_REC_SIZE) } if (Config(F_F_Type)<7) { Goto_Pos(#106) // Block begin (or BOF) BOL() #103 = #106 = Cur_Pos #105=0 if (#103+10000 > #107 ) { // if(BlockSize < 10000) Goto_Pos(#107) if (!At_BOL) { Line(1,NOERR) #107 = Cur_Pos } } else { if (#103+10000 < EOB_Pos) { // (BlockSize > 10000 && first 10000 bytes in memory) EOF(LOCAL) Line(0,LOCAL) if ( Cur_Pos > #107 ) { Goto_Pos(#107) if (!At_BOL) { Line(1,NOERR) #107 = Cur_Pos } } } else { // BlockSize > 10000 but first 10000 bytes NOT completely in memory Char(10000) EOF(LOCAL) Line(0,LOCAL) if ( Cur_Pos > #107 ) { Goto_Pos(#107) if (!At_BOL) { Line(1,NOERR) #107 = Cur_Pos } } }} // while (Cur_Pos>#103) { Char(-Newline_Chars) #102=Cur_Col-1 if (CF(sdspmode)&8) { #102=Cur_Pos-BOL_Pos } // Adjust for hex display mode if (#102>#105) { #105=#102 } Line(-1,NOERR) } } // // For columnar blocks, use defined parameters. // } else { #104=Column_Begin #105=Column_End #106=Block_Begin if ((#107=Block_End)==-1) { #105 = Cur_Col #107 = Cur_Pos } #102=Config(S_DSP_MODE) if (#102&8) { // hex #104 = ((#104-1)/3)+1 #105 = ((#105-1)/3)+1 } if (#102&16) { // octal #104 = ((#104-1)/4)+1 #105 = ((#105-1)/4)+1 } if (#102&32) { // bit-wise #104 = ((#104-1)/9)+1 #105 = ((#105-1)/9)+1 } // // Increment end position for 1-column columnar blocks at BOL. // Final line doesn't get sorted otherwise. // if ( #104 == 1 && #105 == 1 && #107 < File_Size ) { #107++ } } // // Normalize the columns #104 < #105. // if (#104>#105) { #103=#104 #104=#105 #105=#103 } // // Normalize the file positions #106 < #107. // if (#106>#107) { #103=#106 #106=#107 #107=#103 } // // Ensure the file/block is large enough to sort. // if ( (#107-#106) < 4) { Alert() DI1(0,"`Nothing to sort!`, `The sort function requires a file or highlighted block\nof at least 2 lines / 4 characters`") if (Is_Auto_Execution && Macro_Num==100) { Qally() } else { Restore_Pos() Break_Out(EXTRA) } } // // Set T-Reg[0] = title: "Sort lines" or "Sort records". // Reg_Push(0,1) //Save caller's T-Regs // if (Config(F_F_Type)<7) { Reg_Set(0,"Sort lines") } else { Reg_Set(0,"Sort records") } Reg_Set(0," using collate table ",APPEND) // // Set #108 = 'r' for Dialog_Input_1(). // Initialize T-Reg[#108] with beginning and ending columns #104 and #105. // #102=Config(S_DSP_MODE,0,LOCAL) Num_Push(102,102) #108=Reg_Free Out_Reg(#108) Num_Type(#104,LEFT+NOCR) Type_Char(',') Num_Type(#105,LEFT+NOCR) Out_Reg(CLEAR) Num_Pop(102,102) Config(S_DSP_MODE,#102,LOCAL) // // Initialize numeric registers 'r' through 'r'+3 to zero. Num_Push(#108,#108+3) for (#100=#108;#100<#108+4;#100++) { // Reset all options #@100=0 } Restore_Pos() ////////////////////////////////////////////////// // // // Prompt // // // ////////////////////////////////////////////////// Repeat(ALL) { // // Initialize numeric registers 'r' through 'r'+3 for check box input. // Options: [] Case sensitive // [] File // [] No collating // [] Reverse // // // Set unchangeable File option if no block markers defined. // if (Block_Begin==-1) { // Block markers defined? #109=#108 // No, #109 refers to File option #@109=1+0x80 // Set [x] File and make unchangeable } // // Grey Case if table loaded but no groups have been equated. // if (!IsSortCase) { Reg_Set(1,SORTTABLE) if (Reg_Size(1)>0) { #109=#108+2 // Case #@109=0x80 // Grey [] Case sensitive } } // // Set unchangeable Case/No collate options if hex/octal/bitwise display. // if (Config(S_DSP_MODE)&0x38) { #109=#108+2 // Case #@109=1+0x80 // Set and grey [x] Case sensitive #109=#108+3 // No collate #@109=1+0x80 // Set and grey [x] No collate } else { call("SETTITLE") } #100=DI1(#108,^`|@(0)|@(1)`, `If a columnar block has been highlighted, the beginning and ending columns will be used for the initial primary sort key. Up to ten column ranges specify the primary and secondary sort keys. Example: 20,25; 1,5; 10,12 We suggest separating the numbers with commas and semicolons as in the example above. However, any common delimiters can be used except "-" which is treated as "minus" and not "dash". Each number can be a numeric expression, including VEDIT variables such as Column_Begin (cb) and Column_End (ce). [Get keyfields] tries to determine the keyfields from the current cursor line. If you select "No collating", sorting is by simple character (hex) value. Tabs will not be expanded. Upper and lower case will not be equated. `, `.g[] Sort entire &file`, `[] &Reverse`, `.h.g.c[] &Case sensitive`, `.c.s.q[] &No collating`, `??&Keyfield(s):`, `[&Ok]`,`[&Load table...]`,`[&Get keyfields]`,`[Cancel]`^, @(#108),APP+CENTER,0,0) // CZ: added info text, // 3rd button [&Get keyfields] // what is the preferred form in DI1(): ???? // SET // @(#108) as default-input-1 // the online help is unclear here ////////////////////////////////////////////////// // // // [Cancel] // // // ////////////////////////////////////////////////// if (#100==4 || #100==0) { // CZ: 3 --> 4 if (Is_Auto_Execution && Macro_Num == 100) { Qally } else { goto DONE } } ////////////////////////////////////////////////// // CZ: function block added // // // [Get keyfields] // // // ////////////////////////////////////////////////// if (#100==3) { Restore_Pos() // restore the cursor position from stack Save_Pos() // and save it for balancing again Begin_of_Line // search for the first not-white-space in the current line if (Search_Block("|!|W", Cur_Pos, EoL_Pos, NOERR+ERRBREAK)==0) { continue } Reg_Push(103,104) // maybe not necessary, but safe is safe Num_Push(103,104) Reg_Empty(104) #104=0 // counter for keyfield while (! At_EoL) { // run through the whole line if (Match("[^ \t]", REGEXP+ALL+ADVANCE)==0) { // and check for the next not-white-space #104++ if (Reg_Size(104) != 0) { // if there is already something in @104 Reg_Set(104, ";", APPEND) // then add a semicolon } Num_Str(Cur_Col-Chars_Matched, 104, LEFT+NOCR+APPEND) // build the column numbers Reg_Set(104, ",", APPEND) // of that block Num_Str(Cur_Col-1, 104, LEFT+NOCR+APPEND) // if (#104>=10) { // only 10 keyfields allowed break } } else { Search_Block("|!|W", Cur_Pos, EoL_Pos, NOERR+ERRBREAK) // or search for the next block } } Reg_Set(#108, @104) // copy it into DI_1()'s input register #103=DI_1(103,"`Confirmation`, `Delete the used formatting line?`, `[&No]`,`[&Yes]`,", APP+CENTER,0,0) if (#103==2) { // if [yes] then delete the used formatting line (caution!) BoL Del_Line(1) Update } Num_Pop(103,104) Reg_Pop(103,104) continue // goto the DI_1() again } ////////////////////////////////////////////////// // // // [Ok] // // // ////////////////////////////////////////////////// if (#100==1) { break } ////////////////////////////////////////////////// // // // [Load table] // // // ////////////////////////////////////////////////// #101 = Reg_Free #100 = Get_Filename(#101,"|(HOME)\COLL*.TBL") if (#100>0) { SortLoad("|@(#101)") } Reg_Empty(#101) } // Input loop ////////////////////////////////////////////////// // // // Process user's selections. // // Set #110 = combined options. // // // ////////////////////////////////////////////////// // // If T-Reg[#108] has been emptied, set keyfield cols = {1, widest line}. // if (Reg_Size(#108)==0) { if ((#105=Config(F_F_Type))<7) { Save_Pos() BOF(LOCAL) Line(1,NOERR) #103=Cur_Pos Char(-Newline_Chars) #105=Cur_Col EOF(LOCAL) Line(0,NOERR+LOCAL) while (Cur_Pos>#103) { Char(-Newline_Chars) if ((#102=Cur_Col)>#105) { #105=#102 } Line(-1) } Restore_Pos() #105-- } Reg_Set(#108,"1,") Out_Reg(#108,APPEND) Num_Type(#105,LEFT+NOCR) Out_Reg(CLEAR) } // // If [x] File, set begpos and endpos. // #109=#108 // #109 refers to File option if (#@109) { #106=0 #107=File_Size } // // If block size > 1 Meg, don't restore edit position. // #110=0 if (#107-#106<1000000) { #110 = NORESTORE } // // [x] Case sensitive? // #109 = #108+2 // #109 refers to Case sensitive option if (#@109) { #110 = #110 | CASE } // // [x] No collate? // #109 = #108+3 // #109 refers to No collate option if (#@109) { #110 = #110 | NOCOLLATE } // // [x] Reverse? // #109 = #108+1 // #109 refers to Reverse-sort option if (#@109) { #110 = #110 | REVERSE } ////////////////////////////////////////////////// // // // Sort // // // ////////////////////////////////////////////////// Sort_Merge(@(#108),#106,#107,#110) ////////////////////////////////////////////////// // // // Terminate // // // ////////////////////////////////////////////////// :DONE: Num_Pop(#108,#108+3) Reg_Pop(0,1) Reg_Empty(#108) Key_Purge() //Purge any pending keystrokes Reg_Empty(Macro_Num,EXTRA) //Empty this macro and return Return //Just in case ////////////////////////////////////////////////// // // // Set Title // // // ////////////////////////////////////////////////// :SETTITLE: Reg_Set(1,SORTTABLE) if (Reg_Size(1)==0) {Reg_Set(1,"Default")} return