Importer and Exporter Examples
JSON Importer Example
In this section a simple JSON document importer is implemented. The format of the JSON supported by this importer is just an example and not based on any existing format.
When the script is run through the File -> Import -> Import via Script -> Open Script⌠command, it will ask for a file to import and then a dialog allowing for two options: Create new document and whether Connections are predecessors or successors:
Example JSON File
{
"groups": [
{
"children": [
4,
2,
3
],
"index": 1,
"title": "G"
}
],
"nodes": [
{
"connections": [],
"index": 2,
"title": "E",
"type": "Action"
},
{
"connections": [],
"index": 3,
"title": "D",
"type": "Action"
},
{
"connections": [
2,
3
],
"index": 4,
"title": "C",
"type": "Intermediate Effect"
},
{
"connections": [],
"index": 5,
"title": "B",
"type": "Action"
},
{
"connections": [
4,
5
],
"index": 6,
"title": "A",
"type": "Goal"
}
]
}
The format of this JSON file allows for a list of ânodesâ that are interpreted as entities and âgroupsâ which are interpreted as, well, groups. Only fields for titles, entity classes, edges (as âconnectionsâ) and children elements of groups are parsed. It is left to an excessive to the reader to add annotations, junctors, project management data, etc.
The example file generates the following diagram:
JSON Importer Code
# import_json_example.py
# import the contents of a simple JSON file into Flying Logic
# option for having connections be to predecessors or successors
# creates entities, groups and edges, but not junctors
# Copyright 2025 Flying Logic
import json
from javax.swing import Box, BoxLayout, JLabel, JCheckBox, JComboBox
importItemLabel = "Import from JSON Example"
# importDocument: required function for an importer
# parameters
# file: filename of the file to import
def importDocument(file):
# make a UI using Java
masterBox = Box(BoxLayout.Y_AXIS)
# new document?
controlBox = Box(BoxLayout.X_AXIS)
newDocCheckbox = JCheckBox("Create new document")
controlBox.add(newDocCheckbox)
controlBox.add(Box.createHorizontalGlue())
masterBox.add(controlBox)
# connections meaning
controlBox = Box(BoxLayout.X_AXIS)
controlBox.add(JLabel("Connections are: "))
connectionsComboBox = JComboBox(["Predecessors", "Successors"])
controlBox.add(connectionsComboBox)
masterBox.add(controlBox)
# display dialog and collect options
if 0 == Application.request("JSON Import Settings", masterBox, ("Cancel", "OK")):
return
# get options from user choices
createNewDocument = newDocCheckbox.isSelected()
connectionsAsSuccessors = (connectionsComboBox.selectedIndex == 1)
# create a new doc if desired
if createNewDocument:
theDoc = Application.newDocument()
else:
theDoc = document
# open and parse JSON file
with open(file, "r") as infile:
json_object = infile.read()
data = json.loads(json_object)
entityDict = { }
groupDict = { }
# process "nodes" as entities
# we will create all entties and then deal with connections
if 'nodes' in data:
for node in data['nodes']:
# with None parameter, no need to clear selection
entity = theDoc.addEntityToTarget(None)[0]
entity.title = node['title']
entity.entityClass = theDoc.getEntityClassByName(node['type'])
entityDict[node['index']] = (entity, node['connections'])
# process "groups" as groups
# we will create all groups and then deal with children
if 'groups' in data:
for node in data['groups']:
# with None parameter, no need to clear selection
group = theDoc.newGroup(None)[0]
group.title = node['title']
groupDict[node['index']] = (group, node['children'])
# process connections
for data in entityDict.values():
entity = data[0]
for connection in data[1]:
other = entityDict[connection][0]
if connectionsAsSuccessors:
theDoc.connect(entity, other)
else:
theDoc.connect(other, entity)
# process children, which can be an entity or group
for data in groupDict.values():
group = data[0]
for index in data[1]:
if index in entityDict:
child = entityDict[index][0]
else:
child = groupDict[index][0]
child.parent = group
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.
When the script is run through the File -> Export -> Export via Script -> Open Script⌠command, it will export the diagram into the file specified.
# 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()
Example Task Duration to CSV Exporter
The code below exports a document with project management information to a CSV (Comma-Separated Values) file. In addition to task names and durations being exported, completion percentage and outline level; i.e., depth in groups, are included. Entities and junctors are treated as tasks, while groups are considered summaries.
# Flying Logic export project management task data to CSV
# Copyright 2025 ARCIEM LLC
from datetime import datetime
# The most common Java libraries are available for import.
from java.io import FileWriter
# Conveniently, Flying Logic uses the Apache's CSV Java library, so it can be imported.
from org.apache.commons.csv import CSVFormat, CSVRecord, CSVPrinter
# label for menu item in FL
exportItemLabel = "Export Task Durations (CSV)"
# exported file should have extension .json
defaultExtensions = ["csv"]
# class that encapsulates collecting and ordering tasks
class TaskDurationsCSV:
def __init__(self):
# list of Concerto tasks
self.tasks = [ ]
# map vertices to Concerto task
self.eid_to_task = { }
# map vertices to an index
self.export_indices = { }
# create optimized order for algorithm
self.orderedVertices = self.makeChartOrderedVertices()
# generate top-level fields to output (only "tasks" right now)
def generate(self):
# generate output tasks in "nice" hierarchical order
return self.addChildrenOfGroup(None):
def fldateToLocalDate(self, fldate):
return datetime.strftime(datetime.strptime(str(fldate), "%Y-%m-%d"), "%x")
def fltimeintervalToTimeOffset(self, fltimeinterval):
h = str(fltimeinterval.hours)
m = str()
s = h + "h"
if fltimeinterval.minutes > 0:
s += " "
if fltimeinterval.minutes < 10:
s += " "
s += str(fltimeinterval.minutes) + "m"
return s
# Calculate the implied completion value for junctor from its predecessors.
# The lowest such value is hoe complete it is.
def junctorCompletion(self, junctor):
completion = 1.0
for edge in junctor.inEdges:
if not edge.isBackEdge:
source = edge.source
if source.isJunctor:
c = self.junctorCompletion(source)
elif source.isEntity:
c = source.completion
else:
c = self.groupCompletion(source)
if c < completion:
completion = c
return completion
# Calculate the implied completion value for group from its children.
# The lowest such value is hoe complete it is.
def groupCompletion(self, group):
completion = 1.0
for child in group.children:
if child.isJunctor:
c = self.junctorCompletion(child)
elif child.isEntity:
c = child.completion
else:
c = self.groupCompletion(child)
if c < completion:
completion = c
return completion
# This recursive method generates tasks in hierarchical order like
# Flying Logic's chart mode, but makes sure children of summary tasks
# are listed directly below the their parent and has corrent outline level.
def addChildrenOfGroup(self, group):
for vertex in self.orderedVertices:
if vertex.parent == group:
if vertex.isJunctor:
# calculate the next row index
index = len(self.export_indices) + 1
self.export_indices[vertex.eid] = index
task = {
"row": index,
"level": str(self.nestingLevel(vertex)),
"name": vertex.operator.abbreviation,
"startDate": self.fldateToLocalDate(vertex.startDate),
"startTime": self.fltimeintervalToTimeOffset(vertex.startTime),
"finishDate": self.fldateToLocalDate(vertex.finishDate),
"finishTime": self.fltimeintervalToTimeOffset(vertex.finishTime),
"complete": self.junctorCompletion(vertex)
}
# append to task list and associate with Flying Logic vertex
self.tasks.append(task)
self.eid_to_task[vertex.eid] = task
elif vertex.isEntity:
# calculate the next row index
index = len(self.export_indices) + 1
self.export_indices[vertex.eid] = index
# make a task object with proper values
task = {
"row": index,
"level": str(self.nestingLevel(vertex)),
"name": vertex.title,
"startDate": self.fldateToLocalDate(vertex.startDate),
"startTime": self.fltimeintervalToTimeOffset(vertex.startTime),
"finishDate": self.fldateToLocalDate(vertex.finishDate),
"finishTime": self.fltimeintervalToTimeOffset(vertex.finishTime),
"complete": vertex.completion
}
# append to task list and associate with Flying Logic vertex
self.tasks.append(task)
self.eid_to_task[vertex.eid] = task
elif vertex.isGroup:
# calculate the next row index
index = len(self.export_indices) + 1
self.export_indices[vertex.eid] = index
# make a task object representing a group of tasks with proper values
name = vertex.title
if len(name) == 0:
name = "[group]"
task = {
"row": index,
"level": str(self.nestingLevel(vertex)),
"name": name,
"startDate": self.fldateToLocalDate(vertex.startDate),
"startTime": self.fltimeintervalToTimeOffset(vertex.startTime),
"finishDate": self.fldateToLocalDate(vertex.finishDate),
"finishTime": self.fltimeintervalToTimeOffset(vertex.finishTime),
"complete": self.groupCompletion(vertex)
}
# append to task list and associate with Flying Logic vertex
self.tasks.append(task)
self.eid_to_task[vertex.eid] = task
# recurse to add children
if not self.addChildrenOfGroup(vertex):
return False
return True
# calculate all parents (if any) of this vertex
def vertexAncestry(self, vertex):
result = [ ]
current = vertex.parent
while current != None:
result.append(current)
current = current.parent
return result
# caluclate vertex's nesting depth
def nestingLevel(self, vertex):
depth = 1
current = vertex.parent
while current != None:
depth += 1
current = current.parent
return depth
# create a new order for vertices that allows optimal export -- similar to algorithm in Flying Logic
def makeChartOrderedVertices(self):
groupsProcessed = [ ]
chartOrderedVertices = [ ]
for vertex in document.orderedVertices:
if not vertex.isGroup:
if vertex.parent != None:
for group in reversed(self.vertexAncestry(vertex)):
if not group in groupsProcessed:
chartOrderedVertices.append(group)
groupsProcessed.append(group)
chartOrderedVertices.append(vertex)
else:
if len(vertex.children) == 0:
if not vertex in groupsProcessed:
chartOrderedVertices.append(vertex)
groupsProcessed.append(vertex)
return chartOrderedVertices
# standard Flying Logic export function
def exportDocument(filename):
if document.startDate == None:
# show error dialog
Application.alert("Error! Document does not have project management information!")
return
# make a new instance of the TaskDurationCSV and call the generate method
taskdata = TaskDurationsCSV()
taskdata.generate()
# writing to CSV file
printer = CSVPrinter(FileWriter(filename), CSVFormat.EXCEL)
# write header row
printer.printRecord("Level", "Task", "Start", "Finish", "Complete")
for task in taskdata.tasks:
printer.printRecord(task['level'], task['name'], task['startDate'] + " " + task['startTime'],
task['finishDate'] + " " + task['finishTime'], str(int(task['complete'] * 100)) + "%")
printer.close(True)