Author: sebb
Date: Mon Nov 26 06:40:14 2007
New Revision: 598290

URL: http://svn.apache.org/viewvc?rev=598290&view=rev
Log:
Add quoted CSV handling

Modified:
    jakarta/jmeter/trunk/src/core/org/apache/jmeter/save/CSVSaveService.java

Modified: 
jakarta/jmeter/trunk/src/core/org/apache/jmeter/save/CSVSaveService.java
URL: 
http://svn.apache.org/viewvc/jakarta/jmeter/trunk/src/core/org/apache/jmeter/save/CSVSaveService.java?rev=598290&r1=598289&r2=598290&view=diff
==============================================================================
--- jakarta/jmeter/trunk/src/core/org/apache/jmeter/save/CSVSaveService.java 
(original)
+++ jakarta/jmeter/trunk/src/core/org/apache/jmeter/save/CSVSaveService.java 
Mon Nov 26 06:40:14 2007
@@ -18,24 +18,34 @@
 
 package org.apache.jmeter.save;
 
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.FileReader;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.text.DateFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.List;
 import java.util.Vector;
 
 import org.apache.commons.collections.map.LinkedMap;
+import org.apache.commons.lang.CharUtils;
+import org.apache.commons.lang.StringUtils;
 import org.apache.jmeter.assertions.AssertionResult;
+import org.apache.jmeter.reporters.ResultCollector;
 import org.apache.jmeter.samplers.SampleEvent;
 import org.apache.jmeter.samplers.SampleResult;
 import org.apache.jmeter.samplers.SampleSaveConfiguration;
 import org.apache.jmeter.samplers.StatisticalSampleResult;
 import org.apache.jmeter.util.JMeterUtils;
+import org.apache.jmeter.visualizers.Visualizer;
 import org.apache.jorphan.logging.LoggingManager;
 import org.apache.jorphan.reflect.Functor;
 import org.apache.jorphan.util.JMeterError;
+import org.apache.jorphan.util.JOrphanUtils;
 import org.apache.log.Logger;
 import org.apache.oro.text.regex.Pattern;
 import org.apache.oro.text.regex.PatternMatcherInput;
@@ -91,13 +101,54 @@
        private CSVSaveService() {
        }
 
-    /**
+       /**
+        * Read Samples from a file; handles quoted strings.
+        * 
+        * @param filename input file
+        * @param visualizer where to send the results
+        * @param resultCollector the parent collector
+        * @throws IOException
+        */
+       public static void processSamples(String filename, Visualizer 
visualizer,
+                       ResultCollector resultCollector) throws IOException {
+       BufferedReader dataReader = null;
+               try {
+                       dataReader = new BufferedReader(new 
FileReader(filename));
+                       dataReader.mark(400);// Enough to read the header 
column names
+                       // Get the first line, and see if it is the header
+                       String line = dataReader.readLine();
+                       long lineNumber=1;
+                       SampleSaveConfiguration saveConfig = 
CSVSaveService.getSampleSaveConfiguration(line,filename);
+                       if (saveConfig == null) {// not a valid header
+                               saveConfig = (SampleSaveConfiguration) 
resultCollector.getSaveConfig().clone(); // may change the format later
+                               dataReader.reset(); // restart from beginning
+                               lineNumber = 0;
+                       }
+                       String [] parts;
+                       while ((parts = csvReadFile(dataReader, 
saveConfig.getDelimiter().charAt(0))).length != 0) {
+                               lineNumber++;
+                               SampleEvent event = 
CSVSaveService.makeResultFromDelimitedString(parts,saveConfig,lineNumber);
+                               if (event != null){
+                                       final SampleResult result = 
event.getResult();
+                                       if 
(resultCollector.isSampleWanted(result.isSuccessful())) {
+                                               visualizer.add(result);
+                                       }
+                               }
+                       }
+               } finally {
+                       JOrphanUtils.closeQuietly(dataReader);
+               }
+       }
+
+       /**
      * Make a SampleResult given a delimited string.
      * 
      * @param inputLine - line from CSV file
      * @param saveConfig - configuration
      * @param lineNumber - line number for error reporting
-     * @return SampleResult or null if header line detected
+     * @return SampleResult
+     * 
+     * @deprecated Does not handle quoted strings
      * 
      * @throws JMeterError
      */
