# Python Script for Adaptive Path
# Convert Mental Model Excel WorkSheet or Word Doc to Visio Chart
#
# 4-6-2006 Gary Wang firepotter@gmail.com
#
#----------------------------------------------------------------------------------------
from win32com.client import Dispatch
import string, os, sys
filePath = '' # leave these blank if you want to use the default
excelFilename = ''
"""
These are the printable characters:
0123456789abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
"""
boxInTopDownOrder = 1
generateDebugFiles = 1 #set to 1 to generate a debug output file
#
# Color Generator Function
def makeColor( red=1, green=1, blue=1 ):
rc = int( red*255 ) % 256
gc = int( green*255 ) % 256
bc = int( blue*255 ) % 256
return ( bc*0x010000 + gc*0x000100 + rc*0x000001 )
colorBlack = makeColor(red=0,green=0,blue=0)
colorRed = makeColor(red=1,green=0,blue=0)
colorGreen = makeColor(red=0,green=1,blue=0)
colorYellow = makeColor(red=1,green=1,blue=0)
colorBlue = makeColor(red=0,green=0,blue=1)
colorMagenta = makeColor(red=1,green=0,blue=1)
colorCyan = makeColor(red=0,green=1,blue=1)
colorWhite = makeColor(red=1,green=1,blue=1)
colorLightCyan = makeColor(red=0.5,green=1,blue=1)
# Chart Property Constants
filePath = '' # leave these blank if you want to use the default
wordFilename = ''
#unit = inch
PageLength=8.5
#PageWidth=88
PageMargin = 0.25
ChartStartX=1+5.0/8
HorizontalLinePosition = PageLength/2.0-0.125
Box2BoxSpacing = 0.0625
Box2TowerSpacing = 0.125
Tower2TowerSpacing = 0.125
Tower2SectionSpacing = 0.125
TowerHeaderExtra = 0.250+0.125
SectionHeight = PageLength/2-PageMargin - 0.25
MaxBoxNumberPerColumn = 7
BOXHEIGHT=0.375
BOXWIDTH=0.625
TOWERWIDTH=0.875
DefaultLineWeight = 0.06
SectionFontPoint = 12
SectionLineWeight = 0.01
SectionFill = 0
SectionFillColor = colorWhite
SectionLine = 0
SectionLineColor = colorWhite
TowerFontPoint = 6
TowerLineWeight = 0.01
TowerFill = 1
TowerFillColor = colorLightCyan
TowerLine = 0
TowerLineColor = colorLightCyan
BoxFontPoint = 6
BoxLineWeight = 0.01
BoxFill = 1
BoxFillColor = colorWhite
BoxLine = 1
BoxLineColor = colorBlack
#
# Global Variables
sectionIndex = 0
towerIndex = 0
boxIndex = 0
sectionX=2
sectionY=0
sectionNextX = ChartStartX
towerX=0
towerY=0
styleList = {}
#----------------------------------------------------------------------------------------
import string, os, sys, time
allowedFileTypeList = ['.xml']
operatingSystem = os.environ['OS']
print 'OS=',operatingSystem
if operatingSystem in ['Windows_NT']:
allowedFileTypeList.append('.xls')
allowedFileTypeList.append('.doc')
#
from xml.sax import saxutils
from xml.sax import ContentHandler
from xml.sax import make_parser
from xml.sax.handler import feature_namespaces
def hex2Integer( s='' ):
s = string.lower(s)
sum = 0
for c in s:
i = string.find( string.hexdigits, c )
if i>=0:
sum = sum*16 + i
return sum
def normalize_whitespace(text):
"Remove redundant whitespace from a string"
return ' '.join(text.split())
class ProcessData(ContentHandler):
def __init__(self):
self.inDataContent = 0
self.inTargetSheet = 0
self.sID = "Default"
self.styleList = {}
self.dataList = {}
def startElement(self, name, attrs):
try:
if name=='Style':
self.sID = attrs.get('ss:ID', None)
self.styleList[self.sID]={}
if name=='Font':
self.styleList[self.sID]['Family']=attrs.get('x:Family', None)
self.styleList[self.sID]['Size'] = attrs.get('ss:Size', None)
self.styleList[self.sID]['Color'] = attrs.get('ss:Color', '#FFFFFF')
self.styleList[self.sID]['Italic'] = attrs.get('ss:Italic', None)
self.styleList[self.sID]['Bold'] = attrs.get('ss:Bold', None)
self.styleList[self.sID]['Underline'] = attrs.get('ss:Underline', None)
if name=='Interior':
self.styleList[self.sID]['iColor'] = attrs.get('ss:Color', '#FFFFFF')
self.styleList[self.sID]['iPattern'] = attrs.get('ss:Pattern', None)
#
if name=='Worksheet':
self.currentSheet = attrs.get('ss:Name', None)
self.currentRow = 0
if name=='Row':
self.currentRow = self.currentRow + 1
self.currentCell = 0
if name=='Cell':
self.sID = attrs.get('ss:StyleID', None)
sIx = attrs.get('ss:Index', None)
if sIx==None:
self.currentCell = self.currentCell + 1
else:
#print sIx
self.currentCell = string.atoi(sIx)
if name=='Data':
self.inDataContent = 1
self.dataContent = ""
except:
pass
def characters(self, ch):
if self.inDataContent:
#XML special characters (< > & ") must be encoded (< > & ")
#to be used in python code. This, unfortunately, is unavoidable.
if ch=='&': ch = '&'
if ch=='<': ch = '<'
if ch=='>': ch = '>'
if ch=='"': ch = '"'
self.dataContent = self.dataContent + ch
def endElement(self, name):
if name=='Data':
self.inDataContent = 0
self.dataContent = normalize_whitespace(self.dataContent)
data = (self.currentRow, self.currentCell, self.sID, self.dataContent)
if not self.dataList.has_key(self.currentSheet):
self.dataList[self.currentSheet] = []
self.dataList[self.currentSheet].append( data )
def readXMLDoc( filename, debug=1 ):
# Create a parser
parser = make_parser()
# Tell the parser we are not interested in XML namespaces
parser.setFeature(feature_namespaces, 0)
# Create the handler
dh = ProcessData()
# Tell the parser to use our handler
parser.setContentHandler(dh)
# Parse the input
parser.parse(filename)
tmpFilename = filename[:-4]+'tmp2139'
fd = open( tmpFilename+".txt", "w" )
operatingSystem = os.environ['OS']
fd.write('\nOS=%s\n' % (operatingSystem) )
fd.write('Script=%s\n' % (sys.argv[0]) )
fd.write('Date=%s\n' % (time.asctime() ) )
fd.flush()
for key in dh.styleList.keys():
ss = '%s' % (key)
for skey in dh.styleList[key].keys():
ss = ss + ', %s:%s ' % (skey, dh.styleList[key][skey])
if debug:
print ss
fd.write(ss+'\n')
fd.write('\n\n')
workSheetKeys = dh.dataList.keys()
#print workSheetKeys
stack = {}
for worksheet in workSheetKeys:
(chart, section, tower) = ([],[],[])
(sectionIndex, towerIndex, boxIndex) = (0,0,0)
#print worksheet
for (row, col, sID, text) in dh.dataList[worksheet]:
if row==1 and text=='Mental Space':
sectionIndex = col
if row==1 and text=='Task Tower':
towerIndex = col
if row==1 and text=='Task':
boxIndex = col
if row>1 and (sectionIndex, towerIndex, boxIndex) == (0,0,0):
break
text = '%s::::%s' % (text , sID)
if row>1 and col==sectionIndex:
if len(section)>0:
if len(tower)>0:
section.append(tower)
chart.append(section)
section=[text]
tower = []
if row>1 and col==towerIndex:
if len(tower)>0:
section.append(tower)
tower=[text]
if row>1 and col==boxIndex:
tower.append( text )
if len(section)>0:
if len(tower)>0:
section.append(tower)
chart.append(section)
if len(chart)>0:
stack[worksheet]=chart
fd.close()
if not debug:
os.remove( tmpFilename+".txt" )
return (dh.styleList, stack)
def process( s ):
s1 = s[10:]
s2 = ''
flag = 0
for i in range(0,len(s1)):
bStart = (s1[i]=='<')
bEnd = (s1[i]=='>')
if bStart:
flag = 1
if not flag:
if s1[i] in string.printable:
s2 = s2 + s1[i]
if bEnd:
flag = 0
s3 = string.split(s2,'>')
s4 = string.split(s3[0],' ')
return (s4[0],s3[-1])
def readWordDocFast( fname='', debug=0 ):
#Faster Method.
#Save to a *.htm file and parse it.
print fname
chart=[]
if fname=='':
return chart
filename = fname[:-4]
style = {}
style['APHeading2'] = 'section'
style['APHeading3'] = 'tower'
style['APHeading4'] = 'box'
tmpFilename = filename+'tmp2138'
filteredHTMLFormat = 8
app = Dispatch("Word.Application")
"""
#using word's File Open GUI ?????
#app.Visible = 1
#app.Dialogs(80).Show()
app.Dialogs(80).Display()
print 'Name is', app.Dialogs(80).Name
"""
app.Visible = 0
doc = app.Documents.Open( filename + ".doc" )
doc.SaveAs(tmpFilename+".htm",filteredHTMLFormat)
doc.Close(0)
del app
fd = open( tmpFilename+".htm", 'r')
lines=fd.readlines()
fd.close()
os.remove( tmpFilename+"_files\\filelist.xml" )
os.remove( tmpFilename+"_files\\header.htm" )
os.rmdir( tmpFilename+"_files")
if not debug:
os.remove( tmpFilename+".htm" )
fd = open( tmpFilename+".txt", "w" )
output = []
flag = 0
tFlag = 0
s = ''
for line in lines:
pStart = string.find(line,'
')>=0
tStart = string.find(line,'
=0
tEnd = string.find(line,'
')>=0
if tStart:
tFlag = 1
if tEnd:
tFlag = 0
pStart = 0
s = ''
flag = 0
if pStart:
flag = 1
if (pStart or pEnd or flag) and (not tFlag):
s = s + ' ' + string.strip(line)
if pEnd:
flag = 0
if len(s)>0:
(key,t) = process(s)
if key in style.keys():
t = string.strip(t)
#t = string.replace(t,'&','&')
#t = string.replace(t,'x',"'")
if style[key]=='section':
section = t
chart.append([section])
sectionIndex = len(chart)-1
if style[key]=='tower':
tower = t
chart[sectionIndex].append([tower])
towerIndex = len(chart[sectionIndex])-1
if style[key]=='box':
chart[sectionIndex][towerIndex].append(t)
ss = "%s %7s %s" % (key, style[key], t)
print ss
fd.write( ss+'\n' )
fd.write( s+'\n' )
s = ''
fd.close()
if not debug:
os.remove( tmpFilename+".txt" )
return chart
def readWordDocSlow( filename='' ):
from win32com.client import Dispatch
#Get the info from Word Objects
#This is slower than readWordDoc2
chart=[]
if filename=='':
return chart
style = {}
style['AP Heading 2'] = 'section'
style['AP Heading 3'] = 'tower'
style['AP Heading 4'] = 'box'
app = Dispatch("Word.Application")
app.Visible = 0
doc = app.Documents.Open(filename)
for para in doc.paragraphs:
s = para.format.style.nameLocal
if s in style.keys():
t = string.strip(para.Range.Text)
t = string.replace(t,'&','&')
if style[key]=='section':
section = t
chart.append([section])
sectionIndex = len(chart)-1
if style[key]=='tower':
tower = t
chart[sectionIndex].append([tower])
towerIndex = len(chart[sectionIndex])-1
if style[key]=='box':
chart[sectionIndex][towerIndex].append(t)
print "%s %7s %s" % (key, style[key], t)
doc.Close(0)
del app
return chart
def drawVisio( filename, shapes, width=88, length=PageLength ):
fd = open( filename, 'w' )
fd.write("\n")
fd.write("\n")
fd.write("\n")
fd.write("\n")
fd.write("\n")
fd.write("\n")
fd.write("%.2f\n" % (width) )
fd.write("%.2f\n" % (length) )
fd.write("1\n")
fd.write("3\n")
fd.write("0\n")
fd.write("\n")
fd.write("\n")
fd.write("8\n")
fd.write("8\n")
fd.write("4\n")
fd.write("4\n")
fd.write("\n")
fd.write("\n")
fd.write("\n")
shapeID = 1
for (X, Y, Width, Height, Text, vAlign, TextColor, TextFont, TextPoint, TextStyle, Line, \
LineColor, LineWeight, Fill, FillColor) in shapes:
shapeWidth = Width
shapeHeight = Height
shapeX = X
shapeY = Y
fillColor = FillColor
lineWeight = LineWeight
lineColor = LineColor
textMarginHorizontal = 0.02
textMarginVertical = 0.02
verticalAlignment = vAlign
fill = Fill
line = Line
text = Text
pSize = TextPoint*(0.11111111111/8)
if string.find(text,'::::')>=0:
tlist = string.split(text,'::::')
text = tlist[0]
try:
fillColor = string.atoi(styleList[tlist[-1]]['iColor'][1:],16)
except:
pass
x1 = 0
x2 = shapeWidth
y1 = 0
y2 = shapeHeight
shapeCenterX = shapeX + shapeWidth/2.0
shapeCenterY = shapeY + shapeHeight/2.0
#LineStyle='6' FillStyle='6' TextStyle='6'
fd.write("\n" % (shapeID, shapeID) )
fd.write("\n")
fd.write("%.4f\n" % (shapeCenterX) )
fd.write("%.4f\n" % (shapeCenterY) )
fd.write("%.4f\n" % (shapeWidth) )
fd.write("%.4f\n" % (shapeHeight) )
fd.write("%.4f\n" % (shapeWidth/2.0) )
fd.write("%.4f\n" % (shapeHeight/2.0) )
fd.write("\n")
fd.write("%.3f\n" % (lineWeight) )
if lineColor!=0:
fd.write("#%x\n" % (lineColor) )
fd.write("\n")
fd.write("#%x\n" % (fillColor) )
fd.write("\n")
fd.write("0\n")
fd.write("0\n")
fd.write("\n" % (TextStyle) )
fd.write("0\n")
fd.write("0\n")
fd.write("%.10f\n" % (pSize) )
fd.write("\n")
fd.write("\n")
fd.write("%.3f\n" % (textMarginHorizontal) )
fd.write("%.3f\n" % (textMarginHorizontal) )
fd.write("%.3f\n" % (textMarginVertical) )
fd.write("%.3f\n" % (textMarginVertical) )
fd.write("%d\n" % (verticalAlignment) )
fd.write("\n")
fd.write("\n")
fd.write("%d\n" % (1-fill) )
fd.write("%d\n" % (1-line) )
fd.write("0\n")
fd.write("0\n")
fd.write("\n%.4f\n%.4f\n\n" % (x1,y1) )
fd.write("\n%.4f\n%.4f\n\n" % (x2,y1) )
fd.write("\n%.4f\n%.4f\n\n" % (x2,y2) )
fd.write("\n%.4f\n%.4f\n\n" % (x1,y2) )
fd.write("\n%.4f\n%.4f\n\n" % (x1,y1) )
fd.write("\n")
try:
fd.write("%s\n" % (text) )
except:
fd.write("%s\n" % ('ERROR???') )
fd.write("\n")
shapeID = shapeID + 1
fd.write("\n\n\n\n")
fd.close()
return 1
def shape( X, Y, Width=1, Height=1, Text='', vAlign=1, TextColor=0xffff80, TextFont='arial', \
TextPoint=6, TextStyle=0, Line=1, LineColor=0x000000, LineWeight=0.02, \
Fill=0, FillColor=0xffffff):
return((X, Y, Width, Height, Text, vAlign, TextColor, TextFont, TextPoint, TextStyle,\
Line, LineColor, LineWeight, Fill, FillColor))
def line( x1, y1, x2, y2, Text='' ):
Width=x2-x1
Height=y2-y1
return shape( x1, y1, Width, Height, Text, LineWeight=DefaultLineWeight )
def sectionBox( section ):
global sectionIndex, towerIndex
global sectionX, sectionY, sectionNextX
global towerX, towerY
Text = section[0]
numberOfTowers = len(section)-1
sectionIndex = sectionIndex + 1
Width = Tower2TowerSpacing
for tower in section[1:]:
m = int(((len(tower)-1)-0.1)/MaxBoxNumberPerColumn) + 1
Width = Width + (0.1+(TOWERWIDTH-0.1)*m) + Tower2TowerSpacing
if numberOfTowers==0:
Width = Width+TOWERWIDTH+Tower2TowerSpacing
Height= SectionHeight
sectionX = sectionNextX
sectionNextX = sectionNextX + Width
sectionY = PageLength/2.0
towerIndex = 0
return shape( sectionX, sectionY, Width, Height, Text, vAlign=0, TextStyle=1, \
LineWeight=SectionLineWeight, Fill=SectionFill, Line=SectionLine,\
FillColor=SectionFillColor, LineColor=SectionLineColor, TextPoint=SectionFontPoint )
def towerBox( tower ):
global towerIndex, boxIndex
global sectionX, sectionY
global towerX, towerY
Text=tower[0]
boxNumber=len(tower)-1
widthMultiplier = 1 + int((boxNumber-0.1)/7)
towerIndex = towerIndex + 1
boxIndex = 0
towerX=sectionX+Tower2SectionSpacing+(towerIndex-1)*(TOWERWIDTH+Tower2TowerSpacing)
towerY=sectionY
bn = boxNumber
if bn>7:
bn=7
Height = bn*(BOXHEIGHT+Box2BoxSpacing)+TowerHeaderExtra
Width = 2*Box2TowerSpacing-Box2BoxSpacing+(BOXWIDTH+Box2BoxSpacing)*widthMultiplier
return shape( towerX, towerY, Width, Height, Text, vAlign=0, \
LineWeight=TowerLineWeight, Fill=TowerFill, Line=TowerLine,\
FillColor=TowerFillColor, LineColor=TowerLineColor, TextPoint=TowerFontPoint )
def boxBox( Text='Box' ):
global boxIndex
global sectionX, sectionY
global towerX, towerY
boxIndex = boxIndex + 1
if boxIndex>MaxBoxNumberPerColumn:
towerX = towerX + BOXWIDTH + Box2BoxSpacing
towerY = sectionY
sectionX = sectionX + BOXWIDTH + Box2BoxSpacing
boxIndex = 1
boxX = towerX + Box2TowerSpacing
boxY = towerY + ((BOXHEIGHT+Box2BoxSpacing)*(boxIndex-1)) + Box2BoxSpacing
Width=BOXWIDTH
Height=BOXHEIGHT
return shape( boxX, boxY, Width, Height, Text, vAlign=1, \
LineWeight=BoxLineWeight, Fill=BoxFill, Line=BoxLine,\
FillColor=BoxFillColor, LineColor=BoxLineColor, TextPoint=BoxFontPoint )
def drawSection( section ):
s = []
s.append( sectionBox( section ) )
for tower in section[1:]:
s = s + drawTower( tower, topDown=boxInTopDownOrder )
return s
def drawTower( tower, topDown=0 ):
s = []
s.append( towerBox( tower ) )
#towerList = tower
towerList = []
for i in range(1,len(tower),MaxBoxNumberPerColumn):
tmp = tower[i:]
if len(tmp)>MaxBoxNumberPerColumn:
tmp = tower[i:i+MaxBoxNumberPerColumn]
if topDown:
tmp.reverse()
towerList = towerList + tmp
for boxText in towerList:
s.append( boxBox( boxText ) )
return s
if __name__ == '__main__':
#get filename from user
if len(filePath)==0:
filePath = string.replace( os.path.abspath('') ,'\\','\\\\') +'\\\\'
if len(excelFilename)==0:
files = []
for file in os.listdir( filePath ):
if file[-4:] in allowedFileTypeList and file[0]!='~':
files.append(file)
files.append('QUIT')
i = 0
for file in files:
print "%2d: %s" % (i, file)
i = i + 1
while 1:
try:
j = string.atoi(raw_input('Select File: '))
excelFilename = files[j]
break
except:
pass
if excelFilename=='QUIT':
sys.exit(0)
print 'Processing', excelFilename, '...'
filename = os.path.abspath('')+'\\'+excelFilename[:-4]+'.xml'
fileType = excelFilename[-4:]
if fileType=='.xls':
# convert file to xml format, window system only, will not work on mac or linux
from win32com.client import Dispatch
app = Dispatch("Excel.Application")
app.Visible = 0
doc = app.Workbooks.Open( filePath+excelFilename )
print 'Saving File As', filename
type = 46
doc.SaveAs(filename, type)
doc.Close(0)
del app
if fileType in ['.xml','.xls']:
(styleList, stack) = readXMLDoc( filename, debug=generateDebugFiles )
#print styleList.keys()
if fileType=='.doc':
filename = os.path.abspath('')+'\\'+excelFilename
chart = readWordDocFast( filename, debug=generateDebugFiles )
stack = { '%FORWORDDOC%': chart }
print stack.keys()
vFileList = []
for key in stack.keys():
ChartStartX=1+5.0/8
sectionIndex = 0
towerIndex = 0
boxIndex = 0
sectionX=2
sectionY=0
sectionNextX = ChartStartX
towerX=0
towerY=0
chart = stack[key]
#print key
#print chart
s = []
for section in chart:
s = s + drawSection( section )
s.append( line(sectionNextX, PageMargin, sectionNextX, PageLength-PageMargin) )
PageWidth= sectionNextX + PageMargin
s.append( line(ChartStartX, HorizontalLinePosition, PageWidth-PageMargin, HorizontalLinePosition) )
tag = '-'+key
if key=='%FORWORDDOC%':
tag = ''
visioFilename = filename[:-4]+tag+ '.vdx'
print visioFilename
vFileList.append(visioFilename)
drawVisio( visioFilename, s, width=PageWidth, length=PageLength )
# Launch Visio and Open the file
visioFilename = vFileList[0]
print '\nLaunching Visio and Opening ' + visioFilename
appVisio = Dispatch("Visio.Application")
appVisio.Visible = 1
doc = appVisio.Documents.Open( visioFilename)