#
# SAX version of XML::Template. Advantages over DOM: Doesn't have to load
# the entire thing into memory, and you can chain filters. Disadvantages:
-# Slightly kludgier interface.
+# Slightly kludgier interface, and you won't get a DOM tree out.
#
# Differences from the DOM version:
#
# in the stream, usually between a parser and a writer (ie.
# parser -> XML::TemplateSAX::Handler -> writer). process_file works as
# before, but it returns a _string_, not a DOM tree.
-# - You can no longer insert a DOM tree. Instead, what you have is -- FIXME:
-# figure out this :-)
+# - You can no longer insert a DOM tree, naturally. Instead, you can set up
+# an XML::TemplateSAX::Buffer, let it gobble up your data, and send it
+# in the way you'd insert a DOM tree. process_file_to_buffer does this
+# transparently for you, returning a buffer you can give in. (In theory,
+# one could avoid the buffering and just defer the parsing/filtering until
+# it's needed, but Expat seems non-reentrant, which means starting a parser
+# from within a begin_element callback blows up.)
#
+use strict;
+use warnings;
use XML::SAX::Expat;
use XML::SAX::Writer;
-use Data::Dumper;
-
-package XML::TemplateSAX::Handler;
-use base qw(XML::SAX::Base);
-
-sub new {
- my $class = shift;
- my %options = @_;
-
- my $self = {
- obj => $options{'Content'},
- stack => [],
- Handler => $options{'Handler'}
- };
- bless($self, $class);
- return $self;
-}
+use XML::TemplateSAX::Buffer;
+use XML::TemplateSAX::Cleaner;
+use XML::TemplateSAX::Handler;
-sub start_element {
- my ($self, $data) = @_;
- my $obj = $self->{'obj'};
-
- # within a replacement; just ignore everything
- return if (!defined($obj));
-
- # substitution: see if this element matches anything. if so,
- # descend down into the tree.
- if (ref($obj) eq 'HASH') {
- my $match = undef;
- for my $key (keys %$obj) {
- if ($key =~ /^#(.*)$/) {
- if ($data->{'NamespaceURI'} eq 'http://template.sesse.net/' && $data->{'LocalName'} eq $1) {
- $match = $obj->{$key};
- last;
- }
- } else {
- if ($data->{'LocalName'} eq $key) {
- $match = $obj->{$key};
- last;
- }
- }
- }
+package XML::TemplateSAX;
- if (defined($match)) {
- $self->SUPER::start_element($data);
-
- push @{$self->{'stack'}}, [ $data->{'Name'}, $obj ];
-
- #
- # This is sort of ugly. We special-case replacement by outputting
- # the string immediately, and then just ignoring the rest of the
- # events until we get to the right end tag. It's not 100% technically
- # correct for the case where you replace an entire document by a
- # string, but that's nonsensical anyway.
- #
- if (!ref($match)) {
- $self->SUPER::characters({ Data => $match });
- $match = undef;
- }
-
- $self->{'obj'} = $match;
- return;
- }
- }
-
- $self->SUPER::start_element($data);
-}
+sub process_file_to_handler {
+ my ($filename, $handler, $obj, $clean) = @_;
+ $clean = 1 unless (defined($clean));
-sub characters {
- my ($self, $data) = @_;
- return if (!defined($self->{'obj'}));
- $self->SUPER::characters($data);
-}
+ my ($cleaner, $filter, $parser);
+ my $str = '';
-sub end_element {
- my ($self, $data) = @_;
-
- my $stack = $self->{'stack'};
- if (scalar @$stack > 0) {
- my $top = $stack->[$#stack];
-
- if ($data->{'Name'} eq $top->[0]) {
- $self->SUPER::end_element($data);
- $self->{'obj'} = $top->[1];
- pop @$stack;
- return;
- }
+ if ($clean) {
+ $cleaner = XML::TemplateSAX::Cleaner->new(Handler => $handler);
+ $filter = XML::TemplateSAX::Handler->new(Handler => $cleaner, Content => $obj);
+ } else {
+ $filter = XML::TemplateSAX::Handler->new(Handler => $handler, Content => $obj);
}
-
- return if (!defined($self->{'obj'}));
- $self->SUPER::end_element($data);
-}
+ # FIXME: hardcoding expat = not good?
+ $parser = XML::SAX::Expat->new(Handler => $filter);
+ $parser->parse_file($filename);
-package XML::TemplateSAX;
+ return $str;
+}
sub process_file {
my ($filename, $obj, $clean) = @_;
my $str = '';
-
- # FIXME: hardcoding expat = not good?
my $writer = XML::SAX::Writer->new(Output => \$str);
- my $filter = XML::TemplateSAX::Handler->new(Handler => $writer, Content => $obj);
- my $parser = XML::SAX::Expat->new(Handler => $filter);
- $parser->parse_file($filename);
+
+ process_file_to_handler($filename, $writer, $obj, $clean);
return $str;
}
+sub process_file_to_buffer {
+ my ($filename, $obj, $clean) = @_;
+
+ my $buffer = XML::TemplateSAX::Buffer->new;
+ process_file_to_handler($filename, $buffer, $obj, $clean);
+
+ return $buffer;
+}
+
+sub alternate {
+ my ($tag, $array, @elems) = @_;
+
+ my $i = 0;
+ my $num = scalar @elems;
+
+ for my $ref (@$array) {
+ if (defined($ref)) {
+ $ref->{$tag} = $elems[$i++ % $num];
+ }
+ }
+
+ return $array;
+}
+
1;