appscript and office automation
My wife does office work for a local professional and whenever I see her doing work that I know the computer could do for her, I chip in. The end-of-the-month scramble is a clear case: they take client reports, print them out and then manually sort them by officer and fax them out.
Surely I could do better with faxaway, I thought. The only question was: since the reports are in MS Word and the database is in FileMaker Pro, all on a Mac laptop, how much would I have to sell my soul to Apple and Microsoft in the process?
appscript let me drive the process from python. I did quite a bit of HyperTalk programming, but somehow I'm still a bit mystified by AppleScript: which are the language keywords and which are the application vocabulary? The FileMakerAppscriptingOverview made it trivial to crib bits like:
fm = app("FileMaker Pro")
if not fm.databases[db].exists():
fm.open(FSSpec(path % db))
return fm.databases[db]
FSSPec is deprecated in the Carbon docs, but I never did figure out a replacement.
appscript's integration of AppleScript references into python with its and con is particularly cute, but I pulled a bit of hair out before I figured out how to use it:
def officerFax(db, oName, cName):
# hmm... I'm not sure why this str() is necessary...
officers = db.tables['officers'].records[
its.fields['name'].cellValue == str(oName)]
If FileMaker has a way to use real SQL, I can't find it. Plus, we're running a PowerPC version on an intel MacBook with only 0.5GB of RAM. Emulating FileMaker and MS Word is using a lot of RAM, I suspect. I looked into open source alternatives and found that OpenOffice's Base looks quite capable, and I'm sure oowriter would do the job as an MS Word replacement. I hope the python-uno bridge works on OS X so that I can switch the whole operation over one of these moths.
I did pay a price for not doing it The Apple Way. Technical Q&A QA1018 Using AppleScript to send an email with an attachment shows exactly how to attach a report to a mail message and send it to faxaway. I was able to create and address a mail message from python/appscript, but making the attachment stumped me. After verifying that the AppleScript example does work as advertised, I gave up and wrote a separate mailfaxes.py program that uses python's email and smtplib modules and skips Mail.app altogether. I had to be a little careful since the laptop runs python2.3 and the email modules have been rearranged a bit in python 2.4 and 2.5, but it was reasonably straightforward.
Driving MS Word was, predicably, even klunkier:
TMP="fax_job.htm"
def asHTML(w, dirpath, fname):
"""save current doc as HTML
"""
w.do_Visual_Basic('ActiveDocument.SaveAs FileName:="%s",'
' FileFormat:= wdFormatHTML,'
' HTMLDisplayOnlyOutput:=True' % (TMP,))
Office X has an AppleScript interface, but it's not as rich as the Visual Basic API. I got Word to save as HTML (for processing with BeautifulSoup) but I never did figure out how to tell MS Word which directory to put it in. I wrote a posix2mac() routine to convert /posix/paths to ::mac:paths as used in AppleScript but that didn't help; I ended up with a hard-coded kludge.
Switching syntaxes with do_Visual_Basic is a little bit painful, but when it goes bad the diagnostics are pretty good. "ActivePrinter is read-only on the Macintosh," it said, where w.active_printer = p had just failed silently. The modern VB.NET PrintOut documentation isn't hard to find, but it's a little more tricky to find the 2002 PrintOut docs that are more relevant. I never did get PrintToFile working, nor did I find a way to script the PDF option in Apple's print dialogs. Thank goodness for the Appscript, Word and PDF clue which pointed me to CUPS-PDF for Mac OS X. It worked as advertised, though writing code to wait for a new PDF document in ~/Desktop/cups-pdf/ was tricky; we sent a number of reports to the wrong place due to a timing bug.
The Python Bindings for Quartz 2D rock; composing fax cover pages couldn't be easier than this:
htmltxt = coverHTML(oName, fax, subject, pages)
ctx.drawHTMLTextInRect(
CG.CGDataProviderCreateWithString(htmltxt),
pageRect.inset(72, 72))
and concatenating several PDFs into one was similarly straightforward. It doesn't hurt that faxing is Apple's example application.
For reference:
hh-fax2$ hg log --template '#rev#:#node|short# #date|shortdate# #desc|firstline|strip#\n' 11:7b497e5881d8 2006-12-07 fixed nasty timing bug with PDF virtual printer 10:5fbd62cf7025 2006-12-07 fixed SMTP details 9:adbc7966d42d 2006-12-07 back to faxaway 8:89f2688b85fc 2006-12-07 smtp host arg 7:eb4eba0ed22c 2006-12-07 mailfaxes.py starting to work 6:c317d0cb9956 2006-12-07 prepares one PDF doc per officer 5:fe8a79a7ed9f 2006-12-06 faxjob.py iterates over reports and looks up fax numbers 4:52f42112c287 2006-12-06 better diagnostics 3:862514804543 2006-12-04 officer update mostly working 2:61dfc88ab652 2006-12-04 connecting to FM from py works 1:a1f813e53e79 2006-12-02 HTML/CSS page break test 0:aa2d0cc8a7e9 2006-12-02 save as html, doc export working
FOAF updates: Trust rankings are now exported, making the data available to other users and websites. An external FOAF URI has been added, allowing users to link to an additional FOAF file.
Keep up with the latest Advogato features by reading the Advogato status blog.
If you're a C programmer with some spare time, take a look at the mod_virgule project page and help us with one of the tasks on the ToDo list!