Importer and Exporter Examples

CSV Format Description

The Python-based CSV (comma-separated values) importer described here is exactly the same as the one built into Flying Logic.

When using the File ➡ Import ➡ Import Diagram from CSV... command, you will first be asked for a text file to import, then you will be presented with two dialogs: The first dialog provides the importer with information on how to read the file (text encoding, whether comma or tab is used as a column delimiter and whether there is a header row) and whether a new document is to be created (instead of importing into the current document).

The second dialog tells the importer how to interpret the columns in the text file. The columns in the text file can either be used as the name of a new entity or group, the entity class of a new entity (ignored from groups), a list of connections to this entity by column number (ignored for groups), a list of children (ignored for entities), or an annotation. A row is considered to represent a group if children is not an empty string of text. The connections can be considered wither a list of predecessors or successors. The valued entered in Internal Separator is the character used to separate column numbers in a connection or children list (defaults to space). The row indexes for predecessors, successors, or children can either start at 1 for the first row, 0 for the first row, or be assigned an index from a value taken from a column.

The entires in a column can be quoted with double quotes. When quoted, appearance of a double quote character in the column text must be escaped with a backslash character.

Example CSV File

Raw text:

"Title","Class","Depends","Group"
"D","Action",,
"E","Action",,
"C","Intermediate Effect","2 3",
"B","Action",,
"A","Goal","4 5",
"G",,,"2 3 4"

Shown as table:

Line #

1

Title

Class

Depends

Group

2

D

Action

3

E

Action

4

C

Intermediate Effect

2 3

5

B

Action

6

A

Goal

4 5

7

G

2 3 4

The example above has a header row, comma as separator, uses quoted strings (unnecessary in this case as there are no embedded commas, etc.), and the default list separator of space. Note that the header row is still considered a row, so the "D" entity is row 2.

The example file generates the following diagram:

CSV Importer Code

# import_csv.py
# import comma separated values from a file into Flying Logic
# supports comma, tab and semicolon as delimiters and quoted values
# creates entities, groups and edges, but not junctors
# Copyright 2013,2014 Sciral

# Java classes needed to create the UI and read files
from java.io import InputStreamReader, BufferedReader, FileInputStream
from java.nio.charset import Charset
from javax.swing import Box, BoxLayout, JLabel, JCheckBox, JComboBox, JTextField

# required variable that provides the label for the item in Flying Logic import menu
importItemLabel = "Import Diagram from CSV File"

# importLine: a subroutine to process a line
# returns an array of values found in the line
def importLine(line, delimiter):
    cols = []
    stage = 0
    quoted = False
    for c in line:
        if stage == 0:
            if c == '"':
                quoted = True
                stage = 1
                s = []
            elif c == delimiter:
                cols.append('')
            elif not c.isspace():
                quoted = False
                stage = 1
                s = [c]
        elif stage == 1:
            if c == '"':
                if quoted:
                    stage = 2
                else:
                    s.append(c)
            elif c == delimiter:
                if quoted:
                    s.append(c)
                else:
                    cols.append(''.join(s).strip())
                    stage = 0
            else:
                s.append(c)
        elif stage == 2:
            if c == '"':
                s.append(c)
                stage = 1
            elif c == delimiter:
                cols.append(''.join(s))
                stage = 0
            elif not c.isspace():
                """ bad format """
                break

    if stage != 0:
        cols.append(''.join(s))

    return cols

# this function nicely adds an annotation from plain text
def setAnnotation(elem, text):
    editor = elem.annotationEditor
    editor.insert(text, { })
    editor.flush()

def columnFromParams(columns, params, key):
    if key in params:
        value = params[key]
        if isinstance(value, int):
            return value
        try:
            return columns.index(value, 1) - 1
        except ValueError:
            return 0
    else:
        return 0

