Data-asXML
view release on metacpan or search on metacpan
lib/Data/asXML.pm view on Meta::CPAN
$indent--;
$self->_indent($where, $indent);
pop @{$self->{'_cur_xpath_steps'}};
}
# scalar reference
elsif (ref($what) eq 'SCALAR') {
push @{$self->{'_cur_xpath_steps'}}, $pos;
# already encoded reference
if (exists $self->{'_href_mapping'}->{$what}) {
$where = $self->_createElement('VALUE');
$where->setAttribute(
'href' =>
$self->_make_relative_xpath(
[ split(',', $self->{'_href_mapping'}->{$what}) ],
$self->{'_cur_xpath_steps'}
)
);
pop @{$self->{'_cur_xpath_steps'}};
return $where;
}
$self->{'_href_mapping'}->{$what.''} = $self->_xpath_steps_string();
$where = $self->encode($$what);
$where->setAttribute('subtype' => 'ref');
pop @{$self->{'_cur_xpath_steps'}};
}
# create text node
elsif (ref($what) eq '') {
$where = $self->_createElement('VALUE');
if (defined $what) {
# uri escape if it contains invalid XML characters
if (not XML::Char->valid($what)) {
$what = join q(), map {
(/[[:^print:]]/ or q(%) eq $_) ? uri_escape $_ : $_
} split //, $what;
$where->setAttribute('type' => 'uriEscape');
}
$where->addChild( $self->_xml->createTextNode( $what ) );
}
else {
# no better way to distinguish between empty string and undef - see http://rt.cpan.org/Public/Bug/Display.html?id=51442
$where->setAttribute('type' => 'undef');
}
}
#
else {
die 'unknown reference - '.$what;
}
# cleanup at the end
if ($indent == 0) {
$self->{'_href_mapping'} = {};
$self->{'_cur_xpath_steps'} = [];
}
# in safe_mode decode back the xml string and compare the data structures
if ($safe_mode) {
my $xml_string = $where->toString;
my $what_decoded = eval { $self->decode($xml_string) };
die 'encoding failed '.$@.' of '.eval('use Data::Dumper; Dumper([$what, $xml_string, $what_decoded])').' failed'
if not eq_deeply($what, $what_decoded);
# set back the safe mode after all was encoded
$self->safe_mode($safe_mode);
}
return $where;
}
sub _xpath_steps_string {
my $self = shift;
my $path_array = shift || $self->{'_cur_xpath_steps'};
return join(',',@{$path_array});
}
sub _make_relative_xpath {
my $self = shift;
my $orig_path = shift;
my $cur_path = shift;
# find how many elements (from beginning) the paths are sharing
my $common_root_index = 0;
while (
($common_root_index < @$orig_path)
and ($orig_path->[$common_root_index] == $cur_path->[$common_root_index])
) {
$common_root_index++;
}
# add '..' to move up the element hierarchy until the common element
my @rel_path = ();
my $i = $common_root_index+1;
while ($i < scalar @$cur_path) {
push @rel_path, '..';
$i++;
}
# add the original element path steps
$i = $common_root_index;
while ($i < scalar @$orig_path) {
push @rel_path, $orig_path->[$i];
$i++;
}
# in case of self referencing the element index is needed
if ($i == $common_root_index) {
push @rel_path, '..', $orig_path->[-1];
}
# return relative xpath
return join('/', map { $_ eq '..' ? $_ : '*['.$_.']' } @rel_path);
}
=head2 decode($xmlstring)
Takes C<$xmlstring> and converts to data structure.
=cut
sub decode {
my $self = shift;
my $xml = shift;
my $pos = shift || 1;
# in safe_mode "encode+decode" the decoded data for comparing
if ($self->safe_mode) {
$self->safe_mode(0);
my $data = $self->decode($xml, $pos);
my $data_redecoded = eval { $self->decode(
$self->encode($data)->toString,
$pos,
)};
die 'redecoding failed "'.$@.'" of '.eval('use Data::Dumper; Dumper([$xml, $data, $data_redecoded])').' failed'
if not eq_deeply($data, $data_redecoded);
$self->safe_mode(1);
return $data;
}
if (not $self->{'_cur_xpath_steps'}) {
local $self->{'_href_mapping'} = {};
local $self->{'_cur_xpath_steps'} = [];
}
my $value;
if (not blessed $xml) {
my $parser = XML::LibXML->new();
my $doc = $parser->parse_string($xml);
my $root_element = $doc->documentElement();
return $self->decode($root_element);
}
if ($xml->nodeName eq 'HASH') {
if (my $xpath_path = $xml->getAttribute('href')) {
my $href_key = $self->_href_key($xpath_path);
return $self->{'_href_mapping'}->{$href_key} || die 'invalid reference - '.$href_key.' ('.$xml->toString.')';
}
push @{$self->{'_cur_xpath_steps'}}, $pos;
my %data;
$self->{'_href_mapping'}->{$self->_xpath_steps_string()} = \%data;
my @keys =
grep { $_->nodeName eq 'KEY' }
grep { $_->nodeType eq XML_ELEMENT_NODE }
$xml->childNodes()
;
my $key_pos = 1;
foreach my $key (@keys) {
push @{$self->{'_cur_xpath_steps'}}, $key_pos;
my $key_name = $key->getAttribute('name');
my $key_value = $self->decode(grep { $_->nodeType eq XML_ELEMENT_NODE } $key->childNodes()); # is always only one
$data{$key_name} = $key_value;
pop @{$self->{'_cur_xpath_steps'}};
$key_pos++;
}
pop @{$self->{'_cur_xpath_steps'}};
return \%data;
}
elsif ($xml->nodeName eq 'ARRAY') {
if (my $xpath_path = $xml->getAttribute('href')) {
my $href_key = $self->_href_key($xpath_path);
return $self->{'_href_mapping'}->{$href_key} || die 'invalid reference - '.$href_key.' ('.$xml->toString.')';
}
push @{$self->{'_cur_xpath_steps'}}, $pos;
my @data;
$self->{'_href_mapping'}->{$self->_xpath_steps_string()} = \@data;
my $array_element_pos = 1;
@data = map { $self->decode($_, $array_element_pos++) } grep { $_->nodeType eq XML_ELEMENT_NODE } $xml->childNodes();
( run in 0.568 second using v1.01-cache-2.11-cpan-39bf76dae61 )