Changeset 1969


Ignore:
Timestamp:
Jul 21, 2011, 4:36:15 PM (8 years ago)
Author:
robert@…
Message:

Added the possibility to export assay data for samples after searching. This also works for samples with multiple assays.

Location:
trunk/grails-app
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/grails-app/controllers/dbnp/query/AdvancedQueryController.groovy

    r1962 r1969  
    726726                                        ]]
    727727                        case "Sample":
    728                                 return []
     728                                def ids = []
     729                                s.filterResults(selectedTokens).each {
     730                                        ids << it.id
     731                                }
     732
     733                                def paramString = ids.collect { return 'ids=' + it }.join( '&' );
     734
     735                                return [[
     736                                                module: "gscf",
     737                                                name:"excel",
     738                                                type: "export",
     739                                                description: "Export as CSV",
     740                                                url: createLink( controller: "assay", action: "exportToSamplesToCsv", params: [ 'ids' : ids ] ),
     741                                                submitUrl: createLink( controller: "assay", action: "exportSamplesToCsv" ),
     742                                                paramString: paramString
     743                                        ]]
    729744                        default:
    730745                                return [];
  • trunk/grails-app/controllers/dbnp/studycapturing/AssayController.groovy

    r1921 r1969  
    142142                                flow.fieldMap = assayService.collectAssayTemplateFields(flow.assay)
    143143
    144                 flash.errorMessage = flow.fieldMap.remove('ModuleError')
     144                                flash.errorMessage = flow.fieldMap.remove('ModuleError')
    145145                                flow.measurementTokens = flow.fieldMap.remove('Module Measurement Data')
    146146                        }.to "selectFields"
     
    151151                selectFields {
    152152                        on ("submit"){
    153                
     153
    154154                                def fieldMapSelection = [:]
    155155
     
    176176                                }
    177177
    178                 // collect the assay data according to user selecting
     178                                // collect the assay data according to user selecting
    179179                                def assayData           = assayService.collectAssayData(flow.assay, fieldMapSelection, measurementTokens)
    180180
    181                 flash.errorMessage      = assayData.remove('ModuleError')
     181                                flash.errorMessage      = assayData.remove('ModuleError')
    182182
    183183                                flow.rowData            = assayService.convertColumnToRowStructure(assayData)
    184184
    185                 // prepare the assay data preview
     185                                // prepare the assay data preview
    186186                                def previewRows         = Math.min(flow.rowData.size()    as int, 5) - 1
    187187                                def previewCols         = Math.min(flow.rowData[0].size() as int, 5) - 1
     
    189189                                flow.assayDataPreview   = flow.rowData[0..previewRows].collect{ it[0..previewCols] as ArrayList }
    190190
    191                 // store the selected file type in the flow
    192                 flow.exportFileType = params.exportFileType
     191                                // store the selected file type in the flow
     192                                flow.exportFileType = params.exportFileType
    193193
    194194                        }.to "compileExportData"
     
    199199                compileExportData {
    200200                        on ("ok"){
    201                 session.rowData = flow.rowData
    202                 session.exportFileType = flow.exportFileType
    203             }.to "export"
     201                                session.rowData = flow.rowData
     202                                session.exportFileType = flow.exportFileType
     203                        }.to "export"
    204204                        on ("cancel").to "selectAssay"
    205205                }
     
    220220        def doExport = {
    221221
    222         // make sure we're coming from the export flow, otherwise redirect there
    223         if (!(session.rowData && session.exportFileType))
    224             redirect(action: 'assayExportFlow')
    225 
    226         // process requested output file type
    227         def outputDelimiter, outputFileExtension, locale = java.util.Locale.US
    228 
    229         switch(session.exportFileType) {
    230             case '2': // Comma delimited csv
    231                 outputDelimiter = ','
    232                 outputFileExtension = 'csv'
    233                 break
    234             case '3': // Semicolon delimited csv
    235                 outputDelimiter = ';'
    236                 outputFileExtension = 'csv'
    237                 locale = java.util.Locale.GERMAN // force use of comma as decimal separator
    238                 break
    239             default: // Tab delimited with .txt extension
    240                 outputDelimiter = '\t'
    241                 outputFileExtension = 'txt'
    242         }
     222                // make sure we're coming from the export flow, otherwise redirect there
     223                if (!(session.rowData && session.exportFileType))
     224                        redirect(action: 'assayExportFlow')
     225
     226                // process requested output file type
     227                def outputDelimiter, outputFileExtension, locale = java.util.Locale.US
     228
     229                switch(session.exportFileType) {
     230                        case '2': // Comma delimited csv
     231                                outputDelimiter = ','
     232                                outputFileExtension = 'csv'
     233                                break
     234                        case '3': // Semicolon delimited csv
     235                                outputDelimiter = ';'
     236                                outputFileExtension = 'csv'
     237                                locale = java.util.Locale.GERMAN // force use of comma as decimal separator
     238                                break
     239                        default: // Tab delimited with .txt extension
     240                                outputDelimiter = '\t'
     241                                outputFileExtension = 'txt'
     242                }
    243243
    244244                def filename = "export.$outputFileExtension"
     
    284284        def exportToExcelAsSheets = {
    285285                def assays = getAssaysFromParams( params );
    286                
     286
    287287                if( !assays )
    288288                        return;
     
    326326        def exportToExcelAsList = {
    327327                def assays = getAssaysFromParams( params );
    328                
     328
    329329                if( !assays )
    330330                        return;
     
    367367                }
    368368        }
     369
     370        /**
     371         * Method to export one or more samples to csv in separate sheets.
     372         *
     373         * @param       params.ids              One or more sample ids to export
     374         */
     375        def exportSamplesToCsv = {
     376                def samples = getSamplesFromParams( params );
     377
     378                if( !samples ) {
     379                        return;
     380                }
     381
     382                // Determine a list of assays these samples have been involved in. That way, we can
     383                // retrieve the data for that assay once, and save precious time doing HTTP calls
     384                def assays = [:];
     385               
     386                samples.each { sample ->
     387                        def thisAssays = sample.getAssays();
     388                       
     389                        // Loop through all assays. If it already exists, add the sample it to the list
     390                        thisAssays.each { assay ->
     391                                if( !assays[ assay.id ] ) {
     392                                        assays[ assay.id ] = [ 'assay': assay, 'samples': [] ]
     393                                }
     394                               
     395                                assays[ assay.id ].samples << sample
     396                        }
     397                }
     398               
     399                // Now collect data for all assays
     400                try {
     401                        // Loop through all assays to collect the data
     402                        def columnWiseAssayData = [];
     403
     404                        assays.each { assayInfo ->
     405                                def assay = assayInfo.value.assay;
     406                                def assaySamples = assayInfo.value.samples;
     407                               
     408                                // Determine which fields should be exported for this assay
     409                                def fieldMap = assayService.collectAssayTemplateFields(assay)
     410                                def measurementTokens = fieldMap.remove('Module Measurement Data')
     411                               
     412                                // Retrieve row based data for this assay
     413                                def assayData = assayService.collectAssayData( assay, fieldMap, measurementTokens, assaySamples );
     414                               
     415                                // Prepend study and assay data to the list
     416                                assayData = assayService.prependAssayData( assayData, assay, assaySamples.size() )
     417                                assayData = assayService.prependStudyData( assayData, assay, assaySamples.size() )
     418                               
     419                                // Make sure the assay data can be distinguished later
     420                                assayData.put( "Assay data - " + assay.name, assayData.remove( "Assay Data") )
     421                                assayData.put( "Module measurement data - " + assay.name, assayData.remove( "Module Measurement Data") )
     422                               
     423                                // Add the sample IDs to the list, in order to be able to combine
     424                                // data for a sample that has been processed in multiple assays
     425                                assayData[ "Sample Data" ][ "id" ] = assaySamples*.id;
     426                               
     427                                println "Assay data"
     428                                assayData.each { println it }
     429
     430                                columnWiseAssayData << assayData;
     431                        }
     432                       
     433                        def mergedColumnWiseData = assayService.mergeColumnWiseDataOfMultipleStudiesForASetOfSamples( columnWiseAssayData );
     434
     435                        def rowData   = assayService.convertColumnToRowStructure(mergedColumnWiseData)
     436                       
     437                        // Send headers to the browser so the user can download the file
     438                        def filename = 'export.csv'
     439                        response.setHeader("Content-disposition", "attachment;filename=\"${filename}\"")
     440                        response.setContentType("application/octet-stream")
     441       
     442                        assayService.exportRowWiseDataToCSVFile( rowData, response.getOutputStream() )
     443
     444                        response.outputStream.flush()
     445
     446                } catch (Exception e) {
     447                        throw e;
     448                }
     449        }
     450
    369451
    370452        def getAssaysFromParams( params ) {
     
    392474                                assays << assay;
    393475                }
    394                
     476
    395477                if( !assays ) {
    396478                        flash.errorMessage = "No assays found";
     
    398480                        return [];
    399481                }
    400                
     482
    401483                return assays.unique();
    402484        }
    403485
     486        def getSamplesFromParams( params ) {
     487                def ids = params.list( 'ids' ).findAll { it.isLong() }.collect { Long.valueOf( it ) };
     488                def tokens = params.list( 'tokens' );
     489
     490                if( !ids && !tokens ) {
     491                        flash.errorMessage = "No sample ids given";
     492                        redirect( action: "errorPage" );
     493                        return [];
     494                }
     495
     496                // Find all assays for the given ids
     497                def samples = [];
     498                ids.each { id ->
     499                        def sample = Sample.get( id );
     500                        if( sample )
     501                                samples << sample;
     502                }
     503
     504                // Also accept tokens for defining studies
     505                tokens.each { token ->
     506                        def sample = Sample.findBySampleUUID( token );
     507                        if( sample )
     508                                samples << sample;
     509                }
     510
     511                if( !samples ) {
     512                        flash.errorMessage = "No assays found";
     513                        redirect( action: "errorPage" );
     514                        return [];
     515                }
     516
     517                return samples.unique();
     518        }
     519
     520       
    404521        def errorPage = {
    405522                render(view: 'assayExport/errorPage')
  • trunk/grails-app/domain/dbnp/studycapturing/Sample.groovy

    r1945 r1969  
    124124        }
    125125
     126        /**
     127         * Returns all assays this samples has been processed in
     128         * @return      List of assays
     129         */
     130        public List getAssays() {
     131                return Assay.executeQuery( 'select distinct a from Assay a inner join a.samples s where s = :sample', ['sample': this] )
     132        }
     133       
    126134        def String toString() {
    127135                return name
  • trunk/grails-app/services/dbnp/studycapturing/AssayService.groovy

    r1937 r1969  
    3434         * module's measurements.
    3535         *
    36          * @param assay the assay for which to collect the fields
     36         * @param assay         the assay for which to collect the fields
     37         * @param samples       list of samples to retrieve the field names for. If not given, all samples from the assay are used.
    3738         * @return a map of categories as keys and field names or measurements as
    3839         *  values
    3940         */
    40         def collectAssayTemplateFields(assay) throws Exception {
     41        def collectAssayTemplateFields(assay, samples = null) throws Exception {
    4142
    4243                def getUsedTemplateFields = { templateEntities ->
     
    5051                }
    5152
    52         def moduleError = '', moduleMeasurements = []
    53 
    54         try {
    55             moduleMeasurements = requestModuleMeasurementNames(assay)
    56         } catch (e) {
    57             moduleError = e.message
    58         }
    59 
    60                 def samples = assay.samples
     53                def moduleError = '', moduleMeasurements = []
     54
     55                try {
     56                        moduleMeasurements = requestModuleMeasurementNames(assay)
     57                } catch (e) {
     58                        moduleError = e.message
     59                }
     60
     61                if( !samples )
     62                        samples = assay.samples
     63
    6164                [               'Subject Data' :            getUsedTemplateFields( samples*."parentSubject".unique() ),
    6265                                        'Sampling Event Data' :     getUsedTemplateFields( samples*."parentEvent".unique() ),
     
    6467                                        'Event Group' :             [[name: 'name', comment: 'Name of Event Group', displayName: 'name']],
    6568                                        'Module Measurement Data':  moduleMeasurements,
    66                     'ModuleError':              moduleError
    67                 ]
     69                                        'ModuleError':              moduleError
     70                                ]
    6871
    6972        }
     
    8083         * @param fieldMap                              map with categories as keys and fields as values
    8184         * @param measurementTokens     selection of measurementTokens
     85         * @param samples                               list of samples for which the data should be retrieved.
     86         *                                                              Defaults to all samples from this assay.
    8287         * @return                              The assay data structure as described above.
    8388         */
    84         def collectAssayData(assay, fieldMap, measurementTokens) throws Exception {
     89        def collectAssayData(assay, fieldMap, measurementTokens, samples = null) throws Exception {
    8590
    8691                def collectFieldValuesForTemplateEntities = { headerFields, templateEntities ->
     
    9297                                map + [(headerField.displayName): templateEntities.collect { entity ->
    9398
    94                     // default to an empty string
    95                     def val = ''
    96 
    97                     if (entity) {
    98                         def field
    99                         try {
    100 
    101                             val = entity.getFieldValue(headerField.name)
    102 
    103                             // Convert RelTime fields to human readable strings
    104                             field = entity.getField(headerField.name)
    105                             if (field.type == TemplateFieldType.RELTIME)
    106                                 val = new RelTime( val as long )
    107 
    108                         } catch (NoSuchFieldException e) { /* pass */ }
    109                     }
    110 
    111                     (val instanceof Number) ? val : val.toString()}]
     99                                                // default to an empty string
     100                                                def val = ''
     101
     102                                                if (entity) {
     103                                                        def field
     104                                                        try {
     105
     106                                                                val = entity.getFieldValue(headerField.name)
     107
     108                                                                // Convert RelTime fields to human readable strings
     109                                                                field = entity.getField(headerField.name)
     110                                                                if (field.type == TemplateFieldType.RELTIME)
     111                                                                        val = new RelTime( val as long )
     112
     113                                                        } catch (NoSuchFieldException e) { /* pass */ }
     114                                                }
     115
     116                                                (val instanceof Number) ? val : val.toString()}]
    112117                        }
    113118                }
     
    172177
    173178                // Find samples and sort by name
    174                 def samples = assay.samples.toList().sort { it.name }
     179                if( !samples )
     180                        samples = assay.samples.toList().sort { it.name }
    175181
    176182                def eventFieldMap = [:]
     
    190196                if (measurementTokens) {
    191197
    192             try {
    193                 moduleMeasurementData = requestModuleMeasurements(assay, measurementTokens, samples)
    194             } catch (e) {
    195                 moduleMeasurementData = ['error' : ['Module error, module not available or unknown assay'] * samples.size() ]
    196                 moduleError =  e.message
    197             }
    198 
    199                 }
    200                
     198                        try {
     199                                moduleMeasurementData = requestModuleMeasurements(assay, measurementTokens, samples)
     200                        } catch (e) {
     201                                moduleMeasurementData = ['error' : ['Module error, module not available or unknown assay'] * samples.size() ]
     202                                moduleError =  e.message
     203                        }
     204
     205                }
     206
    201207                [       'Subject Data' :            getFieldValues(samples, fieldMap['Subject Data'], 'parentSubject'),
    202                                 'Sampling Event Data' :     getFieldValues(samples, fieldMap['Sampling Event Data'], 'parentEvent'),
    203                 'Sample Data' :             getFieldValues(samples, fieldMap['Sample Data']),
    204                 'Event Group' :             eventFieldMap,
    205                 'Module Measurement Data' : moduleMeasurementData,
    206                 'ModuleError' :             moduleError
     208                                        'Sampling Event Data' :     getFieldValues(samples, fieldMap['Sampling Event Data'], 'parentEvent'),
     209                                        'Sample Data' :             getFieldValues(samples, fieldMap['Sample Data']),
     210                                        'Event Group' :             eventFieldMap,
     211                                        'Module Measurement Data' : moduleMeasurementData,
     212                                        'ModuleError' :             moduleError
    207213                                ]
    208214        }
     
    268274
    269275                def path = moduleUrl + "/rest/getMeasurements/query"
    270         def query = "assayToken=${assay.giveUUID()}"
    271         def jsonArray
    272 
    273         try {
    274             jsonArray = moduleCommunicationService.callModuleMethod(moduleUrl, path, query, "POST")
    275         } catch (e) {
    276             throw new Exception("An error occured while trying to get the measurement tokens from the $assay.module.name. \
     276                def query = "assayToken=${assay.giveUUID()}"
     277                def jsonArray
     278
     279                try {
     280                        jsonArray = moduleCommunicationService.callModuleMethod(moduleUrl, path, query, "POST")
     281                } catch (e) {
     282                        throw new Exception("An error occured while trying to get the measurement tokens from the $assay.module.name. \
    277283             This means the module containing the measurement data is not available right now. Please try again \
    278284             later or notify the system administrator if the problem persists. URL: $path?$query.")
    279         }
     285                }
    280286
    281287                def result = jsonArray.collect {
     
    285291                                return it.toString()
    286292                }
    287                        
     293
    288294                return result
    289295        }
     
    309315                def path = moduleUrl + "/rest/getMeasurementData/query"
    310316
    311         def query = "assayToken=$assay.assayUUID$tokenString"
     317                def query = "assayToken=$assay.assayUUID$tokenString"
    312318
    313319                def sampleTokens = [], measurementTokens = [], moduleData = []
    314320
    315         try {
    316             (sampleTokens, measurementTokens, moduleData) = moduleCommunicationService.callModuleMethod(moduleUrl, path, query, "POST")
    317         } catch (e) {
    318             throw new Exception("An error occured while trying to get the measurement data from the $assay.module.name. \
     321                try {
     322                        (sampleTokens, measurementTokens, moduleData) = moduleCommunicationService.callModuleMethod(moduleUrl, path, query, "POST")
     323                } catch (e) {
     324                        throw new Exception("An error occured while trying to get the measurement data from the $assay.module.name. \
    319325             This means the module containing the measurement data is not available right now. Please try again \
    320326             later or notify the system administrator if the problem persists. URL: $path?$query.")
    321         }
     327                }
    322328
    323329                if (!sampleTokens?.size()) return []
     
    350356                                        }  else {
    351357
    352                         def val
    353                         def measurement = moduleData[ valueIndex ]
    354 
    355                         if          (measurement == JSONObject.NULL)    val = ""
    356                         else if     (measurement instanceof Number)     val = measurement
    357                         else if     (measurement.isDouble())            val = measurement.toDouble()
    358                         else val =   measurement.toString()
     358                                                def val
     359                                                def measurement = moduleData[ valueIndex ]
     360
     361                                                if          (measurement == JSONObject.NULL)    val = ""
     362                                                else if     (measurement instanceof Number)     val = measurement
     363                                                else if     (measurement.isDouble())            val = measurement.toDouble()
     364                                                else val =   measurement.toString()
    359365                                                measurements << val
    360366                                        }
     
    463469
    464470        /**
     471         * Merges the data from multiple studies into a structure that can be exported to an excel file. The format for each assay is
     472         *
     473         *      [Category1:
     474         *      [Column1: [1,2,3], Column2: [4,5,6]],
     475         *   Category2:
     476         *      [Column3: [7,8,9], Column4: [10,11,12], Column5: [13,14,15]]]
     477         *
     478         * Where the category describes the category of data that is presented (e.g. subject, sample etc.) and the column names describe
     479         * the fields that are present. Each entry in the lists shows the value for that column for an entity. In this case, 3 entities are described.
     480         * Each field should give values for all entities, so the length of all value-lists should be the same.
     481         *
     482         * Example: If the following input is given (2 assays)
     483         *
     484         *      [
     485         *    [Category1:
     486         *      [Column1: [1,2,3], Column2: [4,5,6]],
     487         *     Category2:
     488         *      [Column3: [7,8,9], Column4: [10,11,12], Column5: [13,14,15]]],
     489         *    [Category1:
     490         *      [Column1: [16,17], Column6: [18,19]],
     491         *     Category3:
     492         *      [Column3: [20,21], Column8: [22,23]]]
     493         * ]
     494         *
     495         * the output will be (5 entries for each column, empty values for fields that don't exist in some assays)
     496         *
     497         *      [
     498         *    [Category1:
     499         *      [Column1: [1,2,3,16,17], Column2: [4,5,6,,], Column6: [,,,18,19]],
     500         *     Category2:
     501         *      [Column3: [7,8,9,,], Column4: [10,11,12,,], Column5: [13,14,15,,]],
     502         *     Category3:
     503         *      [Column3: [,,,20,21], Column8: [,,,22,23]]
     504         * ]
     505         *
     506         *
     507         * @param columnWiseAssayData   List with each entry being the column wise data of an assay. The format for each
     508         *                                                              entry is described above. The data MUST have a category named 'Sample Data' and in that map a field
     509         *                                                              named 'id'. This field is used for matching rows. However, the column is removed, unless
     510         *                                                              removeIdColumn is set to false
     511         * @param removeIdColumn                If set to true (default), the values for the sample id are removed from the output.
     512         * @return      Hashmap                         Combined assay data, in the same structure as each input entry. Empty values are given as an empty string.
     513         *                                                              So for input entries
     514         */
     515        def mergeColumnWiseDataOfMultipleStudiesForASetOfSamples(def columnWiseAssayData, boolean removeIdColumn = true ) {
     516                // Merge all assays and studies into one list
     517                def mergedData = mergeColumnWiseDataOfMultipleStudies( columnWiseAssayData )
     518
     519                // A map with keys being the sampleIds, and the values are the indices of that sample in the values list
     520                def idMap = [:]
     521               
     522                // A map with the key being an index in the value list, and the value is the index the values should be copied to
     523                def convertMap = [:]
     524
     525                for( int i = 0; i < mergedData[ "Sample Data" ][ "id" ].size(); i++ ) {
     526                        def id = mergedData[ "Sample Data" ][ "id" ][ i ];
     527
     528                        if( idMap[ id ] == null ) {
     529                                // This id occurs for the first time
     530                                idMap[ id ] = i;
     531                                convertMap[ i ] = i;
     532                        } else {
     533                                convertMap[ i ] = idMap[ id ];
     534                        }
     535                }
     536               
     537                /*
     538                 * Example output:
     539                 * idMap:      [ 12: 0, 24: 1, 26: 3 ]
     540                 * convertMap: [ 0: 0, 1: 1, 2: 0, 3: 3, 4: 3 ]
     541                 *   (meaning: rows 0, 1 and 3 should remain, row 2 should be merged with row 0 and row 4 should be merged with row 3)
     542                 *   
     543                 * The value in the convertMap is always lower than its key. So we sort the convertMap on the keys. That way, we can
     544                 * loop through the values and remove the row that has been merged.
     545                 */
     546               
     547                convertMap.sort { a, b -> b.key <=> a.key }.each {
     548                        def row = it.key;
     549                        def mergeWith = it.value;
     550                       
     551                        if( row != mergeWith ) {
     552                                // Combine the data on row [row] with the data on row [mergeWith]
     553                               
     554                                mergedData.each {
     555                                        def cat = it.key; def fields = it.value;
     556                                        fields.each { fieldData ->
     557                                                def fieldName = fieldData.key;
     558                                                def fieldValues = fieldData.value;
     559                                               
     560                                                // If one of the fields to merge is empty, use the other one
     561                                                // Otherwise the values should be the same (e.g. study, subject, sample data)
     562                                                fieldValues[ mergeWith ] = ( fieldValues[ mergeWith ] == null || fieldValues[ mergeWith ] == "" ) ? fieldValues[ row ] : fieldValues[ mergeWith ]
     563                                               
     564                                                // Remove the row from this list
     565                                                fieldValues.remove( row );
     566                                        }
     567                                }
     568                        }
     569                }
     570               
     571                // Remove sample id if required
     572                if( removeIdColumn )
     573                        mergedData[ "Sample Data" ].remove( "id" );
     574               
     575                return mergedData
     576        }
     577
     578        /**
    465579         * Converts column
    466580         * @param columnData multidimensional map containing column data.
     
    564678        /**
    565679         * Export row wise data in CSV to a stream. All values are surrounded with
    566     * double quotes (" ").
     680        * double quotes (" ").
    567681         *
    568682         * @param rowData List of lists containing for each row all cell values
     
    572686        def exportRowWiseDataToCSVFile(rowData, outputStream, outputDelimiter = '\t', locale = java.util.Locale.US) {
    573687
    574         def formatter = NumberFormat.getNumberInstance(locale)
    575         formatter.setGroupingUsed false // we don't want grouping (thousands) separators
    576 
    577         outputStream << rowData.collect { row ->
    578           row.collect{
    579 
    580               // omit quotes in case of numeric values and format using chosen locale
    581               if (it instanceof Number) return formatter.format(it)
    582 
    583               def s = it?.toString() ?: ''
    584 
    585               def addQuotes = false
    586 
    587               // escape double quotes with double quotes if they exist and
    588               // enable surround with quotes
    589               if (s.contains('"')) {
    590                   addQuotes = true
    591                   s = s.replaceAll('"','""')
    592               } else {
    593                   // enable surround with quotes in case of comma's
    594                   if (s.contains(',') || s.contains('\n')) addQuotes = true
    595               }
    596 
    597               addQuotes ? "\"$s\"" : s
    598 
    599           }.join(outputDelimiter)
    600         }.join('\n')
     688                def formatter = NumberFormat.getNumberInstance(locale)
     689                formatter.setGroupingUsed false // we don't want grouping (thousands) separators
     690
     691                outputStream << rowData.collect { row ->
     692                        row.collect{
     693
     694                                // omit quotes in case of numeric values and format using chosen locale
     695                                if (it instanceof Number) return formatter.format(it)
     696
     697                                def s = it?.toString() ?: ''
     698
     699                                def addQuotes = false
     700
     701                                // escape double quotes with double quotes if they exist and
     702                                // enable surround with quotes
     703                                if (s.contains('"')) {
     704                                        addQuotes = true
     705                                        s = s.replaceAll('"','""')
     706                                } else {
     707                                        // enable surround with quotes in case of comma's
     708                                        if (s.contains(',') || s.contains('\n')) addQuotes = true
     709                                }
     710
     711                                addQuotes ? "\"$s\"" : s
     712
     713                        }.join(outputDelimiter)
     714                }.join('\n')
    601715
    602716                outputStream.close()
Note: See TracChangeset for help on using the changeset viewer.