1# ResultSet.pm - Will pull settings from an XML config file into a format usable by the system. 2# Created by James A. Pattie. Copyright (c) 2001-2002, Xperience, Inc. 3 4package DBIWrapper::ResultSet; 5 6use strict; 7use XML::LibXML; 8use vars qw ($AUTOLOAD @ISA @EXPORT $VERSION); 9 10require Exporter; 11@ISA = qw(Exporter AutoLoader); 12@EXPORT = qw(); 13 14$VERSION = "1.1"; 15 16# new 17sub new 18{ 19 my $that = shift; 20 my $class = ref($that) || $that; 21 my $self = bless {}, $class; 22 my $errStr = "ResultSet->new() - Error:"; 23 24 $self->{version} = "1.2"; 25 $self->{resultFile} = ""; 26 $self->{select} = 0; # indicates if a <select /> tag was in the xml. 27 $self->{sql} = ""; 28 $self->{plugs} = ""; 29 $self->{result} = ""; 30 $self->{error} = ""; 31 $self->{columns} = "0"; 32 $self->{numRows} = "0"; 33 $self->{rows} = []; 34 $self->{columnNames} = []; # the column names used. 35 $self->{columnNamesHash} = {}; # same as columnNames but for quicker lookups. 36 $self->{errorCodes} = { 37 0 => "version = '%s' is invalid", 38 1 => "resultFile must be specified", 39 2 => "sql must be specified", 40 3 => "result = '%s' is invalid", 41 4 => "columns = '%s' is invalid", 42 5 => "numRows = '%s' is invalid", 43 6 => "numRows = '%s' but rows = '%s'!", 44 7 => "no columnNames defined", 45 8 => "columnNames count != columnNamesHash", 46 9 => "column = '%s' in columnNamesHash, not in columnNames", 47 10 => "row = '%s' only has '%s' columns defined", 48 11 => "row = '%s', column = '%s' defined but not in columnNames!", 49 }; 50 51 return $self; 52} 53 54sub AUTOLOAD 55{ 56 my $self = shift; 57 my $type = ref($self) || die "$self is not an object"; 58 my $name = $AUTOLOAD; 59 $name =~ s/.*://; # strip fully-qualified portion 60 unless (exists $self->{$name}) 61 { 62 die "Can't access `$name' field in object of class $type"; 63 } 64 if (@_) 65 { 66 return $self->{$name} = shift; 67 } 68 else 69 { 70 return $self->{$name}; 71 } 72} 73 74sub isValid 75{ 76 my $self = shift; 77 my @errors = (); 78 79 my $strict = 0; 80 81 if ($self->{version} !~ /^(1.2)$/) 82 { 83 push @errors, sprintf($self->{errorCodes}->{0}, $self->{version}); 84 } 85 if (length $self->{resultFile} == 0) 86 { 87 push @errors, $self->{errorCodes}->{1}; 88 } 89 if (length $self->{sql} == 0) 90 { 91 push @errors, $self->{errorCodes}->{2}; 92 } 93 if ($self->{result} !~ /^(Ok|Error)$/) 94 { 95 push @errors, sprintf($self->{errorCodes}->{3}, $self->{result}); 96 } 97 if ($self->{columns} !~ /^(0|1)$/) 98 { 99 push @errors, sprintf($self->{errorCodes}->{4}, $self->{columns}); 100 } 101 if ($self->{numRows} !~ /^(\d+)$/) 102 { 103 push @errors, sprintf($self->{errorCodes}->{5}, $self->{numRows}); 104 } 105 else 106 { 107 # validate that we have the expected number of rows. 108 if (scalar @{$self->{rows}} != $self->{numRows}) 109 { 110 push @errors, sprintf($self->{errorCodes}->{6}, $self->{numRows}, scalar @{$self->{rows}}); 111 } 112 if ($self->{numRows} > 0) 113 { 114 my $numColumns = scalar @{$self->{columnNames}}; 115 if ($numColumns == 0) 116 { 117 push @errors, $self->{errorCodes}->{7}; 118 } 119 else 120 { 121 if ($numColumns != scalar keys %{$self->{columnNamesHash}}) 122 { 123 push @errors, $self->{errorCodes}->{8}; 124 } 125 # now verify that all entries in the columnNamesHash exist in columnNames 126 foreach my $column (keys %{$self->{columnNamesHash}}) 127 { 128 my $found = 0; 129 for (my $k=0; $k < scalar @{$self->{columnNames}} && !$found; $k++) 130 { 131 if ($self->{columnNames}->[$k] == $column) 132 { 133 $found = 1; 134 } 135 } 136 if (!$found) 137 { 138 push @errors, sprintf($self->{errorCodes}->{9}, $column); 139 } 140 } 141 # make sure each row has the correct number of columns defined. 142 for (my $i=0; $i < scalar @{$self->{rows}}; $i++) 143 { 144 my $rowRef = $self->{rows}->[$i]; 145 if (scalar(keys %{$rowRef}) != $numColumns) 146 { 147 push @errors, sprintf($self->{errorCodes}->{10}, $i, scalar(keys %{$rowRef})); 148 } 149 else # validate that all columns defined in the row exist in columnNames 150 { 151 foreach my $columnName (keys %{$self->{rows}->[$i]}) 152 { 153 if (not exists $self->{columnNamesHash}->{$columnName}) 154 { 155 push @errors, sprintf($self->{errorCodes}->{11}, $i, $columnName); 156 } 157 } 158 } 159 } 160 } 161 } 162 } 163 164 return ((scalar @errors > 0 ? 0 : 1), \@errors); 165} 166 167sub displayData 168{ 169 my $self = shift; 170 my %args = ( style => "text", @_ ); 171 my $style = $args{style}; 172 my $result = ""; 173 my $errStr = "ResultSet->displayData() - Error:"; 174 175 if ($style !~ /^(text|html)$/) 176 { 177 die "$errStr style = '$style' is invalid!\n"; 178 } 179 180 my @valid = $self->isValid(); 181 if ($valid[0]) 182 { 183 my $columnNames = "'" . join ("', '", @{$self->{columnNames}}) . "'"; 184 if ($style eq "text") 185 { 186 $result .= "version = '$self->{version}'\n"; 187 $result .= "resultFile = '$self->{resultFile}'\n"; 188 $result .= "sql = '$self->{sql}'\n"; 189 $result .= "plug = '$self->{plug}'\n"; 190 $result .= "result = '$self->{result}'\n"; 191 $result .= "error = '$self->{error}'\n"; 192 $result .= "columns = '$self->{columns}'\n"; 193 $result .= "numRows = '$self->{numRows}'\n"; 194 $result .= "columnNames = '$columnNames'\n"; 195 for (my $i=0; $i < $self->{numRows}; $i++) 196 { 197 $result .= "row $i: "; 198 for (my $j=0; $j < scalar @{$self->{columnNames}}; $j++) 199 { 200 my $column = $self->{columnNames}->[$j]; 201 $result .= ", " if ($j > 0); 202 $result .= "'$column' = '$self->{rows}->[$i]->{$column}'"; 203 } 204 } 205 } 206 elsif ($style eq "html") 207 { 208 my $resultFile = $self->encodeHTML(string => $self->{resultFile}); 209 210 my $error = $self->encodeHTML(string => $self->{error}); 211 my $sql = $self->encodeHTML(string => $self->{sql}); 212 my $plug = $self->encodeHTML(string => $self->{plug}); 213 214 $result .= <<"END_OF_CODE"; 215 216<center><h1>ResultSet Version $self->{version}</h1></center> 217 <table border="1" cellpadding="2" cellspacing="0" width="100%"> 218 <tr> 219 <td colspan="2" bgcolor="yellow"><b>Info</b></td> 220 </tr> 221 <tr> 222 <td colspan="2"> 223 <table border="1" cellpadding="2" cellspacing="0" width="100%"> 224 <tr> 225 <td valign="top" align="right"><b>resultFile</b></td> 226 <td bgcolor="cyan">'$resultFile'</td> 227 </tr> 228 <tr> 229 <td valign="top" align="right"><b>sql</b></td> 230 <td bgcolor="cyan">'$sql'</td> 231 </tr> 232 <tr> 233 <td valign="top" align="right"><b>plug</b></td> 234 <td bgcolor="cyan">'$plug'</td> 235 </tr> 236 <tr> 237 <td valign="top" align="right"><b>result</b></td> 238 <td bgcolor="cyan">'$self->{result}'</td> 239 </tr> 240 <tr> 241 <td valign="top" align="right"><b>error</b></td> 242 <td bgcolor="cyan">'$error'</td> 243 </tr> 244 <tr> 245 <td valign="top" align="right"><b>columns</b></td> 246 <td bgcolor="cyan">'$self->{columns}'</td> 247 </tr> 248 <tr> 249 <td valign="top" align="right"><b>numRows</b></td> 250 <td bgcolor="cyan">'$self->{numRows}'</td> 251 </tr> 252 <tr> 253 <td valign="top" align="right"><b>columnNames</b></td> 254 <td bgcolor="cyan">'$columnNames'</td> 255 </tr> 256 </table> 257 </td> 258 </tr> 259 <tr> 260 <td colspan="2" bgcolor="yellow"><b>rows</b></td> 261 </tr> 262 <tr> 263 <td colspan="2"> 264 <table border="0" cellpadding="0" cellspacing="0" width="100%"> 265 <tr> 266 <td width="25%"> </td> 267 <td width="75%"> 268 <table border="1" cellpadding="2" cellspacing="0" width="100%"> 269END_OF_CODE 270 for (my $index=0; $index < $self->{numRows}; $index++) 271 { 272 $result .= <<"END_OF_CODE"; 273 <tr> 274 <td align="right" width="25%"><b><font color="red">$index</font></b></td> 275 <td bgcolor="cyan" width="75%"> </td> 276 </tr> 277 <tr> 278 <td colspan="2"> 279 <table border="0" cellpadding="0" cellspacing="0" width="100%"> 280 <tr> 281 <td width="25%"> </td> 282 <td width="75%"> 283 <table border="1" cellpadding="2" cellspacing="0" width="100%"> 284END_OF_CODE 285 foreach my $column (@{$self->{columnNames}}) 286 { 287 my $value = $self->encodeHTML(string => $self->{rows}->[$index]->{$column}, double => 1); 288 $result .= <<"END_OF_CODE"; 289 <tr> 290 <td align="right"><b>$column</b></td> 291 <td bgcolor="cyan">'$value'</td> 292 </tr> 293END_OF_CODE 294 } 295 $result .= <<"END_OF_CODE"; 296 </table> 297 </td> 298 </tr> 299 </table> 300 </td> 301 </tr> 302END_OF_CODE 303 } 304 $result .= <<"END_OF_CODE"; 305 </table> 306 </td> 307 </tr> 308 </table> 309 </td> 310 </tr> 311 </table> 312END_OF_CODE 313 } 314 } 315 else 316 { 317 if ($style eq "text") 318 { 319 $result .= "Config File not valid!\n\n"; 320 $result .= join("\n", @{$valid[1]}) . "\n"; 321 } 322 elsif ($style eq "html") 323 { 324 $result .= "Config File not valid!<br>\n<br>\n"; 325 $result .= join("<br>\n", @{$valid[1]}) . "<br>\n"; 326 } 327 die $result; 328 } 329 330 return $result; 331} 332 333sub encodeHTML 334{ 335 my $self = shift; 336 my %args = ( string => "", double => 0, @_ ); 337 my $string = $args{string}; 338 my $double = $args{double}; 339 340 $string =~ s/&/&/g; 341 $string =~ s/</</g; 342 $string =~ s/>/>/g; 343 if ($double) 344 { 345 $string =~ s/\\n/<br>\n/g; 346 } 347 else 348 { 349 $string =~ s/\n/<br>\n/g; 350 } 351 $string =~ s/ / /g; 352 353 return $string; 354} 355 356sub generateXML 357{ 358 my $self = shift; 359 my $result = ""; 360 my $errStr = "ResultSet->generateXML() - Error:"; 361 362 my @valid = $self->isValid(); 363 if ($valid[0]) 364 { 365 my $sql = $self->encodeEntities(string => $self->{sql}); 366 my $plug = $self->encodeEntities(string => $self->{plug}); 367 my $error = $self->encodeEntities(string => $self->{error}); 368 $result .= <<"END_OF_XML"; 369<?xml version="1.0" encoding="ISO-8859-1"?> 370<resultset version="$self->{version}"> 371END_OF_XML 372 $result .= qq( <select sql="$sql" plug="$plug"/>\n) if ($self->{select}); 373 $result .= <<"END_OF_XML"; 374 <status result="$self->{result}" error="$error"/> 375 <rows numRows="$self->{numRows}" columns="$self->{columns}"> 376END_OF_XML 377 for (my $i=0; $i < $self->{numRows}; $i++) 378 { 379 if ($self->{columns}) 380 { 381 $result .= " <row>\n"; 382 foreach my $column (@{$self->{columnNames}}) 383 { 384 my $data = $self->{rows}->[$i]->{$column}; 385 my $value = $self->encodeEntities(string => $data); 386 my $name = $self->fixupAttributes($column); 387 if ($self->{columns} == 1) 388 { 389 $result .= " <column name=\"$name\" value=\"$value\"/>\n"; 390 } 391 elsif ($self->{columns} == 2) 392 { 393 $result .= " <$name><![CDATA[$data]]></$name>\n"; 394 } 395 } 396 $result .= " </row>\n"; 397 } 398 else 399 { 400 $result .= " <row"; 401 foreach my $column (@{$self->{columnNames}}) 402 { 403 my $value = $self->encodeEntities(string => $self->{rows}->[$i]->{$column}); 404 my $name = $self->fixupAttributes($column); 405 $result .= " $name=\"$value\""; 406 } 407 $result .= "/>\n"; 408 } 409 } 410 $result .= <<"END_OF_XML"; 411 </rows> 412</resultset> 413END_OF_XML 414 } 415 else 416 { 417 $result .= "ResultSet not valid!\n\n"; 418 $result .= join("\n", @{$valid[1]}) . "\n"; 419 die $result; 420 } 421 422 return $result; 423} 424 425# string encodeEntities(string) 426# requires: string - string to encode 427# optional: 428# returns: string that has been encoded. 429# summary: replaces all special characters with their XML entity equivalent. " => " 430sub encodeEntities 431{ 432 my $self = shift; 433 my %args = ( string => "", @_ ); 434 my $string = $args{string}; 435 436 my @entities = ('&', '"', '<', '>', '\n'); 437 my %entities = ('&' => '&', '"' => '"', '<' => '<', '>' => '>', '\n' => '\\n'); 438 439 return $string if (length $string == 0); 440 441 foreach my $entity (@entities) 442 { 443 $string =~ s/$entity/$entities{$entity}/g; 444 } 445 446 return $string; 447} 448 449=item scalar fixupAttributes(string) 450 451Attempts to make sure that the given string can be a valid attribute in 452an xml document. 453 454Converts (, ), -, \, /, =, >, <, & to _ 455Deletes ', ", \n 456 457=cut 458sub fixupAttributes 459{ 460 my $self = shift; 461 my $string = ""; 462 if (scalar @_ == 1) 463 { 464 $string = shift; 465 } 466 else 467 { 468 my %args = ( string => "", @_ ); 469 $string = $args{string}; 470 } 471 472 my @replace = ('&', '<', '>', '-', '\\(', '\\)', '\\\\', '/', '='); 473 my @delete = ("'", '"', '\n'); 474 475 return $string if (length $string == 0); 476 477 foreach my $entity (@replace) 478 { 479 $string =~ s/$entity/_/g; 480 } 481 482 foreach my $entity (@delete) 483 { 484 $string =~ s/$entity//g; 485 } 486 487 return $string; 488} 489 4901; 491__END__ 492 493=head1 NAME 494 495ResultSet - The XML Database ResultSet Module. 496 497=head1 SYNOPSIS 498 499 use DBIWrapper::ResultSet; 500 my $obj = DBIWrapper::ResultSet->new(); 501 502=head1 DESCRIPTION 503 504ResultSet will contain the parsed XML file generated by readXML(). 505It provides a method to validate that it is complete and also a method 506to generate a valid XML file from the data stored in the data hash. 507 508=head1 FUNCTIONS 509 510 scalar new() 511 Creates a new instance of the DBIWrapper::ResultSet 512 object. 513 514 array isValid(void) 515 Determines if the data structure is complete and usable for 516 generating an XML file from. 517 Returns an array. The first index is boolean (1 or 0 to indicate 518 if the object is valid or not). The second index is an array of 519 error messages that were generated based upon the errors found. 520 521 string displayData(style) 522 Outputs the contents of the perl data structure after validating 523 it. If style = 'text' then formats for the console, else if 524 style = 'html' we generate valid html output and format in a table. 525 The content is suitable to place straight into the <body> section 526 of your HTML page. 527 528 string generateXML(void) 529 Creates an XML file based upon the info stored in the ResultSet. 530 It first calls isValid() to make sure this is possible. If 531 not then we die with an informative error message. 532 533=head1 VARIABLES 534 535 version - version of the XML file parsed 536 537 resultFile - the name of the file parsed or the string of XML 538 539 sql - the SELECT statement issued which generated the XML 540 541 plug - the options that were plugged into the SQL statement 542 543 result - Ok or Error. Indicates the status returned by the database 544 545 error - The error string returned if result = Error 546 547 numRows - the number of rows returned from the database 548 549 columns - 0 or 1. Indicates if we had <column> children for each <row> 550 or if the data returned was stored by the name given from the 551 database in the <row> with no children tags. 552 553 rows - the array of row entries returned. Each row is a hash of 554 column name = column value attributes. 555 556 columnNames - The names of the columns used in the XML file. 557 558 errorCodes - hash of error codes to messages returned by isValid(). 559 560 NOTE: All data fields are accessible by specifying the object 561 and pointing to the data member to be modified on the 562 left-hand side of the assignment. 563 Ex. $obj->variable($newValue); or $value = $obj->variable; 564 565=head1 AUTHOR 566 567Xperience, Inc. (mailto:admin at pcxperience.com) 568 569=head1 SEE ALSO 570 571perl(1), DBIWrapper(3) 572 573=cut 574