A Custom Report Script in AppleScript

The AppleScript for this report in the "CreateReportTutorial.gplug" extension. The main script is in the "Generation Ages To Report.applescript". To run this tutorial report, select "Custom Report Tutorial" from the Extensions menu and then choose the "AppleScript Custom Report" option. To view the script source, expand "Properties" section of the extension, click on "other files" and then double click on "Generation Ages To Report.applescript". The AppleScript will open in Script Editor for viewing and editing.

Main Script

Listing 1
-- Key properties and variables
property scriptName : "Generational Ages"
global numHusbAge, numWifeAge, numFathAge, numMothAge
global sumHusbAge, sumWifeAge, sumFathAge, sumMothAge

-- Verify OK to run this script
if CheckAvailable(scriptName,2.0) is false then return

-- Choose all or currently selected family records
tell application "GEDitCOM II"
  set whichOnes to user option title Â
    "Get report for All or just Selected family records" Â
    buttons {"All", "Cancel", "Selected"}
  if whichOnes is "Cancel" then return

  -- Get a list of the chosen family records
  if whichOnes is "All" then
    set fams to every family of front document
  else
    set selRecs to selected records of front document
    set fams to {}
    repeat with selRec in selRecs
      if record type of selRec is "FAM" then
        set end of fams to selRec
      end if
    end repeat
  end if
end tell

-- Exit now if no family records were chosen
if (count of fams) is 0 then
  return "No family records were selected"
end if

-- Collect all report data in a subroutine
CollectAges(fams)

-- Write to report and then done
WriteToReport()

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 defines a AppleScipt property and global variables used by this script. Next, a CheckAvalable() utility method is called to verify a document is open the current version being reun is sufficient to run this script. Although GEDitCOM II does not have an AppleScripting module (as in Python), you can copy and paste any of the utility methods on geditcom.com or write your own methods and copy them to multiple AppleScripts,.

If CheckAvailable() returns false, the script cannot run and it exits. It it returns true, the next section targets the GEDitCOM II application to choose and find the records used for the report. The choosing is done with the "User Option" command, Once the choice is made, the records to be analyzed are loaded into a list in fams. The process for "selected records" is harder in AppleScript then in Internal Scripts or in Python scripts. If no family records are found, the script exits.

The remainder of the main script does the bulk of the work in two subrouties defined in the next two sections


CollectAges() Subroutine

Listing 2
(* Collect data for the generational ages report *)
on CollectAges(famList)
  -- initialize variables
  set {numHusbAge, sumHusbAge, numFathAge, sumFathAge} to {0, 0, 0, 0}
  set {numWifeAge, sumWifeAge, numMothAge, sumMothAge} to {0, 0, 0, 0}
  set {fractionStepSize, nextFraction} to {0.01, 0.01}
  set numFams to number of items in famList
  
  tell application "GEDitCOM II"
    repeat with i from 1 to numFams
      -- read family record information
      tell item i of famList
        set {husbRef, wifeRef, chilList} to {husband, wife, children}
        set mdate to marriage SDN
      end tell
			
      -- read parent birthdates
      set {hbdate, wbdate} to {0, 0}
      if husbRef is not "" then
        set hbdate to birth SDN of husbRef
      end if
      if wifeRef is not "" then
        set wbdate to birth SDN of wifeRef
      end if
			
      -- spouse ages at marriage
      if mdate > 0 and hbdate > 0 then
        set husbAge to my GetAgeSpan(hbdate, mdate)
        set {numHusbAge, sumHusbAge} to {numHusbAge+1, sumHusbAge+husbAge}
      end if
      if mdate > 0 and wbdate > 0 then
        set wifeAge to my GetAgeSpan(wbdate, mdate)
        set {numWifeAge, sumWifeAge} to {numWifeAge+1, sumWifeAge+wifeAge}
      end if
			
      -- spouse ages when children were born
      if hbdate > 0 or wbdate > 0 then
        repeat with chilRef in chilList
          set cbdate to birth SDN of chilRef
          if cbdate > 0 and hbdate > 0 then
            set fathAge to my GetAgeSpan(hbdate, cbdate)
            set {numFathAge, sumFathAge} to {numFathAge + 1, Â
                sumFathAge + fathAge}
          end if
          if cbdate > 0 and wbdate > 0 then
            set mothAge to my GetAgeSpan(wbdate, cbdate)
            set {numMothAge, sumMothAge} to {numMothAge + 1, Â
                sumMothAge + mothAge}
          end if
        end repeat
      end if
			
      -- time for progress
      set fractionDone to i / numFams
      if fractionDone > nextFraction then
        notify progress fraction fractionDone
        set nextFraction to nextFraction + fractionStepSize
      end if
    end repeat
  end tell
	
