Tuturial Index → Internal Scripting
The Internal Scripts for this report are in two scripts in the "CreateReportTutorial.gplug" extension. The main script is in the "Generation Ages IS.gcscpt" script. Subroutines needed by that script are in the "ISAgeModule.gcscpt" module. Those subroutines could be moved to the start of the main script, but having them in separate script both simplifes the main script and demonstrates Load option for Internal Scripts. To run this tutorial report, select "Custom Report Tutorial" from the Extensions menu and then choose the "Internal Script Custom Report" option.
Internal Scripting is the recommended language for sripting GEDitCOM II. It is a fairly simple language, but has some unusual syntax (driven by methods used to program the language). Much of the odd syntax is caused by Internal Scripting having distiguishable variables and scripting objects. Variables correspond to strings and numbers. Objects correspond to Internal Scripting objects defined in the Internal Scripting dictionary (see the GEDitCOM II help on the "Dictionary of Internal Scripting Objects.")
Variables can be assigned to expressions of other variables, used in conditionals, etc.; in other words that are like variables in other languages. Objects are created by other methods (often by "Create" commands or the "get" command). Once you have an object, you can read its properties or get other objects related to that object. Variable and object names are case sensitive. Although not required, Internal Scripts should start all variable names with number sign ("#"). The GEDitCOM Editor uses the number sign to color code variables in Internal Scripts. Furthermore, some Internal Scripting commands require input of a variable name and those commands may require that variable name to start in "#". Another difference in Internal Scripts is that all variables and scripting objects are global variables (if needed, this behavoir can be changed for selected subroutine calls).
You can consult the GEDitCOM Editor help for full documentation of Internal Scripting methods, variables, objects, and commands. Another way to learn Internal Scripting is to compare this sample script to the same script in another langauge. For example, many people are familiar with Python. By looking at each section below and then comparing to corresponding sections of a Python script, one can see translations of a more standard langauge into the syntax used by Internal Scripts.
! Preamble Load ISAgeModule ! Script name and version requirement #scriptName="Generation Ages to Report (IS)" PreChecks #scriptName,3.1,1,1,1 gcapp.get gdoc,frontDocument ! choose all or currently selected family records #msg="Get report for All or just Selected family records" UserOption "#whichOnes",#msg,"","All","Cancel","Selected" if #whichOnes="Cancel" exit endif ! Get of list of the choosen family records if #whichOnes="All" gdoc.get fams,"families" else gdoc.get fams,"selectedRecordsFAM" endif ! No report if no family records were found if @fams.count=0 UserOption "#var","No family records were selected","","OK" exit endif ! Collect all report data in a subroutine GoSub CollectAges ! write to report and then done GoSub 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 first loads the subroutines in "ISAgeModule.gcscpt" to the start of the main script. The preamble continues by verifying the script can run. All the work is done by the PreChecks scripting commdand (note: all scripting commands are documented in the GEDitCOM Editor help). This command checks that a document is open and if the current GEDitCOM II (or GEDitCOM) version is new enough for this script. If the checks fail, the script exits with an error message. If it works, gcapp will be set as a scripting object for the main application object. Finally, the get command sets gdoc using the FrontDocument object attribute of an application object. This structure is an example of Internal Scripting syntax for scripting objects. In effect, this command results in
gdoc = gcapp.FrontDocument.
but scripting objects cannot be assigned like regular variables. The get command achieves the same programming goal in Internal Scripts. The get command "gets" the frontDocument object in gcapp and sets scripting object gdoc to that object.
The next section chooses and finds the records used for the report. The choosing is done with the UserOption command. Once the choice is made, the records to be analyzed are loaded into a list in fams using a get command with the appropriate document object relation. If no family records were found, the user is alerted and the script quits. The syntax @fams.count starting with '@' sign fetches a string or numeric property of an object. This syntax can be used in expressions, conditionals, and with variables.
The remainder of the script does the bulk of the work in two subrouties defined in the ISAgeModule.gcscpt script and described in the next two sections
! collect data for te generation ages report Sub CollectAges ! initialize counters #numHusbAge=0 #sumHusbAge=0 #numFathAge=0 #sumFathAge=0 #numWifeAge=0 #sumWifeAge=0 #numMothAge=0 #sumMothAge=0 ! progress reporting interval #fractionStepSize=0.01 #nextFraction=0.01 Repeat "#i",0,@fams.count-1 fams.#i.get fam fam.get husb,"husband" fam.get wife,"wife" #mdate=@fam.marriageSDN ! read parent birthdates #hbdate=0 #wbdate=0 ifDef husb #hbdate=@husb.birthSDN endif ifDef wife #wbdate=@wife.birthSDN endif ! need at least one birth date if #hbdate+#wbdate=0 continue endif ! spouse ages at marriage if #mdate>0: if #hbdate>0: #sumHusbAge+=(#mdate-#hbdate)/365.25 #numHusbAge+=1 endif if #wbdate>0: #sumWifeAge+=(#mdate-#wbdate)/365.25 #numWifeAge+=1 endif endif ! spouse ages when children were born fam.get chil,"children" Repeat "#c",0,@chil.count-1 chil.#c.get child #cbdate=@child.birthSDN if #cbdate>0 if #hbdate>0: #sumFathAge+=(#cbdate-#hbdate)/365.25 #numFathAge+=1 endif if #wbdate>0: #sumMothAge+=(#cbdate-#wbdate)/365.25 #numMothAge+=1 endif endif EndRepeat ! time for progress #fractionDone=(#i+1)/@fams.count if #fractionDone>#nextFraction NotifyProgress #fractionDone #nextFraction+=#fractionStepSize endif EndRepeat EndSub
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 "gets" the next family record (here fams.#i means ith element of the fams list) and then reads family record information — namely the husband and wife objects and the marriage date. The marriage date, like all dates in this script, is read as a serial day number (SDN).
The next section reads the parents' birth dates (if available). If neither spouse has a birthdate (both SDNs are zero), no age calculations are possible for this family so the loop continues to the next family. If either has a birthdate, the loop proceeds. If marriage date is known, the age of each spouse when married is calculated and added to global variables.
The age at child birth section is similar. It "gets" of list of children in the family and then loops over each one. 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.
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 NotifyProgress command. Note that Internal Scripts run much faster than other languages meaning these progress posting might not even be visible. They will appear in very large files. Also note that the "continue" command if neither spouse has a birthdate means the progress check will not occur for that loop iteration. This issue could be resolved by reprogramming, but would not cause any problems (at worst, the progress indicator would not update enough).
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 Report and open in a window Sub WriteToReport CreateScriptOutput rpt,#scriptName,"html" ! main title rpt.out "<h1>Generational Age Analysis in "&@gdoc.name&"</h1>"&return ! start table and give it a caption rpt.out "<table>"&return&"<caption>"&return rpt.out "Summary of spouse ages when married and when children were born"&return rpt.out "</caption>"&return ! column labels in the section rpt.out "<thead><tr>"&return rpt.out "<th>Age Item</th><th>Husband</th><th>Wife</th>"&return rpt.out "</tr></thead>"&return ! the rows are in the tbody element rpt.out "<tbody>"&return ! rows for ages when married and when children were borm GoSub InsertRow "Avg. Age at Marriage",#numHusbAge,#sumHusbAge,#numWifeAge,#sumWifeAge GoSub InsertRow "Avg. Age at Childbirth",#numFathAge,#sumFathAge,#numMothAge,#sumMothAge ! end the tbody and table elements rpt.out "</tbody>"&return&"</table>" ! open in report window for this document rpt.write gdoc EndSub ! Insert a row into the table Sub InsertRow #rowLabel,#numHusb,#sumHusb,#numWife,#sumWife rpt.out "<tr><td>"rowLabel&"</td><td align='" if #numHusb>0 rpt.out "right'>"&round((#sumHusb/#numHusb)&" 2") else rpt.out "center'>-" endif rpt.out "</td><td align='" if #numWife>0 rpt.out "right'>"&round((#sumWife/#numWife)&" 2") else rpt.out "center'>-" endif rpt.out "</td></tr>"&return EndSubA good way to format reports in scripts is to use
htmlelements. The process is made easier by a customScriptOutputobject defined for Internal Scripts (and documented in the GEDitCOM Editor help). This subroutine starts by creating aScriptOutputobject inrpt(an example of a "Create" command initializing a scripting object). This subroutine then mainly userpt.outobject commands to add text to the report.This report creates a small table using standard
htmlelements for a table. The table begins with a caption and a header row. TheInsertRow()subroutine is called twice to insert two rows for age at marriage and age at child birth. When inserting numbers, around()expression in the Internal Scripting language is used to round the results to two digits.When the subroutine is done, the
rpt.writeobject command creates a report record and displays it in a GEDitCOM II window. To see the results of this subroutine, run the "Internal Script Custom Report" option of this extension. When the report is done, use the "View HTML Source" menu command to see thehtmlcontent. The content added in this subroutine is enclosed within adivelement.