Tab delimited exports

dmuth - April 3, 2006 - 00:35
Project:Webform
Version:4.6.x-1.x-dev
Component:Code
Category:feature request
Priority:normal
Assigned:Unassigned
Status:closed
Description

I had some problems when trying to do comma-delimited exports and load them into NeoOffice. There were issues with commas in the data, and issues with people having commas in IP addresses, which happens when a submission comes from someone behind a proxy.

Anyway, I've written patches that now export data into tab delimited files, and put quotes around the IP addresses. Let me know if these work out for you.

-- Doug

--- webform.module 2006/03/24 18:26:01 1.1
+++ webform.module 2006/03/31 04:45:44
@@ -1,5 +1,5 @@
<?php
-// $Id: webform.module,v 1.1 2006/03/24 18:26:01 me Exp $
+// $Id: webform.module,v 1.2 2006/03/31 04:45:20 doug Exp doug $

   /** This module provides a simple way to create forms and questionnaires
    * for your website.
@@ -377,6 +377,10 @@
       $items[] = array('path' => 'node/'.$node->nid.'/results/download', 'title' => t('download'),
                              'callback' => 'webform_results', 'access' => user_access('maintain webforms'),
                              'type' => MENU_LOCAL_TASK, 'weight' => 7);
+
+      $items[] = array('path' => 'node/'.$node->nid.'/results/download-tsv', 'title' => t('download (tsv)'),
+                             'callback' => 'webform_results', 'access' => user_access('maintain webforms'),
+                             'type' => MENU_LOCAL_TASK, 'weight' => 8);
     }
   }

@@ -1119,6 +1123,9 @@
         break;
       case 'download':
         $content = _webform_results_download($nid);
+        break;
+      case 'download-tsv':
+        $content = _webform_results_download_tab($nid);
         break;
       case 'submissions':
       default:

--- webform.inc 2006/03/24 18:26:01 1.1
+++ webform.inc 2006/03/31 04:45:42
@@ -1,5 +1,5 @@
<?php
-  // $Id: webform.inc,v 1.1 2006/03/24 18:26:01 me Exp $
+  // $Id: webform.inc,v 1.2 2006/03/31 04:45:20 doug Exp doug $

   /**
    * @author Pontus Ullgren <ullgren@user.sourceforge.net>
@@ -119,6 +119,44 @@
   exit(0);
}

+/**
+* This function is basically a copy of _webform_results_download() except
+* it sends back a file that is tab-delimited.
+*/
+function _webform_results_download_tab($nid) {
+  $node_info = _webform_get_node_info($nid);
+  $title_string = $node_info->title;
+
+  $file_name = _webform_records_string($title_string, $nid, 'file', "\r\n",
+ "\t");
+
+  drupal_set_header("Content-type: text/plain; charset=utf-8");
+  //
+  // The file will have a .csv extension, despite being tab delimited,
+  // because NeoOffice/J 1.1 for OX/S 10.4 loads a .tsv file into its
+  // word processor for some reason.  It has no problem with a tab-delimieted
+  // file that ends in .csv, however.
+  //
+  drupal_set_header("Content-Disposition: attachment; filename="
+ . preg_replace('/\.$/', '', str_replace(' ', '_', $title_string))
+ . ".csv");
+
+ //
+ // Dump the contents of the file to the browser and then delete the
+ // file.
+ //
+  @readfile($file_name);  // The @ makes it silent
+  @unlink($file_name);  // Clean up, the @ makes it silent
+
+ //
+ // Exit after the download is done so that nothing else gets
+ // into the file.
+ //
+  exit(0);
+
+} // End of _webform_results_download_tab()
+
+
/** function _webform_results_submissions() is a database accessor function designed to return lists
  *  of submissions and data.
  * @param $nid the node id of the webform
@@ -184,10 +222,12 @@
  * @param $nid - The drupal node id representing the webform data that is to be extracted.
  * @param $print_now Whether to print the result as it is formed or store it to a string and return it.
  * @param $new_line - A string to be used to render a new line.  Defaults for print on windows based systems
+ * @param $delimiter - A string or character to seperate fields
  * @return a reference to a string representing the file in csv format.
  **/

