Tuturial Index → Python Scripting
The Python scripts for this report are in two scripts in the "CreateReportTutorial.gplug" extension. The main script is in the "Generation Ages Python.py" script. Subroutines needed by that script are in the "PyAgeModule.py" module. Those subroutines could be moved to the start of the main script, but having them in a module both simplifes the main script and demonstrates the use of a Python module. To run this tutorial report, select "Custom Report Tutorial" from the Extensions menu and then choose the "Python Custom Report" option. Note that a slight delay occurs before the script starts, which is caused by loading the Python environment. An advantage of switching to Internal Scripting is that scripts both start faster and run faster.
# Preamble from GEDitCOMII import * from PyAgeModule import * # Verify version and document available gedit = CheckVersionAndDocument("Generation Ages to Report (Python)",1.6,2) if not(gedit) : quit() gdoc = FrontDocument() # choose all or currently selected family records whichOnes = GetOption("Get report for All or just Selected family records",\ None,["All", "Cancel", "Selected"]) if whichOnes=="Cancel" : quit() # Get of list of the choosen family records if whichOnes==""All": fams = gdoc.families() else: fams = GetSelectedType("FAM") # No report if no family records were found if len(fams)==0: Alert("No family records were selected") quit() # Collect all report data in a subroutine CollectAges(fams) # write to report and then done WriteToReport(GetScriptName(),gdoc.name())
Listing 1 shows the main report script, This listing omits comments at the start of the script you can see in the GEDitCOM Editor. All good scripts should start with comments that describe its function along with any other relevant details.
The Preamble section loads two modules — the GEDitCOMII module, which is provided with GEDitCOM II and has numerous subroutines for use in Python scripts, and the "PyAgeModule.py" module, which has subroutines unique to this sample report. Some Python scripts may want to use Apple ScriptingBridge objects or even call Cocoa Foundation classes. If these will be used this section can import them as well using:
from Foundation import *
from ScriptingBridge import *
The preamble section continues by verifying the script can run. All the work is done in the CheckVersionAndDocument() module method. This subroutine check that a document is open and if the current GEDitCOM II version is new enough for this script. It will return a reference to the GEDitCOM II application object if successful, or None if it fails. This script exits on failure. If it works, the next line grabs a reference to the current front document (in gdoc using the FrontDocument() module method).
The next section chooses and finds the records used for the report. The choosing is done with the "User Option" command, but made simpler by the GetOption() module method. Once the choice is made, the records to be analyzed are loaded into a list in fams by either a document property or the GetSelectedType() module method. If no family records were found, the user is alerted and the script quits.
The remainder of the script does the bulk of the work in two subrouties defined in the PyAgeModule.py module and described in the next two sections
# Collect data for the generation ages report
def CollectAges(famList):
global numHusbAge,sumHusbAge,numFathAge,sumFathAge
global numWifeAge,sumWifeAge,numMothAge,sumMothAge
global gdoc
# initialize counters
numHusbAge=sumHusbAge=numFathAge=sumFathAge=0
numWifeAge=sumWifeAge=numMothAge=sumMothAge=0
# progress reporting interval
fractionStepSize=nextFraction=0.01
numFams=len(famList)
for (i,fam) in enumerate(famList):
# read family record information
husbRef = fam.husband()
wifeRef = fam.wife()
chilList = fam.children()
mdate = fam.marriageSDN()
# read parent birthdates
hbdate = wbdate = 0
if husbRef != "":
hbdate = husbRef.birthSDN()
if wifeRef != "":
wbdate = wifeRef.birthSDN()
# spouse ages at marriage
if mdate>0:
if hbdate>0:
sumHusbAge = sumHusbAge + GetAgeSpan(hbdate,mdate)
numHusbAge = numHusbAge+1
if wbdate>0:
sumWifeAge = sumWifeAge + GetAgeSpan(wbdate,mdate)
numWifeAge = numWifeAge+1
# spouse ages when children were born
if hbdate > 0 or wbdate > 0:
for chilRef in chilList:
cbdate = chilRef.birthSDN()
if cbdate > 0 and hbdate > 0:
sumFathAge = sumFathAge + GetAgeSpan(hbdate,cbdate)
numFathAge = numFathAge + 1
if cbdate > 0 and wbdate > 0:
sumMothAge = sumMothAge + GetAgeSpan(wbdate,cbdate)
numMothAge = numMothAge + 1
# time for progress
fractionDone = float(i+1)/float(numFams)
if fractionDone > nextFraction:
ProgressMessage(fractionDone)
nextFraction = nextFraction+fractionStepSize
This subroutine collects all data on ages from the choosen family records in the file. It is where most of the work of this script is done; the work is done by interaction with your data through GEDitCOM II's scripting objects and their properties.
The first section defines and initializes global variables to accumulate age information. The for loop enumerates over all family records passed to this subroutine. The loop starts by reading data from the family record — namely references to the husband and wife records, a list of all children records, and the marriage date. The marriage date, like all dates in this script, is read as a serial day number.
The next section reads the parents' birth dates (if available). If marriage date is known, the age of each spouse when married is calculated and added to global variables. The calculations use the GetAgeSpan() module method.
The age at child birth section is similar. It contains a loop over all children in the family. For each child, it looks for their birth date. If a birth date is found, the ages of each parent with a known birth date are added to global variables. This entire section is enclosed in a conditional that says to do these calculations only if at least one parent birthdate is known.
Scripts that process many records, especially in large files, might take while to finish. It is good practice to provide feedback of script progress to the user. Here the variable fractionStepSize defines how often to notify the user and nextFraction is when the next notification occurs. The progress section at the end of the loop calculates current fraction done and when needed, notifies user of progress using the ProgressMessage() module nmthod.
When this subroutine is done, the global variables (e.g., numHusbAge, sumHusbAge, etc.) will contain all data needed to output the report. The subroutine ends and returns control to the main script. The next section explains formatting of the output report.
# Write the results now using html elements
def WriteToReport(sname,fname):
# begin report with <h1> for title
rpt = ScriptOutput(sname,"html")
rpt.out("<h1>Generational Age Analysis in " + fname + "</h1>\n")
# start table, add caption and header, stary body
rpt.out(MakeTable("begin","caption",\
"Summary of spouse ages when married and when children were born"))
rpt.out(MakeTable("head",["Age Item","Husband","Wife"],"body"))
# rows for ages when married and when children were borm
InsertRow("Avg. Age at Marriage", numHusbAge, sumHusbAge,\
numWifeAge, sumWifeAge)
InsertRow("Avg. Age at Childbirth", numFathAge, sumFathAge,\
numMothAge, sumMothAge)
# end the <tbody> and <table> elements
rpt.out(MakeTable("endbody","end"))
# write out the report
rpt.write()
# Insert table row with husband and wife results
def InsertRow(rowLabel, numHusb, sumHusb, numWife, sumWife):
tr = [rowLabel]
if numHusb > 0:
tr.append("{0:.2f}".format(sumHusb / numHusb))
else:
tr.append("-")
if numWife > 0:
tr.append("{0:.2f}".format(sumWife / numWife))
else:
tr.append("-")
rpt.out(MakeTable("row l r r",tr))
A good way to format reports in sripts is to use html elements. The process is made easier by using ScriptOutput class in the GEDitCOM II module. This subroutine starts by creating a ScriptOutput object in rpt. This subroutine then uses methods defined in module for the ScriptOutput class to add text with html elements to the report.
This report creates a small table. The MakeTable() module method helps create html elements for a table. The table begins with a caption and a header row. The InsertRow() is called twice to insert to rows for age at marriage and age at child birth. When inserting numbers, a Python method is used to round the results to two digits.
When the subroutine is done, the rpt.write() method creates a report record and displays it in a GEDitCOM II window. To see the result of this subroutine, run the "Python Custom Report" option of this extension. When the report is done, use the "View HTML Source" menu command to see the html content. The content added in this subroutine is enclosed within a div element.