# importDocument: required function for an importer
# parameters
#      file: filename of the file to import
def importDocument(file):

    params = document.importExportParams
    if params != None:
        createNewDocument = False
        if Application.IMPORT_DIAGRAM_NEW_DOCUMENT in params:
            createNewDocument = params[Application.IMPORT_DIAGRAM_NEW_DOCUMENT]

        hasHeader = False
        if Application.CSV_HEADER_ROW in params:
            hasHeader = params[Application.CSV_HEADER_ROW]

        encoding = 'ISO-8859-1'
        if Application.CSV_ENCODING in params:
            value = params[Application.CSV_ENCODING]
            if value == Application.CSV_ENCODING_UTF8:
                encoding = 'UTF-8'
            elif value == Application.CSV_ENCODING_ASCII:
                encoding = 'US-ASCII'

        columnDelimiter = ','
        if Application.CSV_SEPARATOR in params:
            value = params[Application.CSV_SEPARATOR]
            if value == Application.CSV_SEPARATOR_TABS:
                columnDelimiter = '\t'
            elif value == Application.CSV_SEPARATOR_SEMICOLONS:
                columnDelimiter = ';'
    else:
        # create a dialog using Java to collect details about the imported file
        masterBox = Box(BoxLayout.Y_AXIS)

        # text encoding
        controlBox = Box(BoxLayout.X_AXIS)
        controlBox.add(JLabel("Text Encoding: "))
        encodingsComboBox = JComboBox(['Windows/Latin-1/ISO-8859-1', 'UTF-8', 'ASCII (US)'])
        controlBox.add(encodingsComboBox)
        masterBox.add(controlBox)

        # delimiter
        controlBox = Box(BoxLayout.X_AXIS)
        controlBox.add(JLabel("Column separator: "))
        columnSepComboBox = JComboBox(["Commas", "Tabs", "Semicolon"])
        controlBox.add(columnSepComboBox)
        masterBox.add(controlBox)

        # header row?
        controlBox = Box(BoxLayout.X_AXIS)
        headerCheckbox = JCheckBox("Has header row")
        controlBox.add(headerCheckbox)
        controlBox.add(Box.createHorizontalGlue())
        masterBox.add(controlBox)

        # create new document? (default is to import into current document)
        controlBox = Box(BoxLayout.X_AXIS)
        newDocCheckbox = JCheckBox("Create new document")
        controlBox.add(newDocCheckbox)
        controlBox.add(Box.createHorizontalGlue())
        masterBox.add(controlBox)

        # display dialog and collect options
        if 0 == Application.request("CSV Import Settings", masterBox, ("Cancel", "OK")):
            return

        createNewDocument = newDocCheckbox.isSelected()

        knownEncodings = ['ISO-8859-1', 'UTF-8', 'US-ACSII']
        encoding = knownEncodings[encodingsComboBox.selectedIndex]

        columnDelimiter = ','
        if columnSepComboBox.selectedIndex == 1:
            columnDelimiter = '\t'
        if columnSepComboBox.selectedIndex == 2:
            columnDelimiter = ';'

        hasHeader = headerCheckbox.isSelected()
    
    theDoc = document
    if createNewDocument:
        theDoc = Application.newDocument()

    # open input file
    reader = BufferedReader( InputStreamReader( FileInputStream( file ), encoding ) )

    firstLine = True
    vertexList = []
    indexMap = {}
    row = 0

    # process file line by line
    while True:
        line = reader.readLine()
        if line == None:
            break

        row = row + 1

        # collect values in line
        columns = importLine(line, columnDelimiter)
        numColumns = len(columns)

        # if first line, ask user to identify meaning if each column
        if firstLine:
            firstLine = False
            
            columnNames = ['Not used']
            indexNames = ['First row is index 1', 'First row is index 0']
            if hasHeader:
                columnNames = columnNames + columns
                indexNames = indexNames + columns
            else:
                for i in range(numColumns):
                    columnNames.append('Column ' + str(i + 1))
                    indexNames.append('Column ' + str(i + 1))

            if params != None:
                titleColumn = columnFromParams(columnNames, params, Application.CSV_TITLE_COLUMN)
                classColumn = columnFromParams(columnNames, params, Application.CSV_CLASS_COLUMN)
                linkColumn = columnFromParams(columnNames, params, Application.CSV_CONNECTIONS_COLUMN)
                isSuccessor = 0
                if Application.CSV_CONNECTIONS_TYPE in params:
                    if params[Application.CSV_CONNECTIONS_TYPE] == Application.CSV_SUCCESSORS:
                        isSuccessor = 1
                childrenColumn = columnFromParams(columnNames, params, Application.CSV_CHILDREN_COLUMN)
                rowDelimiter = ' '
                if Application.CSV_CHILDREN_SEPARATOR in params:
                    rowDelimiter = params[Application.CSV_CHILDREN_SEPARATOR]

                indexColumn = columnFromParams(columnNames, params, Application.CSV_ROW_INDEX)

                noteColumn = columnFromParams(columnNames, params, Application.CSV_ANNOTATION_COLUMN)
            else:
                # make Java dialog
                masterBox = Box(BoxLayout.Y_AXIS)

                controlBox = Box(BoxLayout.X_AXIS)
                controlBox.add(JLabel("Please match attributes with columns:"))
                controlBox.add(Box.createHorizontalGlue())
                masterBox.add(controlBox)

                masterBox.add(Box.createVerticalStrut(20))

                controlBox = Box(BoxLayout.X_AXIS)
                controlBox.add(JLabel("Element Title: "))
                titleColumnComboBox = JComboBox(columnNames)
                controlBox.add(titleColumnComboBox)
                controlBox.add(Box.createHorizontalGlue())
                masterBox.add(controlBox)

                controlBox = Box(BoxLayout.X_AXIS)
                controlBox.add(JLabel("Entity Class: "))
                classColumnComboBox = JComboBox(columnNames)
                controlBox.add(classColumnComboBox)
                controlBox.add(Box.createHorizontalGlue())
                masterBox.add(controlBox)

                controlBox = Box(BoxLayout.X_AXIS)
                controlBox.add(JLabel("Connections: "))
                linkColumnComboBox = JComboBox(columnNames)
                controlBox.add(linkColumnComboBox)
                predColumnComboBox = JComboBox(['Predecessors', 'Successors'])
                controlBox.add(predColumnComboBox)
                controlBox.add(Box.createHorizontalGlue())
                masterBox.add(controlBox)

                controlBox = Box(BoxLayout.X_AXIS)
                controlBox.add(JLabel("Children: "))
                childColumnComboBox = JComboBox(columnNames)
                controlBox.add(childColumnComboBox)
                controlBox.add(Box.createHorizontalGlue())
                masterBox.add(controlBox)

                controlBox = Box(BoxLayout.X_AXIS)
                controlBox.add(JLabel("Internal separator: "))
                rowSepTextField = JTextField(5)
                controlBox.add(rowSepTextField)
                controlBox.add(Box.createHorizontalGlue())
                masterBox.add(controlBox)

                controlBox = Box(BoxLayout.X_AXIS)
                controlBox.add(JLabel("Row Index: ")),
                indexColumnComboBox = JComboBox(indexNames)
                controlBox.add(indexColumnComboBox)
                controlBox.add(Box.createHorizontalGlue())
                masterBox.add(controlBox)

                controlBox = Box(BoxLayout.X_AXIS)
                controlBox.add(JLabel("Annotation: "))
                noteColumnComboBox = JComboBox(columnNames)
                controlBox.add(noteColumnComboBox)
                controlBox.add(Box.createHorizontalGlue())
                masterBox.add(controlBox)

                if 0 == Application.request("CSV Column Interpretation", masterBox, ("Cancel", "OK")):
                    if createNewDocument:
                        theDoc.closeDocument(False)
                    return

                titleColumn = titleColumnComboBox.selectedIndex - 1

                classColumn = classColumnComboBox.selectedIndex - 1

                linkColumn = linkColumnComboBox.selectedIndex - 1

                childrenColumn = childColumnComboBox.selectedIndex - 1

                isSuccessor = (predColumnComboBox.selectedIndex == 1)

                indexColumn = indexColumnComboBox.selectedIndex - 2

                noteColumn = noteColumnComboBox.selectedIndex - 1

                rowDelimiter = rowSepTextField.text.strip()
                if len(rowDelimiter) == 0:
                    rowDelimiter = ' '

            # if first line is a header, skip line
            if hasHeader:
                continue
    
        # default entity and group attributes
        entityTitle = 'untitled'
        entityClass = 'Generic'
        entityLinks = ''
        groupChildren = ''
        annotation = None

        # match values with identified attributes
        if titleColumn >= 0 and titleColumn < numColumns:
            entityTitle = columns[titleColumn]
        if classColumn >= 0 and classColumn < numColumns:
            entityClass = columns[classColumn]
        if linkColumn >= 0 and linkColumn < numColumns:
            entityLinks = columns[linkColumn]
        if childrenColumn >= 0 and childrenColumn < numColumns:
            groupChildren = columns[childrenColumn]
        if noteColumn >= 0 and noteColumn < numColumns:
            annotation = columns[noteColumn]
            if len(annotation) == 0:
                annotation = None

        # create index mapping based on user choice of one-based, zero-based or by column value
        indexRow = row
        if indexColumn >= 0:
            indexRow = int(columns[indexColumn])
        elif indexColumn == -1:
            indexRow = row - 1
        indexMap[indexRow] = row

        # either handle as group or entity -- no junctors yet
        if groupChildren != '':
            group = theDoc.newGroup(None)[0]
            if entityTitle != 'untitled':
                group.title = entityTitle
            if annotation != None:
                setAnnotation(group, annotation)

            vertexList.append( (group, None, groupChildren) )
        else:
            entity = theDoc.addEntityToTarget(None)[0] # no need to clearSelection each iteration
            entity.title = entityTitle

            eCls = theDoc.getEntityClassByName(entityClass)
            if eCls != None:
                entity.entityClass = eCls
            if annotation != None:
                setAnnotation(entity, annotation)

            vertexList.append( (entity, entityLinks, None) )

    # generate new elements from collected vertex data
    for data in vertexList:
        if data[1] != None:
            predList = data[1].split(rowDelimiter)
            for pred in predList:
                if len(pred) > 0:
                    index = indexMap[int(pred)] - 1
                    if hasHeader:
                        index = index - 1
                    if index >= 0 and index < len(vertexList):
                        if isSuccessor:
                            theDoc.connect(data[0], vertexList[index][0])
                        else:
                            theDoc.connect(vertexList[index][0], data[0])
        if data[2] != None:
            childList = data[2].split(rowDelimiter)
            for child in childList:
                if len(child) > 0:
                    index = indexMap[int(child)] - 1
                    if hasHeader:
                        index = index - 1
                    if index >= 0 and index < len(vertexList):
                        vertexList[index][0].parent = data[0]

    return theDoc

Example DOT Exporter

The code below exports a document to a DOT (GraphViz) file, but with less features than the native export option in Flying Logic.

# export_dot.py
# a simple DOT format exporter, less complete then the native version in Flying Logic
# Copyright 2013 Arciem LLC

# required variable that provided the label for the item in Flying Logic export menu
exportMenuLabel = "Export Diagram to simple DOT format"

# exportDocument: required function for an exporter
# parameters
# file: filename of the file to export
def exportDocument(file):
  # open output file using Python file I/O
  fh = open(file, 'w')

  fh.write("digraph graphname {\n")
  for elem in document.all:
    if elem.isGroup or elem.isEdge:
      continue

    # use the element unique id’s (eid) to create unique id’s in DOT
    if elem.isEntity:
      fh.write("\tn" + str(elem.eid) + " [label=\"" + elem.title + "\"];\n")
    if elem.isJunctor:
      fh.write("\tn" + str(elem.eid) + " [label=\"" + elem.operator.abbreviation + "];\n")
    for outEdge in elem.outEdges:
      fh.write("\tn" + str(elem.eid) + " -> n" + str(outEdge.target.eid) + ";\n")
      fh.write("\t}\n")

  fh.close()

Last updated