home *** CD-ROM | disk | FTP | other *** search
/ PC Professionell 2004 December / PCpro_2004_12.ISO / files / webserver / xampp / xampp-perl-addon-1.4.9-installer.exe / XMLForm.pm < prev    next >
Encoding:
Perl POD Document  |  2002-02-14  |  14.0 KB  |  586 lines

  1. package CGI::XMLForm;
  2.  
  3. use strict;
  4. use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
  5.  
  6. use CGI;
  7. use CGI::XMLForm::Path;
  8. use XML::Parser;
  9.  
  10. @ISA = qw(CGI);
  11. # Items to export into callers namespace by default. Note: do not export
  12. # names by default without a very good reason. Use EXPORT_OK instead.
  13. # Do not simply export all your public functions/methods/constants.
  14. @EXPORT = qw(
  15.  
  16. );
  17. $VERSION = '0.10';
  18.  
  19. sub new {
  20.     my $proto = shift;
  21.     my $class = ref($proto) || $proto;
  22.     my $self  = $class->SUPER::new(@_);
  23.     bless ($self, $class);          # reconsecrate
  24.     return $self;
  25. }
  26.  
  27. sub readXML {
  28.     my $self = shift;
  29.     my $xml = shift;
  30.  
  31.     my @queries = @_;
  32.  
  33.     my @Requests;
  34.  
  35.     my $req = new CGI::XMLForm::Path();
  36.     do {
  37.         $req = new CGI::XMLForm::Path(shift @queries, $req);
  38.         push @Requests, $req;
  39.     } while @queries;
  40.  
  41.     my $currenttree = new CGI::XMLForm::Path();
  42.  
  43.     my $p = new XML::Parser(Style => 'Stream',
  44.         _parseresults => [],
  45.         _currenttree => $currenttree,
  46.         _requests => \@Requests,
  47.         );
  48.  
  49.     my $results;
  50.     eval {
  51.         $results = $p->parse($xml);
  52. #        warn "Parse returned ", @{$results}, "\n";
  53.     };
  54.     if ($@) {
  55.         return $@;
  56.     }
  57.     else {
  58.         return @{$results};
  59.     }
  60. }
  61.  
  62. sub StartTag {
  63.     my $expat = shift;
  64.     return $expat->finish() if $expat->{_done};
  65.     my $element = shift;
  66. #    my %attribs = %_;
  67.  
  68. #warn "Start: $element\n";
  69.     $expat->{_currenttree}->Append($element, %_);
  70.     my $current = $expat->{_currenttree};
  71.  
  72. #warn "Path now: ", $expat->{_currenttree}->Path, "\n";
  73.  
  74.     foreach (0..$#{$expat->{_requests}}) {
  75.         next unless defined $expat->{_requests}->[$_]->Attrib;
  76. # warn "Looking for attrib: ", $expat->{_requests}->[$_]->Attrib, "\n";
  77.         if (defined $_{$expat->{_requests}->[$_]->Attrib}) {
  78.             # Looking for attrib
  79.             if ($expat->{_requests}->[$_]->isEqual($current)) {
  80.                 # We have equality!
  81.                 found($expat, $expat->{_requests}->[$_], $_{$expat->{_requests}->[$_]->Attrib});
  82.                 splice(@{$expat->{_requests}}, $_, 1) unless $expat->{_requests}->[$_]->isRepeat;
  83.                 $expat->{_done} = 1 if (@{$expat->{_requests}} == 0);
  84.                 return;
  85.             }
  86.         }
  87.     }
  88. }
  89.  
  90. sub EndTag {
  91.     my $expat = shift;
  92.     return $expat->finish() if $expat->{_done};
  93. # warn "End: $_\n";
  94.  
  95.     $expat->{_currenttree}->Pop();
  96. }
  97.  
  98. sub Text {
  99.     my $expat = shift;
  100.     my $text = $_;
  101.  
  102.     return $expat->finish() if $expat->{_done};
  103.  
  104.     my @Requests = @{$expat->{_requests}};
  105.     my $current = $expat->{_currenttree};
  106.  
  107.     foreach (0..$#Requests) {
  108.         if (!$Requests[$_]->Attrib) {
  109.             # Not looking for an attrib
  110. #            warn "Comparing : ", $Requests[$_]->Path, " : ", $expat->{_currenttree}->Path, "\n";
  111.             if ($Requests[$_]->isEqual($current)) {
  112.                 found($expat, $Requests[$_], $text);
  113.                 splice(@{$expat->{_requests}}, $_, 1) unless $Requests[$_]->isRepeat;
  114.                 $expat->{_done} = 1 if (@Requests == 0);
  115.                 return;
  116.             }
  117.         }
  118.     }
  119. }
  120.  
  121. sub found {
  122.     my $expat = shift;
  123.     my ($request, $found) = @_;
  124.  
  125. #warn "Found: ", $request->Path, " : $found\n";
  126.  
  127.     if ($request->Path =~ /\.\*/) {
  128.         # Request path contains a regexp
  129.         my $match = $request->Path;
  130.         $match =~ s/\[(.*?)\]/\\\[$1\\\]/g;
  131.  
  132. #        warn "Regexp: ", $expat->{_currenttree}->Path, " =~ |$match|\n";
  133.         $expat->{_currenttree}->Path =~ /$match/;
  134.         push @{$expat->{_parseresults}}, $&, $found;
  135.     }
  136.     else {
  137.         push @{$expat->{_parseresults}}, $request->Path, $found;
  138.     }
  139.  
  140. }
  141.  
  142. sub EndDocument {
  143.     my $expat = shift;
  144.     delete $expat->{_done};
  145.     delete $expat->{_currenttree};
  146.     delete $expat->{_requests};
  147.     return $expat->{_parseresults};
  148. }
  149.  
  150. sub formatElement($$) {
  151.     # Properly formats elements whether opening or closing.
  152.  
  153.     my $cgi = shift;
  154.     my $open = shift;
  155.     my $element = shift;
  156.     my $level = shift;
  157.  
  158.     $element =~ s/&slash;/\//g;
  159.  
  160.     $element =~ /^(.*?)(\[(.*)\])?$/;
  161.     my $output = $1;
  162.     my $attribs = $3 || "";
  163.  
  164.     if (!$open) {
  165.         if (!$cgi->{'.closetags'}) {
  166.             $cgi->{'.closetags'} = $level;
  167.             return "</$output>\n";
  168.         }
  169.         else {
  170.             return ("\t" x --$cgi->{'.closetags'}) . "</$output>\n";
  171.         }
  172.     }
  173.  
  174.     # If we have attributes
  175.     while ($attribs =~ /\@(\w+?)=([\"\'])(.*?)\2(\s+and\s+)?/g) {
  176.         $output .= " $1=\"$3\"";
  177.     }
  178.     my $save = $cgi->{'.closetags'};
  179.     $cgi->{'.closetags'} = 0;
  180.     return ($save ? '' : "\n") . ("\t" x $level) . "<$output>";
  181. }
  182.  
  183. sub ToXML {
  184.     shift()->toXML(@_);
  185. }
  186.  
  187. sub toXML {
  188.     my $self = shift;
  189.     my $filename = shift;
  190.  
  191.     if (defined $filename) {
  192.         local *OUTPUT;
  193.         open(OUTPUT, ">$filename") or die "Can't open $filename for output: $!";
  194.         print OUTPUT $self->{".xml"};
  195.         close OUTPUT;
  196.     }
  197.  
  198.     defined wantarray && return $self->{".xml"};
  199. }
  200.  
  201. sub parse_params {
  202.     my($self,$tosplit) = @_;
  203.     my(@pairs) = split('&',$tosplit);
  204.     my($param,$value);
  205.     my $output = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>";
  206.  
  207.     my @prevStack;
  208.     my @stack;
  209.     my @rawParams;
  210.     my $relative;
  211.     $self->{'.closetags'} = 0;
  212.  
  213.     foreach (@pairs) {
  214.         ($param,$value) = split('=',$_,2);
  215.         $param = $self->unescape($param);
  216.         $value = $self->unescape($value);
  217.  
  218.         $self->add_parameter($param);
  219.         push (@{$self->{$param}},$value);
  220.  
  221.         next if $param =~ /^xmlcgi:ignore/;
  222.         next if $param =~ /^\.\w/; # Skip CGI.pm ".submit" and other buttons
  223.  
  224.         push @rawParams, $param, $value;
  225.  
  226.         # Encode values
  227.         $value =~ s/&/&/g;
  228.         $value =~ s/</</g;
  229.         $value =~ s/>/>/g;
  230.         $value =~ s/'/'/g;
  231.         $value =~ s/"/"/g;
  232.  
  233.         $value =~ s/\//\&slash;/g; # We decode this later...
  234.         $param =~ s/\[(.*?)\/(.*?)\]/\[$1\&slash;$2\]/g;
  235.  
  236.         # Here we make the attribute into an internal attrib
  237.         # so that tree compares work properly
  238.         my $attrib = 0;
  239.         if($param =~ s/(\])?\/(\@\w+)$/(($1 && " and ")||"[").qq($2="$value"])/e) {
  240.             $attrib = 1;
  241.         }
  242.  
  243.         # Do work here
  244.         if ($param =~ s/^\///) {
  245.             # If starts with a slash it's a root element
  246.             @stack = split /\//, $param;
  247.             $relative = 0;
  248.         }
  249.         else {
  250.             # Otherwise it's a relative path
  251.  
  252.             # - We don't need to do this, but it's here commented out
  253.             # to show what we're implying.
  254.             # @stack = @prevStack;
  255.  
  256.  
  257.             # We don't want the last element if the previous param
  258.             # was also a relative param.
  259.             my $top = pop @stack if ($relative);
  260.  
  261.             foreach ( split(/\//, $param)) {
  262.                 if ($_ eq "..") {
  263.                     if ($top) {
  264.                         $output .= $self->formatElement(0, $top, scalar @stack);
  265.                         $top = '';
  266.                         pop @prevStack;
  267.                     }
  268.                     $output .= $self->formatElement(0, pop(@stack), scalar @stack);
  269.                     pop @prevStack;
  270.                 }
  271.                 else {
  272.                     push @stack, $_;
  273.                 }
  274.             }
  275.             $relative++;
  276.         }
  277.  
  278.     #    print STDERR "Prev Stack: ", join(", ", @prevStack), "\n";
  279.     #    print STDERR "New  Stack: ", join(", ", @stack), "\n----------\n";
  280.  
  281.         foreach my $i (0..$#stack) {
  282.  
  283.             if (defined $prevStack[$i]) {
  284.  
  285.                 # We've travelled along this branch of the tree before.
  286.                 if (($i == $#stack) || ($prevStack[$i] ne $stack[$i])) {
  287.  
  288.                     # If we've reached the end of the branch, or the branch has changed...
  289.                     while ($i <= $#prevStack) {
  290.                         # Close the previous branch
  291.                         $output .= $self->formatElement(0, pop(@prevStack),
  292.                             scalar @prevStack);
  293.                     }
  294.  
  295.                     # And add this new branch
  296.                     $output .= $self->formatElement(1, $stack[$i], scalar
  297.                         @prevStack);
  298.                     push @prevStack, $stack[$i];
  299.                 }
  300.             }
  301.  
  302.             else {
  303.                 # here we're traversing out into the tree where we've not travelled before.
  304.                 $output .= $self->formatElement(1, $stack[$i], scalar @prevStack);
  305.                 push @prevStack, $stack[$i];
  306.             }
  307.         }
  308.  
  309.         # Finally, we output the contents of the form field, unless it's an attribute form field
  310.         if (!$attrib) {
  311.             $output .= $value;
  312.         }
  313.  
  314.         # Store the previous stack.
  315.         @prevStack = @stack;
  316.     }
  317.  
  318.     # Finish by completely popping the stack off.
  319.     while (@prevStack) {
  320.         $output .= $self->formatElement(0, pop(@prevStack), scalar @prevStack);
  321.     }
  322.  
  323.     $self->{".xml"} = $output;
  324.     $self->{rawParams} = \@rawParams;
  325.  
  326.     1;
  327. }
  328.  
  329. 1;
  330. __END__
  331.  
  332. =head1 NAME
  333.  
  334. CGI::XMLForm - Extension of CGI.pm which reads/generates formated XML.
  335.  
  336. NB: This is a subclass of CGI.pm, so can be used in it's place.
  337.  
  338. =head1 SYNOPSIS
  339.  
  340.   use CGI::XMLForm;
  341.  
  342.   my $cgi = new CGI::XMLForm;
  343.  
  344.   if ($cgi->param) {
  345.       print $cgi->header, $cgi->pre($cgi->escapeHTML($cgi->toXML));
  346.   }
  347.   else {
  348.       open(FILE, "test.xml") or die "Can't open: $!";
  349.     my @queries = ('/a', '/a/b*', '/a/b/c*', /a/d');
  350.     print $cgi->header,
  351.           $cgi->pre($cgi->escapeHTML(
  352.           join "\n", $cgi->readXML(*FILE, @queries)));
  353.   }
  354.  
  355. =head1 DESCRIPTION
  356.  
  357. This module can either create form field values from XML based on XQL/XSL style
  358. queries (full XQL is _not_ supported - this module is designed for speed), or it
  359. can create XML from form values. There are 2 key functions: toXML and readXML.
  360.  
  361. =head2 toXML
  362.  
  363. The module takes form fields given in a specialised format,
  364. and outputs them to XML based on that format. The idea is that you
  365. can create forms that define the resulting XML at the back end.
  366.  
  367. The format for the form elements is:
  368.  
  369.   <input name="/body/p/ul/li">
  370.  
  371. which creates the following XML:
  372.  
  373.   <body>
  374.     <p>
  375.       <ul>
  376.         <li>Entered Value</li>
  377.       </ul>
  378.     </p>
  379.   </body>
  380.  
  381. It's the user's responsibility to design appropriate forms to make
  382. use of this module. Details of how come below...
  383.  
  384. Also supported are attribute form items, that allow creation
  385. of element attributes. The syntax for this is:
  386.  
  387.   <input name="/body/p[@id='mypara' and @onClick='someFunc()']/@class">
  388.  
  389. Which creates the following XML:
  390.  
  391.   <body>
  392.     <p id="mypara" onClick="someFunc()" class="Entered Value"></p>
  393.   </body>
  394.  
  395. Also possible are relative paths. So the following form elements:
  396.  
  397.   <input type="hidden" name="/table/tr">
  398.   <input type="text" name="td">
  399.   <input type="text" name="td">
  400.   <input type="text" name="../tr/td">
  401.  
  402. Will create the following XML:
  403.  
  404.   <table>
  405.     <tr>
  406.       <td>value1</td>
  407.       <td>value2</td>
  408.     </tr>
  409.     <tr>
  410.       <td>value3</td>
  411.     </tr>
  412.   </table>
  413.  
  414. =head1 SYNTAX
  415.  
  416. The following is a brief syntax guideline
  417.  
  418. Full paths start with a "/" :
  419.  
  420.   "/table/tr/td"
  421.  
  422. Relative paths start with either ".." or just a tag name.
  423.  
  424.   "../tr/td"
  425.   "td"
  426.  
  427. B<Relative paths go at the level above the previous path, unless the previous
  428. path was also a relative path, in which case it goes at the same level.> This
  429. seems confusing at first (you might expect it to always go at the level above
  430. the previous element), but it makes your form easier to design. Take the
  431. following example: You have a timesheet (see the example supplied in the
  432. archive) that has monday,tuesday,etc. Our form can look like this:
  433.  
  434.   <input type="text" name="/timesheet/projects/project/@Name">
  435.   <input type="text" name="monday">
  436.   <input type="text" name="tuesday">
  437.   ...
  438.  
  439. Rather than:
  440.  
  441.   <input type="text" name="/timesheet/projects/project/@Name">
  442.   <input type="text" name="monday">
  443.   <input type="text" name="../tuesday">
  444.   <input type="text" name="../wednesday">
  445.   ...
  446.  
  447. If unsure I recommend using full paths, relative paths are great for repeating
  448. groups of data, but weak for heavily structured data. Picture the following
  449. paths:
  450.  
  451.   /timesheet/employee/name/forename
  452.   ../surname
  453.   title
  454.   ../department
  455.  
  456. This actually creates the following XML:
  457.  
  458.   <timesheet>
  459.     <employee>
  460.       <name>
  461.         <forename>val1</forname>
  462.         <surname>val2</surname>
  463.         <title>val3></title>
  464.       </name>
  465.       <department>val4</department>
  466.     </employee>
  467.   </timesheet>
  468.  
  469. Confusing eh? Far better to say:
  470.  
  471.   /timesheet/employee/name/forename
  472.   /timesheet/employee/name/surname
  473.   /timesheet/employee/name/title
  474.   /timesheet/employee/department
  475.  
  476. Or alternatively, better still:
  477.  
  478.   /timesheet/employee/name (Make hidden and no value)
  479.   forename
  480.   surname
  481.   title
  482.   ../department
  483.  
  484. Attributes go in square brackets. Attribute names are preceded with an "@",
  485. and attribute values follow an "=" sign and are enclosed in quotes. Multiple
  486. attributes are separated with " and ".
  487.  
  488.   /table[@bgcolor="blue" and @width="100%"]/tr/td
  489.  
  490. If setting an attribute, it follows after the tag that it is associated with,
  491. after a "/" and it's name is preceded with an "@".
  492.  
  493.   /table/@bgcolor
  494.  
  495. =head2 readXML
  496.  
  497. readXML takes either a file handle or text as the first parameter and a list of
  498. queries following that. The XML is searched for the queries and it returns a
  499. list of tuples that are the query and the match.
  500.  
  501. It's easier to demonstrate this with an example. Given the following XML:
  502.  
  503.   <a>Foo
  504.     <b>Bar
  505.       <c>Fred</c>
  506.       <c>Blogs</c>
  507.     </b>
  508.     <b>Red
  509.       <c>Barbara</c>
  510.       <c>Cartland</c>
  511.     </b>
  512.     <d>Food</d>
  513.   </a>
  514.  
  515. And the following queries:
  516.  
  517.   /a
  518.   /a/b*
  519.   c*
  520.   /a/d
  521.  
  522. it returns the following result as a list:
  523.  
  524.   /a
  525.   Foo
  526.   /a/b
  527.   Bar
  528.   c
  529.   Fred
  530.   c
  531.   Blogs
  532.   /a/b
  533.   Red
  534.   c
  535.   Barbara
  536.   c
  537.   Cartland
  538.   /a/d
  539.   Food
  540.  
  541. (NB: This is slightly incorrect - for /a and /a/b it will return "Foo\n    " and
  542. "Bar\n      " respectively).
  543.  
  544. The queries support relative paths like toXML (including parent paths), and
  545. they also support wildcards using ".*" or ".*?" (preferably ".*?" as it's
  546. probably a better match). If a wildcard is specified the results will have the
  547. actual value substituted with the wildcard. Wildcards are a bit experimental,
  548. so be careful ;-)
  549.  
  550. =head2 Caveats
  551.  
  552. There are a few caveats to using this module:
  553.  
  554. =over
  555.  
  556. =item * Parameters must be on the form in the order they will appear in the XML.
  557.  
  558. =item * There is no support for multiple attribute setting (i.e. you can only
  559. set one attribute for an element at a time).
  560.  
  561. =item * You can't set an attribute B<and> a value for that element, it's one or the
  562. other.
  563.  
  564. =item * You can use this module in place of CGI.pm, since it's a subclass.
  565.  
  566. =item * There are bound to be lots of bugs! Although it's in production use
  567. right now - just watch CPAN for regular updates.
  568.  
  569. =back
  570.  
  571. =head1 AUTHOR
  572.  
  573. Matt Sergeant msergeant@ndirect.co.uk, sergeant@geocities.com
  574.  
  575. Based on an original concept, and discussions with, Jonathan Eisenzopf.
  576. Thanks to the Perl-XML mailing list for suggesting the XSL syntax.
  577.  
  578. Special thanks to Francois Belanger (francois@sitepak.com) for
  579. his mentoring and help with the syntax design.
  580.  
  581. =head1 SEE ALSO
  582.  
  583. CGI(1), CGI::XML
  584.  
  585. =cut
  586.