1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
|
package XML::Struct::Writer::Stream;
use strict;
use Moo;
our $VERSION = '0.27';
has fh => (is => 'rw', default => sub { *STDOUT });
has pretty => (is => 'rw');
our %ESCAPE = (
'&' => '&',
'<' => '<',
'>' => '>',
'"' => '"',
);
use constant {
DOCUMENT_STARTED => 0,
TAG_STARTED => 1,
CHAR_CONTENT => 2,
CHILD_ELEMENT => 3,
};
sub xml_decl {
my ($self, $data) = @_;
my $xml = "<?xml version=\"$data->{Version}\"";
$xml .= " encoding=\"$data->{Encoding}\"" if $data->{Encoding};
$xml .= " standalone=\"$data->{Standalone}\"" if $data->{Standalone};
$xml .= "?>\n";
print {$self->fh} $xml;
}
sub start_document {
my ($self) = @_;
$self->{_stack} = [];
$self->{_status} = DOCUMENT_STARTED;
}
sub start_element {
my ($self, $data) = @_;
my $tag = $data->{Name};
my $attr = $data->{Attributes};
my $xml = "<$tag";
my $status = $self->{_status} // DOCUMENT_STARTED;
if ($status == TAG_STARTED) {
print {$self->fh} '>';
if ($self->pretty) {
print {$self->fh} "\n".(' ' x (scalar @{$self->{_stack}}));
}
} elsif ($status == CHILD_ELEMENT) {
if ($self->pretty) {
print {$self->fh} "\n".(' ' x (scalar @{$self->{_stack}}));
}
} elsif ($status == CHAR_CONTENT) {
print {$self->fh} $self->{_chars};
} # else: DOCUMENT_STARTED
push @{$self->{_stack}}, $tag;
if ($attr && %$attr) {
foreach my $key (sort keys %$attr) {
my $value = $attr->{$key};
$value =~ s/([&<>"])/$ESCAPE{$1}/geo;
$xml .= " $key=\"$value\"";
}
}
$self->{_status} = TAG_STARTED;
print {$self->fh} $xml;
}
sub end_element {
my ($self) = @_;
my $tag = pop @{$self->{_stack}} or return;
if ($self->{_status} == TAG_STARTED) {
print {$self->fh} '/>';
} elsif ($self->{_status} == CHAR_CONTENT) {
print {$self->fh} $self->{_chars} . "</$tag>";
$self->{_chars} = "";
} else { # CHILD_ELEMENT
if ($self->pretty) {
print {$self->fh} "\n".(' ' x (scalar @{$self->{_stack}}));
}
print {$self->fh} "</$tag>";
}
$self->{_status} = CHILD_ELEMENT;
}
sub characters {
my ($self, $data) = @_;
my $xml = $data->{Data};
$xml =~ s/([&<>])/$ESCAPE{$1}/geo;
if ($self->{_status} == TAG_STARTED) {
print {$self->fh} '>';
$self->{_status} = CHAR_CONTENT;
$self->{_chars} = $xml;
} elsif ($self->{_status} == CHILD_ELEMENT) {
print {$self->fh} $xml;
} else {
$self->{_chars} .= $xml;
}
}
sub end_document {
my ($self) = @_;
$self->end_element while @{$self->{_stack}};
print {$self->fh} "\n";
}
1;
__END__
=head1 NAME
XML::Struct::Writer::Stream - simplified SAX handler to serialize (Micro)XML
=head1 DESCRIPTION
This class implements a simplfied SAX handler for stream-based serialization
of XML. DTDs, comments, processing instructions and similar features not part
of MicroXML are not supported.
The handler is written to reproduce the serialization of libxml.
=head1 CONFIGURATION
=over
=item fh
File handle or compatible object to write to (standard output by default).
=item pretty
Pretty-print XML if enabled.
=back
=head1 SEE ALSO
See L<XML::SAX::Writer>, L<XML::Genx::SAXWriter>, and L<XML::Handler::YAWriter>
for more elaborated SAX writers and L<XML::Writer> for a general XML writer,
not based on SAX.
=cut
|