⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 ch10_02.htm

📁 Perl & XML. by Erik T. Ray and Jason McIntosh ISBN 0-596-00205-X First Edition, published April
💻 HTM
字号:
<html><head><title>Subclassing (Perl and XML)</title><link rel="stylesheet" type="text/css" href="../style/style1.css" /><meta name="DC.Creator" content="Erik T. Ray and Jason McIntosh" /><meta name="DC.Format" content="text/xml" scheme="MIME" /><meta name="DC.Language" content="en-US" /><meta name="DC.Publisher" content="O'Reilly &amp; Associates, Inc." /><meta name="DC.Source" scheme="ISBN" content="059600205XL" /><meta name="DC.Subject.Keyword" content="stuff" /><meta name="DC.Title" content="Perl and XML" /><meta name="DC.Type" content="Text.Monograph" /></head><body bgcolor="#ffffff"><img alt="Book Home" border="0" src="gifs/smbanner.gif" usemap="#banner-map" /><map name="banner-map"><area shape="rect" coords="1,-2,616,66" href="index.htm" alt="Perl &amp; XML" /><area shape="rect" coords="629,-11,726,25" href="jobjects/fsearch.htm" alt="Search this book" /></map><div class="navbar"><table width="684" border="0"><tr><td align="left" valign="top" width="228"><a href="ch10_01.htm"><img alt="Previous" border="0" src="../gifs/txtpreva.gif" /></a></td><td align="center" valign="top" width="228" /><td align="right" valign="top" width="228"><a href="ch10_03.htm"><img alt="Next" border="0" src="../gifs/txtnexta.gif" /></a></td></tr></table></div><h2 class="sect1">10.2. Subclassing</h2><p>When<a name="INDEX-775" />writing XML-hacking Perl modules, another path to laziness involvesstanding on (and reading over) the shoulders of giants by subclassinggeneral XML parsers as a quick way to build application-specificmodules.</p><p>You don't have to use object inheritance; the leastcomplicated way to accomplish this sort of thing involvesconstructing a parser object in the usual way, sticking it somewhereconvenient, and turning around whenever you want to do somethingXMLy. Here is some bogus code for you:</p><blockquote><pre class="code">package XML::MyThingy;use strict; use warnings;use XML::SomeSortOfParser;sub new {  # Ye Olde Constructor  my $invocant = shift;  my $self = {};  if (ref($invocant)) {    bless ($self, ref($invocant));  } else {    bless ($self, $invocant);  }  # Now we make an XML parser...  my $parser = XML::SomeSortOfParser-&gt;new       or die "Oh no, I couldn't make an XML parser. How very sad.";  # ...and stick it on this object, for later reference.  $self-&gt;{xml} = $parser;  return $self;}sub parse_file {  # We'll just pass on the user's request to our parser object (which  # just happens to have a method named parse_file)...  my $self = shift;  my $result = $self-&gt;{xml}-&gt;parse_file;  # What happens now depends on whatever a XML::SomeSortOfParser  # object does when it parses a file. Let's say it modifies itself and  # returns a success code, so we'll just keep hold of the now-modified  # object under this object's 'xml' key, and return the code.  return $result;}</pre></blockquote><p>Choosing to subclass a parser has some bonuses, though. First, itgives your module the same basic user API as the module in question,including all the methods for parsing, which can be quite lazilyuseful -- especially if the module you're writingis an XML application helper module. Second, ifyou're using a tree-based parser, you cansteal -- er, I mean embrace and extend -- thatparser's data structure representation of the parseddocument and then twist it to better serve your own nefarious goalwhile doing as little extra work as possible. This step is possiblethrough the magic of Perl's class blessing andinheritance functionality.</p><a name="perlxml-CHP-10-SECT-2.1" /><div class="sect2"><h3 class="sect2">10.2.1. Subclassing Example: XML::ComicsML </h3><p>For this example, we're going to set our notionalMonkeyML aside in favor of the grim reality of ComicsML, a markuplanguage for describing online comics.<a href="#FOOTNOTE-39">[39]</a> It shares a lot of features and philosophies with RSS,providing, among other things, a standard way for comics to shareweb-syndication information, so a ComicsML helper module might be aboon for any Perl hacker who wishes to write programs that work withsyndicated web comics.</p><blockquote class="footnote"> <a name="FOOTNOTE-39" /><p>[39]See<a href="http://comicsml.jmac.org/">http://comicsml.jmac.org/</a>.</p></blockquote><p>We will go down a DOMmish path for this example and pull<tt class="literal">XML::LibXML</tt> down as our internal mechanism ofchoice, since it's (mostly) DOM compliant and is afast parser. Our goal is to create a fully object-oriented API formanipulating ComicsML documents and all the major child elementswithin them:</p><blockquote><pre class="code">use XML::ComicsML;# parse an existing ComicsML filemy $parser = XML::ComicsML::Parser-&gt;new;my $comic = $parser-&gt;parsefile('my_comic.xml');my $title = $comic-&gt;title;print "The title of this comic is $title\n";my @strips = $comic-&gt;strips;print "It has ".scalar(@strips)." strips associated with it.\n";</pre></blockquote><p>Without further ado, let's start coding. </p><blockquote><pre class="code">package XML::ComicsML;# A helper module for parsing and generating ComicsML documents.use XML::LibXML;use base qw(XML::LibXML);# PARSING# We catch the output of all XML::LibXML parsing methods in our hot# little hands, then proceed to rebless selected nodes into our own# little claseessub parse_file {  # Parse as usual, but then rebless the root element and return it.  my $self = shift;  my $doc = $self-&gt;SUPER::parse_file(@_);  my $root = $doc-&gt;documentElement;  return $self-&gt;rebless($root);}sub parse_string {  # Parse as usual, but then rebless the root element and return it.  my $self = shift;  my $doc = $self-&gt;SUPER::parse_string(@_);  my $root = $doc-&gt;documentElement;  return $self-&gt;rebless($root);}</pre></blockquote><p>What exactly are we doing, here? So far, we declared the package tobe a child of <tt class="literal">XML::LibXML</tt> (by way of the<tt class="literal">use base</tt> pragma), but then we write our ownversions of its three parsing methods. All do the same thing, though:they call <tt class="literal">XML::LibXML</tt>'s ownmethod of the same name, capture the root element of the returneddocument tree object, and then pass it to these internal methods:</p><blockquote><pre class="code">sub rebless {  # Accept  some kind of XML::LibXML::Node (or a subclass  # thereof) and, based on its name, rebless it into one of  # our ComicsML classes.  my $self = shift;  my ($node) = @_;  # Define a has of interesting element types. (hash for easier searching.)  my %interesting_elements = (comic=&gt;1,                              person=&gt;1,                              panel=&gt;1,                              panel-desc=&gt;1,                              line=&gt;1,                              strip=&gt;1,                             );  # Toss back this node unless it's an Element, and Interesting. Else,  # carry on.    my $name = $node-&gt;getName;    return $node unless ( (ref($node) eq 'XML::LibXML::Element')         and (exists($interesting_elements{$name})) );        # It is an interesting element! Figure out what class it gets, and rebless it.    my $class_name = $self-&gt;element2class($name);    bless ($node, $class_name);  return $node;}sub element2class {  # Munge an XML element name into something resembling a class name.  my $self = shift;  my ($class_name) = @_;  $class_name = ucfirst($class_name);  $class_name =~ s/-(.?)/uc($1)/e;  $class_name = "XML::ComicsML::$class_name";}</pre></blockquote><p>The <tt class="literal">rebless</tt> method takes an element node, peeks atits name, and sees if it appears on a hardcoded list it has of"interesting" element names. If itappears on the list, it chooses a class name for it (with the help ofthat silly <tt class="literal">element2class</tt> method) and reblesses itinto that class.</p><p>This behavior may seem irrational until you consider the fact that<tt class="literal">XML::LibXML</tt> objects are not very persistent, dueto the way they are bound with the low-level, C-based structuresunderneath the Perly exterior. If I get a list of objectsrepresenting some node's children, and then ask forthe list again later, I might not get the same Perl objects, thoughthey'll both work (being APIs to the same structureson the C library-produced tree). This lack of persistence prevents usfrom, say, crawling the whole tree as soon as the document is parsed,blessing the "interesting" elementsinto our own ComicsML-specific classes, and calling it done.</p><p>To get around this behavior, we do a little dirty work, quietlyturning the <tt class="literal">Element</tt> objects that<tt class="literal">XML::LibXML</tt> hands us into our own kinds ofobjects, where applicable. The main advantage of this, beyond theegomaniacal glee of putting our own (class) name on someoneelse's work, is the fact that these reblessedobjects are now subject to having some methods of our own designcalled on them. Now we can finally define these classes.</p><p>First, we will taunt you by way of the <tt class="literal">AUTOLOAD</tt>method that exists in <tt class="literal">XML::ComicsML::Element</tt>, avirtual base class from which our"real" element classes all inherit.This glop of code lords it over all our elementclasses' basic child-element and attributeaccessors; when called due to the invocation of an undefined method(as all <tt class="literal">AUTOLOAD</tt> methods answer to), it firstchecks to see if the method exists in that class'shardcoded list of legal child elements and attributes (availablethrough the <tt class="literal">element()</tt> and<tt class="literal">attribute()</tt> methods, respectively); failing that,if the method had a name like <tt class="literal">add_foo</tt> or<tt class="literal">remove_foo</tt>, it enters either constructor ordestructor mode:</p><blockquote><pre class="code">package XML::ComicsML::Element;# This is an abstract class for all ComicsML Node types.use base qw(XML::LibXML::Element);use vars qw($AUTOLOAD @elements @attributes);sub AUTOLOAD {  my $self = shift;  my $name = $AUTOLOAD;  $name =~ s/^.*::(.*)$/$1/;  my @elements = $self-&gt;elements;  my @attributes = $self-&gt;attributes;  if (grep (/^$name$/, @elements)) {    # This is an element accessor.    if (my $new_value = $_[0]) {      # Set a value, overwriting that of any current element of this type.      my $new_node = XML::LibXML::Element-&gt;new($name);      my $new_text = XML::LibXML::Text-&gt;new($new_value);      $new_node-&gt;appendChild($new_text);      my @kids = $new_node-&gt;childNodes;      if (my ($existing_node) = $self-&gt;findnodes("./$name")) {        $self-&gt;replaceChild($new_node, $existing_node);      } else {        $self-&gt;appendChild($new_node);      }    }    # Return the named child's value.    if (my ($existing_node) = $self-&gt;findnodes("./$name")) {      return $existing_node-&gt;firstChild-&gt;getData;    } else {      return '';    }  } elsif (grep (/^$name$/, @attributes)) {    # This is an attribute accessor.    if (my $new_value = $_[0]) {      # Set a value for this attribute.      $self-&gt;setAttribute($name, $new_value);    }    # Return the names attribute's value.    return $self-&gt;getAttribute($name) || '';    # These next two could use some error-checking.  } elsif ($name =~ /^add_(.*)/) {    my $class_to_add = XML::ComicsML-&gt;element2class($1);    my $object = $class_to_add-&gt;new;    $self-&gt;appendChild($object);    return $object;  } elsif ($name =~ /^remove_(.*)/) {    my ($kid) = @_;    $self-&gt;removeChild($kid);    return $kid;  }}# Stubs         sub elements {  return ();}sub attributes {  return ();}package XML::ComicsML::Comic;use base qw(XML::ComicsML::Element);sub elements {  return qw(version title icon description url);}sub new {  my $class = shift;  return $class-&gt;SUPER::new('comic');}sub strips {  # Return a list of all strip objects that are children of this comic.  my $self = shift;  return map {XML::ComicsML-&gt;rebless($_)}  $self-&gt;findnodes("./strip");}sub get_strip {  # Given an ID, fetch a strip with that 'id' attribute.  my $self = shift;  my ($id) = @_;  unless ($id) {    warn "get_strip needs a strip id as an argument!";    return;  }  my (@strips) = $self-&gt;findnodes("./strip[attribute::id='$id']");  if (@strips &gt; 1) {    warn "Uh oh, there is more than one strip with an id of $id.\n";  }  return XML::ComicsML-&gt;rebless($strips[0]);}</pre></blockquote><p>Many more element classes exist in the real-life version ofComicsML -- ones that deal with people, strips within a comic,panels within a strip, and so on. Later in this chapter,we'll take what we've written hereand apply<a name="INDEX-776" /> it to an actual problem.</p></div><hr width="684" align="left" /><div class="navbar"><table width="684" border="0"><tr><td align="left" valign="top" width="228"><a href="ch10_01.htm"><img alt="Previous" border="0" src="../gifs/txtpreva.gif" /></a></td><td align="center" valign="top" width="228"><a href="index.htm"><img alt="Home" border="0" src="../gifs/txthome.gif" /></a></td><td align="right" valign="top" width="228"><a href="ch10_03.htm"><img alt="Next" border="0" src="../gifs/txtnexta.gif" /></a></td></tr><tr><td align="left" valign="top" width="228">10. Coding Strategies</td><td align="center" valign="top" width="228"><a href="index/index.htm"><img alt="Book Index" border="0" src="../gifs/index.gif" /></a></td><td align="right" valign="top" width="228">10.3. Converting XML to HTML with XSLT </td></tr></table></div><hr width="684" align="left" /><img alt="Library Navigation Links" border="0" src="../gifs/navbar.gif" usemap="#library-map" /><p><p><font size="-1"><a href="copyrght.htm">Copyright &copy; 2002</a> O'Reilly &amp; Associates. All rights reserved.</font></p><map name="library-map"><area shape="rect" coords="1,0,85,94" href="../index.htm"><area shape="rect" coords="86,1,178,103" href="../lwp/index.htm"><area shape="rect" coords="180,0,265,103" href="../lperl/index.htm"><area shape="rect" coords="267,0,353,105" href="../perlnut/index.htm"><area shape="rect" coords="354,1,446,115" href="../prog/index.htm"><area shape="rect" coords="448,0,526,132" href="../tk/index.htm"><area shape="rect" coords="528,1,615,119" href="../cookbook/index.htm"><area shape="rect" coords="617,0,690,135" href="../pxml/index.htm"></map></body></html>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -