Genealogy Introduction Comment book Family Album Christmas 1998 Cats Our Cats Neighbor Cats Boldt Introduction Album Mecklenburg Names index Moll Introduction Moll Reunion 2003 MollFest 2000 Album Group Photos Famous relatives Names index Genealogy Computing Introduction Genealogy & Perl Perl Gedcom Objects Modules Programs 1 Programs 2 |
Computing - Object Oriented DesignObject-oriented design is a technique where data in a program is represented by a collection of objects that have some relationship to the reality of the data being modelled. For example, in a payroll application, each employee would be represented by an object. An employee object contains data that is pertinent to the employee, such as employee number, date of employment, position, department number, manager, and salary. Normally, in an object-oriented design, the data is not accessed directly, but is read and changed using procedure calls, or methods. To carry the example further, let's consider a special type of employee, the manager. A manager is an employee, but with additional characteristics, such as a list of employees reporting to her. In an object-oriented design, there are two basic relationships between various objects: the "IsA" and "HasA" relationships. In this example, a manager is an employee and an employee has an employee number, date of employment, position, department number, manager, and salary. In a nutshell, object-oriented design is determining the objects in an application and their "IsA" and "HasA" relationships with each other. Definitions
GenealogyObject-oriented design is an obvious choice for genealogy. But then again, with a bit of thought, most application domains can fall nicely into an object-oriented design. Here are some of the obvious objects in a genealogy application:
The key trick in object-oriented design is defining the objects and the "HasA" and "IsA" relationships between them. What do all of the objects above have in common? Well, for one thing, they are all records in a gedcom file. We'll cheat here and define our objects using the structure of gedcom files. In other words, one genealogy object can be modelled by one gedcom record. So, the base class for gedcom records can be defined as containing the following elements of a gedcom record:
All of the Gedcom record types can be considered subclasses of gedcom record. In other words, an individual is a gedcom record, a family is a gedcom record, an event is a gedcom record, etc. We can take this deeper: Births, deaths, and marriages are events. The "HasA" relationships are modelled by the list of subsidiary gedcom records. For example, an individual has a name, a birth family, spouse families, and events, such as birth, baptism, and death. A family has a husband, a wife, zero or more children, and events, such as marriage and divorce. Object-Oriented Perl ExampleI won't describe fully how to do object-oriented programming in
Perl, since there are better documents at the Perl Institute. But to briefly
summarize, an object in Perl is defined by a Here's an example of a Perl program that uses object-oriented programming. This program reads a gedcom file and creates the objects that represent the data, and then writes out the data to another file. In a real application, there would be additional processing
between these two steps of reading and writing the gedcom file. In
addition, a real object-oriented application would contain
subclasses derived from
#!/usr/bin/perl -w
# Get names of input and output files
($infile, $outfile, @rest) = @ARGV;
if (!$infile || $infile eq '')
{
die "No filenames are specified";
}
# Create gedcom object
$ged = Gedcom->new();
$ged->read($infile);
# Note: Insert additional processing here!
# Write out gedcom file
$ged->write($outfile);
exit;
########################################################################
# package Gedcom -- Gedcom object. #
#----------------------------------------------------------------------#
# Methods: #
# #
# new() - create new gedcom file object #
# data() - return gedcom records in this object #
# read(filename) - read gedcom records from specified file #
# write(filename) - write gedcom records to the specified file #
########################################################################
package Gedcom;
sub new
{
my $that = shift;
my $class = ref($that) || $that;
$self = { };
$self->{'INDEX'} = { };
bless $self, $class;
return $self;
}
sub data
{
my $self = shift;
if (@_) { $self->{'DATA'} = shift }
return $self->{'DATA'};
}
sub read
{
my $self = shift;
my $filename = shift;
my ($node, $label, $level, $tag, $text);
my $lastlevel = -1;
my $newged = GedcomRecord->new('_BASE_');
my $lastitem = $newged;
my $parentitem;
# open file for input
open INFILE, $filename or die "Can't open file $filename";
# read records of file
while (<INFILE>)
{
# Get rid of CR/LF chars
chomp;
s/[\cM\cJ]//;
# Parse record
$label = '';
/^(\d+)\s+(@\S+@)?\s*(\S+)\s*(.*)/;
$level = $1;
$label = $2 if ($2);
$tag = uc($3);
$text = $4;
# Where do we put this record?
if ($level > $lastlevel)
{
$parentitem = $lastitem;
}
else
{
$i = $lastlevel;
$parentitem = $lastitem->parent;
while ($i != $level)
{
$parentitem = $parentitem->parent;
$i--;
}
}
if ($tag eq 'CONT')
{
$lastitem->text ($lastitem->text . "\n" . $text);
}
elsif ($tag eq 'CONC')
{
$lastitem->text ($lastitem->text . $text);
}
else
{
# create new gedcom record
$node = GedcomRecord->new($tag,$text,$label);
# Link record into current
$parentitem->additem ($node);
# Setup for next record
$lastitem = $node;
$lastlevel = $level;
}
# Add top level item to index
if ($level == 0
&& $label
&& $label ne '')
{
${$self->{'INDEX'}}{$label} = $lastitem;
}
}
# Set node in gedcom object
$self->{'DATA'} = $newged;
# Close input file
close INFILE;
}
sub write
{
my $self = shift;
my $filename = shift;
my $level = 0;
# open file for input
open OUTFILE, ">$filename" or die "Can't open file $filename";
# Loop through items of base item
$base = $self->{'DATA'};
foreach $rec (@{$base->{'ITEMS'}})
{
$rec->print (\*OUTFILE, 0);
}
# Close file
close OUTFILE;
}
########################################################################
# package GedcomRecord -- Gedcom record object. #
#----------------------------------------------------------------------#
# Methods: #
# #
# new(tagname, text, label) - create new gedcom record #
# tag(tagname) - get or set tag name in gedcom record #
# text(text) - get or set text #
# label(label) - get or set label #
# parent() - get parent of this record #
# additem(gedcom-record) - add a gedcom record to this record #
# print() - print this record and subsidiary records #
# record() - return string representing this record #
########################################################################
package GedcomRecord;
sub new
{
my $that = shift;
my $class = ref($that) || $that;
$self = { };
$self->{'ITEMS'} = [];
# Initialize object based on parameters to constructors
if (@_) { $self->{'TAG'} = shift }
if (@_) { $self->{'TEXT'} = shift }
if (@_) { $self->{'LABEL'} = shift }
# bless object
bless $self, $class;
return $self;
}
sub tag
{
my $self = shift;
if (@_) { $self->{'TAG'} = shift }
return $self->{'TAG'};
}
sub label
{
my $self = shift;
if (@_) { $self->{'LABEL'} = shift }
return $self->{'LABEL'};
}
sub text
{
my $self = shift;
if (@_) { $self->{'TEXT'} = shift }
return $self->{'TEXT'};
}
sub parent
{
my $self = shift;
if (@_) { $self->{'PARENT'} = shift }
return $self->{'PARENT'};
}
sub additem
{
my $self = shift;
my $item = shift;
push @{$self->{'ITEMS'}}, $item;
$item->parent($self);
}
sub print
{
my $self = shift;
my $file = shift;
my $lvl = shift;
my $str = $self->record;
my $tag = $self->{'TAG'};
if ($tag eq 'NOTE'
|| $tag eq 'AUTH'
|| $tag eq 'TITL'
|| $tag eq 'PUBL'
|| $tag eq 'TEXT'
|| $tag eq 'ADDR'
|| $tag eq 'SOUR')
{
# We need to split records at newlines and long lines
# (Note: splitting long lines into CONC records is not
# yet implemented)
@reclist = split /\n/, $str;
print $file $lvl . ' ' . shift(@reclist) . "\n";
foreach $rec (@reclist)
{
print $file $lvl+1 . ' CONT ' . $rec . "\n";
}
}
else
{
print $file $lvl . ' ' . $self->record . "\n";
}
# Print out sub-items:
foreach $rec (@{$self->{'ITEMS'}})
{
$rec->print ($file, $lvl+1);
}
}
sub record
{
my $self = shift;
my $str = '';
if ($self->{'LABEL'})
{
$str .= $self->{'LABEL'} . ' ';
}
$str .= $self->{'TAG'} . ' ' . $self->{'TEXT'};
return $str;
}
|