-function _webform_records_string($title_string,$nid,$mode = 'file',$new_line = "\r\n"){
+function _webform_records_string($title_string, $nid, $mode = 'file',
+ $new_line = "\r\n", $delimiter = ","){

   // Because the components may have changed during the lifestyle of the form
   // and because we may be using a low version of MySQL et al. The query is done in two
@@ -214,9 +254,13 @@
   $record_array[0]['components'] = array();
   $record_array[0]['index'] = array(); // Converts between name and index;

-  $header[0] =  "$title_string,,,,"; // The inner quotes allow the title to include commas
-  $header[1] .= "Submission Details,,,,";
-  $header[2] .= "Serial,Timestamp,SID,User,Host";
+// The inner quotes allow the title to include commas
+  $header[0] =  "$title_string" . $delimiter . $delimiter . $delimiter
+ . $delimiter;
+  $header[1] .= "Submission Details" . $delimiter . $delimiter . $delimiter
+ . $delimiter;
+  $header[2] .= "Serial" . $delimiter . "Timestamp" . $delimiter . "SID"
+ . $delimiter . "User" . $delimiter . "Host";

   $index = 0; // Ensures they retain the correct order

@@ -229,8 +273,8 @@
     $record_array[0]['components'][$index]['type'] = $record->type;
     $record_array[0]['components'][$index]['value'] = $record->value;

-    $header[0] .= ',';
-    $header[1] .= ',' . $record->name;
+    $header[0] .= $delimiter;
+    $header[1] .= $delimiter . $record->name;

     if($record->type == 'select'){

@@ -242,17 +286,17 @@
         $record_array[0]['components'][$index]['options']['items'][$key] = trim($item);  // Trim off any linefeeds etc. and put them back in the same place
       }
      
-      $header[0] .= str_pad('', count($items) - 1,',');
-      $header[1] .= str_pad('', count($items) - 1,',');
+      $header[0] .= str_pad('', count($items) - 1, $delimiter);
+      $header[1] .= str_pad('', count($items) - 1, $delimiter);
      
       foreach($items as $item){
         //print trim($item);
-        $header[2] .= ',' . '"'.trim($item) . '"'; // The quotes are added so the items can contain commas.
+        $header[2] .= $delimiter . '"'.trim($item) . '"'; // The quotes are added so the items can contain commas.
       }
      
     } else {
       $record_array[0]['components'][$index]['data'][0] = '';
-      $header[2] .= ',';
+      $header[2] .= $delimiter;
     }
    
     $record_array[0]['components'][$index]['extra'] = $record->extra;
@@ -324,14 +368,16 @@
           break;

         case 'print':
-          print _webform_make_record_csv_string($record_array,$new_line);
+          print _webform_make_record_csv_string($record_array, $new_line,
+ $delimiter);
           break;
         case 'string':
-          $records .= _webform_make_record_csv_string($record_array,$new_line);
+          $records .= _webform_make_record_csv_string($record_array,
+ $new_line, $delimiter);
           break;
         case 'file':
         default:
-          $file_record = _webform_make_record_csv_string($record_array,$new_line);
+          $file_record = _webform_make_record_csv_string($record_array,$new_line, $delimiter);
           @fwrite($handle,$file_record);
         }
       }
@@ -392,14 +438,17 @@
         $submission_list_array[] = array($record_count,$record_array[1]['sid'],$record_array[1]['__timestamp'],$record_array[1]['__userid'],$record_array[1]['__remotehost'],$print_view_ref,$print_delete_ref);
         break;
       case 'print':
-        print _webform_make_record_csv_string($record_array,$new_line);
+        print _webform_make_record_csv_string($record_array,
+ $new_line, $delimiter);
         break;
       case 'string':
-        $records .= _webform_make_record_csv_string($record_array,$new_line);
+        $records .= _webform_make_record_csv_string($record_array,
+ $new_line, $delimiter);
         break;
       case 'file':
       default:
-        $file_record = _webform_make_record_csv_string($record_array,$new_line);
+        $file_record = _webform_make_record_csv_string($record_array,
+ $new_line, $delimiter);
         @fwrite($handle,$file_record);
       }
     } // end if ($serial != 0)
@@ -422,17 +471,26 @@
}

/** _webform_make_record_csv_string - Decomposes a single session ID to a flat structure.
+ *
+ * NOTE: Since $delimiter was added, this function's name is misleading. 
+ * It should probably be changed at some point.
+ *
  * @param &$record_array - A reference to the filled record array element.
  * @param $new_line - A string to be used to render a new line.
+ * @param $delimiter - A character or string that is our field seperator
  * @return a string representing the file in csv format.
  **/
-function _webform_make_record_csv_string(&$ra,$new_line){
+function _webform_make_record_csv_string(&$ra, $new_line, $delimiter = ",") {
  
-  $output =  $ra[1]['serial'] . ",";
-  $output .= $ra[1]['__timestamp'] . ",";
-  $output .= $ra[1]['sid'] . ",";
-  $output .= $ra[1]['__userid'] . ",";
-  $output .= $ra[1]['__remotehost'];
+  $output =  $ra[1]['serial'] . $delimiter;
+  $output .= $ra[1]['__timestamp'] . $delimiter;
+  $output .= $ra[1]['sid'] . $delimiter;
+  $output .= $ra[1]['__userid'] . $delimiter;
+  //
+  // Put quotes around the IP as connections through a proxy sometimes
+  // have the internal IP, a comma, and then the external IP.
+  //
+  $output .= '"' . $ra[1]['__remotehost'] . '"';
  
   foreach($ra[0]['index'] as $name => $index){
    
@@ -448,24 +506,33 @@
         foreach($ra[0]['components'][$index]['options']['items'] as $choice){
           if(is_array($ra[1]['components'][$index]['data'])){  // When all null, then not array?
             if(in_array($choice,$ra[1]['components'][$index]['data'])){ // This was throwing a warning! [Wrong datatype for second argument]
-              $output .= ',YES';
+              $output .= $delimiter . 'YES';
             } else {
-              $output .= ',no';
+              $output .= $delimiter . 'no';
             }
           } else {
-            $output .= ',no';
+            $output .= $delimiter . 'no';
           }
         }
       } else {
         $d = $ra[1]['components'][$index]['data'][0];
-        $output .= ",\"$d\""; // The internal slashed quotes allow the field to include commas
+ //
+ // Strip newlines and carriage returns.  They cause major problems
+ // when trying to import a CSV or TSV file.
+ //
+ $d = str_replace("\n", "", $d);
+ $d = str_replace("\r", "", $d);
+
+        $output .= $delimiter . "\"$d\""; // The internal slashed quotes allow the field to include commas
       }
     }
   }
  
   $output .= $new_line;
   return $output;
-}
+
+} // End of _webform_make_record_csv_string()
+

/**
  * Pulls the information block for a given node

AttachmentSize
webform.inc.diff.txt8.74 KB

#1

quicksketch - September 18, 2006 - 04:36
Status:patch (code needs review)» closed
 
 

Drupal is a registered trademark of Dries Buytaert.