/usr/local/CPAN/YATT/YATT/LRXML/NodeCursor.pm
# -*- mode: perl; coding: utf-8 -*-
package YATT::LRXML::NodeCursor; # Location, Zipper?
use strict;
use warnings FATAL => qw(all);
use base qw(YATT::Class::Configurable);
use YATT::Fields qw(^tree ^cf_metainfo cf_path);
sub Path () {'YATT::LRXML::NodeCursor::Path'}
use YATT::Util::Symbol;
use YATT::LRXML::Node qw(stringify_node
stringify_attlist
create_node
create_node_from
copy_array);
use Carp;
# XXX: Configurable ã« init 㨠clone ã®ãããã³ã«ãâ¦ã£ã¦ã
# fields ã®ä¸èº«ã«ä¾åããããããã°ããã
BEGIN {
package YATT::LRXML::NodeCursor::Path;
use base qw(YATT::Class::ArrayScanner);
use YATT::Fields qw(cf_path cur_postype prev_postype);
use YATT::LRXML::Node qw(node_type ATTRIBUTE_TYPE);
use YATT::Util::Enum -prefix => 'POSTYPE_', qw(UNKNOWN ATTLIST BODY);
sub init {
my ($self, $array, $path, $index0) = splice @_, 0, 4;
$self->SUPER::init(array => $array
, index => ($index0 || 0)
+ YATT::LRXML::Node::_BODY
, path => $path, @_)
->after_next;
}
sub clone {
my MY $orig = shift;
ref($orig)->new($orig->{cf_array}, $orig->{cf_path}
# XXX: To compensate init()
, $orig->{cf_index} - YATT::LRXML::Node::_BODY);
}
sub parent {
my MY $path = shift; $path->{cf_path}
}
sub after_next {
(my MY $path) = @_;
return $path unless defined $path->{cf_index}
and $path->{cf_index} <= $#{$path->{cf_array}};
my $val = $path->{cf_array}->[$path->{cf_index}];
$path->{prev_postype} = $path->{cur_postype};
if (not defined $path->{cur_postype}
or $path->{cur_postype} == POSTYPE_ATTLIST) {
$path->{cur_postype} = ref $val && node_type($val) == ATTRIBUTE_TYPE
? POSTYPE_ATTLIST : POSTYPE_BODY;
}
$path
}
sub is_beginning {
(my MY $path) = @_;
return 1 unless defined $path->{prev_postype};
return unless $path->{cur_postype} == POSTYPE_BODY;
$path->{prev_postype} == POSTYPE_ATTLIST;
}
}
sub initargs {qw(tree)}
sub new_opened {
my ($class, $tree) = splice @_, 0, 2;
$class->new($tree, path => $class->Path->new($tree), @_);
}
sub new_path {
my MY $self = shift;
$self->Path->new($self->{tree}, shift); # XXX: tree ã§ããã®?
}
sub clone_path {
my MY $self = shift;
my Path $path = shift || $self->{cf_path};
$self->Path->new($path->{cf_array}, $path ? $path->{cf_path} : undef);
}
sub clone {
(my MY $self, my ($path)) = @_;
# XXX: ä»ã®ãã©ã¡ã¼ã¿ã¯? ç¹ã«ãç¶æ¿å
ã§è¶³ãããã©ã¡ã¼ã¿ã
ref($self)->new($self->{tree}
, metainfo => $self->{cf_metainfo}
, path => ($path || ($self->{cf_path} ? $self->{cf_path}->clone
: undef)));
}
sub variant_builder {
my MY $self = shift;
my Path $orig = $self->{cf_path};
my $variant = do {
if (@_) {
$self->create_node(@_);
} else {
$self->create_node_from($orig->{cf_array});
}
};
$self->adopter_for($variant, $orig->{cf_path});
}
sub adopter_for {
(my MY $self, my ($array, $path)) = @_;
$self->clone($self->Path->new($array, $path || $self->{cf_path}))
}
sub add_node {
my MY $self = shift;
my Path $path = $self->{cf_path};
push @{$path->{cf_array}}, @_;
$self;
}
sub create_attribute {
(my MY $self, my ($name)) = splice @_, 0, 2;
$self->create_node([attribute => 0], $name, @_);
}
sub add_attribute {
(my MY $self, my ($name)) = splice @_, 0, 2;
$self->add_node(my $attr = $self->create_node([attribute => 0], $name, @_));
$attr;
}
sub add_filtered_copy {
(my MY $self, my ($node, $filter, $primary_only)) = @_;
my $boundary = $primary_only ? 'is_primary_attribute' : 'readable';
for (; $node->$boundary(); $node->next) {
my @node = do {
if ($node->is_attribute) {
my ($sub, @rest) = ref $filter eq 'ARRAY' ? @$filter : $filter;
$sub->(@rest, $node->node_name, $node->current);
} else {
copy_array($node->current);
}
};
$self->add_node(@node) if @node;
}
$self;
}
sub copy_from {
(my MY $clone, my MY $orig) = @_;
for (my $n = $orig->clone; $n->readable; $n->next) {
$clone->add_node(copy_array($n->current));
}
$clone;
}
sub clone_filtered_by {
my MY $orig = shift;
# XXX: $orig ã next ãã¦ãã¾ã£ã¦ãè¯ãã®ãï¼ clone ããæ¹ãè¯ããã?
my MY $clone = $orig->variant_builder;
my ($hash, $all) = @_;
my $boundary = $all ? 'readable' : 'is_primary_attribute';
for (; $orig->$boundary(); $orig->next) {
my @name;
if ($orig->is_attribute and @name = $orig->node_path
and $hash->{$name[0]}) {
${$hash->{$name[0]}} = $orig->current;
next;
}
$clone->add_node(copy_array($orig->current));
}
$clone;
}
sub copy {
(my MY $self, my ($node)) = @_;
copy_array($node);
}
sub copy_renamed {
(my MY $self, my ($name, $node)) = @_;
if (defined $name) {
$self->create_node_from
($node, $name, copy_array(YATT::LRXML::Node::node_children($node)));
} else {
copy_array($node);
}
}
sub make_wrapped {
(my MY $self, my ($type, $name)) = splice @_, 0, 3;
my Path $orig = $self->{cf_path};
my $wrap = $self->create_node($type || 'unknown', $name, $orig->{cf_array});
my $path = $self->Path->new($wrap, $orig);
ref($self)->new($self->{tree}
, metainfo => $self->{cf_metainfo}
, path => $path);
}
sub filter_or_add_from {
(my MY $self, my ($node, $except, %opts)) = @_;
my $boundary = delete $opts{primary_only}
? 'is_primary_attribute' : 'readable';
croak "Invalid option: " . join(",", keys %opts) if %opts;
my ($name, @filtered);
for (; $node->$boundary(); $node->next) {
if ($node->is_attribute
and defined ($name = $node->node_name)
and exists $except->{$name}) {
# clone ã¯ï¼
# name ãæ¸ãæãã¦ãè¯ãã®ã§ã¯ï¼
my $cur = $node->current;
push @filtered, do {
if (defined $except->{$name}) {
$self->copy_renamed($cur, $except->{$name});
} else {
$cur
}
};
} else {
$self->add_node($node->current);
}
}
@filtered;
}
sub open {
my MY $self = shift;
my $obj;
unless (defined (my Path $path = $self->{cf_path})) {
$self->clone($self->new_path);
} elsif (not defined ($obj = $path->{cf_array}->[$path->{cf_index}])
or ref $obj ne 'ARRAY') {
$obj;
} else {
# æ¬å½ã« clone ãè¯ãã®ã ããã?
$self->clone($self->Path->new($obj, $path));
}
}
# cursor æ¬ä½ã§ã¯ãªããpath ã ããæ¬²ããã¨ãã®ããã«ã
# â open ãã«ã¹ã¿ãã¤ãºãããæã«ç¨ããã
sub open_path {
my MY $self = shift;
unless (defined (my Path $path = $self->{cf_path})) {
$self->new_path;
} else {
my $obj = $path->{cf_array}->[$path->{cf_index}];
die "Not an object!" unless defined $obj && ref $obj eq 'ARRAY';
$self->Path->new($obj, $path);
}
}
sub can_open {
my MY $self = shift;
my Path $path = $self->{cf_path};
my $obj = $path->{cf_array}->[$path->{cf_index}];
defined $obj && ref $obj eq 'ARRAY';
}
sub close {
my MY $self = shift;
if (my Path $parent = $self->{cf_path}->parent) {
$parent->{cf_index}++;
$self->clone($parent);
} else {
return
}
}
sub parent {
my MY $self = shift;
$self->clone($self->{cf_path}->parent);
}
sub can_close {
my MY $self = shift;
defined $self->{cf_path};
}
BEGIN {
my @delegate_to_path =
qw(read
current
next
prev
array
);
foreach my $meth (@delegate_to_path) {
*{globref(__PACKAGE__, $meth)} = sub {
my MY $self = shift;
return unless defined $self->{cf_path};
$self->{cf_path}->$meth(@_);
};
}
my @delegate_and_self = qw(go_next);
foreach my $meth (@delegate_and_self) {
*{globref(__PACKAGE__, $meth)} = sub {
my MY $self = shift;
return unless defined $self->{cf_path};
$self->{cf_path}->$meth(@_);
$self;
};
}
foreach my $meth (grep {/^(node|is)_/} YATT::LRXML::Node->exports) {
my $for_text = do {no strict 'refs'; \&{"text_$meth"}};
my $sub = YATT::LRXML::Node->can($meth);
*{globref(__PACKAGE__, $meth)} = sub {
my MY $cursor = shift;
return unless $cursor->readable;
if (ref(my $value = $cursor->current)) {
$sub->($value, @_);
} else {
$for_text->($value, @_);
}
};
}
foreach my $meth (my @delegate_to_meta = qw(filename)) {
*{globref(__PACKAGE__, $meth)} = sub {
my MY $cursor = shift;
defined (my $meta = $cursor->{cf_metainfo})
or return;
$meta->$meth(@_);
};
}
}
sub rewind {
my MY $self = shift;
if (my Path $path = $self->{cf_path}) {
$path->{cf_index} = YATT::LRXML::Node::_BODY;
}
$self
}
sub readable {
my MY $self = shift;
defined $self->{cf_path} && $self->{cf_path}->readable;
}
# value, size ã¯å
¨ä½ã
sub value {
my MY $self = shift;
unless (defined $self->{cf_path}) {
$self->{tree}
} else {
$self->{cf_path}->value;
}
}
sub array_size {
my MY $self = shift;
YATT::LRXML::Node::node_size(do {
unless (defined (my Path $path = $self->{cf_path})) {
$self->{tree};
} else {
$path->{cf_array};
}
});
}
sub size {
my MY $self = shift;
unless (defined (my Path $path = $self->{cf_path})) {
YATT::LRXML::Node::node_size($self->{tree});
} elsif (not defined (my $obj = $path->{cf_array}->[$path->{cf_index}])) {
0
} elsif (ref $obj) {
YATT::LRXML::Node::node_size($obj);
} else {
1;
}
}
sub has_parent {
my MY $self = shift;
defined (my Path $path = $self->{cf_path}) or return 0;
$path->{cf_path}
}
sub depth {
my MY $self = shift;
my $depth = 0;
while (defined (my Path $path = $self->{cf_path})) {
$depth++;
}
$depth;
}
sub startline {
my MY $self = shift;
$self->metainfo->cget('startline');
}
sub linenum {
(my MY $self, my ($offset_atstart)) = @_;
my $linenum = $self->startline;
my Path $path = $self->{cf_path};
my $offset = $offset_atstart;
while ($path) {
$linenum += $self->count_lines_of(map {
$path->{cf_array}[$_]
} YATT::LRXML::Node::_BODY .. $path->{cf_index} - 1 + ($offset || 0));
$path = $path->{cf_path};
undef $offset;
}
$linenum;
}
sub count_lines_of {
# XXX: ä»ã§ã使ãããã«ã
my ($pack) = shift;
my $sum = 0;
foreach my $item (@_) {
next unless defined $item;
$sum += do {
if (ref $item) {
YATT::LRXML::Node::node_nlines($item);
} else {
$item =~ tr:\n::;
}
};
}
$sum;
}
sub node_is_beginning {
my MY $self = shift;
my Path $path = $self->{cf_path} or return;
$path->is_beginning;
}
sub node_is_end {
my MY $self = shift;
my Path $path = $self->{cf_path} or return;
defined $path->{cf_index} or return;
$path->{cf_index} >= $#{$path->{cf_array}};
}
*stringify = *stringify_current; *stringify = *stringify_current;
sub stringify_current {
my MY $self = shift;
my Path $path = $self->{cf_path};
unless (defined $path) {
stringify_node($self->{tree});
} elsif (ref (my $value = $path->current)) {
stringify_node($value);
} else {
$value;
}
}
sub stringify_all {
my MY $self = shift;
my Path $path = $self->{cf_path};
unless (defined $path) {
stringify_node($self->{tree});
} else {
stringify_node($path->{cf_array});
}
}
sub path_list {
my MY $self = shift;
my @path;
if (my Path $path = $self->{cf_path}) {
# XXX: ä¸ãããã¦ãããããã¨ãå¼ãã®?
do {
unshift @path, $path->{cf_index} - YATT::LRXML::Node::_BODY;
$path = $path->{cf_path};
} while $path;
}
wantarray ? @path : join ", ", @path;
}
sub parse_typespec {
my MY $self = shift;
my ($head, @rest) = $self->node_children;
unless (defined $head) {
()
} elsif ($head =~ s{^(\w+((?:\:\w+)*))?(?:([|/?!])(.*))?}{}s) {
# $1 can undef.
($1 && $2 ? [split /:/, $1] : $1
, default => @rest ? [defined $4 ? ($4) : (), @rest] : $4
, default_mode => $3)
} else {
(undef);
}
}
sub next_is_body {
my MY $self = shift;
my Path $path = $self->{cf_path} or return;
my $next = $path->{cf_index} + 1;
return if $next >= @{$path->{cf_array}};
my $item = $path->{cf_array}[$next];
return unless defined $item;
return 1 unless ref $item;
not YATT::LRXML::Node::is_primary_attribute($item);
}
sub text_is_attribute { 0 }
sub text_is_bare_attribute { 0 }
sub text_is_primary_attribute { 0 }
sub text_is_quoted_by_element { 0 }
sub text_node_size { 1 }
sub text_node_type { YATT::LRXML::Node::TEXT_TYPE }
sub text_node_body { shift }
sub text_node_type_name { 'text' }
sub text_node_flag { 0 }
sub text_node_name { undef }
sub text_node_children {
if (ref $_[0]) {
YATT::LRXML::Node::node_children($_[0])
} else {
$_[0];
}
}
1;