package XML::Generator::FromDTD;
use strict;
use SGML::DTD;
sub makepackage {
# all of this symbolic referencing makes me nervous. it's like disabling a wood chipper's saftey.
no strict;
*{"$_[0]::new"}=
sub {
my $proto = shift;
my $class = ref($proto) || $proto;
my $self = {};
bless ($self, $class);
return $self;
} ;
}
sub sub_maker {
my $s =shift;
my $attrlist = join " ",@{+shift};
sub {
my $self = shift;
my %attrs;
my @attrs;
%attrs = %{+shift}if ref $_[0] eq "HASH" ;
foreach my $thisattr(keys %attrs){
# leave it in, so...
warn "no attribute $thisattr\n" unless $attrlist=~/$thisattr/;
# is there text?
if ($attrs{$thisattr}) {
# if so, make foo="bar",
push @attrs,"$thisattr=\"". $attrs{$thisattr} ."\""
} else {
# otherwise, just add an attribute foo
push @attrs,"$thisattr"
}
}
# assemble attribute text
my $attrtext = join (" ",@attrs);
$attrtext= " ".$attrtext if $attrtext;
# return an empty tag if there's no text to go with it.
return "<$s$attrtext/>" if (@_ == 0);
# if there's an arrayref, make a tag for each element
# yes, anything after $_[0] gets ignored.
if (ref ($_[0]) eq "ARRAY") {
return join "", map {"<$s$attrtext>$_$s"} @{ $_[0] };
}
# otherwise, return the whole length of @_ inside a tag.
return "<$s$attrtext>@_$s";
}
}
sub createFromDTD {
my ($fn, $packagename) = @_;
my $dtd = new SGML::DTD;
my %elements;
open FH, $fn ||die $!;
$dtd->read_dtd(\*FH) || die $!;
# now that the file's been read and parsed by SGML::DTD object,
# begin the real work
makepackage ($packagename);
{
no strict "refs";
foreach ($dtd->get_elements()){
# read in the attributes of each element
my %hr = $dtd->get_elem_attr($_);
my @attributes;
my $hr;
@attributes = keys %hr if (defined (%hr));
# make a new sub in the user specified package
# named after the current element
*{"$packagename:".":$_"} =sub_maker ($_, $dtd->get_elem_attr, \@attributes);
# create a list of attributes for later use
# this hash is also used in the 'methods' method, below
$elements {$_}=\@attributes;
}
*{"$packagename:".":_methods"} = sub {return keys %{$_[0]->{_elements}} };
*{"$packagename:".":_attributes"} = sub {
my $self=$_[0];
print "got $_[1]\n";
return @{$self->{_elements}{$_[1]}}
};
}
# finish off the newly created package
my $tmp = new $packagename;
$$tmp{_elements} = \%elements;
return $tmp;
}
return 1;
####
ELEMENT cookbook (recipe+)
ELEMENT recipe (head?, (ingredientList|procedure|para)*)
ATTLIST recipe serves CDATA #IMPLIED
ELEMENT head (#PCDATA)
ELEMENT ingredientlist (ingredient+)
ELEMENT ingredient (#PCDATA|food|quantity)*
ELEMENT procedure (step+)
ELEMENT food (#PCDATA)
ATTLIST food
type (veg|prot|fat|sugar|flavour|unspec) "unspec"
calories (high|medium|low|none|unknown) "unknown"
ELEMENT quantity EMPTY
ATTLIST quantity value CDATA #REQUIRED
units CDATA #IMPLIED
exact (Y|N) "N"
ELEMENT para (#PCDATA|food)*
ELEMENT step (#PCDATA|food)*
####
$r=XML::Generator::FromDTD::createFromDTD ("cookbook.dtd","cookbook");
print $r->cookbook (
$r->recipe({serves=>"one"},
$r->head("Breakfast burrito"),
$r->ingredientlist (
$r->ingredient (
$r->food ({type=>"unspec", calories=>"unknown"},
[ "tortilla", "egg","hash browns","cheese","bacon"]),
)
)
)
)
####
Breakfast burrito
tortilla
egg
hash browns
cheese
bacon