James Edward Gray II said:
> On Nov 7, 2004, at 12:18 PM, Dennis Ranke wrote:
>
>> Here is a very simple solution. It doesn't try to understand much of
>> the contents of the .ged file, it just builds a tree of nodes and
>> dumps them to xml.
>
> I took a similar approach, parse and print. My code doesn't really
> understand GEDCOM.
Right. No real understanding, although I did implement a few enhancments
discussed on the list (see comments for details). I used my XML Builder
to generate the XML, so the approach is parse with interwoven printing.
-- BEGIN CODE --------------------------------------------------------
#!/usr/bin/env ruby
######################################################################
#
# RubyQuiz #6: Convert a GED Geneological file to XML
#
# This implements the quiz requirements with the following exceptions:
#
# 1. Nodes that have a @xxx@ reference style value put the data in a
# 'ref' attribute rather than a straight data.
# 2. Child nodes of type CONC or CONT are concatenated to the data
# value of their parent node.
#
# Test data can be found at:
# http://search.cpan.org/src/PJCJ/Gedcom-1.11/royal.ged
begin
require 'rubygems'
rescue LoadError => ex
end
require 'builder'
######################################################################
# Read a GED file line by line. Each line is broken into three
# tokens:
# * level (integer) -- Nesting level of node.
# * tag (string) -- Node tag.
# * data -- Data on line after tag (can be empty or nil).
#
class GedReader
attr_reader :level, :tag, :data, :line
# Initialize the GED reader to read the given file by name.
def initialize(file_name)
@file = open(file_name)
advance
end
# Advance the reader to the next non-blank line.
def advance
@level = -1
loop do
read_line
return if @line.nil?
break if @line !~ /^\s*$/
end
@level, @tag, @data = @line.chomp.split(/\s+/, 3)
@level = @level.to_i
end
# Return the line broken into the three components.
def info
[@level, @tag, @data]
end
# Read the next line of the GED file.
def read_line
@line = @file.gets
@file.close if @line.nil?
end
end
######################################################################
# GedParser ... Translate a GED data file in to a XML representation.
#
class GedParser
attr_reader :xml
# Initialize a GED parser.
def initialize(filename)
@reader = GedReader.new(filename)
@builder = Builder::XmlMarkup.new(:indent=>2)
@builder.gencom { parse }
@xml = @builder.target!
end
# Are there children to this node?
def children?(this_level)
@reader.level > this_level
end
# Is the data a reference key (e.g. "@xx@")?
def ref?(data)
data =~ /^@.*@$/
end
# Does the data exist? (i.e. not nil and not empty)
def exist?(data)
(! data.nil?) && (! data.empty?)
end
# Is the data continued in the next data item?
def continued?(level)
@reader.level == (level+1) && @reader.tag =~ /^(cont|conc)$/i
end
# Parse the next chunck of the GED file consisting of all GED
# elements at the current level. Children of elements of the
# current level are handled recursively. All elements parsed will
# be added to the XML builder.
def parse
level = @reader.level
while @reader.level && @reader.level == level
lev, tag, data = @reader.info
@reader.advance
# The default arguments to the XML builder will use the tag
# value as the XML tag.
xml_tag = tag.downcase
attrs = {}
# Concatendate any lower level continued data.
while data && @reader.level && continued?(level)
data << (@reader.data || "") << "\n"
@reader.advance
end
# If there are children, we will parse them in a block passed to
# the builder.
block = children?(level) ? lambda { parse } : nil
# if the tag is a @xx@ reference, then the data becomes the tag
# and the reference is passed as an 'id' attributes.
if ref?(tag)
xml_tag = data.downcase
data = nil
attrs['id'] = tag
end
# If the data is a @xx@ reference, then pass it as a 'ref'
# attribute rather than a data value.
if ref?(data)
attrs['ref'] = data
data = nil
end
# if there are children, then pass the data as a value attribute.
if children?(level)
attrs['value'] = data if exist?(data)
data = nil
end
# Construct the arguments to the XML builder and call it.
args = [xml_tag]
args << data if exist?(data)
args << attrs if exist?(attrs)
@builder.tag!(*args, &block)
end
end
end
if __FILE__ == $0 then
puts GedParser.new( ARGV[0] || 'royal.ged' ).xml
end
--
-- Jim Weirich jim / weirichhouse.org http://onestepback.org
-----------------------------------------------------------------
"Beware of bugs in the above code; I have only proved it correct,
not tried it." -- Donald Knuth (in a memo to Peter van Emde Boas)