Configuring an Ignition CSV-to-Table Jython Button Script

James Nishida10 min read
HMI / SCADAOther ManufacturerTechnical Reference
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

Prerequisites and Expected Text Layout

Before anything else, confirm the window structure. The script resolves its components through event.source.parent.getComponent(). That call only finds components that sit in the same container as the button. The names must match exactly: 'Text Area' for the input and 'Table' for the output. A renamed component or a button moved into a different container breaks the script on the first line that touches the component.

The parser is a small state machine driven by headerFlag and typeFlag. It expects the text in this order:

Line Content Script variable
1 Column names headers
2 Column types: Integer, Float, String, Boolean, Date types
3 onward Data rows rowOut, appended to dataOut

A valid input looks like this:

Name,Count,Rate,Active,Stamp
String,Integer,Float,Boolean,Date
Pump1,4,12.5,True,Mon Jan 05 10:15:00 -0500 2015
Pump2,7,9.75,False,Tue Jan 06 08:00:00 -0500 2015

The reader is built with delimiter=',' and quotechar="'". The quote character is the single quote, not the double quote. A field that contains a comma must therefore be wrapped in single quotes. Double quotes pass through as literal characters. Do not move on until the Text Area content follows this three-part layout.

Symptom-to-Check Map

Start at the check that matches what the button does when pressed. Each check below states the reading to take and where to go next.

Symptom on button press Likely cause Go to
SyntaxError or IndentationError before anything runs Curly quotes or lost indentation from copy/paste Check 1
ImportError Module or class name typo Check 2
Printed types list is wrong or missing Header/type rows malformed or out of order Check 3
ValueError or parse exception A cell does not match its declared type Check 4
IndexError on types[i] Data row has more fields than the type row Check 5
Wrong values repeated across columns, or NameError on dataPoint Type name not matched by any branch Check 4
Error building the dataset, or Table shows wrong shape Ragged rows, blank lines, or wrong append indentation Check 5

Check 1: Does the Script Compile?

Open the button's event script and look at the quote characters. Code copied through a web page or word processor often arrives with typographic quotes (‘ ’ “ ”) instead of straight ASCII quotes (' and "). Jython rejects typographic quotes with a syntax error. Replace every one of them.

Next, check indentation. Python uses indentation as its block structure, so pasted code that lost its leading whitespace is either invalid or means something different. Three placements control the behaviour of this script:

  1. Everything from element=... down to dataOut.append(rowOut) sits inside the for row in reader: loop.
  2. rowOut.append(dataPoint) sits inside the inner for i in range(len(element)): loop, at the same level as the if type== chain.
  3. dataOut.append(rowOut) sits inside the else: branch, after the inner loop ends. The final ...getComponent('Table').data=... line sits outside every loop, at the left margin.

Outcome: if the script now compiles, go to Check 2. If it still fails, read the line number in the error and compare that line's quotes and indentation against the corrected listing at the end of this reference.

Check 2: Do the Imports Resolve?

The first three lines load libraries that are not part of the core language:

Line Source Role in this script
import StringIO Python standard library, bundled with Jython Wraps the Text Area string so it behaves like an open file. csv.reader expects a file-like, iterable object.
import csv Python standard library, bundled with Jython Splits each line into fields and honours the delimiter and quote character.
from java.text import SimpleDateFormat as SDF Java class library on the JVM Parses date text into a Java date. The as SDF part only shortens the name.

Ignition scripting runs on Jython, which is a Python implementation on the Java virtual machine. That is why one script can import both Python modules and Java classes. Imports load into the script's namespace each time the event runs and are discarded when it finishes. Keep the import lines at the top of every script that uses them.

If the data comes from a file rather than a Text Area, you do not need StringIO. Open the file and pass the file object straight to csv.reader.

For reference material, use the Jython standard library documentation index for StringIO and csv. Use the Java SE API documentation for java.text.SimpleDateFormat. An ImportError almost always means a misspelled module or class name, and the names are case-sensitive. Once the imports pass, go to Check 3.

Check 3: Are the Header and Type Rows Read Correctly?

The first pass through the loop stores headers. It then clears headerFlag and sets typeFlag. The second pass stores types and runs print types. Every later pass falls into the else: data branch. Each field is whitespace-trimmed by [r.strip() for r in row] before it is used.

  1. Press the button and read the print types output in the console of the scope that ran the script. In the Designer, that is the output console.
  2. Confirm the printed list has the same number of entries as the header line.
  3. Confirm the first line of the Text Area is the header line. A blank or comment line at the top shifts every role down by one line: the headers are treated as types and the types as data.

Outcome: a correct types printout sends you to Check 4. A wrong printout means the input layout is at fault, not the code.

Check 4: Does Each Cell Convert to Its Declared Type?

In the data branch, the script walks the row by column index. It reads the declared type from types[i] and converts the cell into dataPoint:

Declared type Conversion Accepts Pitfall
Integer int(rowList[i]) Whole numbers such as 4 A blank cell or 4.0 raises ValueError
Float float(rowList[i]) 12.5, 7 A blank cell raises ValueError. The extra print rowList[i] is debug output only.
String str(), or unicode() after stripping a u'...' wrapper Any text The u' branch removes the first two characters and the last one. It is meant for text written out as a Python unicode representation.
Boolean rowList[i]=='True' Exactly True true, TRUE, 1 and yes all become False with no error

