home *** CD-ROM | disk | FTP | other *** search
/ Acorn User 10 / AU_CD10.iso / Updates / Perl / Perl_Libs / site_perl / HTML / Element.pm < prev    next >
Text File  |  1997-12-02  |  12KB  |  604 lines

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