end CollectAges

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 repeat 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. In AppleScript, one has to be careful about targeting of commands. The two options are to embed commands within a "tell" block. For example, the tell item i of famList is target the next family record in famList. The commands in that blick get data from that record. The other option is to include the object in the command. For example:

	set hbdate to birth SDN of husbRef

gets birth serial day number of the husband record.

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() utility 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 fracture done and when needed, notifies user of progress using the notify progress scripting command.

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.


WriteToReport() Subroutine

Listing 3
(* Write the results not in the global variables to a
     GEDitCOM II report *)
on WriteToReport()	
  -- build report using <html> elements beginning with <div>
  set rpt to {"<div>" & return}
	
  -- begin report with <h1> for title
  set fname to name of front document of application "GEDitCOM II"
  set end of rpt to "<h1>Generational Age Analysis in " & Â
      fname & "</h1>" & return
	
  -- start <table> and give it a caption
  set end of rpt to "<table>" & return
  set end of rpt to "<caption>" & return
  set end of rpt to "Summary of spouse ages when married " Â
      & "and when children were born" & return
  set end of rpt to "</caption>" & return
	
  -- column labels in the <thead> section
  set end of rpt to "<thead><tr>" & return
  set end of rpt to "<th>Age Item</th><th>Husband</th><th>Wife</th>" & return
  set end of rpt to "</tr></thead>" & return
	
  -- the rows are in the <tbody> element
  set end of rpt to "<tbody>" & return
	
  -- rows for ages when married and when children were borm
  set end of rpt to InsertRow("Avg. Age at Marriage", numHusbAge, Â
      sumHusbAge, numWifeAge, sumWifeAge)
  set end of rpt to InsertRow("Avg. Age at Childbirth", numFathAge, Â
      sumFathAge, numMothAge, sumMothAge)
	
  -- end the <tbody> and <table> elements
  set end of rpt to "</tbody>" & return
  set end of rpt to "</table>" & return
	
  -- end the report by ending <div> element
  set end of rpt to "</div>"
	
  -- create a report and open it in a browser window
  tell front document of application "GEDitCOM II"
    set newreport to make new report with properties {name:"Generation Ages", Â
        body:rpt as string}
    show browser of newreport
 end tell
	
end WriteToReport

(* Insert table row with husband and wife results *)
on InsertRow(rowLabel, numHusb, sumHusb, numWife, sumWife)
  set tr to "<tr><td>" & rowLabel & "</td><td align='"
  if numHusb > 0 then
    set tr to tr & "right'>" & RoundNum(sumHusb / numHusb, 2)
  else
    set tr to tr & "center'>-"
  end if
  set tr to tr & "</td><td align='"
  if numWife > 0 then
    set tr to tr & "right'>" & RoundNum(sumWife / numWife, 2)
  else
    set tr to tr & "center'>-"
  end if
  set tr to tr & "</td></tr>" & return
  return tr
end InsertRow

A good way to format reports in sripts is to use html elements enclosed within a div element. This subroutine builds the report in the rpt list that begins with a single element and the starting div element and then appends a second time for the report header. This subroutine then mainly use adds items to the rpt list. The list is converted to a string at the end.

This report creates a small table using standard html elements for a table. The table begins with a caption and a header row. The InsertRow() subroutine is called twice to insert to rows for age at marriage and age at child birth. When inserting numbers, RoundNum() utility method is used to round the results to two digits.

When the subroutine is done, a new report object is created in the target document and the body of that report is set to the rpt list cast as a string. The show browser command is used to open the report in a GEDitCOM II window. To see the results of this subroutine, run the "AppleScript Custom Report" option of this extension. When the report is done, use the "View HTML Source" menu command to see the html content.