// Description: Sort table // Peter Kahrel // Place the cursor in the column to be sorted. // You can indicate header and footer rows that should be ignored, // or select a range of rows to be sorted. // and whether the sort should be ascending or descending. // The script recognises numerical data and deals with that correctly. app.scriptPreferences.enableRedraw = false; params = getParams(); app.dialogs.everyItem().destroy(); // getparams() returns an object with six fields: // tbl: the selected table as object // col: the index of the selected column // sortType: 'ascend' or 'descend' // alphanum: 'alpha' or 'num' // hdrLines: a number // ftrLines: a number tablesort( params.tbl, params.col, params.hdrLines, params.ftrLines, params.sortType, params.alphanum ); //----------------------------------------------------- function tablesort( tbl, col, hdr, ftr, stype, alphanum ) { ftr = tbl.rows.length-ftr-1; // add dummy row, used for the row swapper tbl.rows.add(); sort_table( tbl, col, hdr, ftr, stype, alphanum ); // delete dummy row tbl.rows[-1].remove(); } //----------------------------------------------------- // Classic quicksort function sort_table( tbl, c, first, last, dir, anum ) { var i = first; var j = last; var pivot = tbl.columns[c].cells[Math.floor( (first+last) / 2)].contents; while( i < j ) { while( larger_than ( pivot, tbl.columns[c].cells[i].contents, dir, anum ) ) i++; while( larger_than ( tbl.columns[c].cells[j].contents, pivot, dir, anum ) ) j--; if( i <= j ) { swap_rows( tbl, i, j ); i++; j--; } } if( first < j ) sort_table( tbl, c, first, j, dir, anum ); if( i < last ) sort_table( tbl, c, i, last, dir, anum ); } /* Swap_rows swaps rows by moving a row x into the dummy row, row y into row x, and the dummy into row y. As we can't move rows, we have to move the contents of the cells. */ function swap_rows(t, x, y) { // This if clause shouldn't be necessary, and it isn't // in plain line sorts, but for some reason is required here. if ( x != y ) { for( var i = 0; i < t.rows[x].cells.length; i++ ) t.rows[x].cells[i].texts[0].move ( LocationOptions.after, t.rows[-1].cells[i].insertionPoints[0] ); for( var i = 0; i < t.rows[y].cells.length; i++ ) t.rows[y].cells[i].texts[0].move ( LocationOptions.after, t.rows[x].cells[i].insertionPoints[0] ); for( var i = 0; i < t.rows[-1].cells.length; i++ ) t.rows[-1].cells[i].texts[0].move ( LocationOptions.after, t.rows[y].cells[i].insertionPoints[0] ); } } //----------------------------------------------------- function larger_than( a, b, dir, num ) { switch ( dir+num ) { case 'ascendalpha': return a.toLowerCase() > b.toLowerCase(); case 'descendalpha': return a.toLowerCase() < b.toLowerCase(); case 'ascendnum': return Number(a) > Number(b); case 'descendnum': return Number(a) < Number(b); } } //----------------------------------------------------- function getParams() { // sel returns four-element object: tbl (table as object), // col (index of selected column), cont (contents of selected cell), // and hdr. The last one is 0 of no rows are selected. var sel = getTable(); var sortTypeButtons; var dlg = app.dialogs.add( { name: 'Table sort' } ); with( dlg ) { with( dialogColumns.add() ) { with( borderPanels.add() ) { with( sortTypeButtons = radiobuttonGroups.add( ) ) { radiobuttonControls.add( { staticLabel: 'Ascending sort' , checkedState: true} ); radiobuttonControls.add( { staticLabel: 'Descending sort ' } ) } with( alphanumButtons = radiobuttonGroups.add( ) ) { radiobuttonControls.add( { staticLabel: 'Numerical' , checkedState: isNumber(sel.cont) == 'num' } ); radiobuttonControls.add( { staticLabel: 'Alphanumerical', checkedState: isNumber(sel.cont) == 'alpha' } ) } } // -1: see comment in getTable() if( sel.hdr == -1 ) { with( borderPanels.add() ) { with( dialogColumns.add() ) { staticTexts.add( { staticLabel: ' Number of header rows:' } ); staticTexts.add( { staticLabel: 'Number of footer rows:' } ) } with( dialogColumns.add() ) { var hdrLines = integerEditboxes.add( { editValue: sel.tbl.headerRowCount, minimumValue: 0, maximumValue: 10, smallNudge: 1 } ); var ftrLines = integerEditboxes.add( { editValue: sel.tbl.footerRowCount, minimumValue: 0, maximumValue: 10, smallNudge: 1, largeNudge: 5 } ) } } } } } if( dlg.show() == false ) { dlg.destroy(); exit() }; return { tbl: sel.tbl, col: sel.col, alphanum: ['num','alpha'][alphanumButtons.selectedButton], sortType: ['ascend','descend'][sortTypeButtons.selectedButton], hdrLines: sel.hdr != -1 ? sel.hdr: hdrLines.editValue, ftrLines: sel.ftr != -1 ? sel.ftr: ftrLines.editValue } } //----------------------------------------------------- function getTable() { try { if( app.selection[0].parent instanceof Cell ) // If an ins. point or part of cell is selected, // return table, col. index, and contents selected cell. return { tbl: app.selection[0].parent.parent, col: app.selection[0].parent.parentColumn.index, cont: app.selection[0].parent.contents, // We set hdr and ftr to -1 here. Zero is no good, // as that's the index returned when the first row // in a selection is the first row of the table. hdr: -1, ftr: -1 } else if( app.selection[0].rows.length > 1 ) // If several rows are selected, then return // hdr and ftr length return { tbl: app.selection[0].parent, col: app.selection[0].parentColumn.index, cont: app.selection[0].cells[0].contents, hdr: app.selection[0].parentRow.index, ftr: app.selection[0].parent.rows.length - app.selection[0].parentRow.index - app.selection[0].rows.length } } catch(_) { errorM( 'Cursor not in a table\ror illegal selection.\r' + '(Select an insertion point\ror some rows.)' ) } } function errorM( m ) { alert( m ); exit(); } function isNumber( x ) { if ( x.search(/^[\d.]+$/) > -1 ) return 'num'; else return 'alpha'; }