home *** CD-ROM | disk | FTP | other *** search
/ PC Professionell 2004 December / PCpro_2004_12.ISO / files / webserver / tsw / TSW_3.4.0.exe / Apache2 / perl / DOMConfigurator.pm < prev    next >
Encoding:
Perl POD Document  |  2003-03-14  |  20.4 KB  |  713 lines

  1. package Log::Log4perl::Config::DOMConfigurator;
  2. #todo
  3. # DONE(param-text) some params not attrs but values, like <sql>...</sql>
  4. # DONE see DEBUG!!!  below
  5. # NO, (really is only used for AsyncAppender) appender-ref in <appender>
  6. # DONE check multiple appenders in a category
  7. # DONE in Config.pm re URL loading, steal from XML::DOM
  8. # DONE, OK see PropConfigurator re importing unlog4j, eval_if_perl
  9. # NO (is specified in DTD) - need to handle 0/1, true/false?
  10. # DONEsee Config, need to check version of XML::DOM
  11. # OK user defined levels? see parse_level
  12. # OK make sure 2nd test is using log4perl constructs, not log4j
  13. # handle new filter stuff
  14. # make sure sample code actually works
  15. # try removing namespace prefixes in the xml
  16.  
  17. use XML::DOM;
  18. use Log::Log4perl::Level;
  19. use strict;
  20.  
  21. use constant DEBUG => 0;
  22.  
  23. our $VERSION = 0.03;
  24.  
  25. our $APPENDER_TAG = qr/((log4j|log4perl):)?appender/;
  26.  
  27. #can't use ValParser here because we're using namespaces? 
  28. #doesn't seem to work - kg 3/2003 
  29. our $PARSER_CLASS = 'XML::DOM::Parser';
  30.  
  31. our $LOG4J_PREFIX = 'log4j';
  32. our $LOG4PERL_PREFIX = 'log4perl';
  33.     
  34.  
  35. #poor man's export
  36. *eval_if_perl = \&Log::Log4perl::Config::eval_if_perl;
  37. *unlog4j      = \&Log::Log4perl::Config::unlog4j;
  38.  
  39.  
  40.  
  41. sub parse {
  42.     my ($text) = @_;
  43.  
  44.     my $parser = $PARSER_CLASS->new;
  45.     my $doc = $parser->parse (join('',@$text));
  46.  
  47.  
  48.     my $l4p_tree = {};
  49.     
  50.     my $config = $doc->getElementsByTagName("$LOG4J_PREFIX:configuration")->item(0)||
  51.                  $doc->getElementsByTagName("$LOG4PERL_PREFIX:configuration")->item(0);
  52.  
  53.     my $threshold = uc($config->getAttribute('threshold'));
  54.     if ($threshold) {
  55.         $l4p_tree->{threshold}{value} = $threshold;
  56.     }
  57.  
  58.     if ($config->getAttribute('oneMessagePerAppender') eq 'true') {
  59.         $l4p_tree->{oneMessagePerAppender}{value} = 1;
  60.     }
  61.  
  62.     for my $kid ($config->getChildNodes){
  63.  
  64.         next unless $kid->getNodeType == ELEMENT_NODE;
  65.  
  66.         my $tag_name = $kid->getTagName;
  67.  
  68.         if ($tag_name =~ $APPENDER_TAG) {
  69.             &parse_appender($l4p_tree, $kid);
  70.  
  71.         }elsif ($tag_name eq 'category' || $tag_name eq 'logger'){
  72.             &parse_category($l4p_tree, $kid);
  73.             #Treating them the same is not entirely accurate, 
  74.             #the dtd says 'logger' doesn't accept
  75.             #a 'class' attribute while 'category' does.
  76.             #But that's ok, log4perl doesn't do anything with that attribute
  77.  
  78.         }elsif ($tag_name eq 'root'){
  79.             &parse_root($l4p_tree, $kid);
  80.  
  81.         }elsif ($tag_name eq 'renderer'){
  82.             warn "Log4perl: ignoring renderer tag in config, unimplemented";
  83.             #"log4j will render the content of the log message according to 
  84.             # user specified criteria. For example, if you frequently need 
  85.             # to log Oranges, an object type used in your current project, 
  86.             # then you can register an OrangeRenderer that will be invoked 
  87.             # whenever an orange needs to be logged. "
  88.          
  89.         }elsif ($tag_name eq 'PatternLayout'){#log4perl only
  90.             &parse_patternlayout($l4p_tree, $kid);
  91.         }
  92.     }
  93.     $doc->dispose;
  94.  
  95.     return $l4p_tree;
  96. }
  97.  
  98. #this is just for toplevel log4perl.PatternLayout tags
  99. #holding the custome cspecs
  100. sub parse_patternlayout {
  101.     my ($l4p_tree, $node) = @_;
  102.  
  103.     my $l4p_branch = {};
  104.  
  105.     for my $child ($node->getChildNodes) {
  106.         next unless $child->getNodeType == ELEMENT_NODE;
  107.  
  108.         my $name = $child->getAttribute('name');
  109.         my $value;
  110.  
  111.         foreach my $grandkid ($child->getChildNodes){
  112.             if ($grandkid->getNodeType == TEXT_NODE) {
  113.                 $value .= $grandkid->getData;
  114.             }
  115.         }
  116.         $value =~ s/^ +//;  #just to make the unit tests pass
  117.         $value =~ s/ +$//;
  118.         $l4p_branch->{$name}{value} = $value;
  119.     }
  120.     $l4p_tree->{PatternLayout}{cspec} = $l4p_branch;
  121. }
  122.  
  123.  
  124. #for parsing the root logger, if any
  125. sub parse_root {
  126.     my ($l4p_tree, $node) = @_;
  127.  
  128.     my $l4p_branch = {};
  129.  
  130.     &parse_children_of_logger_element($l4p_branch, $node);
  131.  
  132.     $l4p_tree->{category}{value} = $l4p_branch->{value};
  133.  
  134. }
  135.  
  136.    
  137. #for parsing a category/logger element
  138. sub parse_category {
  139.     my ($l4p_tree, $node) = @_;
  140.  
  141.     my $name = $node->getAttribute('name');
  142.  
  143.     $l4p_tree->{category} ||= {};
  144.  
  145.     my $ptr = $l4p_tree->{category};
  146.  
  147.     for my $part (split /\.|::/, $name) {
  148.         $ptr->{$part} = {} unless exists $ptr->{$part};
  149.         $ptr = $ptr->{$part};
  150.     }
  151.  
  152.     my $l4p_branch = $ptr;
  153.  
  154.     my $class = $node->getAttribute('class');
  155.     $class                       && 
  156.        $class ne 'Log::Log4perl' &&
  157.        $class ne 'org.apache.log4j.Logger' &&
  158.        warn "setting category $name to class $class ignored, only Log::Log4perl implemented";
  159.  
  160.     #this is kind of funky, additivity has its own spot in the tree
  161.     my $additivity = $node->getAttribute('additivity');
  162.     if (length $additivity > 0) {
  163.         $l4p_tree->{additivity} ||= {};
  164.         my $add_ptr = $l4p_tree->{additivity};
  165.  
  166.         for my $part (split /\.|::/, $name) {
  167.             $add_ptr->{$part} = {} unless exists $add_ptr->{$part};
  168.             $add_ptr = $add_ptr->{$part};
  169.         }
  170.         $add_ptr->{value} = &parse_boolean($additivity);
  171.     }
  172.  
  173.     &parse_children_of_logger_element($l4p_branch, $node);
  174. }
  175.  
  176. # parses the children of a category element
  177. sub parse_children_of_logger_element {
  178.     my ($l4p_branch, $node) = @_;
  179.  
  180.     my (@appenders, $priority);
  181.  
  182.     for my $child ($node->getChildNodes) {
  183.         next unless $child->getNodeType == ELEMENT_NODE;
  184.             
  185.         my $tag_name = $child->getTagName();
  186.  
  187.         if ($tag_name eq 'param') {
  188.             my $name = $child->getAttribute('name');
  189.             my $value = $child->getAttribute('value');
  190.             if ($value =~ /^(all|debug|info|warn|error|fatal|off|null)^/) {
  191.                 $value = uc $value;
  192.             }
  193.             $l4p_branch->{$name} = {value => $value};
  194.         
  195.         }elsif ($tag_name eq 'appender-ref'){
  196.             push @appenders, $child->getAttribute('ref');
  197.             
  198.         }elsif ($tag_name eq 'level' || $tag_name eq 'priority'){
  199.             $priority = &parse_level($child);
  200.         }
  201.     }
  202.     $l4p_branch->{value} = $priority.', '.join(',', @appenders);
  203.     
  204.     return;
  205. }
  206.  
  207.  
  208. sub parse_level {
  209.     my $node = shift;
  210.  
  211.     my $level = uc ($node->getAttribute('value'));
  212.  
  213.     die "Log4perl: invalid level in config: $level"
  214.         unless Log::Log4perl::Level::is_valid($level);
  215.  
  216.     return $level;
  217. }
  218.  
  219.  
  220.  
  221. sub parse_appender {
  222.     my ($l4p_tree, $node) = @_;
  223.  
  224.     my $name = $node->getAttribute("name");
  225.  
  226.     my $l4p_branch = {};
  227.  
  228.     my $class = $node->getAttribute("class");
  229.  
  230.     $l4p_branch->{value} = $class;
  231.  
  232.     print "looking at $name----------------------\n"  if DEBUG;
  233.  
  234.     for my $child ($node->getChildNodes) {
  235.         next unless $child->getNodeType == ELEMENT_NODE;
  236.  
  237.         my $tag_name = $child->getTagName();
  238.  
  239.         my $name = unlog4j($child->getAttribute('name'));
  240.  
  241.  
  242.         if ($tag_name =~ /^(param|param-nested|param-text)$/) {
  243.  
  244.             &parse_any_param($l4p_branch, $child);
  245.  
  246.             my $value;
  247.  
  248.         }elsif ($tag_name =~ /($LOG4PERL_PREFIX:)?layout/){
  249.             $l4p_branch->{layout} = parse_layout($child);
  250.  
  251.         }elsif ($tag_name eq 'filter'){
  252.             die "filters not supported yet";
  253.  
  254.         }elsif ($tag_name eq 'errorHandler'){
  255.             die "errorHandlers not supported yet";
  256.  
  257.         }elsif ($tag_name eq 'appender-ref'){
  258.             #dtd: Appenders may also reference (or include) other appenders. 
  259.             #This feature in log4j is only for appenders who implement the 
  260.             #AppenderAttachable interface, and the only one that does that
  261.             #is the AsyncAppender, which writes logs in a separate thread.
  262.             #I don't see the need to support this on the perl side any 
  263.             #time soon.  --kg 3/2003
  264.             die "Log4perl: in config file, <appender-ref> tag is unsupported in <appender>";
  265.         }
  266.     }
  267.     $l4p_tree->{appender}{$name} = $l4p_branch;
  268. }
  269.  
  270.  
  271. sub parse_any_param {
  272.     my ($l4p_branch, $child) = @_;
  273.  
  274.     my $tag_name = $child->getTagName();
  275.     my $name = $child->getAttribute('name');
  276.     my $value;
  277.  
  278.     print "parse_any_param: <$tag_name name=$name\n" if DEBUG;
  279.  
  280.     #<param-nested>
  281.     #note we don't set it to { value => $value }
  282.     #and we don't test for multiple values
  283.     if ($tag_name eq 'param-nested'){
  284.         
  285.         if ($l4p_branch->{$name}){
  286.             die "Log4perl: in config file, multiple param-nested tags for $name not supported";
  287.         }
  288.         $l4p_branch->{$name} = &parse_param_nested($child); 
  289.  
  290.         return;
  291.  
  292.     #<param>
  293.     }elsif ($tag_name eq 'param') {
  294.  
  295.          $value = $child->getAttribute('value');
  296.  
  297.          print "parse_param_nested: got param $name = $value\n"  if DEBUG;
  298.         
  299.          if ($value =~ /^(all|debug|info|warn|error|fatal|off|null)$/) {
  300.              $value = uc $value;
  301.          }
  302.  
  303.          if ($name !~ /warp_message|filter/ &&
  304.             $child->getParentNode->getAttribute('name') ne 'cspec') {
  305.             $value = eval_if_perl($value);
  306.          }
  307.     #<param-text>
  308.     }elsif ($tag_name eq 'param-text'){
  309.  
  310.         foreach my $grandkid ($child->getChildNodes){
  311.             if ($grandkid->getNodeType == TEXT_NODE) {
  312.                 $value .= $grandkid->getData;
  313.             }
  314.         }
  315.         if ($name !~ /warp_message|filter/ &&
  316.             $child->getParentNode->getAttribute('name') ne 'cspec') {
  317.             $value = eval_if_perl($value);
  318.         }
  319.     }
  320.     
  321.  
  322.      #multiple values for the same param name
  323.      if (defined $l4p_branch->{$name}{value} ) {
  324.          if (ref $l4p_branch->{$name}{value} ne 'ARRAY'){
  325.              my $temp = $l4p_branch->{$name}{value};
  326.              $l4p_branch->{$name}{value} = [$temp];
  327.          }
  328.          push @{$l4p_branch->{$name}{value}}, $value;
  329.      }else{
  330.          $l4p_branch->{$name} = {value => $value};
  331.      }
  332. }
  333.  
  334. #handles an appender's <param-nested> elements
  335. sub parse_param_nested {
  336.     my ($node) = shift;
  337.  
  338.     my $l4p_branch = {};
  339.  
  340.     for my $child ($node->getChildNodes) {
  341.         next unless $child->getNodeType == ELEMENT_NODE;
  342.  
  343.         my $tag_name = $child->getTagName();
  344.  
  345.         if ($tag_name =~ /^param|param-nested|param-text$/) {
  346.             &parse_any_param($l4p_branch, $child);
  347.         }
  348.     }
  349.  
  350.     return $l4p_branch;
  351. }
  352.  
  353.  
  354. sub parse_layout {
  355.     my $node = shift;
  356.  
  357.     my $layout_tree = {};
  358.  
  359.     my $class_name = $node->getAttribute('class');
  360.     
  361.     $layout_tree->{value} = $class_name;
  362.     #
  363.     print "\tparsing layout $class_name\n"  if DEBUG;  
  364.     for my $child ($node->getChildNodes) {
  365.         next unless $child->getNodeType == ELEMENT_NODE;
  366.         if ($child->getTagName() eq 'param') {
  367.             my $name = $child->getAttribute('name');
  368.             my $value = $child->getAttribute('value');
  369.             if ($value =~ /^(all|debug|info|warn|error|fatal|off|null)$/) {
  370.                 $value = uc $value;
  371.             }
  372.             print "\tparse_layout: got param $name = $value\n"  if DEBUG;
  373.             $layout_tree->{$name}{value} = $value;  
  374.  
  375.         }elsif ($child->getTagName() eq 'cspec') {
  376.             my $name = $child->getAttribute('name');
  377.             my $value;
  378.             foreach my $grandkid ($child->getChildNodes){
  379.                 if ($grandkid->getNodeType == TEXT_NODE) {
  380.                     $value .= $grandkid->getData;
  381.                 }
  382.             }
  383.             $value =~ s/^ +//;
  384.             $value =~ s/ +$//;
  385.             $layout_tree->{cspec}{$name}{value} = $value;  
  386.         }
  387.     }
  388.     return $layout_tree;
  389. }
  390.  
  391. sub parse_boolean {
  392.     my $a = shift;
  393.  
  394.     if ($a eq '0' || lc $a eq 'false') {
  395.         return '0';
  396.     }elsif ($a eq '1' || lc $a eq 'true'){
  397.         return '1';
  398.     }else{
  399.         return $a; #probably an error, punt
  400.     }
  401. }
  402.  
  403. 1;
  404.  
  405. __END__
  406.  
  407. =head1 NAME
  408.  
  409. Log::Log4perl::Config::DOMConfigurator - reads xml config files
  410.  
  411. =head1 SYNOPSIS
  412.  
  413.     --------------------------
  414.     --using the log4j DTD--
  415.     --------------------------
  416.  
  417.     <?xml version="1.0" encoding="UTF-8"?>
  418.     <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
  419.  
  420.     <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
  421.  
  422.     <appender name="FileAppndr1" class="org.apache.log4j.FileAppender">
  423.         <layout class="Log::Log4perl::Layout::PatternLayout">
  424.                 <param name="ConversionPattern"
  425.                        value="%d %4r [%t] %-5p %c %t - %m%n"/>
  426.         </layout>
  427.         <param name="File" value="t/tmp/DOMtest"/>
  428.         <param name="Append" value="false"/>
  429.     </appender>
  430.  
  431.     <category name="a.b.c.d" additivity="false">
  432.         <level value="warn"/>  <!-- note lowercase! -->
  433.         <appender-ref ref="FileAppndr1"/>
  434.     </category>
  435.  
  436.    <root>
  437.         <priority value="warn"/>
  438.         <appender-ref ref="FileAppndr1"/>
  439.    </root>
  440.  
  441.    </log4j:configuration>
  442.    
  443.    
  444.    
  445.    --------------------------
  446.    --using the log4perl DTD--
  447.    --------------------------
  448.    
  449.    <?xml version="1.0" encoding="UTF-8"?>
  450.     <!DOCTYPE log4perl:configuration SYSTEM "log4perl.dtd">
  451.  
  452.     <log4perl:configuration xmlns:log4perl="http://log4perl.sourceforge.net/"
  453.         threshold="debug" oneMessagePerAppender="true">
  454.  
  455.     <log4perl:appender name="jabbender" class="Log::Dispatch::Jabber">
  456.  
  457.             <param-nested name="login">
  458.                    <param name="hostname" value="a.jabber.server"/>
  459.                    <param name="password" value="12345"/>
  460.                    <param name="port"     value="5222"/>
  461.                    <param name="resource" value="logger"/>
  462.                    <param name="username" value="bobjones"/>
  463.             </param-nested>
  464.  
  465.             <param name="to" value="bob@a.jabber.server"/>
  466.  
  467.             <param-text name="to">
  468.                   mary@another.jabber.server
  469.             </param-text>
  470.  
  471.             <log4perl:layout class="org.apache.log4j.PatternLayout">
  472.                 <param name="ConversionPattern" value = "%K xx %G %U"/>
  473.                 <cspec name="K">
  474.                     sub { return sprintf "%1x", $$}
  475.                 </cspec>
  476.                 <cspec name="G">
  477.                     sub {return 'thisistheGcspec'}
  478.                 </cspec>
  479.             </log4perl:layout>
  480.     </log4perl:appender>
  481.  
  482.     <log4perl:appender name="DBAppndr2" class="Log::Log4perl::Appender::DBI">
  483.               <param name="warp_message" value="0"/>
  484.               <param name="datasource" value="DBI:CSV:f_dir=t/tmp"/>
  485.               <param name="bufferSize" value="2"/>
  486.               <param name="password" value="sub { $ENV{PWD} }"/>
  487.               <param name="username" value="bobjones"/>
  488.  
  489.               <param-text name="sql">
  490.                   INSERT INTO log4perltest
  491.                             (loglevel, message, shortcaller, thingid,
  492.                             category, pkg, runtime1, runtime2)
  493.                   VALUES
  494.                              (?,?,?,?,?,?,?,?)
  495.               </param-text>
  496.  
  497.                <param-nested name="params">
  498.                     <param name="1" value="%p"/>
  499.                     <param name="3" value="%5.5l"/>
  500.                     <param name="5" value="%c"/>
  501.                     <param name="6" value="%C"/>
  502.                </param-nested>
  503.  
  504.                <layout class="Log::Log4perl::Layout::NoopLayout"/>
  505.     </log4perl:appender>
  506.  
  507.     <category name="animal.dog">
  508.                <priority value="info"/>
  509.                <appender-ref ref="jabbender"/>
  510.                <appender-ref ref="DBAppndr2"/>
  511.     </category>
  512.  
  513.     <category name="plant">
  514.             <priority value="debug"/>
  515.             <appender-ref ref="DBAppndr2"/>
  516.     </category>
  517.  
  518.     <PatternLayout>
  519.         <cspec name="U"><![CDATA[
  520.             sub {
  521.                 return "UID $< GID $(";
  522.             }
  523.         ]]></cspec>
  524.     </PatternLayout>
  525.  
  526.     </log4perl:configuration>
  527.     
  528.  
  529.  
  530.  
  531. =head1 DESCRIPTION
  532.  
  533. This module implements an XML config, complementing the properties-style
  534. config described elsewhere.
  535.  
  536. =head1 WHY
  537.  
  538. "Why would I want my config in XML?" you ask.  Well, there are a couple
  539. reasons you might want to.  Maybe you have a personal preference
  540. for XML.  Maybe you manage your config with other tools that have an
  541. affinity for XML, like XML-aware editors or automated config
  542. generators.  Or maybe (and this is the big one) you don't like
  543. having to run your application just to check the syntax of your
  544. config file.
  545.  
  546. By using an XML config and referencing a DTD, you can use a namespace-aware
  547. validating parser to see if your XML config at least follows the rules set 
  548. in the DTD. 
  549.  
  550. =head1 HOW
  551.  
  552. To reference a DTD, drop this in after the <?xml...> declaration
  553. in your config file:
  554.  
  555.     <!DOCTYPE log4perl:configuration SYSTEM "log4perl.dtd">
  556.  
  557. That tells the parser to validate your config against the DTD in
  558. "log4perl.dtd", which is available in the xml/ directory of
  559. the log4perl distribution.  Note that you'll also need to grab
  560. the log4j-1.2.dtd from there as well, since the it's included
  561. by log4perl.dtd.
  562.  
  563. Namespace-aware validating parsers are not the norm in Perl.  
  564. But the Xerces project 
  565. (http://xml.apache.org/xerces-c/index.html --lots of binaries available, 
  566. even rpm's)  does provide just such a parser
  567. that you can use like this:
  568.  
  569.     StdInParse -ns -v < my-log4perl-config.xml
  570.  
  571. This module itself does not use a validating parser, the obvious
  572. one XML::DOM::ValParser doesn't seem to handle namespaces.
  573.  
  574. =head1 WHY TWO DTDs
  575.  
  576. The log4j DTD is from the log4j project, they designed it to 
  577. handle their needs.  log4perl has added some extensions to the 
  578. original log4j functionality which needed some extensions to the
  579. log4j DTD.  If you aren't using these features then you can validate
  580. your config against the log4j dtd and know that you're using
  581. unadulterated log4j config tags.   
  582.  
  583. The features added by the log4perl dtd are:
  584.  
  585. =over 4
  586.  
  587. =item 1 oneMessagePerAppender global setting
  588.  
  589.     log4perl.oneMessagePerAppender=1
  590.  
  591. =item 2 globally defined user conversion specifiers
  592.  
  593.     log4perl.PatternLayout.cspec.G=sub { return "UID $< GID $("; }
  594.  
  595. =item 3 appender-local custom conversion specifiers
  596.  
  597.      log4j.appender.appndr1.layout.cspec.K = sub {return sprintf "%1x", $$ }
  598.  
  599. =item 4 nested options
  600.  
  601.      log4j.appender.jabbender          = Log::Dispatch::Jabber
  602.      #(note how these are nested under 'login')
  603.      log4j.appender.jabbender.login.hostname = a.jabber.server
  604.      log4j.appender.jabbender.login.port     = 5222
  605.      log4j.appender.jabbender.login.username = bobjones
  606.  
  607. =item 5 the new filter stuff!! (TBD)
  608.  
  609. =back
  610.  
  611.  
  612. So we needed to extend the log4j dtd to cover these additions.
  613. Now I could have just taken a 'steal this code' approach and mixed
  614. parts of the log4j dtd into a log4perl dtd, but that would be
  615. cut-n-paste programming.  So I've used namespaces and
  616.  
  617. =over 4
  618.  
  619. =item * 
  620.  
  621. replaced three elements:
  622.  
  623. =over 4
  624.  
  625. =item <log4perl:configuration>
  626.  
  627. handles #1) and accepts <PatternLayout>
  628.  
  629. =item  <log4perl:appender> 
  630.  
  631. accepts <param-nested> and <param-text>
  632.  
  633. =item <log4perl:layout> 
  634.  
  635. accepts custom cspecs for #3)
  636.  
  637. =back
  638.  
  639. =item * 
  640.  
  641. added a <param-nested> element (complementing the <param> element)
  642.     to handle #4)
  643.  
  644. =item * 
  645.  
  646. added a root <PatternLayout> element to handle #2)
  647.  
  648. =item * 
  649.  
  650. added <param-text> which lets you put things like perl code
  651.     into escaped CDATA between the tags, so you don't have to worry
  652.     about escaping characters and quotes
  653.  
  654. =item * 
  655.  
  656. added <cspec>
  657.  
  658. =back
  659.  
  660. See the examples up in the L<"SYNOPSIS"> for how all that gets used.
  661.  
  662. =head1 WHY NAMESPACES
  663.  
  664. I liked the idea of using the log4j DTD I<in situ>, so I used namespaces
  665. to extend it.  If you really don't like having to type <log4perl:appender>
  666. instead of just <appender>, you can make your own DTD combining
  667. the two DTDs and getting rid of the namespace prefixes.  Then you can
  668. validate against that, and log4perl should accept it just fine.
  669.  
  670. =head1 REQUIRES
  671.  
  672. To use this module you need XML::DOM installed.  
  673.  
  674. To use the log4perl.dtd, you'll have to reference it in your XML config,
  675. and you'll also need to note that log4perl.dtd references the 
  676. log4j dtd as "log4j-1.2.dtd", so your validator needs to be able
  677. to find that file as well.  If you don't like having to schlep two
  678. files around, feel free
  679. to dump the contents of "log4j-1.2.dtd" into your "log4perl.dtd" file.
  680.  
  681. =head1 CAVEATS
  682.  
  683. You can't mix a multiple param-nesteds with the same name, I'm going to
  684. leave that for now, there's presently no need for a list of structs
  685. in the config.
  686.  
  687. =head1 CHANGES
  688.  
  689. 0.03 2/26/2003 Added support for log4perl extensions to the log4j dtd
  690.  
  691. =head1 SEE ALSO
  692.  
  693. t/038XML-DOM1.t, t/039XML-DOM2.t for examples
  694.  
  695. xml/log4perl.dtd, xml/log4j-1.2.dtd
  696.  
  697. Log::Log4perl::Config
  698.  
  699. Log::Log4perl::Config::PropertyConfigurator
  700.  
  701. Log::Log4perl::Config::LDAPConfigurator (coming soon!)
  702.  
  703. =head1 AUTHOR
  704.  
  705. Kevin Goess, <cpan@goess.org> Jan-2003
  706.  
  707. The code is brazenly modeled on log4j's DOMConfigurator class, (by 
  708. Christopher Taylor, Ceki Gⁿlcⁿ and Anders Kristensen) and any
  709. perceived similarity is not coincidental.
  710.  
  711.  
  712. =cut
  713.