Every type branch assigns dataPoint, but nothing covers a type name the chain does not recognise. When a type is unrecognised, dataPoint keeps the value from the previous column. You get silently duplicated data, or a NameError if the bad type is in the first column of the first data row. Treat repeated values across adjacent columns as a type-name error and return to Check 3.

The line SDF.parse(inputFormat, str(rowList[i])) calls the Java instance method in unbound form. It does the same thing as inputFormat.parse(...).

Check 5: Are rowOut, dataOut and the Dataset Built Correctly?

dataOut=[] creates an empty Python list, not a dataset. rowOut=[] is reset at the start of each data row. rowOut.append(dataPoint) adds one converted value per column. dataOut.append(rowOut) then adds the finished row. When the loop ends, dataOut is a list of lists: one inner list per data line, one value per column.

system.dataset.toDataSet(headers, dataOut) turns that list of lists into an Ignition dataset. headers supplies the column names. The result is written to the Table's data property. The builder needs a rectangular structure, so every inner list must have exactly len(headers) entries. Keep each column a single Python type as well.

  1. Add print len(headers), [len(r) for r in dataOut] just before the final assignment line.
  2. If every row length equals the header count, the shape is correct. Any remaining failure is a type mix inside a column, so go back to Check 4.
  3. If one row has zero length, the Text Area has a blank line, usually a trailing newline. csv.reader returns an empty row for it, and the script appends an empty rowOut.
  4. If a row is longer than the header count, the IndexError on types[i] fires first. Look for an unquoted comma inside a field and wrap that field in single quotes.
  5. If a row is shorter than the header count, a trailing field is missing from the source line. The inner loop runs over len(element), so it does not pad the row.
  6. If there are as many rows as cells, or one row per line with a single value, dataOut.append or rowOut.append is at the wrong indentation level. Return to Check 1.

Corrected Script, Reuse Procedure and Verification

This is the original logic with straight quotes and block indentation restored:

import StringIO
import csv
from java.text import SimpleDateFormat as SDF

dataIn = StringIO.StringIO(event.source.parent.getComponent('Text Area').text)
reader = csv.reader(dataIn, delimiter=',', quotechar="'")

dataOut = []
headerFlag = 1
typeFlag = 0

for row in reader:
    element = [r.strip() for r in row]
    if headerFlag == 1:
        headers = element
        headerFlag = 0
        typeFlag = 1
    elif typeFlag == 1:
        types = element
        typeFlag = 0
        print types
    else:
        rowList = list(element)
        rowOut = []
        for i in range(len(element)):
            type = types[i].strip()
            if type == 'Integer':
                dataPoint = int(rowList[i])
            elif type == 'Float':
                print rowList[i]
                dataPoint = float(rowList[i])
            elif type == 'String':
                if str(rowList[i])[:2] == "u'":
                    dataPoint = unicode(str(rowList[i])[2:-1])
                else:
                    dataPoint = str(rowList[i])
            elif type == 'Boolean':
                dataPoint = (rowList[i] == 'True')
            elif type == 'Date':
                inputFormat = SDF('EEE MMM dd HH:mm:ss Z yyyy')
                dataPoint = SDF.parse(inputFormat, str(rowList[i]))
            rowOut.append(dataPoint)
        dataOut.append(rowOut)

event.source.parent.getComponent('Table').data = system.dataset.toDataSet(headers, dataOut)

Before you reuse this script in a project, apply these hardening edits. Each one closes a failure mode from the checks above:

  1. Add if not element: continue as the first line inside the for row loop. This skips blank lines. Confirm by adding a trailing newline to the Text Area: the Table should still load.
  2. Add a final else: raise ValueError('Unknown type: ' + type) to the type chain. This turns silent value carry-over into a clear error. Confirm by typing Int in the type row: the button should report the bad name.
  3. Rename type to something like colType so it no longer shadows the built-in type(). Confirm the script still compiles.
  4. Create the SDF instance once, before the loop, instead of once per date cell. Change the pattern to match the real date text in your source. Confirm with one sample date before you load bulk data.
  5. If your source writes booleans as true or 1, change the Boolean test to compare the lower-cased cell against the accepted spellings. Confirm that a known-true row shows as true in the Table.
  6. Remove the two print statements after commissioning. Confirm that the console stays quiet on a normal press.

Verification: load a known sample into the Text Area and press the button, then check these results in order:

  1. The console shows no exception.
  2. The Table's column names match line 1 of the input.
  3. The Table's row count equals the number of non-blank lines minus two.
  4. One spot-checked cell per column shows the expected converted value: numeric for Integer and Float, true/false for Boolean, and a date rather than text for Date.

FAQ

What happens if a CSV cell is blank in an Integer or Float column?

int('') and float('') both raise ValueError. The script stops before the final assignment line, so the Table keeps its previous data. Guard blank cells with an explicit check and substitute a default or None before converting.

What happens if the type row contains a name the script does not recognize?

No branch assigns dataPoint, so the column silently receives the previous column's value. If there is no previous value yet, the script raises NameError. Add a final else that raises an error naming the unknown type.

What happens if the Text Area ends with a blank line?

csv.reader returns an empty row for that line. The script appends an empty rowOut to dataOut, and the dataset is no longer rectangular. Skip empty rows with if not element: continue at the top of the loop.

Where is documentation for StringIO, csv and SimpleDateFormat in Ignition scripting?

StringIO and csv are Python standard library modules, documented in the Jython standard library reference. SimpleDateFormat is a Java class in java.text, documented in the Java SE API reference. Jython can import both because it runs on the JVM.

Back to blog