@@ -105,17 +156,33 @@
                final String inputLine, 
                final SampleSaveConfiguration saveConfig, // may be updated
                final long lineNumber) {
- 
-       SampleResult result = null;
-        String hostname = "";// $NON-NLS-1$
-               long timeStamp = 0;
-               long elapsed = 0;
                /*
                 * Bug 40772: replaced StringTokenizer with String.split(), as 
the
                 * former does not return empty tokens.
                 */
                // The \Q prefix is needed to ensure that meta-characters (e.g. 
".") work.
                String 
parts[]=inputLine.split("\\Q"+saveConfig.getDelimiter());// $NON-NLS-1$
+       return makeResultFromDelimitedString(parts, saveConfig, lineNumber);
+    }
+    
+    /**
+     * Make a SampleResult given a set of tokens
+     * @param parts tokens parsed from the input
+     * @param saveConfig the save configuration (may be updated)
+     * @param lineNumber
+     * @return the sample result
+     * 
+     * @throws JMeterError
+     */
+    public static SampleEvent makeResultFromDelimitedString(
+               final String[] parts, 
+               final SampleSaveConfiguration saveConfig, // may be updated
+               final long lineNumber) {
+    
+       SampleResult result = null;
+        String hostname = "";// $NON-NLS-1$
+               long timeStamp = 0;
+               long elapsed = 0;
                String text = null;
                String field = null; // Save the name for error reporting
                int i=0;
@@ -513,62 +580,104 @@
     /**
      * Convert a result into a string, where the fields of the result are
      * separated by a specified String.
-     * 
+     *
      * @param event
      *            the sample event to be converted
      * @param delimiter
      *            the separation string
      * @return the separated value representation of the result
      */
-    public static String resultToDelimitedString(SampleEvent event, String 
delimiter) {
-       StringBuffer text = new StringBuffer();
+    public static String resultToDelimitedString(SampleEvent event, final 
String delimiter) {
+       
+       /*
+        * Class to handle generating the delimited string.
+        * - adds the delimiter if not the first call
+        * - escapes (quotes) any strings that require it 
+        */
+       final class StringEscaper{
+               final StringBuffer sb = new StringBuffer();
+               private final char[] specials;
+               private boolean addDelim;
+               public StringEscaper(char delim) {
+                       specials = new char[] {delim, '"', CharUtils.CR, 
CharUtils.LF};
+                       addDelim=false; // Don't add delimiter first time round
+                       }
+               
+               private void addDelim(){
+                       if (addDelim){
+                               sb.append(specials[0]);
+                       } else {
+                               addDelim = true;
+                       }
+               }
+               // These methods handle parameters that could contain 
delimiters or escapes:
+               public void append(String s){
+                       addDelim();
+                       sb.append(escapeDelimiters(s,specials));
+               }
+               public void append(Object obj){
+                       append(String.valueOf(obj));
+               }
+               
+               // These methods handle parameters that cannot contain 
delimiters or escapes
+               public void append(int i){
+                       addDelim();
+                       sb.append(i);
+               }
+               public void append(long l){
+                       addDelim();
+                       sb.append(l);
+               }
+               public void append(boolean b){
+                       addDelim();
+                       sb.append(b);
+               }
+
+               public String toString(){
+                       return sb.toString();
+               }
+       }
+       
+       StringEscaper text = new StringEscaper(delimiter.charAt(0));
+       
        SampleResult sample = event.getResult();
        SampleSaveConfiguration saveConfig = sample.getSaveConfig();
     
        if (saveConfig.saveTimestamp()) {
                if (saveConfig.printMilliseconds()){
                        text.append(sample.getTimeStamp());
-                       text.append(delimiter);
                } else if (saveConfig.formatter() != null) {
                        String stamp = saveConfig.formatter().format(new 
Date(sample.getTimeStamp()));
                        text.append(stamp);
-                       text.append(delimiter);
                }
        }
     
        if (saveConfig.saveTime()) {
                text.append(sample.getTime());
-               text.append(delimiter);
        }
     
        if (saveConfig.saveLabel()) {
                text.append(sample.getSampleLabel());
-               text.append(delimiter);
        }
     
        if (saveConfig.saveCode()) {
                text.append(sample.getResponseCode());
-               text.append(delimiter);
        }
     
        if (saveConfig.saveMessage()) {
                text.append(sample.getResponseMessage());
-               text.append(delimiter);
        }
     
        if (saveConfig.saveThreadName()) {
                text.append(sample.getThreadName());
-               text.append(delimiter);
        }
     
        if (saveConfig.saveDataType()) {
                text.append(sample.getDataType());
-               text.append(delimiter);
        }
     
        if (saveConfig.saveSuccess()) {
                text.append(sample.isSuccessful());
-               text.append(delimiter);
        }
     
        if (saveConfig.saveAssertionResultsFailureMessage()) {
@@ -585,63 +694,183 @@
     
                if (message != null) {
                        text.append(message);
+               } else {
+                       text.append(""); // Need to append something so 
delimiter is added
                }
-               text.append(delimiter);
        }
     
         if (saveConfig.saveBytes()) {
             text.append(sample.getBytes());
-            text.append(delimiter);
         }
     
         if (saveConfig.saveThreadCounts()) {
             text.append(sample.getGroupThreads());
-            text.append(delimiter);
             text.append(sample.getAllThreads());
-            text.append(delimiter);
         }
         if (saveConfig.saveUrl()) {
             text.append(sample.getURL());
-            text.append(delimiter);
         }
     
         if (saveConfig.saveFileName()) {
             text.append(sample.getResultFileName());
-            text.append(delimiter);
         }
     
         if (saveConfig.saveLatency()) {
             text.append(sample.getLatency());
-            text.append(delimiter);
         }
 
         if (saveConfig.saveEncoding()) {
             text.append(sample.getDataEncoding());
-            text.append(delimiter);
         }
 
        if (saveConfig.saveSampleCount()) {// Need both sample and error count 
to be any use
                text.append(sample.getSampleCount());
-               text.append(delimiter);
                text.append(sample.getErrorCount());
-               text.append(delimiter);
        }
     
         if (saveConfig.saveHostname()) {
             text.append(event.getHostname());
-            text.append(delimiter);
         }
 
-       String resultString = null;
-       int size = text.length();
-       int delSize = delimiter.length();
+       return text.toString();
+    }
+
+    // =================================== CSV escape/unescape handling 
==============================
     
-       // Strip off the trailing delimiter
-       if (size >= delSize) {
-               resultString = text.substring(0, size - delSize);
-       } else {
-               resultString = text.toString();
-       }
-       return resultString;
+    /*
+     * Private versions of what might eventually be part of Commons-CSV or 
Commons-Lang/Io...
+     */
+    
+    /*
+     * <p>
+     * Returns a <code>String</code> value for a character-delimited column 
value
+     * enclosed in the escape character, if required.
+     * </p>
+     *
+     * <p>
+     * If the value contains a special character,
+     * then the String value is returned enclosed in the escape character.
+     * </p>
+     *
+     * <p>
+     * Any escape characters in the value are escaped with another escape.
+     * </p>
+     *
+     * <p>
+     * If the value does not contain any special characters,
+     * then the String value is returned unchanged.
+     * </p>
+     * 
+     * <p>
+     * N.B. The list of special characters includes the escape character.
+     * </p>
+     * 
+     * @param input the input column String, may be null (without enclosing 
delimiters)
+     * @param specialChars special characters; second one must be the escape 
character 
+     * @return the input String, enclosed in escape if the value contains a 
special character,
+     * <code>null</code> for null string input
+     */
+    private static String escapeDelimiters(String input, char[] specialChars) {
+        if (StringUtils.containsNone(input, specialChars)) {
+            return input;
+        }
+        StringBuffer buffer = new StringBuffer(input.length() + 10);
+        final char escape = specialChars[1];
+        buffer.append(escape);
+        for (int i = 0; i < input.length(); i++) {
+            char c = input.charAt(i);
+            if (c == escape) {
+                buffer.append(escape); // escape the escape char
+            }
+            buffer.append(c);
+        }
+        buffer.append(escape);
+        return buffer.toString();
     }
+
+    /*
+     * Reads from file and splits input into strings according to the 
delimiter,
+     * taking note of quoted strings.
+     * 
+     * Handles DOS (CRLF), Unix (LF), and Mac (CR) line-endings equally.
+     * 
+     * @param infile input file
+     * @param delim delimiter (e.g. comma)
+     * @return array of strings
+     * @throws IOException
+     */
+    // State of the parser
+       private static final int INITIAL=0, PLAIN = 1, QUOTED = 2, 
EMBEDDEDQUOTE = 3;
+       public static final char QUOTING_CHAR = '"';
+       private static String[] csvReadFile(BufferedReader infile, char delim) 
throws IOException {
+               int ch;
+               int state = INITIAL;
+               List list = new ArrayList();
+               ByteArrayOutputStream baos = new ByteArrayOutputStream(200);
+               while(-1 != (ch=infile.read())){
+                       boolean push = false;
+                       switch(state){
+                       case INITIAL:
+                               if (ch == QUOTING_CHAR){
+                                       state = QUOTED;
+                               } else if (isDelimOrEOL(delim, ch)) {
+                                       push = true;
+                               } else {
+                                       baos.write(ch); 
+                                       state = PLAIN;
+                               }
+                               break;
+                       case PLAIN:
+                               if (ch == QUOTING_CHAR){
+                                       throw new IllegalStateException("Cannot 
have quote-char in plain field:["+baos.toString()+"]");
+                               } else if (isDelimOrEOL(delim, ch)) {
+                                       push = true;
+                                       state = INITIAL;
+                               } else {
+                                       baos.write(ch);
+                               }
+                               break;
+                       case QUOTED:
+                               if (ch == QUOTING_CHAR){
+                                       state=EMBEDDEDQUOTE;
+                               } else {
+                                       baos.write(ch);
+                               }
+                               break;
+                       case EMBEDDEDQUOTE:
+                               if (ch == QUOTING_CHAR){
+                                       baos.write(QUOTING_CHAR); // doubled 
escape => escape
+                                       state = QUOTED;
+                               } else if (isDelimOrEOL(delim, ch)) {
+                                       push = true;
+                                       state = INITIAL;
+                               } else {
+                                       throw new IllegalStateException("Cannot 
have single quote-char in quoted field:["+baos.toString()+"]");
+                               }
+                               break;
+                       }
+                       if (push) {
+                               if (ch == '\r') {// Remove following \n if 
present
+                                       infile.mark(1);
+                                       if (infile.read() != '\n'){
+                                               infile.reset(); // did not find 
\n, put the character back
+                                       }
+                               }
+                               String s = baos.toString();
+                               list.add(s);
+                               baos.reset();
+                       }
+                       if ((ch == '\n' || ch == '\r') && state != QUOTED) {
+                               break;
+                       }
+               }
+               if (ch == -1 && baos.size() > 0){
+                       list.add(baos.toString());
+               }
+               return (String[]) list.toArray(new String[]{});
+       }
+
+       private static boolean isDelimOrEOL(char delim, int ch) {
+               return ch == delim || ch == '\n' || ch == '\r';
+       }
 }



---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to