7 use Scalar::Util qw(refaddr);
9 our $VERSION = '0.23'; # VERSION
11 has document => (is => 'rw');
12 has parent => (is => 'rw');
13 has children => (is => 'rw');
15 # store the raw string (to preserve original formatting), not all elements use
16 # this, usually only more complex elements
17 has _str => (is => 'rw');
18 has _str_include_children => (is => 'rw');
20 sub children_as_string {
22 return "" unless $self->children;
23 join "", map {$_->as_string} @{$self->children};
29 if (defined $self->_str) {
31 ($self->_str_include_children ? "" : $self->children_as_string);
33 return "" . $self->children_as_string;
40 return -4 unless $self->parent && ($c = $self->parent->children);
41 my $addr = refaddr($self);
42 for (my $i=0; $i < @$c; $i++) {
43 return $i if refaddr($c->[$i]) == $addr;
51 my $sen = $self->seniority;
52 return undef unless defined($sen) && $sen > 0;
53 my $c = $self->parent->children;
60 my $sen = $self->seniority;
61 return undef unless defined($sen);
62 my $c = $self->parent->children;
63 return undef unless $sen < @$c-1;
68 my ($self, $name, $search_parent) = @_;
69 #$log->tracef("-> get_property(%s, search_par=%s)", $name, $search_parent);
70 my $p = $self->parent;
71 my $s = $p->children if $p;
75 #$log->tracef("searching in sibling: %s (%s)", $d->as_string, ref($d));
76 next unless $d->isa('Org::Element::Drawer')
77 && $d->name eq 'PROPERTIES' && $d->properties;
78 return $d->properties->{$name} if defined $d->properties->{$name};
82 if ($p && $search_parent) {
83 my $res = $p->get_property($name, 1);
84 return $res if defined $res;
87 $log->tracef("Getting property from document's .properties");
88 $self->document->properties->{$name};
92 my ($self, $code) = @_;
94 if ($self->children) {
95 $_->walk($code) for @{$self->children};
100 my ($self, $criteria) = @_;
101 return unless $self->children;
106 if (ref($criteria) eq 'CODE') {
107 push @res, $el if $criteria->($el);
108 } elsif ($criteria =~ /^\w+$/) {
109 push @res, $el if $el->isa("Org::Element::$criteria");
111 push @res, $el if $el->isa($criteria);
118 my ($self, $code) = @_;
119 my $parent = $self->parent;
121 return $parent unless $code->($self, $parent);
122 $parent = $parent->parent;
133 if ($p->isa('Org::Element::Headline')) {
145 my $prev = $self->prev_sibling;
146 if ($prev && $prev->isa('Org::Element::Text')) {
147 my $text = $prev->as_string;
148 if ($text =~ /(?:\A|\R)\s*(.+?)\s*:\s*\z/) {
152 my $parent = $self->parent;
153 if ($parent && $parent->isa('Org::Element::ListItem')) {
154 my $list = $parent->parent;
155 if ($list->type eq 'D') {
156 return $parent->desc_term->as_string;
160 #if ($parent && $parent->isa('Org::Element::Drawer') &&
161 # $parent->name eq 'PROPERTIES') {
168 my $parent = $self->parent;
169 return unless $parent;
170 splice @{$parent->children}, $self->seniority, 1;
174 # ABSTRACT: Base class for Org document elements
182 Org::Element - Base class for Org document elements
190 # Don't use directly, use the other Org::Element::* classes.
194 This is the base class for all the other Org element classes.
198 =head2 document => DOCUMENT
200 Link to document object. Elements need this to access file-wide settings,
203 =head2 parent => undef | ELEMENT
205 Link to parent element. Undef if this element is the root element.
207 =head2 children => undef | ARRAY_OF_ELEMENTS
211 =head2 $el->children_as_string() => STR
213 Return a concatenation of children's as_string(), or "" if there are no
216 =head2 $el->as_string() => STR
218 Return the string representation of element. The default implementation will
219 just use _str (if defined) concatenated with children_as_string().
221 =head2 $el->seniority => INT
223 Find out the ranking of brothers/sisters of all sibling. If we are the first
224 child of parent, return 0. If we are the second child, return 1, and so on.
226 =head2 $el->prev_sibling() => ELEMENT | undef
228 =head2 $el->next_sibling() => ELEMENT | undef
230 =head2 $el->get_property($name, $search_parent) => VALUE
232 Search for property named $name in the nearest properties drawer. If
233 $search_parent is set to true (default is false), will also search in
234 upper-level properties (useful for searching for inherited property, like
235 foo_ALL). Return undef if property cannot be found in all drawers.
237 Regardless of $search_parent setting, file-wide properties will be consulted if
238 property is not found in nearest properties drawer.
240 =head2 $el->walk(CODEREF)
242 Call CODEREF for node and all descendent nodes, depth-first. Code will be given
243 the element object as argument.
245 =head2 $el->find(CRITERIA) => ELEMENTS
247 Find subelements. CRITERIA can be a word (e.g. 'Headline' meaning of class
248 'Org::Element::Headline') or a class name ('Org::Element::ListItem') or a
249 coderef (which will be given the element to test). Will return matched elements.
251 =head2 $el->walk_parents(CODE)
253 Run CODEREF for parent, and its parent, and so on until the root element (the
254 document), or until CODEREF returns a false value. CODEREF will be supplied
255 ($el, $parent). Will return the last parent walked.
257 =head2 $el->headline() => ELEMENT
259 Get current headline.
261 =head2 $el->field_name() => STR
263 Try to extract "field name", being defined as either some text on the left side:
265 DEADLINE: <2011-06-09 >
267 or a description term in a description list:
269 - wedding anniversary :: <2011-06-10 >
273 Remove element from the tree. Basically just remove the element from its parent.
277 Steven Haryanto <stevenharyanto@gmail.com>
279 =head1 COPYRIGHT AND LICENSE
281 This software is copyright (c) 2012 by Steven Haryanto.
283 This is free software; you can redistribute it and/or modify it under
284 the same terms as the Perl 5 programming language system itself.