4 # SAX version of XML::Template. Advantages over DOM: Doesn't have to load
5 # the entire thing into memory, and you can chain filters. Disadvantages:
6 # Slightly kludgier interface.
8 # Differences from the DOM version:
10 # - There is no process(). Instead, it works as a SAX filter, so you put it
11 # in the stream, usually between a parser and a writer (ie.
12 # parser -> XML::TemplateSAX::Handler -> writer). process_file works as
13 # before, but it returns a _string_, not a DOM tree.
14 # - You can no longer insert a DOM tree. Instead, what you have is -- FIXME:
22 package XML::TemplateSAX::Handler;
23 use base qw(XML::SAX::Base);
30 obj => $options{'Content'},
32 Handler => $options{'Handler'}
39 my ($self, $data) = @_;
40 my $obj = $self->{'obj'};
43 my $id = $data->{'Attributes'}->{'{http://template.sesse.net/}id'};
44 $id = $id->{'Value'} if (defined($id));
46 # within a replacement; just ignore everything
47 return if (!defined($obj));
49 # substitution: see if this element matches anything. if so,
50 # descend down into the tree.
51 if (ref($obj) eq 'HASH') {
53 for my $key (keys %$obj) {
54 if ($key =~ /^#(.*)$/) {
55 if (defined($id) && $id eq $1) {
56 $match = $obj->{$key};
60 if ($data->{'LocalName'} eq $key) {
61 $match = $obj->{$key};
67 if (defined($match)) {
68 $self->SUPER::start_element($data);
70 push @{$self->{'stack'}}, [ $data->{'Name'}, $obj ];
73 # This is sort of ugly. We special-case replacement by outputting
74 # the string immediately, and then just ignoring the rest of the
75 # events until we get to the right end tag. It's not 100% technically
76 # correct for the case where you replace an entire document by a
77 # string, but that's nonsensical anyway.
80 $self->SUPER::characters({ Data => $match });
84 $self->{'obj'} = $match;
89 $self->SUPER::start_element($data);
93 my ($self, $data) = @_;
94 return if (!defined($self->{'obj'}));
95 $self->SUPER::characters($data);
99 my ($self, $data) = @_;
101 my $stack = $self->{'stack'};
102 if (scalar @$stack > 0) {
103 my $top = $stack->[$#stack];
105 if ($data->{'Name'} eq $top->[0]) {
106 $self->SUPER::end_element($data);
107 $self->{'obj'} = $top->[1];
113 return if (!defined($self->{'obj'}));
115 $self->SUPER::end_element($data);
118 package XML::TemplateSAX::Cleaner;
119 use base qw(XML::SAX::Base);
122 my ($self, $data) = @_;
123 my $attrs = $data->{'Attributes'};
125 for my $a (keys %$attrs) {
126 if ($attrs->{$a}->{'NamespaceURI'} eq 'http://template.sesse.net/') {
131 $self->SUPER::start_element($data);
134 package XML::TemplateSAX;
137 my ($filename, $obj, $clean) = @_;
138 $clean = 1 unless (defined($clean));
140 my ($writer, $cleaner, $filter, $parser);
143 # FIXME: hardcoding expat = not good?
144 $writer = XML::SAX::Writer->new(Output => \$str);
147 $cleaner = XML::TemplateSAX::Cleaner->new(Handler => $writer, Content => $obj);
148 $filter = XML::TemplateSAX::Handler->new(Handler => $cleaner, Content => $obj);
150 $filter = XML::TemplateSAX::Handler->new(Handler => $writer, Content => $obj);
153 $parser = XML::SAX::Expat->new(Handler => $filter);
154 $parser->parse_file($filename);