home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / perl_ste.zip / HTML / Element.pm < prev    next >
Text File  |  1997-10-16  |  12KB  |  605 lines

  1. package HTML::Element;
  2.  
  3. # $Id: Element.pm,v 1.36 1997/10/16 09:08:37 aas Exp $
  4.  
  5. =head1 NAME
  6.  
  7. HTML::Element - Class for objects that represent HTML elements
  8.  
  9. =head1 SYNOPSIS
  10.  
  11.  require HTML::Element;
  12.  $a = new HTML::Element 'a', href => 'http://www.oslonett.no/';
  13.  $a->push_content("Oslonett AS");
  14.  
  15.  $tag = $a->tag;
  16.  $tag = $a->starttag;
  17.  $tag = $a->endtag;
  18.  $ref = $a->attr('href');
  19.  
  20.  $links = $a->extract_links();
  21.  
  22.  print $a->as_HTML;
  23.  
  24. =head1 DESCRIPTION
  25.  
  26. Objects of the HTML::Element class can be used to represent elements
  27. of HTML.  These objects have attributes and content.  The content is an
  28. array of text segments and other HTML::Element objects.  Thus a
  29. tree of HTML::Element objects as nodes can represent the syntax tree
  30. for a HTML document.
  31.  
  32. The following methods are available:
  33.  
  34. =cut
  35.  
  36.  
  37. use strict;
  38. use Carp ();
  39. use HTML::Entities ();
  40.  
  41. use vars qw($VERSION
  42.         %emptyElement %optionalEndTag %linkElements %boolean_attr
  43.            );
  44.  
  45. $VERSION = sprintf("%d.%02d", q$Revision: 1.36 $ =~ /(\d+)\.(\d+)/);
  46. sub Version { $VERSION; }
  47.  
  48. # Elements that does not have corresponding end tags (i.e. are empty)
  49. %emptyElement   = map { $_ => 1 } qw(base link meta isindex
  50.                          img br hr wbr
  51.                          input area param
  52.                         );
  53. %optionalEndTag = map { $_ => 1 } qw(p li dt dd option); # th tr td);
  54.  
  55. # Elements that might contain links and the name of the link attribute
  56. %linkElements =
  57. (
  58.  body   => 'background',
  59.  base   => 'href',
  60.  a      => 'href',
  61.  img    => [qw(src lowsrc usemap)],   # lowsrc is a Netscape invention
  62.  form   => 'action',
  63.  input  => 'src',
  64. 'link'  => 'href',          # need quoting since link is a perl builtin
  65.  frame  => 'src',
  66.  applet => 'codebase',
  67.  area   => 'href',
  68. );
  69.  
  70. # These attributes are normally printed without showing the "='value'".
  71. # This representation works as long as no element has more than one
  72. # attribute like this.
  73. %boolean_attr = (
  74.  area   => 'nohref',
  75.  dir    => 'compact',
  76.  dl     => 'compact',
  77.  hr     => 'noshade',
  78.  img    => 'ismap',
  79.  input  => 'checked',
  80.  menu   => 'compact',
  81.  ol     => 'compact',
  82.  option => 'selected',
  83. 'select'=> 'multiple',
  84.  td     => 'nowrap',
  85.  th     => 'nowrap',
  86.  ul     => 'compact',
  87. );
  88.  
  89. =head2 $h = HTML::Element->new('tag', 'attrname' => 'value',...)
  90.  
  91. The object constructor.  Takes a tag name as argument. Optionally,
  92. allows you to specify initial attributes at object creation time.
  93.  
  94. =cut
  95.  
  96. #
  97. # An HTML::Element is represented by blessed hash reference.  Key-names
  98. # not starting with '_' are reserved for the SGML attributes of the element.
  99. # The following special keys are used:
  100. #
  101. #    '_tag':    The tag name
  102. #    '_parent': A reference to the HTML::Element above (when forming a tree)
  103. #    '_pos':    The current position (a reference to a HTML::Element) is
  104. #               where inserts will be placed (look at the insert_element method)
  105. #
  106. # Example: <img src="gisle.jpg" alt="Gisle's photo"> is represented like this:
  107. #
  108. #  bless {
  109. #     _tag => 'img',
  110. #     src  => 'gisle.jpg',
  111. #     alt  => "Gisle's photo",
  112. #  }, HTML::Element;
  113. #
  114.  
  115. sub new
  116. {
  117.     my $class = shift;
  118.     my $tag   = shift;
  119.     Carp::croak("No tag") unless defined $tag or length $tag;
  120.     my $self  = bless { _tag => lc $tag }, $class;
  121.     my($attr, $val);
  122.     while (($attr, $val) = splice(@_, 0, 2)) {
  123.     $val = $attr unless defined $val;
  124.     $self->{lc $attr} = $val;
  125.     }
  126.     if ($tag eq 'html') {
  127.     $self->{'_pos'} = undef;
  128.     }
  129.     $self;
  130. }
  131.  
  132.  
  133.  
  134. =head2 $h->tag()
  135.  
  136. Returns (optionally sets) the tag name for the element.  The tag is
  137. always converted to lower case.
  138.  
  139. =cut
  140.  
  141. sub tag
  142. {
  143.     my $self = shift;
  144.     if (@_) {
  145.     $self->{'_tag'} = lc $_[0];
  146.     } else {
  147.     $self->{'_tag'};
  148.     }
  149. }
  150.  
  151.  
  152.  
  153. =head2 $h->starttag()
  154.  
  155. Returns the complete start tag for the element.  Including leading
  156. "<", trailing ">" and attributes.
  157.  
  158. =cut
  159.  
  160. sub starttag
  161. {
  162.     my $self = shift;
  163.     my $name = $self->{'_tag'};
  164.     my $tag = "<\U$name";
  165.     for (sort keys %$self) {
  166.     next if /^_/;
  167.     my $val = $self->{$_};
  168.     if ($_ eq $val &&
  169.         exists($boolean_attr{$name}) && $boolean_attr{$name} eq $_) {
  170.         $tag .= " \U$_";
  171.     } else {
  172.         if ($val !~ /^\d+$/) {
  173.         # count number of " compared to number of '
  174.         if (($val =~ tr/\"/\"/) > ($val =~ tr/\'/\'/)) {
  175.             # use single quotes around the attribute value
  176.             HTML::Entities::encode_entities($val, "&'>");
  177.             $val = qq('$val');
  178.         } else {
  179.             HTML::Entities::encode_entities($val, '&">');
  180.             $val = qq{"$val"};
  181.         }
  182.         }
  183.         $tag .= qq{ \U$_\E=$val};
  184.     }
  185.     }
  186.     "$tag>";
  187. }
  188.  
  189.  
  190.  
  191. =head2 $h->endtag()
  192.  
  193. Returns the complete end tag.  Includes leading "</" and the trailing
  194. ">".
  195.  
  196. =cut
  197.  
  198. sub endtag
  199. {
  200.     "</\U$_[0]->{'_tag'}>";
  201. }
  202.  
  203.  
  204.  
  205. =head2 $h->parent([$newparent])
  206.  
  207. Returns (optionally sets) the parent for this element.
  208.  
  209. =cut
  210.  
  211. sub parent
  212. {
  213.     my $self = shift;
  214.     if (@_) {
  215.     $self->{'_parent'} = $_[0];
  216.     } else {
  217.     $self->{'_parent'};
  218.     }
  219. }
  220.  
  221.  
  222.  
  223. =head2 $h->implicit([$bool])
  224.  
  225. Returns (optionally sets) the implicit attribute.  This attribute is
  226. used to indicate that the element was not originally present in the
  227. source, but was inserted in order to conform to HTML strucure.
  228.  
  229. =cut
  230.  
  231. sub implicit
  232. {
  233.     shift->attr('_implicit', @_);
  234. }
  235.  
  236.  
  237.  
  238. =head2 $h->is_inside('tag',...)
  239.  
  240. Returns true if this tag is contained inside one of the specified tags.
  241.  
  242. =cut
  243.  
  244. sub is_inside
  245. {
  246.     my $self = shift;
  247.     my $p = $self;
  248.     while (defined $p) {
  249.     my $ptag = $p->{'_tag'};
  250.     for (@_) {
  251.         return 1 if $ptag eq $_;
  252.     }
  253.     $p = $p->{'_parent'};
  254.     }
  255.     0;
  256. }
  257.  
  258.  
  259.  
  260. =head2 $h->pos()
  261.  
  262. Returns (and optionally sets) the current position.  The position is a
  263. reference to a HTML::Element object that is part of the tree that has
  264. the current object as root.  This restriction is not enforced when
  265. setting pos(), but unpredictable things will happen if this is not
  266. true.
  267.  
  268.  
  269. =cut
  270.  
  271. sub pos
  272. {
  273.     my $self = shift;
  274.     my $pos = $self->{'_pos'};
  275.     if (@_) {
  276.     $self->{'_pos'} = $_[0];
  277.     }
  278.     return $pos if defined($pos);
  279.     $self;
  280. }
  281.  
  282.  
  283.  
  284. =head2 $h->attr('attr', [$value])
  285.  
  286. Returns (and optionally sets) the value of some attribute.
  287.  
  288. =cut
  289.  
  290. sub attr
  291. {
  292.     my $self = shift;
  293.     my $attr = lc shift;
  294.     my $old = $self->{$attr};
  295.     if (@_) {
  296.     $self->{$attr} = $_[0];
  297.     }
  298.     $old;
  299. }
  300.  
  301.  
  302.  
  303. =head2 $h->content()
  304.  
  305. Returns the content of this element.  The content is represented as a
  306. reference to an array of text segments and references to other
  307. HTML::Element objects.
  308.  
  309. =cut
  310.  
  311. sub content
  312. {
  313.     shift->{'_content'};
  314. }
  315.  
  316.  
  317.  
  318. =head2 $h->is_empty()
  319.  
  320. Returns true if there is no content.
  321.  
  322. =cut
  323.  
  324. sub is_empty
  325. {
  326.     my $self = shift;
  327.     !exists($self->{'_content'}) || !@{$self->{'_content'}};
  328. }
  329.  
  330.  
  331.  
  332. =head2 $h->insert_element($element, $implicit)
  333.  
  334. Inserts a new element at current position and updates pos() to point
  335. to the inserted element.  Returns $element.
  336.  
  337. =cut
  338.  
  339. sub insert_element
  340. {
  341.     my($self, $tag, $implicit) = @_;
  342.     my $e;
  343.     if (ref $tag) {
  344.     $e = $tag;
  345.     $tag = $e->tag;
  346.     } else {
  347.     $e = new HTML::Element $tag;
  348.     }
  349.     $e->{'_implicit'} = 1 if $implicit;
  350.     my $pos = $self->{'_pos'};
  351.     $pos = $self unless defined $pos;
  352.     $pos->push_content($e);
  353.     unless ($emptyElement{$tag}) {
  354.     $self->{'_pos'} = $e;
  355.     $pos = $e;
  356.     }
  357.     $pos;
  358. }
  359.  
  360.  
  361. =head2 $h->push_content($element_or_text,...)
  362.  
  363. Adds to the content of the element.  The content should be a text
  364. segment (scalar) or a reference to a HTML::Element object.
  365.  
  366. =cut
  367.  
  368. sub push_content
  369. {
  370.     my $self = shift;
  371.     $self->{'_content'} = [] unless exists $self->{'_content'};
  372.     my $content = $self->{'_content'};
  373.     for (@_) {
  374.     if (ref $_) {
  375.         $_->{'_parent'} = $self;
  376.         push(@$content, $_);
  377.     } else {
  378.         # The current element is a text segment
  379.         if (@$content && !ref $content->[-1]) {
  380.         # last content element is also text segment
  381.         $content->[-1] .= $_;
  382.         } else {
  383.         push(@$content, $_);
  384.         }
  385.     }
  386.     }
  387.     $self;
  388. }
  389.  
  390.  
  391.  
  392. =head2 $h->delete_content()
  393.  
  394. Clears the content.
  395.  
  396. =cut
  397.  
  398. sub delete_content
  399. {
  400.     my $self = shift;
  401.     for (@{$self->{'_content'}}) {
  402.     $_->delete if ref $_;
  403.     }
  404.     delete $self->{'_content'};
  405.     $self;
  406. }
  407.  
  408.  
  409.  
  410. =head2 $h->delete()
  411.  
  412. Frees memory associated with the element and all children.  This is
  413. needed because perl's reference counting does not work since we use
  414. circular references.
  415.  
  416. =cut
  417. #'
  418.  
  419. sub delete
  420. {
  421.     $_[0]->delete_content;
  422.     delete $_[0]->{'_parent'};
  423.     delete $_[0]->{'_pos'};
  424.     $_[0] = undef;
  425. }
  426.  
  427.  
  428.  
  429. =head2 $h->traverse(\&callback, [$ignoretext])
  430.  
  431. Traverse the element and all of its children.  For each node visited, the
  432. callback routine is called with the node, a startflag and the depth as
  433. arguments.  If the $ignoretext parameter is true, then the callback
  434. will not be called for text content.  The flag is 1 when we enter a
  435. node and 0 when we leave the node.
  436.  
  437. If the returned value from the callback is false then we will not
  438. traverse the children.
  439.  
  440. =cut
  441.  
  442. sub traverse
  443. {
  444.     my($self, $callback, $ignoretext, $depth) = @_;
  445.     $depth ||= 0;
  446.  
  447.     if (&$callback($self, 1, $depth)) {
  448.     for (@{$self->{'_content'}}) {
  449.         if (ref $_) {
  450.         $_->traverse($callback, $ignoretext, $depth+1);
  451.         } else {
  452.         &$callback($_, 1, $depth+1) unless $ignoretext;
  453.         }
  454.     }
  455.     &$callback($self, 0, $depth) unless $emptyElement{$self->{'_tag'}};
  456.     }
  457.     $self;
  458. }
  459.  
  460.  
  461.  
  462. =head2 $h->extract_links([@wantedTypes])
  463.  
  464. Returns links found by traversing the element and all of its children.
  465. The return value is a reference to an array.  Each element of the
  466. array is an array with 2 values; the link value and a reference to the
  467. corresponding element.
  468.  
  469. You might specify that you just want to extract some types of links.
  470. For instance if you only want to extract <a href="..."> and <img
  471. src="..."> links you might code it like this:
  472.  
  473.   for (@{ $e->extract_links(qw(a img)) }) {
  474.       ($link, $linkelem) = @$_;
  475.       ...
  476.   }
  477.  
  478. =cut
  479.  
  480. sub extract_links
  481. {
  482.     my $self = shift;
  483.     my %wantType; @wantType{map { lc $_ } @_} = (1) x @_;
  484.     my $wantType = scalar(@_);
  485.     my @links;
  486.     $self->traverse(
  487.     sub {
  488.         my($self, $start, $depth) = @_;
  489.         return 1 unless $start;
  490.         my $tag = $self->{'_tag'};
  491.         return 1 if $wantType && !$wantType{$tag};
  492.         my $attr = $linkElements{$tag};
  493.         return 1 unless defined $attr;
  494.         $attr = [$attr] unless ref $attr;
  495.             for (@$attr) {
  496.            my $val = $self->attr($_);
  497.            push(@links, [$val, $self]) if defined $val;
  498.             }
  499.         1;
  500.     }, 'ignoretext');
  501.     \@links;
  502. }
  503.  
  504.  
  505.  
  506. =head2 $h->dump()
  507.  
  508. Prints the element and all its children to STDOUT.  Mainly useful for
  509. debugging.  The structure of the document is shown by indentation (no
  510. end tags).
  511.  
  512. =cut
  513.  
  514. sub dump
  515. {
  516.     my $self = shift;
  517.     my $depth = shift || 0;
  518.     print STDERR "  " x $depth;
  519.     print STDERR $self->starttag, "\n";
  520.     for (@{$self->{'_content'}}) {
  521.     if (ref $_) {
  522.         $_->dump($depth+1);
  523.     } else {
  524.         print STDERR "  " x ($depth + 1);
  525.         print STDERR qq{"$_"\n};
  526.     }
  527.     }
  528. }
  529.  
  530.  
  531.  
  532. =head2 $h->as_HTML()
  533.  
  534. Returns a string (the HTML document) that represents the element and
  535. its children.
  536.  
  537. =cut
  538.  
  539. sub as_HTML
  540. {
  541.     my $self = shift;
  542.     my @html = ();
  543.     $self->traverse(
  544.         sub {
  545.         my($node, $start, $depth) = @_;
  546.         if (ref $node) {
  547.         my $tag = $node->tag;
  548.         if ($start) {
  549.             push(@html, $node->starttag);
  550.         } elsif (not ($emptyElement{$tag} or $optionalEndTag{$tag})) {
  551.             push(@html, $node->endtag);
  552.         }
  553.         } else {
  554.         # simple text content
  555.         HTML::Entities::encode_entities($node, "<>&");
  556.         push(@html, $node);
  557.         }
  558.         }
  559.     );
  560.     join('', @html, "\n");
  561. }
  562.  
  563. sub format
  564. {
  565.     my($self, $formatter) = @_;
  566.     unless (defined $formatter) {
  567.     require HTML::FormatText;
  568.     $formatter = new HTML::FormatText;
  569.     }
  570.     $formatter->format($self);
  571. }
  572.  
  573.  
  574. 1;
  575.  
  576. __END__
  577.  
  578.  
  579. =head1 BUGS
  580.  
  581. If you want to free the memory assosiated with a tree built of
  582. HTML::Element nodes then you will have to delete it explicitly.  The
  583. reason for this is that perl currently has no proper garbage
  584. collector, but depends on reference counts in the objects.  This
  585. scheme fails because the parse tree contains circular references
  586. (parents have references to their children and children have a
  587. reference to their parent).
  588.  
  589. =head1 SEE ALSO
  590.  
  591. L<HTML::AsSubs>
  592.  
  593. =head1 COPYRIGHT
  594.  
  595. Copyright 1995,1996 Gisle Aas.  All rights reserved.
  596.  
  597. This library is free software; you can redistribute it and/or
  598. modify it under the same terms as Perl itself.
  599.  
  600. =head1 AUTHOR
  601.  
  602. Gisle Aas <aas@sn.no>
  603.  
  604. =cut
  605.