| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | # | 
| 2 |  |  |  |  |  |  | # Web::DataService::Output | 
| 3 |  |  |  |  |  |  | # | 
| 4 |  |  |  |  |  |  | # This module provides a role that is used by 'Web::DataService'.  It implements | 
| 5 |  |  |  |  |  |  | # routines for configuring and generating data service output. | 
| 6 |  |  |  |  |  |  | # | 
| 7 |  |  |  |  |  |  | # Author: Michael McClennen | 
| 8 |  |  |  |  |  |  |  | 
| 9 | 2 |  |  | 2 |  | 15 | use strict; | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 80 |  | 
| 10 |  |  |  |  |  |  |  | 
| 11 |  |  |  |  |  |  | package Web::DataService::Output; | 
| 12 |  |  |  |  |  |  |  | 
| 13 | 2 |  |  | 2 |  | 12 | use Encode; | 
|  | 2 |  |  |  |  | 6 |  | 
|  | 2 |  |  |  |  | 171 |  | 
| 14 | 2 |  |  | 2 |  | 13 | use Scalar::Util qw(reftype); | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 84 |  | 
| 15 | 2 |  |  | 2 |  | 12 | use Carp qw(carp croak); | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 103 |  | 
| 16 |  |  |  |  |  |  |  | 
| 17 | 2 |  |  | 2 |  | 14 | use Moo::Role; | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 18 |  | 
| 18 |  |  |  |  |  |  |  | 
| 19 |  |  |  |  |  |  |  | 
| 20 |  |  |  |  |  |  | sub define_output_map { | 
| 21 |  |  |  |  |  |  |  | 
| 22 | 0 |  |  | 0 | 0 | 0 | goto \&Web::DataService::Set::define_set; | 
| 23 |  |  |  |  |  |  | } | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  |  | 
| 26 |  |  |  |  |  |  | # define_block ( name, specification... ) | 
| 27 |  |  |  |  |  |  | # | 
| 28 |  |  |  |  |  |  | # Define an output block with the specified name, using the given | 
| 29 |  |  |  |  |  |  | # specification records. | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | sub define_block { | 
| 32 |  |  |  |  |  |  |  | 
| 33 | 1 |  |  | 1 | 0 | 12 | my $ds = shift; | 
| 34 | 1 |  |  |  |  | 1 | my $name = shift; | 
| 35 |  |  |  |  |  |  |  | 
| 36 |  |  |  |  |  |  | # Check to make sure that we were given a valid name. | 
| 37 |  |  |  |  |  |  |  | 
| 38 | 1 | 50 |  |  |  | 7 | if ( ref $name ) | 
|  |  | 50 |  |  |  |  |  | 
| 39 |  |  |  |  |  |  | { | 
| 40 | 0 |  |  |  |  | 0 | croak "define_block: the first argument must be an output block name"; | 
| 41 |  |  |  |  |  |  | } | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | elsif ( not $ds->valid_name($name) ) | 
| 44 |  |  |  |  |  |  | { | 
| 45 | 0 |  |  |  |  | 0 | croak "define_block: invalid block name '$name'"; | 
| 46 |  |  |  |  |  |  | } | 
| 47 |  |  |  |  |  |  |  | 
| 48 |  |  |  |  |  |  | # Make sure the block name is unique. | 
| 49 |  |  |  |  |  |  |  | 
| 50 | 1 | 50 |  |  |  | 7 | if ( $ds->{block}{$name} ) | 
| 51 |  |  |  |  |  |  | { | 
| 52 | 0 |  |  |  |  | 0 | my $location = $ds->{block_loc}{$name}; | 
| 53 | 0 |  |  |  |  | 0 | croak "define_block: '$name' was already defined at $location\n"; | 
| 54 |  |  |  |  |  |  | } | 
| 55 |  |  |  |  |  |  |  | 
| 56 |  |  |  |  |  |  | else | 
| 57 |  |  |  |  |  |  | { | 
| 58 | 1 |  |  |  |  | 5 | my ($package, $filename, $line) = caller; | 
| 59 | 1 |  |  |  |  | 7 | $ds->{block_loc}{$name} = "$filename at line $line"; | 
| 60 |  |  |  |  |  |  | } | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | # Create a new block object. | 
| 63 |  |  |  |  |  |  |  | 
| 64 | 1 |  |  |  |  | 6 | my $block = { name => $name, | 
| 65 |  |  |  |  |  |  | include_list => [], | 
| 66 |  |  |  |  |  |  | output_list => [] }; | 
| 67 |  |  |  |  |  |  |  | 
| 68 | 1 |  |  |  |  | 10 | $ds->{block}{$name} = bless $block, 'Web::DataService::Block'; | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | # Then process the records one by one.  Make sure to throw an error if we | 
| 71 |  |  |  |  |  |  | # find a record whose type is ambiguous or that is otherwise invalid.  Each | 
| 72 |  |  |  |  |  |  | # record gets put in a list that is stored under the section name. | 
| 73 |  |  |  |  |  |  |  | 
| 74 | 1 |  |  |  |  | 2 | foreach my $item (@_) | 
| 75 |  |  |  |  |  |  | { | 
| 76 |  |  |  |  |  |  | # A scalar is interpreted as a documentation string. | 
| 77 |  |  |  |  |  |  |  | 
| 78 | 4 | 100 |  |  |  | 10 | unless ( ref $item ) | 
| 79 |  |  |  |  |  |  | { | 
| 80 | 2 |  |  |  |  | 5 | $ds->add_doc($block, $item); | 
| 81 | 2 |  |  |  |  | 5 | next; | 
| 82 |  |  |  |  |  |  | } | 
| 83 |  |  |  |  |  |  |  | 
| 84 |  |  |  |  |  |  | # Any item that is not a hashref is an error. | 
| 85 |  |  |  |  |  |  |  | 
| 86 | 2 | 50 |  |  |  | 6 | unless ( ref $item eq 'HASH' ) | 
| 87 |  |  |  |  |  |  | { | 
| 88 | 0 |  |  |  |  | 0 | croak "the arguments to 'output_section' must be hashrefs or scalars"; | 
| 89 |  |  |  |  |  |  | } | 
| 90 |  |  |  |  |  |  |  | 
| 91 |  |  |  |  |  |  | # Check the output record to make sure it was specified correctly. | 
| 92 |  |  |  |  |  |  |  | 
| 93 | 2 |  |  |  |  | 6 | my ($type) = $ds->check_output_record($item); | 
| 94 |  |  |  |  |  |  |  | 
| 95 |  |  |  |  |  |  | # If the type is 'field', then any subsequent documentation strings | 
| 96 |  |  |  |  |  |  | # will be added to that record. | 
| 97 |  |  |  |  |  |  |  | 
| 98 | 2 | 50 |  |  |  | 11 | $ds->add_doc($block, $item) if $type eq 'output'; | 
| 99 |  |  |  |  |  |  |  | 
| 100 |  |  |  |  |  |  | # Add the record to the appropriate list(s). | 
| 101 |  |  |  |  |  |  |  | 
| 102 | 2 | 50 |  |  |  | 12 | if ( $type eq 'include' ) | 
| 103 |  |  |  |  |  |  | { | 
| 104 | 0 |  |  |  |  | 0 | push @{$ds->{block}{$name}{include_list}}, $item; | 
|  | 0 |  |  |  |  | 0 |  | 
| 105 |  |  |  |  |  |  | } | 
| 106 |  |  |  |  |  |  |  | 
| 107 | 2 |  |  |  |  | 3 | push @{$ds->{block}{$name}{output_list}}, $item; | 
|  | 2 |  |  |  |  | 7 |  | 
| 108 |  |  |  |  |  |  | } | 
| 109 |  |  |  |  |  |  |  | 
| 110 | 1 |  |  |  |  | 4 | $ds->process_doc($block); | 
| 111 |  |  |  |  |  |  | } | 
| 112 |  |  |  |  |  |  |  | 
| 113 |  |  |  |  |  |  |  | 
| 114 |  |  |  |  |  |  | our %OUTPUT_DEF = (output => 'type', | 
| 115 |  |  |  |  |  |  | set => 'type', | 
| 116 |  |  |  |  |  |  | select => 'type', | 
| 117 |  |  |  |  |  |  | filter => 'type', | 
| 118 |  |  |  |  |  |  | include => 'type', | 
| 119 |  |  |  |  |  |  | check => 'type', | 
| 120 |  |  |  |  |  |  | if_block => 'set', | 
| 121 |  |  |  |  |  |  | not_block => 'set', | 
| 122 |  |  |  |  |  |  | if_vocab => 'set', | 
| 123 |  |  |  |  |  |  | not_vocab => 'set', | 
| 124 |  |  |  |  |  |  | if_format => 'set', | 
| 125 |  |  |  |  |  |  | not_format => 'set', | 
| 126 |  |  |  |  |  |  | if_field => 'single', | 
| 127 |  |  |  |  |  |  | not_field => 'single', | 
| 128 |  |  |  |  |  |  | if_code => 'code', | 
| 129 |  |  |  |  |  |  | dedup => 'single', | 
| 130 |  |  |  |  |  |  | name => 'single', | 
| 131 |  |  |  |  |  |  | value => 'single', | 
| 132 |  |  |  |  |  |  | always => 'single', | 
| 133 |  |  |  |  |  |  | text_join => 'single', | 
| 134 |  |  |  |  |  |  | xml_join => 'single', | 
| 135 |  |  |  |  |  |  | show_as_list => 'single', | 
| 136 |  |  |  |  |  |  | data_type => 'single', | 
| 137 |  |  |  |  |  |  | sub_record => 'single', | 
| 138 |  |  |  |  |  |  | from => 'single', | 
| 139 |  |  |  |  |  |  | from_each => 'single', | 
| 140 |  |  |  |  |  |  | append => 'single', | 
| 141 |  |  |  |  |  |  | code => 'code', | 
| 142 |  |  |  |  |  |  | lookup => 'hash', | 
| 143 |  |  |  |  |  |  | default => 'single', | 
| 144 |  |  |  |  |  |  | split => 'regexp', | 
| 145 |  |  |  |  |  |  | join => 'single', | 
| 146 |  |  |  |  |  |  | tables => 'set', | 
| 147 |  |  |  |  |  |  | disabled => 'single', | 
| 148 |  |  |  |  |  |  | doc_string => 'single'); | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  | our %SELECT_KEY = (select => 1, tables => 1, if_block => 1); | 
| 151 |  |  |  |  |  |  |  | 
| 152 |  |  |  |  |  |  | our %FIELD_KEY = (dedup => 1, name => 1, value => 1, always => 1, sub_record => 1, if_field => 1, | 
| 153 |  |  |  |  |  |  | not_field => 1, if_block => 1, not_block => 1, if_format => 1, not_format => 1, | 
| 154 |  |  |  |  |  |  | if_vocab => 1, not_vocab => 1, | 
| 155 |  |  |  |  |  |  | text_join => 1, xml_join => 1, doc_string => 1, show_as_list => 1, disabled => 1, undocumented => 1); | 
| 156 |  |  |  |  |  |  |  | 
| 157 |  |  |  |  |  |  | our %PROC_KEY = (set => 1, check => 1, append => 1, from => 1, from_each => 1, | 
| 158 |  |  |  |  |  |  | if_vocab => 1, not_vocab => 1, if_block => 1, not_block => 1, | 
| 159 |  |  |  |  |  |  | if_format => 1, not_format => 1, if_field => 1, not_field => 1, | 
| 160 |  |  |  |  |  |  | code => 1, lookup => 1, split => 1, join => 1, default => 1, disabled => 1); | 
| 161 |  |  |  |  |  |  |  | 
| 162 |  |  |  |  |  |  | sub check_output_record { | 
| 163 |  |  |  |  |  |  |  | 
| 164 | 2 |  |  | 2 | 0 | 5 | my ($ds, $record) = @_; | 
| 165 |  |  |  |  |  |  |  | 
| 166 | 2 |  |  |  |  | 4 | my $type = ''; | 
| 167 |  |  |  |  |  |  |  | 
| 168 | 2 |  |  |  |  | 7 | foreach my $k (keys %$record) | 
| 169 |  |  |  |  |  |  | { | 
| 170 | 2 |  |  |  |  | 3 | my $v = $record->{$k}; | 
| 171 |  |  |  |  |  |  |  | 
| 172 | 2 | 50 |  |  |  | 23 | if ( $k =~ qr{ ^ (\w+) _ (name|value) $ }x ) | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 173 |  |  |  |  |  |  | { | 
| 174 |  |  |  |  |  |  | croak "define_output: unknown format or vocab '$1' in '$k'" | 
| 175 | 0 | 0 | 0 |  |  | 0 | unless defined $ds->{vocab}{$1} || defined $ds->{format}{$1}; | 
| 176 |  |  |  |  |  |  | } | 
| 177 |  |  |  |  |  |  |  | 
| 178 |  |  |  |  |  |  | elsif ( ! defined $OUTPUT_DEF{$k} ) | 
| 179 |  |  |  |  |  |  | { | 
| 180 | 0 |  |  |  |  | 0 | croak "define_output: unrecognized attribute '$k'"; | 
| 181 |  |  |  |  |  |  | } | 
| 182 |  |  |  |  |  |  |  | 
| 183 |  |  |  |  |  |  | elsif ( $OUTPUT_DEF{$k} eq 'type' ) | 
| 184 |  |  |  |  |  |  | { | 
| 185 | 2 | 50 |  |  |  | 5 | croak "define_output: you cannot have both attributes '$type' and '$k' in one record" | 
| 186 |  |  |  |  |  |  | if $type; | 
| 187 |  |  |  |  |  |  |  | 
| 188 | 2 |  |  |  |  | 6 | $type = $k; | 
| 189 |  |  |  |  |  |  | } | 
| 190 |  |  |  |  |  |  |  | 
| 191 |  |  |  |  |  |  | elsif ( $OUTPUT_DEF{$k} eq 'single' ) | 
| 192 |  |  |  |  |  |  | { | 
| 193 | 0 | 0 |  |  |  | 0 | croak "define_output: the value of '$k' must be a scalar" if ref $v; | 
| 194 |  |  |  |  |  |  | } | 
| 195 |  |  |  |  |  |  |  | 
| 196 |  |  |  |  |  |  | elsif ( $OUTPUT_DEF{$k} eq 'set' ) | 
| 197 |  |  |  |  |  |  | { | 
| 198 | 0 | 0 | 0 |  |  | 0 | croak "define_output: the value of '$k' must be an array ref or string" | 
| 199 |  |  |  |  |  |  | if ref $v && reftype $v ne 'ARRAY'; | 
| 200 |  |  |  |  |  |  |  | 
| 201 | 0 | 0 |  |  |  | 0 | unless ( ref $v ) | 
| 202 |  |  |  |  |  |  | { | 
| 203 | 0 |  |  |  |  | 0 | $record->{$k} = [ split(qr{\s*,\s*}, $v) ]; | 
| 204 |  |  |  |  |  |  | } | 
| 205 |  |  |  |  |  |  | } | 
| 206 |  |  |  |  |  |  |  | 
| 207 |  |  |  |  |  |  | elsif ( $OUTPUT_DEF{$k} eq 'code' ) | 
| 208 |  |  |  |  |  |  | { | 
| 209 | 0 | 0 | 0 |  |  | 0 | croak "define_output: the value of '$k' must be a code ref" | 
| 210 |  |  |  |  |  |  | unless ref $v && reftype $v eq 'CODE'; | 
| 211 |  |  |  |  |  |  | } | 
| 212 |  |  |  |  |  |  |  | 
| 213 |  |  |  |  |  |  | elsif ( $OUTPUT_DEF{$k} eq 'hash' ) | 
| 214 |  |  |  |  |  |  | { | 
| 215 | 0 | 0 | 0 |  |  | 0 | croak "define_output: the value of '$k' must be a hash ref" | 
| 216 |  |  |  |  |  |  | unless ref $v && reftype $v eq 'HASH'; | 
| 217 |  |  |  |  |  |  | } | 
| 218 |  |  |  |  |  |  |  | 
| 219 |  |  |  |  |  |  | elsif ( $OUTPUT_DEF{$k} eq 'regexp' ) | 
| 220 |  |  |  |  |  |  | { | 
| 221 | 0 | 0 | 0 |  |  | 0 | croak "define_output: the value of '$k' must be a regexp or string" | 
| 222 |  |  |  |  |  |  | if ref $v && reftype $v ne 'REGEXP'; | 
| 223 |  |  |  |  |  |  | } | 
| 224 |  |  |  |  |  |  | } | 
| 225 |  |  |  |  |  |  |  | 
| 226 |  |  |  |  |  |  | # Now make sure that each record has a 'type' attribute. | 
| 227 |  |  |  |  |  |  |  | 
| 228 | 2 | 50 |  |  |  | 7 | croak "each record passed to define_output must include one attribute from the \ | 
| 229 |  |  |  |  |  |  | following list: 'include', 'output', 'set', 'select', 'filter'" | 
| 230 |  |  |  |  |  |  | unless $type; | 
| 231 |  |  |  |  |  |  |  | 
| 232 | 2 |  |  |  |  | 4 | return $type; | 
| 233 |  |  |  |  |  |  | } | 
| 234 |  |  |  |  |  |  |  | 
| 235 |  |  |  |  |  |  |  | 
| 236 |  |  |  |  |  |  | # _setup_output ( request ) | 
| 237 |  |  |  |  |  |  | # | 
| 238 |  |  |  |  |  |  | # Determine the list of selection, processing and output rules for the | 
| 239 |  |  |  |  |  |  | # specified query, based on the query's attributes.  These attributes include: | 
| 240 |  |  |  |  |  |  | # | 
| 241 |  |  |  |  |  |  | # - the output map | 
| 242 |  |  |  |  |  |  | # - the output format | 
| 243 |  |  |  |  |  |  | # - the output vocabulary | 
| 244 |  |  |  |  |  |  | # - the selected output keys | 
| 245 |  |  |  |  |  |  | # | 
| 246 |  |  |  |  |  |  | # Depending upon the attributes of the various output records, all, some or | 
| 247 |  |  |  |  |  |  | # none of them may be relevant to a particular query. | 
| 248 |  |  |  |  |  |  |  | 
| 249 |  |  |  |  |  |  | sub _setup_output { | 
| 250 |  |  |  |  |  |  |  | 
| 251 | 0 |  |  | 0 |  | 0 | my ($ds, $request) = @_; | 
| 252 |  |  |  |  |  |  |  | 
| 253 |  |  |  |  |  |  | # Extract the relevant attributes of the request | 
| 254 |  |  |  |  |  |  |  | 
| 255 | 0 |  |  |  |  | 0 | my $path = $request->node_path; | 
| 256 | 0 |  |  |  |  | 0 | my $format = $request->output_format; | 
| 257 | 0 |  |  |  |  | 0 | my $vocab = $request->output_vocab; | 
| 258 |  |  |  |  |  |  |  | 
| 259 | 0 | 0 | 0 |  |  | 0 | my $require_vocab; $require_vocab = 1 if $vocab and not $ds->{vocab}{$vocab}{use_field_names}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 260 |  |  |  |  |  |  |  | 
| 261 |  |  |  |  |  |  | # Add fields to the request object to hold the output configuration. | 
| 262 |  |  |  |  |  |  |  | 
| 263 | 0 |  |  |  |  | 0 | $request->{select_list} = []; | 
| 264 | 0 |  |  |  |  | 0 | $request->{select_hash} = {}; | 
| 265 | 0 |  |  |  |  | 0 | $request->{tables_hash} = {}; | 
| 266 | 0 |  |  |  |  | 0 | $request->{filter_hash} = {}; | 
| 267 | 0 |  |  |  |  | 0 | $request->{proc_list} = []; | 
| 268 | 0 |  |  |  |  | 0 | $request->{field_list} = []; | 
| 269 | 0 |  |  |  |  | 0 | $request->{block_keys} = {}; | 
| 270 | 0 |  |  |  |  | 0 | $request->{block_hash} = {}; | 
| 271 |  |  |  |  |  |  |  | 
| 272 |  |  |  |  |  |  | # Use the output and output_opt attributes of the request to determine | 
| 273 |  |  |  |  |  |  | # which output blocks we will be using to express the request result. | 
| 274 |  |  |  |  |  |  |  | 
| 275 |  |  |  |  |  |  | # We start with 'output', which specifies a list of blocks that are always | 
| 276 |  |  |  |  |  |  | # included. | 
| 277 |  |  |  |  |  |  |  | 
| 278 | 0 |  |  |  |  | 0 | my $output_list = $ds->node_attr($path, 'output'); | 
| 279 | 0 | 0 |  |  |  | 0 | my @output_list; @output_list = @$output_list if ref $output_list eq 'ARRAY'; | 
|  | 0 |  |  |  |  | 0 |  | 
| 280 |  |  |  |  |  |  |  | 
| 281 | 0 |  |  |  |  | 0 | my @blocks; | 
| 282 |  |  |  |  |  |  |  | 
| 283 | 0 |  |  |  |  | 0 | foreach my $block_name ( @output_list ) | 
| 284 |  |  |  |  |  |  | { | 
| 285 | 0 | 0 |  |  |  | 0 | if ( ref $ds->{block}{$block_name} eq 'Web::DataService::Block' ) | 
| 286 |  |  |  |  |  |  | { | 
| 287 | 0 |  |  |  |  | 0 | push @blocks, $block_name; | 
| 288 |  |  |  |  |  |  | } | 
| 289 |  |  |  |  |  |  |  | 
| 290 |  |  |  |  |  |  | else | 
| 291 |  |  |  |  |  |  | { | 
| 292 | 0 |  |  |  |  | 0 | $request->add_warning("Output block '$block_name' not found"); | 
| 293 |  |  |  |  |  |  | } | 
| 294 |  |  |  |  |  |  | } | 
| 295 |  |  |  |  |  |  |  | 
| 296 |  |  |  |  |  |  | # The special parameter 'show' is used to select optional output blocks. | 
| 297 |  |  |  |  |  |  |  | 
| 298 | 0 |  |  |  |  | 0 | my @optional_keys = $request->special_value('show'); | 
| 299 |  |  |  |  |  |  |  | 
| 300 |  |  |  |  |  |  | # The attribute 'optional_output' specifies a map which maps the keys from the | 
| 301 |  |  |  |  |  |  | # output_param value to block names.  We go through the keys one by one | 
| 302 |  |  |  |  |  |  | # and add each key and the name of the associated block to the relevant hash. | 
| 303 |  |  |  |  |  |  |  | 
| 304 | 0 |  |  |  |  | 0 | my $optional_output = $ds->node_attr($path, 'optional_output'); | 
| 305 | 0 |  |  |  |  | 0 | my $output_map; $output_map = $ds->{set}{$optional_output} if defined $optional_output && | 
| 306 | 0 | 0 | 0 |  |  | 0 | ref $ds->{set}{$optional_output} eq 'Web::DataService::Set'; | 
| 307 |  |  |  |  |  |  |  | 
| 308 | 0 | 0 |  |  |  | 0 | if ( $output_map ) | 
|  |  | 0 |  |  |  |  |  | 
| 309 |  |  |  |  |  |  | { | 
| 310 | 0 |  |  |  |  | 0 | foreach my $key ( @optional_keys ) | 
| 311 |  |  |  |  |  |  | { | 
| 312 | 0 | 0 |  |  |  | 0 | next unless defined $key; | 
| 313 | 0 |  |  |  |  | 0 | my $block = $output_map->{value}{$key}{maps_to}; | 
| 314 | 0 |  |  |  |  | 0 | $request->{block_keys}{$key} = 1; | 
| 315 | 0 |  |  |  |  | 0 | $request->{block_hash}{$key} = 1; | 
| 316 |  |  |  |  |  |  |  | 
| 317 | 0 | 0 | 0 |  |  | 0 | if ( $block && ref $ds->{block}{$block} eq 'Web::DataService::Block' ) | 
|  |  | 0 |  |  |  |  |  | 
| 318 |  |  |  |  |  |  | { | 
| 319 | 0 |  |  |  |  | 0 | $request->{block_hash}{$block} = $key; | 
| 320 | 0 |  |  |  |  | 0 | push @blocks, $block; | 
| 321 |  |  |  |  |  |  | } | 
| 322 |  |  |  |  |  |  |  | 
| 323 |  |  |  |  |  |  | elsif ( $block ) | 
| 324 |  |  |  |  |  |  | { | 
| 325 | 0 |  |  |  |  | 0 | $request->add_warning("Output block '$block' not found"); | 
| 326 |  |  |  |  |  |  | } | 
| 327 |  |  |  |  |  |  | } | 
| 328 |  |  |  |  |  |  | } | 
| 329 |  |  |  |  |  |  |  | 
| 330 |  |  |  |  |  |  | elsif ( $optional_output ) | 
| 331 |  |  |  |  |  |  | { | 
| 332 | 0 |  |  |  |  | 0 | $request->add_warning("Output map '$optional_output' not found"); | 
| 333 |  |  |  |  |  |  | } | 
| 334 |  |  |  |  |  |  |  | 
| 335 |  |  |  |  |  |  | # Now warn the user if no output blocks were specified for this request, | 
| 336 |  |  |  |  |  |  | # because it means that no output will result. | 
| 337 |  |  |  |  |  |  |  | 
| 338 | 0 | 0 |  |  |  | 0 | unless ( @blocks ) | 
| 339 |  |  |  |  |  |  | { | 
| 340 | 0 |  |  |  |  | 0 | $request->add_warning("No output blocks were specified for this request."); | 
| 341 | 0 |  |  |  |  | 0 | return; | 
| 342 |  |  |  |  |  |  | } | 
| 343 |  |  |  |  |  |  |  | 
| 344 |  |  |  |  |  |  | # Then scan through the list of blocks and check for include_list | 
| 345 |  |  |  |  |  |  | # entries, and add the included blocks to the list as well.  This | 
| 346 |  |  |  |  |  |  | # allows us to know before the rest of the processing exactly which blocks | 
| 347 |  |  |  |  |  |  | # are included. | 
| 348 |  |  |  |  |  |  |  | 
| 349 | 0 |  |  |  |  | 0 | my %uniq_block; | 
| 350 | 0 |  |  |  |  | 0 | my @include_scan = @blocks; | 
| 351 | 0 |  |  |  |  | 0 | my $bound = 0; | 
| 352 |  |  |  |  |  |  |  | 
| 353 |  |  |  |  |  |  | INCLUDE_BLOCK: | 
| 354 | 0 |  |  |  |  | 0 | while ( my $block = shift @include_scan ) | 
| 355 |  |  |  |  |  |  | { | 
| 356 |  |  |  |  |  |  | # Make sure that each block is checked only once, and add a bounds | 
| 357 |  |  |  |  |  |  | # check to prevent a runaway loop. | 
| 358 |  |  |  |  |  |  |  | 
| 359 | 0 | 0 |  |  |  | 0 | next if $uniq_block{$block}; $uniq_block{$block} = 1; | 
|  | 0 |  |  |  |  | 0 |  | 
| 360 | 0 | 0 |  |  |  | 0 | next if ++$bound > 999; | 
| 361 |  |  |  |  |  |  |  | 
| 362 | 0 |  |  |  |  | 0 | my $include_list = $ds->{block}{$block}{include_list}; | 
| 363 | 0 | 0 |  |  |  | 0 | next unless ref $include_list eq 'ARRAY'; | 
| 364 |  |  |  |  |  |  |  | 
| 365 |  |  |  |  |  |  | INCLUDE_RECORD: | 
| 366 | 0 |  |  |  |  | 0 | foreach my $r ( @$include_list ) | 
| 367 |  |  |  |  |  |  | { | 
| 368 |  |  |  |  |  |  | # Evaluate dependency on the output section list | 
| 369 |  |  |  |  |  |  |  | 
| 370 |  |  |  |  |  |  | next INCLUDE_RECORD if $r->{if_block} | 
| 371 | 0 | 0 | 0 |  |  | 0 | and not check_set($r->{if_block}, $request->{block_hash}); | 
| 372 |  |  |  |  |  |  |  | 
| 373 |  |  |  |  |  |  | next INCLUDE_RECORD if $r->{not_block} | 
| 374 | 0 | 0 | 0 |  |  | 0 | and check_set($r->{not_block}, $request->{block_hash}); | 
| 375 |  |  |  |  |  |  |  | 
| 376 |  |  |  |  |  |  | # Evaluate dependency on the output format | 
| 377 |  |  |  |  |  |  |  | 
| 378 |  |  |  |  |  |  | next INCLUDE_RECORD if $r->{if_format} | 
| 379 | 0 | 0 | 0 |  |  | 0 | and not check_value($r->{if_format}, $format); | 
| 380 |  |  |  |  |  |  |  | 
| 381 |  |  |  |  |  |  | next INCLUDE_RECORD if $r->{not_format} | 
| 382 | 0 | 0 | 0 |  |  | 0 | and check_value($r->{not_format}, $format); | 
| 383 |  |  |  |  |  |  |  | 
| 384 |  |  |  |  |  |  | # Evaluate dependency on the vocabulary | 
| 385 |  |  |  |  |  |  |  | 
| 386 |  |  |  |  |  |  | next INCLUDE_RECORD if $r->{if_vocab} | 
| 387 | 0 | 0 | 0 |  |  | 0 | and not check_value($r->{if_vocab}, $vocab); | 
| 388 |  |  |  |  |  |  |  | 
| 389 |  |  |  |  |  |  | next INCLUDE_RECORD if $r->{not_vocab} | 
| 390 | 0 | 0 | 0 |  |  | 0 | and check_value($r->{not_vocab}, $vocab); | 
| 391 |  |  |  |  |  |  |  | 
| 392 |  |  |  |  |  |  | # If the 'include' record specified a key, figure out its | 
| 393 |  |  |  |  |  |  | # corresponding block if any. | 
| 394 |  |  |  |  |  |  |  | 
| 395 | 0 |  |  |  |  | 0 | my ($include_key, $include_block); | 
| 396 |  |  |  |  |  |  |  | 
| 397 | 0 | 0 |  |  |  | 0 | if ( ref $output_map->{value}{$r->{include}} ) | 
| 398 |  |  |  |  |  |  | { | 
| 399 | 0 |  |  |  |  | 0 | $include_key = $r->{include}; | 
| 400 | 0 |  |  |  |  | 0 | $include_block = $output_map->{value}{$include_key}{maps_to}; | 
| 401 |  |  |  |  |  |  | } | 
| 402 |  |  |  |  |  |  |  | 
| 403 |  |  |  |  |  |  | else | 
| 404 |  |  |  |  |  |  | { | 
| 405 | 0 |  |  |  |  | 0 | $include_block = $r->{include}; | 
| 406 |  |  |  |  |  |  | } | 
| 407 |  |  |  |  |  |  |  | 
| 408 |  |  |  |  |  |  | # Modify the record so that we know what block to include in the | 
| 409 |  |  |  |  |  |  | # loop below. | 
| 410 |  |  |  |  |  |  |  | 
| 411 | 0 | 0 |  |  |  | 0 | $r->{include_block} = $include_block if $include_block; | 
| 412 |  |  |  |  |  |  |  | 
| 413 |  |  |  |  |  |  | # Now add the specified key and block to the output hash, if they | 
| 414 |  |  |  |  |  |  | # are defined. | 
| 415 |  |  |  |  |  |  |  | 
| 416 | 0 | 0 |  |  |  | 0 | $request->{block_keys}{$include_key} = 1 if $include_key; | 
| 417 | 0 | 0 |  |  |  | 0 | $request->{block_hash}{$include_block} = 1 if $include_block; | 
| 418 | 0 | 0 |  |  |  | 0 | push @include_scan, $include_block if $include_block; | 
| 419 |  |  |  |  |  |  | } | 
| 420 |  |  |  |  |  |  | } | 
| 421 |  |  |  |  |  |  |  | 
| 422 |  |  |  |  |  |  | # Now run through all of the blocks we have identified and collect up the | 
| 423 |  |  |  |  |  |  | # various kinds of records they contain. | 
| 424 |  |  |  |  |  |  |  | 
| 425 | 0 |  |  |  |  | 0 | %uniq_block = ();	# $$$$ | 
| 426 |  |  |  |  |  |  |  | 
| 427 |  |  |  |  |  |  | BLOCK: | 
| 428 | 0 |  |  |  |  | 0 | foreach my $block (@blocks) | 
| 429 |  |  |  |  |  |  | { | 
| 430 |  |  |  |  |  |  | # Add this block to the output configuration. | 
| 431 |  |  |  |  |  |  |  | 
| 432 | 0 |  |  |  |  | 0 | $ds->add_output_block($request, \%uniq_block, $block); | 
| 433 |  |  |  |  |  |  | } | 
| 434 |  |  |  |  |  |  |  | 
| 435 | 0 |  |  |  |  | 0 | my $a = 1;	# We can stop here when debugging | 
| 436 |  |  |  |  |  |  | } | 
| 437 |  |  |  |  |  |  |  | 
| 438 |  |  |  |  |  |  |  | 
| 439 |  |  |  |  |  |  | # add_output_block ( request, block_name ) | 
| 440 |  |  |  |  |  |  | # | 
| 441 |  |  |  |  |  |  | # Add the specified block to the output configuration for the specified | 
| 442 |  |  |  |  |  |  | # request. | 
| 443 |  |  |  |  |  |  |  | 
| 444 |  |  |  |  |  |  | sub add_output_block { | 
| 445 |  |  |  |  |  |  |  | 
| 446 | 0 |  |  | 0 | 0 | 0 | my ($ds, $request, $uniq_block, $block_name) = @_; | 
| 447 |  |  |  |  |  |  |  | 
| 448 |  |  |  |  |  |  | # Make sure that each block is only processed once, even if it is | 
| 449 |  |  |  |  |  |  | # listed more than once. | 
| 450 |  |  |  |  |  |  |  | 
| 451 | 0 | 0 |  |  |  | 0 | return if $uniq_block->{$block_name}; $uniq_block->{$block_name} = 1; | 
|  | 0 |  |  |  |  | 0 |  | 
| 452 |  |  |  |  |  |  |  | 
| 453 |  |  |  |  |  |  | # Generate a warning if the specified block does not exist, but do | 
| 454 |  |  |  |  |  |  | # not abort the request. | 
| 455 |  |  |  |  |  |  |  | 
| 456 | 0 |  |  |  |  | 0 | my $block_list = $ds->{block}{$block_name}{output_list}; | 
| 457 |  |  |  |  |  |  |  | 
| 458 | 0 | 0 |  |  |  | 0 | unless ( ref $block_list eq 'ARRAY' ) | 
| 459 |  |  |  |  |  |  | { | 
| 460 | 0 |  |  |  |  | 0 | warn "undefined output block '$block_name' for path '$request->{path}'\n"; | 
| 461 | 0 |  |  |  |  | 0 | $request->add_warning("undefined output block '$block_name'"); | 
| 462 | 0 |  |  |  |  | 0 | return; | 
| 463 |  |  |  |  |  |  | } | 
| 464 |  |  |  |  |  |  |  | 
| 465 |  |  |  |  |  |  | # Extract the relevant request attributes. | 
| 466 |  |  |  |  |  |  |  | 
| 467 | 0 |  |  |  |  | 0 | my $class = ref $request; | 
| 468 | 0 |  |  |  |  | 0 | my $format = $request->output_format; | 
| 469 | 0 |  |  |  |  | 0 | my $vocab = $request->output_vocab; | 
| 470 | 0 | 0 | 0 |  |  | 0 | my $require_vocab; $require_vocab = 1 if $vocab and not $ds->{vocab}{$vocab}{use_field_names}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 471 |  |  |  |  |  |  |  | 
| 472 |  |  |  |  |  |  | # Now go through the output list for this block and collect up | 
| 473 |  |  |  |  |  |  | # all records that are selected for this query. | 
| 474 |  |  |  |  |  |  |  | 
| 475 | 0 |  |  |  |  | 0 | my @records = @$block_list; | 
| 476 |  |  |  |  |  |  |  | 
| 477 |  |  |  |  |  |  | RECORD: | 
| 478 | 0 |  |  |  |  | 0 | while ( my $r = shift @records ) | 
| 479 |  |  |  |  |  |  | { | 
| 480 |  |  |  |  |  |  | # Evaluate dependency on the output block list | 
| 481 |  |  |  |  |  |  |  | 
| 482 |  |  |  |  |  |  | next RECORD if $r->{if_block} | 
| 483 | 0 | 0 | 0 |  |  | 0 | and not check_set($r->{if_block}, $request->{block_hash}); | 
| 484 |  |  |  |  |  |  |  | 
| 485 |  |  |  |  |  |  | next RECORD if $r->{not_block} | 
| 486 | 0 | 0 | 0 |  |  | 0 | and check_set($r->{not_block}, $request->{block_hash}); | 
| 487 |  |  |  |  |  |  |  | 
| 488 |  |  |  |  |  |  | # Evaluate dependency on the output format | 
| 489 |  |  |  |  |  |  |  | 
| 490 |  |  |  |  |  |  | next RECORD if $r->{if_format} | 
| 491 | 0 | 0 | 0 |  |  | 0 | and not check_value($r->{if_format}, $format); | 
| 492 |  |  |  |  |  |  |  | 
| 493 |  |  |  |  |  |  | next RECORD if $r->{not_format} | 
| 494 | 0 | 0 | 0 |  |  | 0 | and check_value($r->{not_format}, $format); | 
| 495 |  |  |  |  |  |  |  | 
| 496 |  |  |  |  |  |  | # Evaluate dependency on the vocabulary | 
| 497 |  |  |  |  |  |  |  | 
| 498 |  |  |  |  |  |  | next RECORD if $r->{if_vocab} | 
| 499 | 0 | 0 | 0 |  |  | 0 | and not check_value($r->{if_vocab}, $vocab); | 
| 500 |  |  |  |  |  |  |  | 
| 501 |  |  |  |  |  |  | next RECORD if $r->{not_vocab} | 
| 502 | 0 | 0 | 0 |  |  | 0 | and check_value($r->{not_vocab}, $vocab); | 
| 503 |  |  |  |  |  |  |  | 
| 504 |  |  |  |  |  |  | # If the record type is 'select', add to the selection list, the | 
| 505 |  |  |  |  |  |  | # selection hash, and the tables hash. | 
| 506 |  |  |  |  |  |  |  | 
| 507 | 0 | 0 | 0 |  |  | 0 | if ( $r->{select} ) | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 508 |  |  |  |  |  |  | { | 
| 509 |  |  |  |  |  |  | croak "value of 'select' must be a string or array" | 
| 510 | 0 | 0 | 0 |  |  | 0 | if ref $r->{select} && ref $r->{select} ne 'ARRAY'; | 
| 511 |  |  |  |  |  |  |  | 
| 512 | 0 |  |  |  |  | 0 | my @select = ref $r->{select} ? @{$r->{select}} | 
| 513 | 0 | 0 |  |  |  | 0 | : split qr{\s*,\s*}, $r->{select}; | 
| 514 |  |  |  |  |  |  |  | 
| 515 | 0 |  |  |  |  | 0 | foreach my $s ( @select ) | 
| 516 |  |  |  |  |  |  | { | 
| 517 | 0 | 0 |  |  |  | 0 | next if exists $request->{select_hash}{$s}; | 
| 518 | 0 |  |  |  |  | 0 | $request->{select_hash}{$s} = 1; | 
| 519 | 0 |  |  |  |  | 0 | push @{$request->{select_list}}, $s; | 
|  | 0 |  |  |  |  | 0 |  | 
| 520 |  |  |  |  |  |  | } | 
| 521 |  |  |  |  |  |  |  | 
| 522 | 0 | 0 |  |  |  | 0 | if ( $r->{tables} ) | 
| 523 |  |  |  |  |  |  | { | 
| 524 |  |  |  |  |  |  | croak "value of 'tables' must be a string or array" | 
| 525 | 0 | 0 | 0 |  |  | 0 | if ref $r->{tables} && ref $r->{tables} ne 'ARRAY'; | 
| 526 |  |  |  |  |  |  |  | 
| 527 | 0 |  |  |  |  | 0 | my @tables = ref $r->{tables} ? @{$r->{tables}} | 
| 528 | 0 | 0 |  |  |  | 0 | : split qr{\s*,\s*}, $r->{tables}; | 
| 529 |  |  |  |  |  |  |  | 
| 530 | 0 |  |  |  |  | 0 | foreach my $t ( @tables ) | 
| 531 |  |  |  |  |  |  | { | 
| 532 | 0 |  |  |  |  | 0 | $request->{tables_hash}{$t} = 1; | 
| 533 |  |  |  |  |  |  | } | 
| 534 |  |  |  |  |  |  | } | 
| 535 |  |  |  |  |  |  |  | 
| 536 | 0 |  |  |  |  | 0 | foreach my $k ( keys %$r ) | 
| 537 |  |  |  |  |  |  | { | 
| 538 |  |  |  |  |  |  | warn "ignored invalid key '$k' in 'select' record" | 
| 539 | 0 | 0 |  |  |  | 0 | unless $SELECT_KEY{$k}; | 
| 540 |  |  |  |  |  |  | } | 
| 541 |  |  |  |  |  |  | } | 
| 542 |  |  |  |  |  |  |  | 
| 543 |  |  |  |  |  |  | # If the record type is 'filter', add to the filter hash. | 
| 544 |  |  |  |  |  |  |  | 
| 545 |  |  |  |  |  |  | elsif ( defined $r->{filter} ) | 
| 546 |  |  |  |  |  |  | { | 
| 547 | 0 |  |  |  |  | 0 | $request->{filter_hash}{$r->{filter}} = $r->{value}; | 
| 548 |  |  |  |  |  |  | } | 
| 549 |  |  |  |  |  |  |  | 
| 550 |  |  |  |  |  |  | # If the record type is 'set' or 'check', add a record to the process list. | 
| 551 |  |  |  |  |  |  |  | 
| 552 |  |  |  |  |  |  | elsif ( defined $r->{set} || defined $r->{check} ) | 
| 553 |  |  |  |  |  |  | { | 
| 554 | 0 |  |  |  |  | 0 | my $proc = { set => $r->{set} }; | 
| 555 |  |  |  |  |  |  |  | 
| 556 | 0 |  |  |  |  | 0 | foreach my $key ( keys %$r ) | 
| 557 |  |  |  |  |  |  | { | 
| 558 | 0 | 0 |  |  |  | 0 | if ( $PROC_KEY{$key} ) | 
| 559 |  |  |  |  |  |  | { | 
| 560 | 0 |  |  |  |  | 0 | $proc->{$key} = $r->{$key}; | 
| 561 |  |  |  |  |  |  | } | 
| 562 |  |  |  |  |  |  |  | 
| 563 |  |  |  |  |  |  | else | 
| 564 |  |  |  |  |  |  | { | 
| 565 | 0 |  |  |  |  | 0 | carp "Warning: unknown key '$key' in proc record\n"; | 
| 566 |  |  |  |  |  |  | } | 
| 567 |  |  |  |  |  |  | } | 
| 568 |  |  |  |  |  |  |  | 
| 569 | 0 |  |  |  |  | 0 | push @{$request->{proc_list}}, $proc; | 
|  | 0 |  |  |  |  | 0 |  | 
| 570 |  |  |  |  |  |  |  | 
| 571 |  |  |  |  |  |  | # If this is a 'check' rule, then complain if the values don't | 
| 572 |  |  |  |  |  |  | # make sense.  Also note that we will have to process the result | 
| 573 |  |  |  |  |  |  | # set in its entirety if we need to compute the size. | 
| 574 |  |  |  |  |  |  |  | 
| 575 | 0 | 0 |  |  |  | 0 | if ( defined $r->{check} ) | 
| 576 |  |  |  |  |  |  | { | 
| 577 | 0 |  |  |  |  | 0 | $request->{process_before_count} = 1; | 
| 578 |  |  |  |  |  |  |  | 
| 579 | 0 |  |  |  |  | 0 | my $check_value = $r->{check}; | 
| 580 |  |  |  |  |  |  |  | 
| 581 | 0 | 0 | 0 |  |  | 0 | if ( $check_value eq '*' || $check_value eq '' ) | 
|  |  | 0 |  |  |  |  |  | 
| 582 |  |  |  |  |  |  | { | 
| 583 |  |  |  |  |  |  | croak "the value of 'code' must be a code ref" | 
| 584 | 0 | 0 |  |  |  | 0 | unless ref $r->{code} eq 'CODE'; | 
| 585 |  |  |  |  |  |  | } | 
| 586 |  |  |  |  |  |  |  | 
| 587 |  |  |  |  |  |  | elsif ( defined $r->{lookup} ) | 
| 588 |  |  |  |  |  |  | { | 
| 589 |  |  |  |  |  |  | croak "the value of 'lookup' must be a hash ref" | 
| 590 | 0 | 0 |  |  |  | 0 | unless ref $r->{lookup} eq 'HASH'; | 
| 591 |  |  |  |  |  |  | } | 
| 592 |  |  |  |  |  |  | } | 
| 593 |  |  |  |  |  |  | } | 
| 594 |  |  |  |  |  |  |  | 
| 595 |  |  |  |  |  |  | # If the record type is 'output', add a record to the field list. | 
| 596 |  |  |  |  |  |  | # The attributes 'name' (the output name) and 'field' (the raw | 
| 597 |  |  |  |  |  |  | # field name) are both set to the indicated name by default. | 
| 598 |  |  |  |  |  |  |  | 
| 599 |  |  |  |  |  |  | elsif ( defined $r->{output} ) | 
| 600 |  |  |  |  |  |  | { | 
| 601 | 0 | 0 |  |  |  | 0 | croak "the value of 'output' must be non-empty" unless $r->{output} ne ''; | 
| 602 |  |  |  |  |  |  |  | 
| 603 | 0 | 0 | 0 |  |  | 0 | next RECORD if $require_vocab and not exists $r->{"${vocab}_name"}; | 
| 604 |  |  |  |  |  |  |  | 
| 605 | 0 |  |  |  |  | 0 | my $field = { field => $r->{output}, name => $r->{output} }; | 
| 606 | 0 |  |  |  |  | 0 | my ($vs_value, $vs_name); | 
| 607 |  |  |  |  |  |  |  | 
| 608 | 0 |  |  |  |  | 0 | foreach my $key ( keys %$r ) | 
| 609 |  |  |  |  |  |  | { | 
| 610 | 0 | 0 |  |  |  | 0 | if ( $FIELD_KEY{$key} ) | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 611 |  |  |  |  |  |  | { | 
| 612 | 0 | 0 | 0 |  |  | 0 | $field->{$key} = $r->{$key} | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
| 613 |  |  |  |  |  |  | unless ($key eq 'value' && $vs_value) || ($key eq 'name' && $vs_name); | 
| 614 |  |  |  |  |  |  | } | 
| 615 |  |  |  |  |  |  |  | 
| 616 |  |  |  |  |  |  | elsif ( $key =~ qr{ ^ (\w+) _ (name|value) $ }x ) | 
| 617 |  |  |  |  |  |  | { | 
| 618 | 0 | 0 | 0 |  |  | 0 | if ( $1 eq $vocab || $1 eq $format ) | 
| 619 |  |  |  |  |  |  | { | 
| 620 | 0 |  |  |  |  | 0 | $field->{$2} = $r->{$key}; | 
| 621 | 0 | 0 |  |  |  | 0 | $vs_value = 1 if $2 eq 'value'; | 
| 622 | 0 | 0 |  |  |  | 0 | $vs_name = 1 if $2 eq 'name'; | 
| 623 |  |  |  |  |  |  | } | 
| 624 |  |  |  |  |  |  | } | 
| 625 |  |  |  |  |  |  |  | 
| 626 |  |  |  |  |  |  | elsif ( $key eq 'data_type' ) | 
| 627 |  |  |  |  |  |  | { | 
| 628 | 0 |  |  |  |  | 0 | my $type_value = $r->{data_type}; | 
| 629 | 0 | 0 | 0 |  |  | 0 | croak "unknown value '$r->{data_type}' for data_type: must be one of 'int', 'pos', 'dec', 'str'" | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
| 630 |  |  |  |  |  |  | unless lc $type_value eq 'int' || lc $type_value eq 'pos' || | 
| 631 |  |  |  |  |  |  | lc $type_value eq 'dec' || lc $type_value eq 'str'; | 
| 632 |  |  |  |  |  |  |  | 
| 633 | 0 |  |  |  |  | 0 | $field->{data_type} = $r->{data_type}; | 
| 634 | 0 |  |  |  |  | 0 | push @{$request->{proc_list}}, { check_field => $r->{output}, data_type => $r->{data_type} } | 
| 635 | 0 | 0 |  |  |  | 0 | unless $r->{data_type} eq 'str'; | 
| 636 |  |  |  |  |  |  | } | 
| 637 |  |  |  |  |  |  |  | 
| 638 |  |  |  |  |  |  | elsif ( $key ne 'output' ) | 
| 639 |  |  |  |  |  |  | { | 
| 640 | 0 |  |  |  |  | 0 | warn "Warning: unknown key '$key' in output record\n"; | 
| 641 |  |  |  |  |  |  | } | 
| 642 |  |  |  |  |  |  | } | 
| 643 |  |  |  |  |  |  |  | 
| 644 | 0 |  |  |  |  | 0 | push @{$request->{field_list}}, $field; | 
|  | 0 |  |  |  |  | 0 |  | 
| 645 |  |  |  |  |  |  | } | 
| 646 |  |  |  |  |  |  |  | 
| 647 |  |  |  |  |  |  | # If the record type is 'include', then add the specified records | 
| 648 |  |  |  |  |  |  | # to the list immediately.  If no 'include_block' was | 
| 649 |  |  |  |  |  |  | # specified, that means that the specified key did not correspond | 
| 650 |  |  |  |  |  |  | # to any block.  So we can ignore it in that case. | 
| 651 |  |  |  |  |  |  |  | 
| 652 |  |  |  |  |  |  | elsif ( defined $r->{include_block} ) | 
| 653 |  |  |  |  |  |  | { | 
| 654 |  |  |  |  |  |  | # If we have already processed this block, then skip it.  A | 
| 655 |  |  |  |  |  |  | # block can only be included once per request.  If we haven't | 
| 656 |  |  |  |  |  |  | # processed it yet, mark it so that it will be skipped if it | 
| 657 |  |  |  |  |  |  | # comes up again. | 
| 658 |  |  |  |  |  |  |  | 
| 659 | 0 |  |  |  |  | 0 | my $include_block = $r->{include_block}; | 
| 660 | 0 | 0 |  |  |  | 0 | next RECORD if $uniq_block->{$include_block}; | 
| 661 | 0 |  |  |  |  | 0 | $uniq_block->{$include_block} = 1; | 
| 662 |  |  |  |  |  |  |  | 
| 663 |  |  |  |  |  |  | # Get the list of block records, or add a warning if no block | 
| 664 |  |  |  |  |  |  | # was defined under that name. | 
| 665 |  |  |  |  |  |  |  | 
| 666 | 0 |  |  |  |  | 0 | my $add_list = $ds->{block}{$include_block}{output_list}; | 
| 667 |  |  |  |  |  |  |  | 
| 668 | 0 | 0 |  |  |  | 0 | unless ( ref $add_list eq 'ARRAY' ) | 
| 669 |  |  |  |  |  |  | { | 
| 670 | 0 |  |  |  |  | 0 | warn "undefined output block '$include_block' for path '$request->{path}'\n"; | 
| 671 | 0 |  |  |  |  | 0 | $request->add_warning("undefined output block '$include_block'"); | 
| 672 | 0 |  |  |  |  | 0 | next RECORD; | 
| 673 |  |  |  |  |  |  | } | 
| 674 |  |  |  |  |  |  |  | 
| 675 |  |  |  |  |  |  | # Now add the included block's records to the front of the | 
| 676 |  |  |  |  |  |  | # record list. | 
| 677 |  |  |  |  |  |  |  | 
| 678 | 0 |  |  |  |  | 0 | unshift @records, @$add_list; | 
| 679 |  |  |  |  |  |  | } | 
| 680 |  |  |  |  |  |  | } | 
| 681 |  |  |  |  |  |  |  | 
| 682 | 0 |  |  |  |  | 0 | my $a = 1;	# we can stop here when debugging | 
| 683 |  |  |  |  |  |  | } | 
| 684 |  |  |  |  |  |  |  | 
| 685 |  |  |  |  |  |  |  | 
| 686 |  |  |  |  |  |  | # get_output_map ( name ) | 
| 687 |  |  |  |  |  |  | # | 
| 688 |  |  |  |  |  |  | # If the specified name is the name of an output map, return a reference to | 
| 689 |  |  |  |  |  |  | # the map.  Otherwise, return undefined. | 
| 690 |  |  |  |  |  |  |  | 
| 691 |  |  |  |  |  |  | sub get_output_map { | 
| 692 |  |  |  |  |  |  |  | 
| 693 | 0 |  |  | 0 | 0 | 0 | my ($ds, $output_name) = @_; | 
| 694 |  |  |  |  |  |  |  | 
| 695 | 0 | 0 |  |  |  | 0 | if ( ref $ds->{set}{$output_name} eq 'Web::DataService::Set' ) | 
| 696 |  |  |  |  |  |  | { | 
| 697 | 0 |  |  |  |  | 0 | return $ds->{set}{$output_name}; | 
| 698 |  |  |  |  |  |  | } | 
| 699 |  |  |  |  |  |  |  | 
| 700 | 0 |  |  |  |  | 0 | return; | 
| 701 |  |  |  |  |  |  | } | 
| 702 |  |  |  |  |  |  |  | 
| 703 |  |  |  |  |  |  |  | 
| 704 |  |  |  |  |  |  | # get_output_block ( name ) | 
| 705 |  |  |  |  |  |  | # | 
| 706 |  |  |  |  |  |  | # If the specified name is the name of an output block, return a reference to | 
| 707 |  |  |  |  |  |  | # the block.  Otherwise, return empty. | 
| 708 |  |  |  |  |  |  |  | 
| 709 |  |  |  |  |  |  | sub get_output_block { | 
| 710 |  |  |  |  |  |  |  | 
| 711 | 0 |  |  | 0 | 0 | 0 | my ($ds, $output_name) = @_; | 
| 712 |  |  |  |  |  |  |  | 
| 713 | 0 | 0 |  |  |  | 0 | if ( ref $ds->{block}{$output_name} eq 'Web::DataService::Block' ) | 
| 714 |  |  |  |  |  |  | { | 
| 715 | 0 |  |  |  |  | 0 | return $ds->{block}{$output_name}; | 
| 716 |  |  |  |  |  |  | } | 
| 717 |  |  |  |  |  |  |  | 
| 718 | 0 |  |  |  |  | 0 | return; | 
| 719 |  |  |  |  |  |  | } | 
| 720 |  |  |  |  |  |  |  | 
| 721 |  |  |  |  |  |  |  | 
| 722 |  |  |  |  |  |  | # get_output_keys ( request, map ) | 
| 723 |  |  |  |  |  |  | # | 
| 724 |  |  |  |  |  |  | # Figure out which output keys have been selected for the specified request, | 
| 725 |  |  |  |  |  |  | # using the specified output map. | 
| 726 |  |  |  |  |  |  |  | 
| 727 |  |  |  |  |  |  | sub get_output_keys { | 
| 728 |  |  |  |  |  |  |  | 
| 729 | 0 |  |  | 0 | 0 | 0 | my ($ds, $request, $output_map) = @_; | 
| 730 |  |  |  |  |  |  |  | 
| 731 | 0 |  |  |  |  | 0 | my $path = $request->{path}; | 
| 732 |  |  |  |  |  |  |  | 
| 733 |  |  |  |  |  |  | # Return empty unless we have a map. | 
| 734 |  |  |  |  |  |  |  | 
| 735 | 0 | 0 |  |  |  | 0 | return unless ref $output_map eq 'Web::DataService::Set'; | 
| 736 |  |  |  |  |  |  |  | 
| 737 |  |  |  |  |  |  | # Start with the fixed blocks. | 
| 738 |  |  |  |  |  |  |  | 
| 739 | 0 | 0 |  |  |  | 0 | my @keys; @keys = @{$output_map->{fixed}} if ref $output_map->{fixed} eq 'ARRAY'; | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 740 |  |  |  |  |  |  |  | 
| 741 |  |  |  |  |  |  | # Then add the optional blocks. | 
| 742 |  |  |  |  |  |  |  | 
| 743 | 0 |  |  |  |  | 0 | my $output_param = $ds->{node_attrs}{$path}{output_param};   # re-do | 
| 744 |  |  |  |  |  |  | # with ->node_attrs | 
| 745 |  |  |  |  |  |  |  | 
| 746 | 0 |  |  |  |  | 0 | push @keys, @{$request->{params}{$output_param}} | 
| 747 | 0 | 0 | 0 |  |  | 0 | if defined $output_param and ref $request->{params}{$output_param} eq 'ARRAY'; | 
| 748 |  |  |  |  |  |  |  | 
| 749 | 0 |  |  |  |  | 0 | return @keys; | 
| 750 |  |  |  |  |  |  | } | 
| 751 |  |  |  |  |  |  |  | 
| 752 |  |  |  |  |  |  |  | 
| 753 |  |  |  |  |  |  | # configure_block ( request, block_name ) | 
| 754 |  |  |  |  |  |  | # | 
| 755 |  |  |  |  |  |  | # Given a block name, determine the list of output fields and proc fields | 
| 756 |  |  |  |  |  |  | # (if any) that are defined for it.  This is used primarily to configure | 
| 757 |  |  |  |  |  |  | # blocks referred to via 'sub_record' attributes. | 
| 758 |  |  |  |  |  |  | # | 
| 759 |  |  |  |  |  |  | # These lists are stored under the keys 'block_proc_list' and | 
| 760 |  |  |  |  |  |  | # 'block_field_list' in the request record.  If these have already been filled | 
| 761 |  |  |  |  |  |  | # in for this block, do nothing. | 
| 762 |  |  |  |  |  |  |  | 
| 763 |  |  |  |  |  |  | sub configure_block { | 
| 764 |  |  |  |  |  |  |  | 
| 765 | 0 |  |  | 0 | 0 | 0 | my ($ds, $request, $block_name) = @_; | 
| 766 |  |  |  |  |  |  |  | 
| 767 |  |  |  |  |  |  | # Return immediately if the relevant lists have already been computed | 
| 768 |  |  |  |  |  |  | # and cached (even if they are empty). | 
| 769 |  |  |  |  |  |  |  | 
| 770 | 0 | 0 |  |  |  | 0 | return 1 if exists $request->{block_field_list}{$block_name}; | 
| 771 |  |  |  |  |  |  |  | 
| 772 |  |  |  |  |  |  | # Otherwise, we need to compute both lists.  Start by determining the | 
| 773 |  |  |  |  |  |  | # relevant attributes of the request and looking up the output list | 
| 774 |  |  |  |  |  |  | # for this block. | 
| 775 |  |  |  |  |  |  |  | 
| 776 | 0 |  |  |  |  | 0 | my $format = $request->output_format; | 
| 777 | 0 |  |  |  |  | 0 | my $vocab = $request->output_vocab; | 
| 778 | 0 | 0 | 0 |  |  | 0 | my $require_vocab; $require_vocab = 1 if $vocab and not $ds->{vocab}{$vocab}{use_field_names}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 779 |  |  |  |  |  |  |  | 
| 780 | 0 |  |  |  |  | 0 | my $block_list = $ds->{block}{$block_name}{output_list}; | 
| 781 |  |  |  |  |  |  |  | 
| 782 |  |  |  |  |  |  | # If no list is available, indicate this to the request object and return | 
| 783 |  |  |  |  |  |  | # false.  Whichever routine called us will be responsible for generating an | 
| 784 |  |  |  |  |  |  | # error or warning if appropriate. | 
| 785 |  |  |  |  |  |  |  | 
| 786 | 0 | 0 |  |  |  | 0 | unless ( ref $block_list eq 'ARRAY' ) | 
| 787 |  |  |  |  |  |  | { | 
| 788 | 0 |  |  |  |  | 0 | $request->{block_field_list}{$block_name} = undef; | 
| 789 | 0 |  |  |  |  | 0 | $request->{block_proc_list}{$block_name} = undef; | 
| 790 | 0 |  |  |  |  | 0 | return; | 
| 791 |  |  |  |  |  |  | } | 
| 792 |  |  |  |  |  |  |  | 
| 793 |  |  |  |  |  |  | # Go through each record in the list, throwing out the ones that don't | 
| 794 |  |  |  |  |  |  | # apply and assigning the ones that do. | 
| 795 |  |  |  |  |  |  |  | 
| 796 | 0 |  |  |  |  | 0 | my (@field_list, @proc_list); | 
| 797 |  |  |  |  |  |  |  | 
| 798 |  |  |  |  |  |  | RECORD: | 
| 799 | 0 |  |  |  |  | 0 | foreach my $r ( @$block_list ) | 
| 800 |  |  |  |  |  |  | { | 
| 801 |  |  |  |  |  |  | # Evaluate dependency on the output block list | 
| 802 |  |  |  |  |  |  |  | 
| 803 |  |  |  |  |  |  | next RECORD if $r->{if_block} | 
| 804 | 0 | 0 | 0 |  |  | 0 | and not check_set($r->{if_block}, $request->{block_set}); | 
| 805 |  |  |  |  |  |  |  | 
| 806 |  |  |  |  |  |  | next RECORD if $r->{not_block} | 
| 807 | 0 | 0 | 0 |  |  | 0 | and check_set($r->{not_block}, $request->{block_set}); | 
| 808 |  |  |  |  |  |  |  | 
| 809 |  |  |  |  |  |  | # Evaluate dependency on the output format | 
| 810 |  |  |  |  |  |  |  | 
| 811 |  |  |  |  |  |  | next RECORD if $r->{if_format} | 
| 812 | 0 | 0 | 0 |  |  | 0 | and not check_value($r->{if_format}, $format); | 
| 813 |  |  |  |  |  |  |  | 
| 814 |  |  |  |  |  |  | next RECORD if $r->{not_format} | 
| 815 | 0 | 0 | 0 |  |  | 0 | and check_value($r->{not_format}, $format); | 
| 816 |  |  |  |  |  |  |  | 
| 817 |  |  |  |  |  |  | # Evaluate dependency on the vocabulary | 
| 818 |  |  |  |  |  |  |  | 
| 819 |  |  |  |  |  |  | next RECORD if $r->{if_vocab} | 
| 820 | 0 | 0 | 0 |  |  | 0 | and not check_value($r->{if_vocab}, $vocab); | 
| 821 |  |  |  |  |  |  |  | 
| 822 |  |  |  |  |  |  | next RECORD if $r->{not_vocab} | 
| 823 | 0 | 0 | 0 |  |  | 0 | and check_value($r->{not_vocab}, $vocab); | 
| 824 |  |  |  |  |  |  |  | 
| 825 |  |  |  |  |  |  | # If the record type is 'output', add a record to the field list. | 
| 826 |  |  |  |  |  |  | # The attributes 'name' (the output name) and 'field' (the raw | 
| 827 |  |  |  |  |  |  | # field name) are both set to the indicated name by default. | 
| 828 |  |  |  |  |  |  |  | 
| 829 | 0 | 0 |  |  |  | 0 | if ( defined $r->{output} ) | 
|  |  | 0 |  |  |  |  |  | 
| 830 |  |  |  |  |  |  | { | 
| 831 | 0 | 0 | 0 |  |  | 0 | next RECORD if $require_vocab and not exists $r->{"${vocab}_name"}; | 
| 832 |  |  |  |  |  |  |  | 
| 833 | 0 |  |  |  |  | 0 | my $output = { field => $r->{output}, name => $r->{output} }; | 
| 834 |  |  |  |  |  |  |  | 
| 835 | 0 |  |  |  |  | 0 | foreach my $key ( keys %$r ) | 
| 836 |  |  |  |  |  |  | { | 
| 837 | 0 | 0 |  |  |  | 0 | if ( $FIELD_KEY{$key} ) | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 838 |  |  |  |  |  |  | { | 
| 839 | 0 |  |  |  |  | 0 | $output->{$key} = $r->{$key}; | 
| 840 |  |  |  |  |  |  | } | 
| 841 |  |  |  |  |  |  |  | 
| 842 |  |  |  |  |  |  | elsif ( $key =~ qr{ ^ (\w+) _ (name|value) $ }x ) | 
| 843 |  |  |  |  |  |  | { | 
| 844 | 0 | 0 |  |  |  | 0 | $output->{$2} = $r->{$key} if $vocab eq $1; | 
| 845 |  |  |  |  |  |  | } | 
| 846 |  |  |  |  |  |  |  | 
| 847 |  |  |  |  |  |  | elsif ( $key ne 'output' ) | 
| 848 |  |  |  |  |  |  | { | 
| 849 | 0 |  |  |  |  | 0 | warn "Warning: unknown key '$key' in output record\n"; | 
| 850 |  |  |  |  |  |  | } | 
| 851 |  |  |  |  |  |  | } | 
| 852 |  |  |  |  |  |  |  | 
| 853 | 0 |  |  |  |  | 0 | push @field_list, $output; | 
| 854 |  |  |  |  |  |  | } | 
| 855 |  |  |  |  |  |  |  | 
| 856 |  |  |  |  |  |  | # If the record type is 'set', add a record to the proc list. | 
| 857 |  |  |  |  |  |  |  | 
| 858 |  |  |  |  |  |  | elsif ( defined $r->{set} ) | 
| 859 |  |  |  |  |  |  | { | 
| 860 | 0 |  |  |  |  | 0 | my $proc = { set => $r->{set} }; | 
| 861 |  |  |  |  |  |  |  | 
| 862 | 0 |  |  |  |  | 0 | foreach my $key ( keys %$r ) | 
| 863 |  |  |  |  |  |  | { | 
| 864 | 0 | 0 |  |  |  | 0 | if ( $PROC_KEY{$key} ) | 
| 865 |  |  |  |  |  |  | { | 
| 866 | 0 |  |  |  |  | 0 | $proc->{$key} = $r->{$key}; | 
| 867 |  |  |  |  |  |  | } | 
| 868 |  |  |  |  |  |  |  | 
| 869 |  |  |  |  |  |  | else | 
| 870 |  |  |  |  |  |  | { | 
| 871 | 0 |  |  |  |  | 0 | carp "Warning: unknown key '$key' in proc record\n"; | 
| 872 |  |  |  |  |  |  | } | 
| 873 |  |  |  |  |  |  | } | 
| 874 |  |  |  |  |  |  |  | 
| 875 | 0 |  |  |  |  | 0 | push @proc_list, $proc; | 
| 876 |  |  |  |  |  |  | } | 
| 877 |  |  |  |  |  |  |  | 
| 878 |  |  |  |  |  |  | # All other record types are ignored. | 
| 879 |  |  |  |  |  |  | } | 
| 880 |  |  |  |  |  |  |  | 
| 881 |  |  |  |  |  |  | # Now cache the results. | 
| 882 |  |  |  |  |  |  |  | 
| 883 | 0 |  |  |  |  | 0 | $request->{block_field_list}{$block_name} = \@field_list; | 
| 884 | 0 |  |  |  |  | 0 | $request->{block_proc_list}{$block_name} = \@proc_list; | 
| 885 |  |  |  |  |  |  |  | 
| 886 | 0 |  |  |  |  | 0 | return 1; | 
| 887 |  |  |  |  |  |  | } | 
| 888 |  |  |  |  |  |  |  | 
| 889 |  |  |  |  |  |  |  | 
| 890 |  |  |  |  |  |  | # check_value ( list, value ) | 
| 891 |  |  |  |  |  |  | # | 
| 892 |  |  |  |  |  |  | # Return true if $list is equal to $value, or if it is a list and one if its | 
| 893 |  |  |  |  |  |  | # items is equal to $value. | 
| 894 |  |  |  |  |  |  |  | 
| 895 |  |  |  |  |  |  | sub check_value { | 
| 896 |  |  |  |  |  |  |  | 
| 897 | 0 |  |  | 0 | 0 | 0 | my ($list, $value) = @_; | 
| 898 |  |  |  |  |  |  |  | 
| 899 | 0 | 0 |  |  |  | 0 | return 1 if $list eq $value; | 
| 900 |  |  |  |  |  |  |  | 
| 901 | 0 | 0 |  |  |  | 0 | if ( ref $list eq 'ARRAY' ) | 
| 902 |  |  |  |  |  |  | { | 
| 903 | 0 |  |  |  |  | 0 | foreach my $item (@$list) | 
| 904 |  |  |  |  |  |  | { | 
| 905 | 0 | 0 |  |  |  | 0 | return 1 if $item eq $value; | 
| 906 |  |  |  |  |  |  | } | 
| 907 |  |  |  |  |  |  | } | 
| 908 |  |  |  |  |  |  |  | 
| 909 | 0 |  |  |  |  | 0 | return; | 
| 910 |  |  |  |  |  |  | } | 
| 911 |  |  |  |  |  |  |  | 
| 912 |  |  |  |  |  |  |  | 
| 913 |  |  |  |  |  |  | # check_set ( list, set ) | 
| 914 |  |  |  |  |  |  | # | 
| 915 |  |  |  |  |  |  | # The parameter $set must be a hashref.  Return true if $list is one of the | 
| 916 |  |  |  |  |  |  | # keys of $set, or if it $list is a list and one of its items is a key in | 
| 917 |  |  |  |  |  |  | # $set.  A key only counts if it has a true value. | 
| 918 |  |  |  |  |  |  |  | 
| 919 |  |  |  |  |  |  | sub check_set { | 
| 920 |  |  |  |  |  |  |  | 
| 921 | 0 |  |  | 0 | 0 | 0 | my ($list, $set) = @_; | 
| 922 |  |  |  |  |  |  |  | 
| 923 | 0 | 0 |  |  |  | 0 | return unless ref $set eq 'HASH'; | 
| 924 |  |  |  |  |  |  |  | 
| 925 | 0 | 0 |  |  |  | 0 | return 1 if $set->{$list}; | 
| 926 |  |  |  |  |  |  |  | 
| 927 | 0 | 0 |  |  |  | 0 | if ( ref $list eq 'ARRAY' ) | 
| 928 |  |  |  |  |  |  | { | 
| 929 | 0 |  |  |  |  | 0 | foreach my $item (@$list) | 
| 930 |  |  |  |  |  |  | { | 
| 931 | 0 | 0 |  |  |  | 0 | return 1 if $set->{$item}; | 
| 932 |  |  |  |  |  |  | } | 
| 933 |  |  |  |  |  |  | } | 
| 934 |  |  |  |  |  |  |  | 
| 935 | 0 |  |  |  |  | 0 | return; | 
| 936 |  |  |  |  |  |  | } | 
| 937 |  |  |  |  |  |  |  | 
| 938 |  |  |  |  |  |  |  | 
| 939 |  |  |  |  |  |  | # add_doc ( node, item ) | 
| 940 |  |  |  |  |  |  | # | 
| 941 |  |  |  |  |  |  | # Add the specified item to the documentation list for the specified node. | 
| 942 |  |  |  |  |  |  | # The item can be either a string or a record (hashref). | 
| 943 |  |  |  |  |  |  |  | 
| 944 |  |  |  |  |  |  | sub add_doc { | 
| 945 |  |  |  |  |  |  |  | 
| 946 | 8 |  |  | 8 | 0 | 15 | my ($ds, $node, $item) = @_; | 
| 947 |  |  |  |  |  |  |  | 
| 948 |  |  |  |  |  |  | # If the item is a record, close any currently pending documentation and | 
| 949 |  |  |  |  |  |  | # start a new "pending" list.  We need to do this because subsequent items | 
| 950 |  |  |  |  |  |  | # may document the record we were just called with. | 
| 951 |  |  |  |  |  |  |  | 
| 952 | 8 | 100 |  |  |  | 37 | if ( ref $item ) | 
|  |  | 50 |  |  |  |  |  | 
| 953 |  |  |  |  |  |  | { | 
| 954 | 4 | 50 |  |  |  | 13 | croak "cannot add non-hash object to documentation" | 
| 955 |  |  |  |  |  |  | unless reftype $item eq 'HASH'; | 
| 956 |  |  |  |  |  |  |  | 
| 957 | 4 |  |  |  |  | 12 | $ds->process_doc($node); | 
| 958 | 4 |  |  |  |  | 4 | push @{$node->{doc_pending}}, $item; | 
|  | 4 |  |  |  |  | 13 |  | 
| 959 |  |  |  |  |  |  | } | 
| 960 |  |  |  |  |  |  |  | 
| 961 |  |  |  |  |  |  | # If this is a string starting with one of the special characters, then | 
| 962 |  |  |  |  |  |  | # handle it properly. | 
| 963 |  |  |  |  |  |  |  | 
| 964 |  |  |  |  |  |  | elsif ( $item =~ qr{ ^ ([!^?] | >>?) (.*) }xs ) | 
| 965 |  |  |  |  |  |  | { | 
| 966 |  |  |  |  |  |  | # If >>, then close the active documentation section (if any) and | 
| 967 |  |  |  |  |  |  | # start a new one that is not tied to any rule.  This will generate an | 
| 968 |  |  |  |  |  |  | # ordinary paragraph starting with the remainder of the line. | 
| 969 |  |  |  |  |  |  |  | 
| 970 | 0 | 0 |  |  |  | 0 | if ( $1 eq '>>' ) | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 971 |  |  |  |  |  |  | { | 
| 972 | 0 |  |  |  |  | 0 | $ds->process_doc($node); | 
| 973 | 0 | 0 |  |  |  | 0 | push @{$node->{doc_pending}}, $2 if $2 ne ''; | 
|  | 0 |  |  |  |  | 0 |  | 
| 974 |  |  |  |  |  |  | } | 
| 975 |  |  |  |  |  |  |  | 
| 976 |  |  |  |  |  |  | # If >, then add to the current documentation a blank line | 
| 977 |  |  |  |  |  |  | # (which will cause a new paragraph) followed by the remainder | 
| 978 |  |  |  |  |  |  | # of this line. | 
| 979 |  |  |  |  |  |  |  | 
| 980 |  |  |  |  |  |  | elsif ( $1 eq '>' ) | 
| 981 |  |  |  |  |  |  | { | 
| 982 | 0 |  |  |  |  | 0 | push @{$node->{doc_pending}}, "\n$2"; | 
|  | 0 |  |  |  |  | 0 |  | 
| 983 |  |  |  |  |  |  | } | 
| 984 |  |  |  |  |  |  |  | 
| 985 |  |  |  |  |  |  | # If !, then discard all pending documentation and mark the node as | 
| 986 |  |  |  |  |  |  | # 'undocumented'.  This will cause it to be elided from the documentation. | 
| 987 |  |  |  |  |  |  |  | 
| 988 |  |  |  |  |  |  | elsif ( $1 eq '!' ) | 
| 989 |  |  |  |  |  |  | { | 
| 990 | 0 |  |  |  |  | 0 | $ds->process_doc($node, 'undocumented'); | 
| 991 |  |  |  |  |  |  | } | 
| 992 |  |  |  |  |  |  |  | 
| 993 |  |  |  |  |  |  | # If ?, then add the remainder of the line to the documentation. | 
| 994 |  |  |  |  |  |  | # The ! prevents the next character from being interpreted specially. | 
| 995 |  |  |  |  |  |  |  | 
| 996 |  |  |  |  |  |  | else | 
| 997 |  |  |  |  |  |  | { | 
| 998 | 0 |  |  |  |  | 0 | push @{$node->{doc_pending}}, $2; | 
|  | 0 |  |  |  |  | 0 |  | 
| 999 |  |  |  |  |  |  | } | 
| 1000 |  |  |  |  |  |  | } | 
| 1001 |  |  |  |  |  |  |  | 
| 1002 |  |  |  |  |  |  | # Otherwise, just add this string to the "pending" list. | 
| 1003 |  |  |  |  |  |  |  | 
| 1004 |  |  |  |  |  |  | else | 
| 1005 |  |  |  |  |  |  | { | 
| 1006 | 4 |  |  |  |  | 6 | push @{$node->{doc_pending}}, $item; | 
|  | 4 |  |  |  |  | 10 |  | 
| 1007 |  |  |  |  |  |  | } | 
| 1008 |  |  |  |  |  |  | } | 
| 1009 |  |  |  |  |  |  |  | 
| 1010 |  |  |  |  |  |  |  | 
| 1011 |  |  |  |  |  |  | # process_doc ( node, disposition ) | 
| 1012 |  |  |  |  |  |  | # | 
| 1013 |  |  |  |  |  |  | # Process all pending documentation items. | 
| 1014 |  |  |  |  |  |  |  | 
| 1015 |  |  |  |  |  |  | sub process_doc { | 
| 1016 |  |  |  |  |  |  |  | 
| 1017 | 6 |  |  | 6 | 0 | 11 | my ($ds, $node, $disposition) = @_; | 
| 1018 |  |  |  |  |  |  |  | 
| 1019 |  |  |  |  |  |  | # Return immediately unless we have something pending. | 
| 1020 |  |  |  |  |  |  |  | 
| 1021 | 6 | 100 | 66 |  |  | 22 | return unless ref $node->{doc_pending} eq 'ARRAY' && @{$node->{doc_pending}}; | 
|  | 4 |  |  |  |  | 13 |  | 
| 1022 |  |  |  |  |  |  |  | 
| 1023 |  |  |  |  |  |  | # If the "pending" list starts with an item record, take that off first. | 
| 1024 |  |  |  |  |  |  | # Everything else on the list should be a string. | 
| 1025 |  |  |  |  |  |  |  | 
| 1026 | 4 |  |  |  |  | 5 | my $primary_item = shift @{$node->{doc_pending}}; | 
|  | 4 |  |  |  |  | 7 |  | 
| 1027 | 4 | 50 |  |  |  | 8 | return unless ref $primary_item; | 
| 1028 |  |  |  |  |  |  |  | 
| 1029 |  |  |  |  |  |  | # Discard all pending documentation if the primary item is disabled or | 
| 1030 |  |  |  |  |  |  | # marked with a '!'.  In the latter case, note this in the item record. | 
| 1031 |  |  |  |  |  |  |  | 
| 1032 | 4 |  | 50 |  |  | 14 | $disposition //= ''; | 
| 1033 |  |  |  |  |  |  |  | 
| 1034 | 4 | 50 | 33 |  |  | 20 | if ( $primary_item->{disabled} or $primary_item->{undocumented} or | 
|  |  |  | 33 |  |  |  |  | 
| 1035 |  |  |  |  |  |  | $disposition eq 'undocumented' ) | 
| 1036 |  |  |  |  |  |  | { | 
| 1037 | 0 |  |  |  |  | 0 | @{$node->{doc_pending}} = (); | 
|  | 0 |  |  |  |  | 0 |  | 
| 1038 | 0 | 0 |  |  |  | 0 | $primary_item->{undocumented} = 1 if $disposition eq 'undocumented'; | 
| 1039 | 0 |  |  |  |  | 0 | return; | 
| 1040 |  |  |  |  |  |  | } | 
| 1041 |  |  |  |  |  |  |  | 
| 1042 |  |  |  |  |  |  | # Put the rest of the documentation items together into a single | 
| 1043 |  |  |  |  |  |  | # string, which may contain a series of Pod paragraphs. | 
| 1044 |  |  |  |  |  |  |  | 
| 1045 | 4 |  |  |  |  | 6 | my $body = ''; | 
| 1046 | 4 |  |  |  |  | 5 | my $last_pod; | 
| 1047 |  |  |  |  |  |  | my $this_pod; | 
| 1048 |  |  |  |  |  |  |  | 
| 1049 | 4 |  |  |  |  | 5 | while (my $line = shift @{$node->{doc_pending}}) | 
|  | 8 |  |  |  |  | 19 |  | 
| 1050 |  |  |  |  |  |  | { | 
| 1051 |  |  |  |  |  |  | # If this line starts with =, then it needs extra spacing. | 
| 1052 |  |  |  |  |  |  |  | 
| 1053 | 4 |  |  |  |  | 15 | my $this_pod = $line =~ qr{ ^ = }x; | 
| 1054 |  |  |  |  |  |  |  | 
| 1055 |  |  |  |  |  |  | # If $body already has something in it, add a newline first.  Add | 
| 1056 |  |  |  |  |  |  | # two if this line starts with =, or if the previously added line | 
| 1057 |  |  |  |  |  |  | # did, so that we get a new paragraph. | 
| 1058 |  |  |  |  |  |  |  | 
| 1059 | 4 | 50 |  |  |  | 9 | if ( $body ne '' ) | 
| 1060 |  |  |  |  |  |  | { | 
| 1061 | 0 | 0 | 0 |  |  | 0 | $body .= "\n" if $last_pod || $this_pod; | 
| 1062 | 0 |  |  |  |  | 0 | $body .= "\n"; | 
| 1063 |  |  |  |  |  |  | } | 
| 1064 |  |  |  |  |  |  |  | 
| 1065 | 4 |  |  |  |  | 6 | $body .= $line; | 
| 1066 | 4 |  |  |  |  | 14 | $last_pod = $this_pod; | 
| 1067 |  |  |  |  |  |  | } | 
| 1068 |  |  |  |  |  |  |  | 
| 1069 |  |  |  |  |  |  | # Then add the documentation to the node's documentation list.  If there | 
| 1070 |  |  |  |  |  |  | # is no primary item, add the body as an ordinary paragraph. | 
| 1071 |  |  |  |  |  |  |  | 
| 1072 | 4 | 50 |  |  |  | 10 | unless ( defined $primary_item ) | 
| 1073 |  |  |  |  |  |  | { | 
| 1074 | 0 |  |  |  |  | 0 | push @{$node->{doc_list}}, clean_doc($body); | 
|  | 0 |  |  |  |  | 0 |  | 
| 1075 |  |  |  |  |  |  | } | 
| 1076 |  |  |  |  |  |  |  | 
| 1077 |  |  |  |  |  |  | # Otherwise, attach the body to the primary item and add it to the list. | 
| 1078 |  |  |  |  |  |  |  | 
| 1079 |  |  |  |  |  |  | else | 
| 1080 |  |  |  |  |  |  | { | 
| 1081 | 4 |  |  |  |  | 8 | $primary_item->{doc_string} = clean_doc($body, 1); | 
| 1082 | 4 |  |  |  |  | 6 | push @{$node->{doc_list}}, $primary_item; | 
|  | 4 |  |  |  |  | 11 |  | 
| 1083 |  |  |  |  |  |  | } | 
| 1084 |  |  |  |  |  |  | } | 
| 1085 |  |  |  |  |  |  |  | 
| 1086 |  |  |  |  |  |  |  | 
| 1087 |  |  |  |  |  |  | # clean_doc ( ) | 
| 1088 |  |  |  |  |  |  | # | 
| 1089 |  |  |  |  |  |  | # Make sure that the indicated string is valid POD.  In particular, if there | 
| 1090 |  |  |  |  |  |  | # are any unclosed =over sections, close them at the end.  Throw an exception | 
| 1091 |  |  |  |  |  |  | # if we find an =item before the first =over or a =head inside an =over. | 
| 1092 |  |  |  |  |  |  |  | 
| 1093 |  |  |  |  |  |  | sub clean_doc { | 
| 1094 |  |  |  |  |  |  |  | 
| 1095 | 4 |  |  | 4 | 0 | 7 | my ($docstring, $item_body) = @_; | 
| 1096 |  |  |  |  |  |  |  | 
| 1097 | 4 |  |  |  |  | 7 | my $list_level = 0; | 
| 1098 |  |  |  |  |  |  |  | 
| 1099 | 4 |  |  |  |  | 11 | while ( $docstring =~ / ^ (=[a-z]+) /gmx ) | 
| 1100 |  |  |  |  |  |  | { | 
| 1101 | 0 | 0 |  |  |  | 0 | if ( $1 eq '=over' ) | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 1102 |  |  |  |  |  |  | { | 
| 1103 | 0 |  |  |  |  | 0 | $list_level++; | 
| 1104 |  |  |  |  |  |  | } | 
| 1105 |  |  |  |  |  |  |  | 
| 1106 |  |  |  |  |  |  | elsif ( $1 eq '=back' ) | 
| 1107 |  |  |  |  |  |  | { | 
| 1108 | 0 |  |  |  |  | 0 | $list_level--; | 
| 1109 | 0 | 0 |  |  |  | 0 | croak "invalid POD string: =back does not match any =over" if $list_level < 0; | 
| 1110 |  |  |  |  |  |  | } | 
| 1111 |  |  |  |  |  |  |  | 
| 1112 |  |  |  |  |  |  | elsif ( $1 eq '=item' ) | 
| 1113 |  |  |  |  |  |  | { | 
| 1114 | 0 | 0 |  |  |  | 0 | croak "invalid POD string: =item outside of =over" if $list_level == 0; | 
| 1115 |  |  |  |  |  |  | } | 
| 1116 |  |  |  |  |  |  |  | 
| 1117 |  |  |  |  |  |  | elsif ( $1 eq '=head' ) | 
| 1118 |  |  |  |  |  |  | { | 
| 1119 | 0 | 0 | 0 |  |  | 0 | croak "invalid POD string: =head inside =over" if $list_level > 0 || $item_body; | 
| 1120 |  |  |  |  |  |  | } | 
| 1121 |  |  |  |  |  |  | } | 
| 1122 |  |  |  |  |  |  |  | 
| 1123 | 4 |  |  |  |  | 7 | $docstring .= "\n\n=back" x $list_level; | 
| 1124 |  |  |  |  |  |  |  | 
| 1125 | 4 |  |  |  |  | 10 | return $docstring; | 
| 1126 |  |  |  |  |  |  | } | 
| 1127 |  |  |  |  |  |  |  | 
| 1128 |  |  |  |  |  |  |  | 
| 1129 |  |  |  |  |  |  | # document_node ( node, state ) | 
| 1130 |  |  |  |  |  |  | # | 
| 1131 |  |  |  |  |  |  | # Return a documentation string for the given node, in Pod format.  This will | 
| 1132 |  |  |  |  |  |  | # consist of a main item list that may start and stop, possibly with ordinary | 
| 1133 |  |  |  |  |  |  | # Pod paragraphs in between list chunks.  If this node contains any 'include' | 
| 1134 |  |  |  |  |  |  | # records, the lists for those nodes will be recursively interpolated into the | 
| 1135 |  |  |  |  |  |  | # main list.  Sublists can only occur if they are explicitly included in the | 
| 1136 |  |  |  |  |  |  | # documentation strings for individual node records. | 
| 1137 |  |  |  |  |  |  | # | 
| 1138 |  |  |  |  |  |  | # If the $state parameter is given, it must be a hashref containing any of the | 
| 1139 |  |  |  |  |  |  | # following keys: | 
| 1140 |  |  |  |  |  |  | # | 
| 1141 |  |  |  |  |  |  | # namespace	A hash ref in which included nodes may be looked up by name. | 
| 1142 |  |  |  |  |  |  | #		If this is not given, then 'include' records are ignored. | 
| 1143 |  |  |  |  |  |  | # | 
| 1144 |  |  |  |  |  |  | # items_only	If true, then ordinary paragraphs will be ignored and a single | 
| 1145 |  |  |  |  |  |  | #		uninterrupted item list will be generated. | 
| 1146 |  |  |  |  |  |  | # | 
| 1147 |  |  |  |  |  |  |  | 
| 1148 |  |  |  |  |  |  | sub document_node { | 
| 1149 |  |  |  |  |  |  |  | 
| 1150 | 0 |  |  | 0 | 0 |  | my ($ds, $node, $state) = @_; | 
| 1151 |  |  |  |  |  |  |  | 
| 1152 |  |  |  |  |  |  | # Return the empty string unless documentation has been added to this | 
| 1153 |  |  |  |  |  |  | # node. | 
| 1154 |  |  |  |  |  |  |  | 
| 1155 | 0 | 0 | 0 |  |  |  | return '' unless ref $node && ref $node->{doc_list} eq 'ARRAY'; | 
| 1156 |  |  |  |  |  |  |  | 
| 1157 |  |  |  |  |  |  | # Make sure we have a state record, if we were not passed one. | 
| 1158 |  |  |  |  |  |  |  | 
| 1159 | 0 |  | 0 |  |  |  | $state ||= {}; | 
| 1160 |  |  |  |  |  |  |  | 
| 1161 |  |  |  |  |  |  | # Make sure that we process each node only once, if it should happen | 
| 1162 |  |  |  |  |  |  | # to be included multiple times.  Also keep track of our recursion level. | 
| 1163 |  |  |  |  |  |  |  | 
| 1164 | 0 | 0 |  |  |  |  | return if $state->{processed}{$node->{name}}; | 
| 1165 |  |  |  |  |  |  |  | 
| 1166 | 0 |  |  |  |  |  | $state->{processed}{$node->{name}} = 1; | 
| 1167 | 0 |  |  |  |  |  | $state->{level}++; | 
| 1168 |  |  |  |  |  |  |  | 
| 1169 |  |  |  |  |  |  | # Go through the list of documentation items, treating each one as a Pod | 
| 1170 |  |  |  |  |  |  | # paragraph.  That means that they will be separated from each other by a | 
| 1171 |  |  |  |  |  |  | # blank line.  List control paragraphs "=over" and "=back" will be added | 
| 1172 |  |  |  |  |  |  | # as necessary to start and stop the main item list. | 
| 1173 |  |  |  |  |  |  |  | 
| 1174 | 0 |  |  |  |  |  | my $doc = ''; | 
| 1175 |  |  |  |  |  |  |  | 
| 1176 |  |  |  |  |  |  | ITEM: | 
| 1177 | 0 |  |  |  |  |  | foreach my $item ( @{$node->{doc_list}} ) | 
|  | 0 |  |  |  |  |  |  | 
| 1178 |  |  |  |  |  |  | { | 
| 1179 |  |  |  |  |  |  | # A string is added as an ordinary paragraph.  The main list is closed | 
| 1180 |  |  |  |  |  |  | # if it is open.  But the item is skipped if we were given the | 
| 1181 |  |  |  |  |  |  | # 'items_only' flag. | 
| 1182 |  |  |  |  |  |  |  | 
| 1183 | 0 | 0 |  |  |  |  | unless ( ref $item ) | 
|  |  | 0 |  |  |  |  |  | 
| 1184 |  |  |  |  |  |  | { | 
| 1185 | 0 | 0 |  |  |  |  | next ITEM if $state->{items_only}; | 
| 1186 |  |  |  |  |  |  |  | 
| 1187 | 0 | 0 |  |  |  |  | if ( $state->{in_list} ) | 
| 1188 |  |  |  |  |  |  | { | 
| 1189 | 0 | 0 |  |  |  |  | $doc .= "\n\n" if $doc ne ''; | 
| 1190 | 0 |  |  |  |  |  | $doc .= "=back"; | 
| 1191 | 0 |  |  |  |  |  | $state->{in_list} = 0; | 
| 1192 |  |  |  |  |  |  | } | 
| 1193 |  |  |  |  |  |  |  | 
| 1194 | 0 | 0 | 0 |  |  |  | $doc .= "\n\n" if $doc ne '' && $item ne ''; | 
| 1195 | 0 |  |  |  |  |  | $doc .= $item; | 
| 1196 |  |  |  |  |  |  | } | 
| 1197 |  |  |  |  |  |  |  | 
| 1198 |  |  |  |  |  |  | # An 'include' record inserts the documentation for the specified | 
| 1199 |  |  |  |  |  |  | # node.  This does not necessarily end the list, only if the include | 
| 1200 |  |  |  |  |  |  | # record itself has a documentation string.  Skip the inclusion if no | 
| 1201 |  |  |  |  |  |  | # hashref was provided for looking up item names. | 
| 1202 |  |  |  |  |  |  |  | 
| 1203 | 0 |  |  |  |  |  | elsif ( defined $item->{include} ) | 
| 1204 |  |  |  |  |  |  | { | 
| 1205 | 0 | 0 | 0 |  |  |  | next ITEM unless ref $state->{namespace} && reftype $state->{namespace} eq 'HASH'; | 
| 1206 |  |  |  |  |  |  |  | 
| 1207 | 0 | 0 | 0 |  |  |  | if ( defined $item->{doc_string} and $item->{doc_string} ne '' and not $state->{items_only} ) | 
|  |  |  | 0 |  |  |  |  | 
| 1208 |  |  |  |  |  |  | { | 
| 1209 | 0 | 0 |  |  |  |  | if ( $state->{in_list} ) | 
| 1210 |  |  |  |  |  |  | { | 
| 1211 | 0 | 0 |  |  |  |  | $doc .= "\n\n" if $doc ne ''; | 
| 1212 | 0 |  |  |  |  |  | $doc .= "=back"; | 
| 1213 | 0 |  |  |  |  |  | $state->{in_list} = 0; | 
| 1214 |  |  |  |  |  |  | } | 
| 1215 |  |  |  |  |  |  |  | 
| 1216 | 0 | 0 |  |  |  |  | $doc .= "\n\n" if $doc ne ''; | 
| 1217 | 0 |  |  |  |  |  | $doc .= $item->{doc_string}; | 
| 1218 |  |  |  |  |  |  | } | 
| 1219 |  |  |  |  |  |  |  | 
| 1220 | 0 |  |  |  |  |  | my $included_node = $state->{namespace}{$item->{include}}; | 
| 1221 |  |  |  |  |  |  |  | 
| 1222 | 0 | 0 | 0 |  |  |  | next unless ref $included_node && reftype $included_node eq 'HASH'; | 
| 1223 |  |  |  |  |  |  |  | 
| 1224 | 0 |  |  |  |  |  | my $subdoc = $ds->document_node($included_node, $state); | 
| 1225 |  |  |  |  |  |  |  | 
| 1226 | 0 | 0 | 0 |  |  |  | $doc .= "\n\n" if $doc ne '' && $subdoc ne ''; | 
| 1227 | 0 |  |  |  |  |  | $doc .= $subdoc; | 
| 1228 |  |  |  |  |  |  | } | 
| 1229 |  |  |  |  |  |  |  | 
| 1230 |  |  |  |  |  |  | # Any other record is added as a list item.  Try to figure out the | 
| 1231 |  |  |  |  |  |  | # item name as best we can. | 
| 1232 |  |  |  |  |  |  |  | 
| 1233 |  |  |  |  |  |  | else | 
| 1234 |  |  |  |  |  |  | { | 
| 1235 |  |  |  |  |  |  | my $name = ref $node eq 'Web::DataService::Set' ? $item->{value} | 
| 1236 |  |  |  |  |  |  | : defined $item->{name}		    ? $item->{name} | 
| 1237 | 0 | 0 |  |  |  |  | : ''; | 
|  |  | 0 |  |  |  |  |  | 
| 1238 |  |  |  |  |  |  |  | 
| 1239 | 0 |  | 0 |  |  |  | $name ||= ''; | 
| 1240 |  |  |  |  |  |  |  | 
| 1241 | 0 | 0 |  |  |  |  | unless ( $state->{in_list} ) | 
| 1242 |  |  |  |  |  |  | { | 
| 1243 | 0 | 0 |  |  |  |  | $doc .= "\n\n" if $doc ne ''; | 
| 1244 | 0 |  |  |  |  |  | $doc .= "=over"; | 
| 1245 | 0 |  |  |  |  |  | $state->{in_list} = 1; | 
| 1246 |  |  |  |  |  |  | } | 
| 1247 |  |  |  |  |  |  |  | 
| 1248 | 0 |  |  |  |  |  | $doc .= "\n\n=item $name"; | 
| 1249 | 0 | 0 | 0 |  |  |  | $doc .= "\n\n$item->{doc_string}" if defined $item->{doc_string} && $item->{doc_string} ne ''; | 
| 1250 |  |  |  |  |  |  | } | 
| 1251 |  |  |  |  |  |  | } | 
| 1252 |  |  |  |  |  |  |  | 
| 1253 |  |  |  |  |  |  | # If we get to the end of the top-level ruleset and we are still in a | 
| 1254 |  |  |  |  |  |  | # list, close it.  Also make sure that our resulting documentation string | 
| 1255 |  |  |  |  |  |  | # ends with a newline. | 
| 1256 |  |  |  |  |  |  |  | 
| 1257 | 0 | 0 |  |  |  |  | if ( --$state->{level} == 0 ) | 
| 1258 |  |  |  |  |  |  | { | 
| 1259 | 0 | 0 |  |  |  |  | $doc .= "\n\n=back" if $state->{in_list}; | 
| 1260 | 0 |  |  |  |  |  | $state->{in_list} = 0; | 
| 1261 | 0 |  |  |  |  |  | $doc .= "\n"; | 
| 1262 |  |  |  |  |  |  | } | 
| 1263 |  |  |  |  |  |  |  | 
| 1264 | 0 |  |  |  |  |  | return $doc; | 
| 1265 |  |  |  |  |  |  | } | 
| 1266 |  |  |  |  |  |  |  | 
| 1267 |  |  |  |  |  |  |  | 
| 1268 |  |  |  |  |  |  | # document_response ( ) | 
| 1269 |  |  |  |  |  |  | # | 
| 1270 |  |  |  |  |  |  | # Generate documentation in Pod format describing the available output fields | 
| 1271 |  |  |  |  |  |  | # for the specified URL path. | 
| 1272 |  |  |  |  |  |  |  | 
| 1273 |  |  |  |  |  |  | sub document_response { | 
| 1274 |  |  |  |  |  |  |  | 
| 1275 | 0 |  |  | 0 | 0 |  | my ($ds, $path) = @_; | 
| 1276 |  |  |  |  |  |  |  | 
| 1277 | 0 |  |  |  |  |  | my @blocks; | 
| 1278 |  |  |  |  |  |  | my @labels; | 
| 1279 |  |  |  |  |  |  |  | 
| 1280 |  |  |  |  |  |  | # First collect up a list of all of the fixed (non-optional) blocks. | 
| 1281 |  |  |  |  |  |  | # Block names that do not correspond to any defined block are ignored, | 
| 1282 |  |  |  |  |  |  | # with a warning. | 
| 1283 |  |  |  |  |  |  |  | 
| 1284 | 0 |  | 0 |  |  |  | my $output_list = $ds->node_attr($path, 'output') // [ ]; | 
| 1285 | 0 |  | 0 |  |  |  | my $fixed_label = $ds->node_attr($path, 'output_label') // 'basic'; | 
| 1286 |  |  |  |  |  |  |  | 
| 1287 | 0 |  |  |  |  |  | foreach my $block_name ( @$output_list ) | 
| 1288 |  |  |  |  |  |  | { | 
| 1289 | 0 | 0 |  |  |  |  | if ( ref $ds->{block}{$block_name} eq 'Web::DataService::Block' ) | 
|  |  | 0 |  |  |  |  |  | 
| 1290 |  |  |  |  |  |  | { | 
| 1291 | 0 |  |  |  |  |  | push @blocks, $block_name; | 
| 1292 | 0 |  |  |  |  |  | push @labels, $fixed_label; | 
| 1293 |  |  |  |  |  |  | } | 
| 1294 |  |  |  |  |  |  |  | 
| 1295 |  |  |  |  |  |  | elsif ( $ds->debug ) | 
| 1296 |  |  |  |  |  |  | { | 
| 1297 |  |  |  |  |  |  | warn "WARNING: block '$block_name' not found" | 
| 1298 | 0 | 0 | 0 |  |  |  | unless $Web::DataService::QUIET || $ENV{WDS_QUIET}; | 
| 1299 |  |  |  |  |  |  | } | 
| 1300 |  |  |  |  |  |  | } | 
| 1301 |  |  |  |  |  |  |  | 
| 1302 |  |  |  |  |  |  | # Then add all of the optional blocks, if an output_opt map was | 
| 1303 |  |  |  |  |  |  | # specified. | 
| 1304 |  |  |  |  |  |  |  | 
| 1305 | 0 |  |  |  |  |  | my $optional_output = $ds->node_attr($path, 'optional_output'); | 
| 1306 | 0 |  |  |  |  |  | my $reverse_map; | 
| 1307 |  |  |  |  |  |  |  | 
| 1308 | 0 | 0 | 0 |  |  |  | if ( $optional_output && ref $ds->{set}{$optional_output} eq 'Web::DataService::Set' ) | 
|  |  | 0 | 0 |  |  |  |  | 
| 1309 |  |  |  |  |  |  | { | 
| 1310 | 0 |  |  |  |  |  | my $output_map = $ds->{set}{$optional_output}; | 
| 1311 | 0 | 0 |  |  |  |  | my @keys; @keys = @{$output_map->{value_list}} if ref $output_map->{value_list} eq 'ARRAY'; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 1312 |  |  |  |  |  |  |  | 
| 1313 |  |  |  |  |  |  | VALUE: | 
| 1314 | 0 |  |  |  |  |  | foreach my $label ( @keys ) | 
| 1315 |  |  |  |  |  |  | { | 
| 1316 | 0 |  |  |  |  |  | my $block_name = $output_map->{value}{$label}{maps_to}; | 
| 1317 | 0 | 0 |  |  |  |  | next VALUE unless defined $block_name; | 
| 1318 |  |  |  |  |  |  | next VALUE if $output_map->{value}{$label}{disabled} || | 
| 1319 | 0 | 0 | 0 |  |  |  | $output_map->{value}{$label}{undocumented}; | 
| 1320 |  |  |  |  |  |  |  | 
| 1321 | 0 |  |  |  |  |  | $reverse_map->{$block_name} = $label; | 
| 1322 |  |  |  |  |  |  |  | 
| 1323 | 0 | 0 |  |  |  |  | if ( ref $ds->{block}{$block_name} eq 'Web::DataService::Block' ) | 
| 1324 |  |  |  |  |  |  | { | 
| 1325 | 0 |  |  |  |  |  | push @blocks, $block_name; | 
| 1326 | 0 |  |  |  |  |  | push @labels, $label; | 
| 1327 |  |  |  |  |  |  | } | 
| 1328 |  |  |  |  |  |  | } | 
| 1329 |  |  |  |  |  |  | } | 
| 1330 |  |  |  |  |  |  |  | 
| 1331 |  |  |  |  |  |  | elsif ( $optional_output && $ds->debug ) | 
| 1332 |  |  |  |  |  |  | { | 
| 1333 |  |  |  |  |  |  | warn "WARNING: output map '$optional_output' not found" | 
| 1334 | 0 | 0 | 0 |  |  |  | unless $Web::DataService::QUIET || $ENV{WDS_QUIET}; | 
| 1335 |  |  |  |  |  |  | } | 
| 1336 |  |  |  |  |  |  |  | 
| 1337 |  |  |  |  |  |  | # If there are no output blocks specified for this path, return an empty | 
| 1338 |  |  |  |  |  |  | # string. | 
| 1339 |  |  |  |  |  |  |  | 
| 1340 | 0 | 0 |  |  |  |  | return '' unless @blocks; | 
| 1341 |  |  |  |  |  |  |  | 
| 1342 |  |  |  |  |  |  | # Otherwise, determine the set of vocabularies that are allowed for this | 
| 1343 |  |  |  |  |  |  | # path.  If none are specifically selected for this path, then all of the | 
| 1344 |  |  |  |  |  |  | # vocabularies defined for this data service are allowed. | 
| 1345 |  |  |  |  |  |  |  | 
| 1346 | 0 |  | 0 |  |  |  | my $vocabularies; $vocabularies = $ds->node_attr($path, 'allow_vocab') || $ds->{vocab}; | 
|  | 0 |  |  |  |  |  |  | 
| 1347 |  |  |  |  |  |  |  | 
| 1348 | 0 | 0 | 0 |  |  |  | unless ( ref $vocabularies eq 'HASH' && keys %$vocabularies ) | 
| 1349 |  |  |  |  |  |  | { | 
| 1350 | 0 | 0 |  |  |  |  | warn "No output vocabularies were selected for path '$path'" if $ds->debug; | 
| 1351 | 0 |  |  |  |  |  | return ''; | 
| 1352 |  |  |  |  |  |  | } | 
| 1353 |  |  |  |  |  |  |  | 
| 1354 |  |  |  |  |  |  | my @vocab_list = grep { $vocabularies->{$_} && | 
| 1355 |  |  |  |  |  |  | ref $ds->{vocab}{$_} && | 
| 1356 | 0 | 0 | 0 |  |  |  | ! $ds->{vocab}{$_}{disabled} } @{$ds->{vocab_list}}; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 1357 |  |  |  |  |  |  |  | 
| 1358 | 0 | 0 |  |  |  |  | unless ( @vocab_list ) | 
| 1359 |  |  |  |  |  |  | { | 
| 1360 | 0 | 0 |  |  |  |  | warn "No output vocabularies were selected for path '$path'" if $ds->debug; | 
| 1361 | 0 |  |  |  |  |  | return ""; | 
| 1362 |  |  |  |  |  |  | } | 
| 1363 |  |  |  |  |  |  |  | 
| 1364 |  |  |  |  |  |  | # Now generate the header for the documentation, in Pod format.  We | 
| 1365 |  |  |  |  |  |  | # include the special "=for wds_table_header" line to give PodParser.pm the | 
| 1366 |  |  |  |  |  |  | # information it needs to generate an HTML table. | 
| 1367 |  |  |  |  |  |  |  | 
| 1368 | 0 |  |  |  |  |  | my $doc_string = ''; | 
| 1369 | 0 |  |  |  |  |  | my $field_count = scalar(@vocab_list); | 
| 1370 | 0 |  |  |  |  |  | my $field_string = join ' / ', @vocab_list; | 
| 1371 |  |  |  |  |  |  |  | 
| 1372 | 0 | 0 |  |  |  |  | if ( $field_count > 1 ) | 
| 1373 |  |  |  |  |  |  | { | 
| 1374 | 0 |  |  |  |  |  | $doc_string .= "=for wds_table_header Field name*/$field_count | Block | Description\n\n"; | 
| 1375 | 0 |  |  |  |  |  | $doc_string .= "=over 4\n\n"; | 
| 1376 | 0 |  |  |  |  |  | $doc_string .= "=item $field_string\n\n"; | 
| 1377 |  |  |  |  |  |  | } | 
| 1378 |  |  |  |  |  |  |  | 
| 1379 |  |  |  |  |  |  | else | 
| 1380 |  |  |  |  |  |  | { | 
| 1381 | 0 |  |  |  |  |  | $doc_string .= "=for wds_table_header Field name* | Block | Description\n\n"; | 
| 1382 | 0 |  |  |  |  |  | $doc_string .= "=over 4\n\n"; | 
| 1383 |  |  |  |  |  |  | } | 
| 1384 |  |  |  |  |  |  |  | 
| 1385 |  |  |  |  |  |  | # Run through each block one at a time, documenting all of the fields in | 
| 1386 |  |  |  |  |  |  | # the corresponding field list. | 
| 1387 |  |  |  |  |  |  |  | 
| 1388 | 0 |  |  |  |  |  | my %uniq_block; | 
| 1389 |  |  |  |  |  |  |  | 
| 1390 | 0 |  |  |  |  |  | foreach my $i (0..$#blocks) | 
| 1391 |  |  |  |  |  |  | { | 
| 1392 | 0 |  |  |  |  |  | my $block_name = $blocks[$i]; | 
| 1393 | 0 |  |  |  |  |  | my $block_label = $labels[$i]; | 
| 1394 |  |  |  |  |  |  |  | 
| 1395 |  |  |  |  |  |  | # Make sure to only process each block once, even if it is listed more | 
| 1396 |  |  |  |  |  |  | # than once. | 
| 1397 |  |  |  |  |  |  |  | 
| 1398 | 0 | 0 |  |  |  |  | next if $uniq_block{$block_name}; $uniq_block{$block_name} = 1; | 
|  | 0 |  |  |  |  |  |  | 
| 1399 |  |  |  |  |  |  |  | 
| 1400 | 0 |  |  |  |  |  | my $output_list = $ds->{block}{$block_name}{output_list}; | 
| 1401 | 0 | 0 |  |  |  |  | next unless ref $output_list eq 'ARRAY'; | 
| 1402 |  |  |  |  |  |  |  | 
| 1403 | 0 |  |  |  |  |  | foreach my $r (@$output_list) | 
| 1404 |  |  |  |  |  |  | { | 
| 1405 | 0 | 0 |  |  |  |  | next unless defined $r->{output}; | 
| 1406 |  |  |  |  |  |  | $doc_string .= $ds->document_field($block_label, \@vocab_list, $r, $reverse_map) | 
| 1407 | 0 | 0 |  |  |  |  | unless $r->{undocumented}; | 
| 1408 |  |  |  |  |  |  | } | 
| 1409 |  |  |  |  |  |  | } | 
| 1410 |  |  |  |  |  |  |  | 
| 1411 | 0 |  |  |  |  |  | $doc_string .= "\n=back\n\n"; | 
| 1412 |  |  |  |  |  |  |  | 
| 1413 | 0 |  |  |  |  |  | return $doc_string; | 
| 1414 |  |  |  |  |  |  | } | 
| 1415 |  |  |  |  |  |  |  | 
| 1416 |  |  |  |  |  |  |  | 
| 1417 |  |  |  |  |  |  | sub document_summary { | 
| 1418 |  |  |  |  |  |  |  | 
| 1419 | 0 |  |  | 0 | 0 |  | my ($ds, $path) = @_; | 
| 1420 |  |  |  |  |  |  |  | 
| 1421 |  |  |  |  |  |  | # Return the empty string unless a summary block was defined for this path. | 
| 1422 |  |  |  |  |  |  |  | 
| 1423 | 0 |  |  |  |  |  | my $summary_block = $ds->node_attr($path, 'summary'); | 
| 1424 | 0 | 0 |  |  |  |  | return '' unless $summary_block; | 
| 1425 |  |  |  |  |  |  |  | 
| 1426 |  |  |  |  |  |  | # Otherwise, determine the set of vocabularies that are allowed for this | 
| 1427 |  |  |  |  |  |  | # path.  If none are specifically selected for this path, then all of the | 
| 1428 |  |  |  |  |  |  | # vocabularies defined for this data service are allowed. | 
| 1429 |  |  |  |  |  |  |  | 
| 1430 | 0 |  | 0 |  |  |  | my $vocabularies; $vocabularies = $ds->node_attr($path, 'allow_vocab') || $ds->{vocab}; | 
|  | 0 |  |  |  |  |  |  | 
| 1431 |  |  |  |  |  |  |  | 
| 1432 | 0 | 0 | 0 |  |  |  | unless ( ref $vocabularies eq 'HASH' && keys %$vocabularies ) | 
| 1433 |  |  |  |  |  |  | { | 
| 1434 | 0 |  |  |  |  |  | return ''; | 
| 1435 |  |  |  |  |  |  | } | 
| 1436 |  |  |  |  |  |  |  | 
| 1437 |  |  |  |  |  |  | my @vocab_list = grep { $vocabularies->{$_} && | 
| 1438 |  |  |  |  |  |  | ref $ds->{vocab}{$_} && | 
| 1439 | 0 | 0 | 0 |  |  |  | ! $ds->{vocab}{$_}{disabled} } @{$ds->{vocab_list}}; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 1440 |  |  |  |  |  |  |  | 
| 1441 | 0 | 0 |  |  |  |  | unless ( @vocab_list ) | 
| 1442 |  |  |  |  |  |  | { | 
| 1443 | 0 |  |  |  |  |  | return ""; | 
| 1444 |  |  |  |  |  |  | } | 
| 1445 |  |  |  |  |  |  |  | 
| 1446 |  |  |  |  |  |  | # Now generate the header for the documentation, in Pod format.  We | 
| 1447 |  |  |  |  |  |  | # include the special "=for wds_table_header" line to give PodParser.pm the | 
| 1448 |  |  |  |  |  |  | # information it needs to generate an HTML table. | 
| 1449 |  |  |  |  |  |  |  | 
| 1450 | 0 |  |  |  |  |  | my $doc_string = ''; | 
| 1451 | 0 |  |  |  |  |  | my $field_count = scalar(@vocab_list); | 
| 1452 | 0 |  |  |  |  |  | my $field_string = join ' / ', @vocab_list; | 
| 1453 |  |  |  |  |  |  |  | 
| 1454 | 0 | 0 |  |  |  |  | if ( $field_count > 1 ) | 
| 1455 |  |  |  |  |  |  | { | 
| 1456 | 0 |  |  |  |  |  | $doc_string .= "=for wds_table_header Field name*/$field_count | Block | Description\n\n"; | 
| 1457 | 0 |  |  |  |  |  | $doc_string .= "=over 4\n\n"; | 
| 1458 | 0 |  |  |  |  |  | $doc_string .= "=item $field_string\n\n"; | 
| 1459 |  |  |  |  |  |  | } | 
| 1460 |  |  |  |  |  |  |  | 
| 1461 |  |  |  |  |  |  | else | 
| 1462 |  |  |  |  |  |  | { | 
| 1463 | 0 |  |  |  |  |  | $doc_string .= "=for wds_table_header Field name* | Block | Description\n\n"; | 
| 1464 | 0 |  |  |  |  |  | $doc_string .= "=over 4\n\n"; | 
| 1465 |  |  |  |  |  |  | } | 
| 1466 |  |  |  |  |  |  |  | 
| 1467 |  |  |  |  |  |  | # Now determine the summary output list. | 
| 1468 |  |  |  |  |  |  |  | 
| 1469 | 0 |  |  |  |  |  | my $output_list = $ds->{block}{$summary_block}{output_list}; | 
| 1470 | 0 | 0 |  |  |  |  | return '' unless ref $output_list eq 'ARRAY'; | 
| 1471 |  |  |  |  |  |  |  | 
| 1472 | 0 |  |  |  |  |  | foreach my $r (@$output_list) | 
| 1473 |  |  |  |  |  |  | { | 
| 1474 | 0 | 0 |  |  |  |  | next unless defined $r->{output}; | 
| 1475 |  |  |  |  |  |  | $doc_string .= $ds->document_field('summary', \@vocab_list, $r, {}) | 
| 1476 | 0 | 0 |  |  |  |  | unless $r->{undocumented}; | 
| 1477 |  |  |  |  |  |  | } | 
| 1478 |  |  |  |  |  |  |  | 
| 1479 | 0 |  |  |  |  |  | $doc_string .= "\n=back\n\n"; | 
| 1480 |  |  |  |  |  |  |  | 
| 1481 | 0 |  |  |  |  |  | return $doc_string; | 
| 1482 |  |  |  |  |  |  | } | 
| 1483 |  |  |  |  |  |  |  | 
| 1484 |  |  |  |  |  |  |  | 
| 1485 |  |  |  |  |  |  | sub document_field { | 
| 1486 |  |  |  |  |  |  |  | 
| 1487 | 0 |  |  | 0 | 0 |  | my ($ds, $block_key, $vocab_list, $r, $rev_map) = @_; | 
| 1488 |  |  |  |  |  |  |  | 
| 1489 | 0 |  |  |  |  |  | my @names; | 
| 1490 |  |  |  |  |  |  |  | 
| 1491 | 0 |  |  |  |  |  | foreach my $v ( @$vocab_list ) | 
| 1492 |  |  |  |  |  |  | { | 
| 1493 |  |  |  |  |  |  | my $n = defined $r->{"${v}_name"}	    ? $r->{"${v}_name"} | 
| 1494 |  |  |  |  |  |  | : defined $r->{name}		    ? $r->{name} | 
| 1495 |  |  |  |  |  |  | : $ds->{vocab}{$v}{use_field_names} ? $r->{output} | 
| 1496 | 0 | 0 |  |  |  |  | :					      ''; | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 1497 |  |  |  |  |  |  |  | 
| 1498 | 0 |  | 0 |  |  |  | $n ||= 'I'; | 
| 1499 |  |  |  |  |  |  |  | 
| 1500 | 0 |  |  |  |  |  | push @names, $n | 
| 1501 |  |  |  |  |  |  | } | 
| 1502 |  |  |  |  |  |  |  | 
| 1503 | 0 |  |  |  |  |  | my $names = join ' / ', @names; | 
| 1504 |  |  |  |  |  |  |  | 
| 1505 | 0 |  | 0 |  |  |  | my $descrip = $r->{doc_string} || ""; | 
| 1506 |  |  |  |  |  |  |  | 
| 1507 | 0 | 0 |  |  |  |  | if ( defined $r->{if_block} ) | 
| 1508 |  |  |  |  |  |  | { | 
| 1509 | 0 | 0 |  |  |  |  | if ( ref $r->{if_block} eq 'ARRAY' ) | 
| 1510 |  |  |  |  |  |  | { | 
| 1511 | 0 |  | 0 |  |  |  | $block_key = join(', ', map { $rev_map->{$_} // $_ } @{$r->{if_block}}); | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 1512 |  |  |  |  |  |  | } | 
| 1513 |  |  |  |  |  |  | else | 
| 1514 |  |  |  |  |  |  | { | 
| 1515 | 0 |  | 0 |  |  |  | $block_key = $rev_map->{$r->{if_block}} // $r->{if_block}; | 
| 1516 |  |  |  |  |  |  | } | 
| 1517 |  |  |  |  |  |  | } | 
| 1518 |  |  |  |  |  |  |  | 
| 1519 | 0 |  |  |  |  |  | my $line = "\n=item $names ( $block_key )\n\n$descrip\n"; | 
| 1520 |  |  |  |  |  |  |  | 
| 1521 | 0 |  |  |  |  |  | return $line; | 
| 1522 |  |  |  |  |  |  | } | 
| 1523 |  |  |  |  |  |  |  | 
| 1524 |  |  |  |  |  |  |  | 
| 1525 |  |  |  |  |  |  | # process_record ( request, record, steps ) | 
| 1526 |  |  |  |  |  |  | # | 
| 1527 |  |  |  |  |  |  | # Execute any per-record processing steps that have been defined for this | 
| 1528 |  |  |  |  |  |  | # record. Return true if the record is to be included in the result, false | 
| 1529 |  |  |  |  |  |  | # otherwise. | 
| 1530 |  |  |  |  |  |  |  | 
| 1531 |  |  |  |  |  |  | sub process_record { | 
| 1532 |  |  |  |  |  |  |  | 
| 1533 | 0 |  |  | 0 | 0 |  | my ($ds, $request, $record, $steps) = @_; | 
| 1534 |  |  |  |  |  |  |  | 
| 1535 |  |  |  |  |  |  | # If there are no processing steps to do, return immediately. | 
| 1536 |  |  |  |  |  |  |  | 
| 1537 | 0 | 0 | 0 |  |  |  | return 1 unless ref $steps eq 'ARRAY' and @$steps; | 
| 1538 |  |  |  |  |  |  |  | 
| 1539 |  |  |  |  |  |  | # Otherwise go through the steps one by one. | 
| 1540 |  |  |  |  |  |  |  | 
| 1541 | 0 |  |  |  |  |  | foreach my $p ( @$steps ) | 
| 1542 |  |  |  |  |  |  | { | 
| 1543 |  |  |  |  |  |  | # Skip this processing step based on a conditional field value, if one | 
| 1544 |  |  |  |  |  |  | # is defined. | 
| 1545 |  |  |  |  |  |  |  | 
| 1546 | 0 | 0 |  |  |  |  | if ( my $cond_field = $p->{if_field} ) | 
|  |  | 0 |  |  |  |  |  | 
| 1547 |  |  |  |  |  |  | { | 
| 1548 | 0 | 0 |  |  |  |  | next unless defined $record->{$cond_field}; | 
| 1549 | 0 | 0 | 0 |  |  |  | next if ref $record->{$cond_field} eq 'ARRAY' && @{$record->{$cond_field}} == 0; | 
|  | 0 |  |  |  |  |  |  | 
| 1550 |  |  |  |  |  |  | } | 
| 1551 |  |  |  |  |  |  |  | 
| 1552 |  |  |  |  |  |  | elsif ( $cond_field = $p->{not_field} ) | 
| 1553 |  |  |  |  |  |  | { | 
| 1554 | 0 | 0 | 0 |  |  |  | next if defined $record->{$cond_field} && ref $record->{$cond_field} ne 'ARRAY'; | 
| 1555 | 0 | 0 | 0 |  |  |  | next if ref $record->{$cond_field} eq 'ARRAY' && @{$record->{$cond_field}} > 0; | 
|  | 0 |  |  |  |  |  |  | 
| 1556 |  |  |  |  |  |  | } | 
| 1557 |  |  |  |  |  |  |  | 
| 1558 |  |  |  |  |  |  | # If this step is a 'check_field' step, then do the check. | 
| 1559 |  |  |  |  |  |  |  | 
| 1560 | 0 | 0 |  |  |  |  | if ( defined $p->{check_field} ) | 
|  |  | 0 |  |  |  |  |  | 
| 1561 |  |  |  |  |  |  | { | 
| 1562 | 0 |  |  |  |  |  | $ds->check_field_type($record, $p->{check_field}, $p->{data_type}, $p->{subst}); | 
| 1563 | 0 |  |  |  |  |  | next; | 
| 1564 |  |  |  |  |  |  | } | 
| 1565 |  |  |  |  |  |  |  | 
| 1566 |  |  |  |  |  |  | # If this step is a 'check' step (i.e. check the entire record) then | 
| 1567 |  |  |  |  |  |  | # do the check. If it fails, we return false. | 
| 1568 |  |  |  |  |  |  |  | 
| 1569 |  |  |  |  |  |  | elsif ( defined $p->{check} ) | 
| 1570 |  |  |  |  |  |  | { | 
| 1571 | 0 |  |  |  |  |  | my $check_value = $p->{check}; | 
| 1572 |  |  |  |  |  |  |  | 
| 1573 |  |  |  |  |  |  | # If the value is '*' or the empty string, then we must have a | 
| 1574 |  |  |  |  |  |  | # code reference to call. | 
| 1575 |  |  |  |  |  |  |  | 
| 1576 | 0 | 0 | 0 |  |  |  | if ( $check_value eq '' || $check_value eq '*' ) | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 1577 |  |  |  |  |  |  | { | 
| 1578 | 0 |  |  |  |  |  | return $p->{code}($request, $record); | 
| 1579 |  |  |  |  |  |  | } | 
| 1580 |  |  |  |  |  |  |  | 
| 1581 |  |  |  |  |  |  | # Otherwise, if the value is '1' or '0' then return that.  The | 
| 1582 |  |  |  |  |  |  | # former will cause the record to be included in the result, the | 
| 1583 |  |  |  |  |  |  | # latter will cause it to be skipped.  This is mainly useful in | 
| 1584 |  |  |  |  |  |  | # conjunction with 'if_field' or 'not_field'. | 
| 1585 |  |  |  |  |  |  |  | 
| 1586 |  |  |  |  |  |  | elsif ( $check_value eq '1' || $check_value eq '0' ) | 
| 1587 |  |  |  |  |  |  | { | 
| 1588 | 0 |  |  |  |  |  | return $check_value; | 
| 1589 |  |  |  |  |  |  | } | 
| 1590 |  |  |  |  |  |  |  | 
| 1591 |  |  |  |  |  |  | # Otherwise, we assume that we have been given a field name and | 
| 1592 |  |  |  |  |  |  | # either call a code reference or do a hash-table lookup. | 
| 1593 |  |  |  |  |  |  |  | 
| 1594 |  |  |  |  |  |  | elsif ( defined $p->{code} ) | 
| 1595 |  |  |  |  |  |  | { | 
| 1596 | 0 |  |  |  |  |  | my $value = $record->{$check_value}; | 
| 1597 | 0 |  |  |  |  |  | return $p->{code}($request, $value); | 
| 1598 |  |  |  |  |  |  | } | 
| 1599 |  |  |  |  |  |  |  | 
| 1600 |  |  |  |  |  |  | elsif ( defined $p->{lookup} ) | 
| 1601 |  |  |  |  |  |  | { | 
| 1602 | 0 |  |  |  |  |  | my $value = $record->{$check_value}; | 
| 1603 | 0 |  | 0 |  |  |  | return $p->{lookup}{$value} // $p->{default}; | 
| 1604 |  |  |  |  |  |  | } | 
| 1605 |  |  |  |  |  |  |  | 
| 1606 |  |  |  |  |  |  | # Otherwise, we just return the value of the specified field. The | 
| 1607 |  |  |  |  |  |  | # record will be included if this value is true, and skipped if | 
| 1608 |  |  |  |  |  |  | # false. | 
| 1609 |  |  |  |  |  |  |  | 
| 1610 |  |  |  |  |  |  | else | 
| 1611 |  |  |  |  |  |  | { | 
| 1612 | 0 |  |  |  |  |  | return $record->{$check_value}; | 
| 1613 |  |  |  |  |  |  | } | 
| 1614 |  |  |  |  |  |  |  | 
| 1615 | 0 |  |  |  |  |  | next; | 
| 1616 |  |  |  |  |  |  | } | 
| 1617 |  |  |  |  |  |  |  | 
| 1618 |  |  |  |  |  |  | # If we get here, the current rule must be a 'set'.  Figure out which | 
| 1619 |  |  |  |  |  |  | # field (if any) we are affecting.  A value of '*' means to use the | 
| 1620 |  |  |  |  |  |  | # entire record (only relevant with 'code'). | 
| 1621 |  |  |  |  |  |  |  | 
| 1622 | 0 |  |  |  |  |  | my $set_field = $p->{set}; | 
| 1623 |  |  |  |  |  |  |  | 
| 1624 |  |  |  |  |  |  | # Figure out which field (if any) we are looking at.  Skip this | 
| 1625 |  |  |  |  |  |  | # processing step if the source field is empty, unless the attribute | 
| 1626 |  |  |  |  |  |  | # 'always' is set. | 
| 1627 |  |  |  |  |  |  |  | 
| 1628 | 0 |  | 0 |  |  |  | my $source_field = $p->{from} || $p->{from_each} || $p->{set}; | 
| 1629 |  |  |  |  |  |  |  | 
| 1630 |  |  |  |  |  |  | # Skip any processing step if the record does not have a non-empty | 
| 1631 |  |  |  |  |  |  | # value in the corresponding field (unless the 'always' attribute is | 
| 1632 |  |  |  |  |  |  | # set). | 
| 1633 |  |  |  |  |  |  |  | 
| 1634 | 0 | 0 | 0 |  |  |  | if ( $source_field && $source_field ne '*' && ! $p->{always} ) | 
|  |  |  | 0 |  |  |  |  | 
| 1635 |  |  |  |  |  |  | { | 
| 1636 | 0 | 0 |  |  |  |  | next unless defined $record->{$source_field}; | 
| 1637 | 0 | 0 | 0 |  |  |  | next if ref $record->{$source_field} eq 'ARRAY' && @{$record->{$source_field}} == 0; | 
|  | 0 |  |  |  |  |  |  | 
| 1638 |  |  |  |  |  |  | } | 
| 1639 |  |  |  |  |  |  |  | 
| 1640 |  |  |  |  |  |  | # Now generate a list of result values, according to the attributes of this | 
| 1641 |  |  |  |  |  |  | # processing step. | 
| 1642 |  |  |  |  |  |  |  | 
| 1643 | 0 |  |  |  |  |  | my @result; | 
| 1644 |  |  |  |  |  |  |  | 
| 1645 |  |  |  |  |  |  | # If we have a 'code' attribute, then call it. | 
| 1646 |  |  |  |  |  |  |  | 
| 1647 | 0 | 0 |  |  |  |  | if ( ref $p->{code} eq 'CODE' ) | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 1648 |  |  |  |  |  |  | { | 
| 1649 | 0 | 0 |  |  |  |  | if ( $source_field eq '*' ) | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 1650 |  |  |  |  |  |  | { | 
| 1651 | 0 |  |  |  |  |  | @result = $p->{code}($request, $record); | 
| 1652 |  |  |  |  |  |  | } | 
| 1653 |  |  |  |  |  |  |  | 
| 1654 |  |  |  |  |  |  | elsif ( $p->{from_each} ) | 
| 1655 |  |  |  |  |  |  | { | 
| 1656 | 0 |  |  |  |  |  | @result = map { $p->{code}($request, $_) } | 
| 1657 |  |  |  |  |  |  | (ref $record->{$source_field} eq 'ARRAY' ? | 
| 1658 | 0 | 0 |  |  |  |  | @{$record->{$source_field}} : $record->{$source_field}); | 
|  | 0 |  |  |  |  |  |  | 
| 1659 |  |  |  |  |  |  | } | 
| 1660 |  |  |  |  |  |  |  | 
| 1661 |  |  |  |  |  |  | elsif ( $p->{from} ) | 
| 1662 |  |  |  |  |  |  | { | 
| 1663 | 0 |  |  |  |  |  | @result = $p->{code}($request, $record->{$source_field}); | 
| 1664 |  |  |  |  |  |  | } | 
| 1665 |  |  |  |  |  |  |  | 
| 1666 |  |  |  |  |  |  | else | 
| 1667 |  |  |  |  |  |  | { | 
| 1668 | 0 |  |  |  |  |  | @result = $p->{code}($request, $record->{$set_field}); | 
| 1669 |  |  |  |  |  |  | } | 
| 1670 |  |  |  |  |  |  | } | 
| 1671 |  |  |  |  |  |  |  | 
| 1672 |  |  |  |  |  |  | # If we have a 'lookup' attribute, then use it. | 
| 1673 |  |  |  |  |  |  |  | 
| 1674 |  |  |  |  |  |  | elsif ( ref $p->{lookup} eq 'HASH' ) | 
| 1675 |  |  |  |  |  |  | { | 
| 1676 | 0 | 0 | 0 |  |  |  | if ( $p->{from_each} ) | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 1677 |  |  |  |  |  |  | { | 
| 1678 | 0 | 0 |  |  |  |  | if ( ref $record->{$source_field} eq 'ARRAY' ) | 
|  |  | 0 |  |  |  |  |  | 
| 1679 |  |  |  |  |  |  | { | 
| 1680 | 0 |  | 0 |  |  |  | @result = map { $p->{lookup}{$_} // $p->{default} } @{$record->{$source_field}}; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 1681 |  |  |  |  |  |  | } | 
| 1682 |  |  |  |  |  |  | elsif ( ! ref $record->{$source_field} ) | 
| 1683 |  |  |  |  |  |  | { | 
| 1684 | 0 |  | 0 |  |  |  | @result = $p->{lookup}{$record->{$source_field}} // $p->{default}; | 
| 1685 |  |  |  |  |  |  | } | 
| 1686 |  |  |  |  |  |  | } | 
| 1687 |  |  |  |  |  |  |  | 
| 1688 |  |  |  |  |  |  | elsif ( $p->{from} ) | 
| 1689 |  |  |  |  |  |  | { | 
| 1690 |  |  |  |  |  |  | @result = $p->{lookup}{$record->{$source_field}} // $p->{default} | 
| 1691 | 0 | 0 | 0 |  |  |  | unless ref $record->{$source_field}; | 
| 1692 |  |  |  |  |  |  | } | 
| 1693 |  |  |  |  |  |  |  | 
| 1694 |  |  |  |  |  |  | elsif ( $set_field ne '*' && ! ref $record->{$set_field} ) | 
| 1695 |  |  |  |  |  |  | { | 
| 1696 | 0 | 0 | 0 |  |  |  | @result = $p->{lookup}{$record->{$set_field}} // $p->{default} if defined $record->{$set_field}; | 
| 1697 |  |  |  |  |  |  | } | 
| 1698 |  |  |  |  |  |  | } | 
| 1699 |  |  |  |  |  |  |  | 
| 1700 |  |  |  |  |  |  | # If we have a 'split' attribute, then use it. | 
| 1701 |  |  |  |  |  |  |  | 
| 1702 |  |  |  |  |  |  | elsif ( defined $p->{split} ) | 
| 1703 |  |  |  |  |  |  | { | 
| 1704 | 0 | 0 |  |  |  |  | if ( $p->{from_each} ) | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 1705 |  |  |  |  |  |  | { | 
| 1706 | 0 | 0 |  |  |  |  | if ( ref $record->{$source_field} eq 'ARRAY' ) | 
|  |  | 0 |  |  |  |  |  | 
| 1707 |  |  |  |  |  |  | { | 
| 1708 | 0 |  |  |  |  |  | @result = map { split($p->{split}, $_) } @{$record->{$source_field}}; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 1709 |  |  |  |  |  |  | } | 
| 1710 |  |  |  |  |  |  | elsif ( ! ref $record->{$source_field} ) | 
| 1711 |  |  |  |  |  |  | { | 
| 1712 | 0 |  |  |  |  |  | @result = split($p->{split}, $record->{$source_field}); | 
| 1713 |  |  |  |  |  |  | } | 
| 1714 |  |  |  |  |  |  | } | 
| 1715 |  |  |  |  |  |  |  | 
| 1716 |  |  |  |  |  |  | elsif ( $p->{from} ) | 
| 1717 |  |  |  |  |  |  | { | 
| 1718 |  |  |  |  |  |  | @result = split $p->{split}, $record->{$source_field} | 
| 1719 | 0 | 0 | 0 |  |  |  | if defined $record->{$source_field} && ! ref $record->{$source_field}; | 
| 1720 |  |  |  |  |  |  | } | 
| 1721 |  |  |  |  |  |  |  | 
| 1722 |  |  |  |  |  |  | elsif ( $set_field ne '*' ) | 
| 1723 |  |  |  |  |  |  | { | 
| 1724 |  |  |  |  |  |  | @result = split $p->{split}, $record->{$set_field} | 
| 1725 | 0 | 0 | 0 |  |  |  | if defined $record->{$set_field} && ! ref $record->{$set_field}; | 
| 1726 |  |  |  |  |  |  | } | 
| 1727 |  |  |  |  |  |  | } | 
| 1728 |  |  |  |  |  |  |  | 
| 1729 |  |  |  |  |  |  | # If we have a 'join' attribute, then use it. | 
| 1730 |  |  |  |  |  |  |  | 
| 1731 |  |  |  |  |  |  | elsif ( defined $p->{join} ) | 
| 1732 |  |  |  |  |  |  | { | 
| 1733 | 0 | 0 |  |  |  |  | if ( $source_field ) | 
|  |  | 0 |  |  |  |  |  | 
| 1734 |  |  |  |  |  |  | { | 
| 1735 | 0 |  |  |  |  |  | @result = join($p->{join}, @{$record->{$source_field}}) | 
| 1736 | 0 | 0 |  |  |  |  | if ref $record->{$source_field} eq 'ARRAY'; | 
| 1737 |  |  |  |  |  |  | } | 
| 1738 |  |  |  |  |  |  |  | 
| 1739 |  |  |  |  |  |  | elsif ( $set_field ne '*' ) | 
| 1740 |  |  |  |  |  |  | { | 
| 1741 | 0 |  |  |  |  |  | @result = join($p->{join}, @{$record->{$set_field}}) | 
| 1742 | 0 | 0 |  |  |  |  | if ref $record->{$set_field} eq 'ARRAY'; | 
| 1743 |  |  |  |  |  |  | } | 
| 1744 |  |  |  |  |  |  | } | 
| 1745 |  |  |  |  |  |  |  | 
| 1746 |  |  |  |  |  |  | # Otherwise, we just use the vaoue of the source field. | 
| 1747 |  |  |  |  |  |  |  | 
| 1748 |  |  |  |  |  |  | else | 
| 1749 |  |  |  |  |  |  | { | 
| 1750 |  |  |  |  |  |  | @result = ref $record->{$source_field} eq 'ARRAY' ? | 
| 1751 | 0 | 0 |  |  |  |  | @{$record->{$source_field}} : $record->{$source_field}; | 
|  | 0 |  |  |  |  |  |  | 
| 1752 |  |  |  |  |  |  | } | 
| 1753 |  |  |  |  |  |  |  | 
| 1754 |  |  |  |  |  |  | # If the value of 'set' is '*', then we're done.  This is generally | 
| 1755 |  |  |  |  |  |  | # only used to call a procedure with side effects. | 
| 1756 |  |  |  |  |  |  |  | 
| 1757 | 0 | 0 |  |  |  |  | next if $set_field eq '*'; | 
| 1758 |  |  |  |  |  |  |  | 
| 1759 |  |  |  |  |  |  | # Otherwise, use the value to modify the specified field of the record. | 
| 1760 |  |  |  |  |  |  |  | 
| 1761 |  |  |  |  |  |  | # If the attribute 'append' is set, then append to the specified field. | 
| 1762 |  |  |  |  |  |  | # Convert the value to an array if it isn't already. | 
| 1763 |  |  |  |  |  |  |  | 
| 1764 | 0 | 0 |  |  |  |  | if ( $p->{append} ) | 
| 1765 |  |  |  |  |  |  | { | 
| 1766 |  |  |  |  |  |  | $record->{$set_field} = [ $record->{$set_field} ] if defined $record->{$set_field} | 
| 1767 | 0 | 0 | 0 |  |  |  | and ref $record->{$set_field} ne 'ARRAY'; | 
| 1768 |  |  |  |  |  |  |  | 
| 1769 | 0 |  |  |  |  |  | push @{$record->{$set_field}}, @result; | 
|  | 0 |  |  |  |  |  |  | 
| 1770 |  |  |  |  |  |  | } | 
| 1771 |  |  |  |  |  |  |  | 
| 1772 |  |  |  |  |  |  | else | 
| 1773 |  |  |  |  |  |  | { | 
| 1774 | 0 | 0 |  |  |  |  | if ( @result == 1 ) | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 1775 |  |  |  |  |  |  | { | 
| 1776 | 0 |  |  |  |  |  | ($record->{$set_field}) = @result; | 
| 1777 |  |  |  |  |  |  | } | 
| 1778 |  |  |  |  |  |  |  | 
| 1779 |  |  |  |  |  |  | elsif ( @result > 1 ) | 
| 1780 |  |  |  |  |  |  | { | 
| 1781 | 0 |  |  |  |  |  | $record->{$set_field} = \@result; | 
| 1782 |  |  |  |  |  |  | } | 
| 1783 |  |  |  |  |  |  |  | 
| 1784 |  |  |  |  |  |  | elsif ( not $p->{always} ) | 
| 1785 |  |  |  |  |  |  | { | 
| 1786 | 0 |  |  |  |  |  | delete $record->{$set_field}; | 
| 1787 |  |  |  |  |  |  | } | 
| 1788 |  |  |  |  |  |  |  | 
| 1789 |  |  |  |  |  |  | else | 
| 1790 |  |  |  |  |  |  | { | 
| 1791 | 0 |  |  |  |  |  | $record->{$set_field} = ''; | 
| 1792 |  |  |  |  |  |  | } | 
| 1793 |  |  |  |  |  |  | } | 
| 1794 |  |  |  |  |  |  | } | 
| 1795 |  |  |  |  |  |  |  | 
| 1796 | 0 |  |  |  |  |  | return 1; | 
| 1797 |  |  |  |  |  |  | } | 
| 1798 |  |  |  |  |  |  |  | 
| 1799 |  |  |  |  |  |  |  | 
| 1800 |  |  |  |  |  |  | # check_field_type ( record, field, type, subst ) | 
| 1801 |  |  |  |  |  |  | # | 
| 1802 |  |  |  |  |  |  | # Make sure that the specified field matches the specified data type.  If not, | 
| 1803 |  |  |  |  |  |  | # substitute the specified value. | 
| 1804 |  |  |  |  |  |  |  | 
| 1805 |  |  |  |  |  |  | sub check_field_type { | 
| 1806 |  |  |  |  |  |  |  | 
| 1807 | 0 |  |  | 0 | 0 |  | my ($ds, $record, $field, $type, $subst) = @_; | 
| 1808 |  |  |  |  |  |  |  | 
| 1809 | 0 | 0 |  |  |  |  | return unless defined $record->{$field}; | 
| 1810 |  |  |  |  |  |  |  | 
| 1811 | 0 | 0 |  |  |  |  | if ( $type eq 'int' ) | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 1812 |  |  |  |  |  |  | { | 
| 1813 | 0 | 0 |  |  |  |  | return if $record->{$field} =~ qr< ^ -? [1-9][0-9]* $ >x; | 
| 1814 |  |  |  |  |  |  | } | 
| 1815 |  |  |  |  |  |  |  | 
| 1816 |  |  |  |  |  |  | elsif ( $type eq 'pos' ) | 
| 1817 |  |  |  |  |  |  | { | 
| 1818 | 0 | 0 |  |  |  |  | return if $record->{$field} =~ qr< ^ [1-9][0-9]* $ >x; | 
| 1819 |  |  |  |  |  |  | } | 
| 1820 |  |  |  |  |  |  |  | 
| 1821 |  |  |  |  |  |  | elsif ( $type eq 'dec' ) | 
| 1822 |  |  |  |  |  |  | { | 
| 1823 | 0 | 0 |  |  |  |  | return if $record->{$field} =~ qr< ^ -? (?: [1-9][0-9]* (?: \. [0-9]* )? | [0]? \. [0-9]+ | [0] \.? ) $ >x; | 
| 1824 |  |  |  |  |  |  | } | 
| 1825 |  |  |  |  |  |  |  | 
| 1826 |  |  |  |  |  |  | elsif ( $type eq 'sci' ) | 
| 1827 |  |  |  |  |  |  | { | 
| 1828 | 0 | 0 |  |  |  |  | return if $record->{$field} =~ qr< ^ -? (?: [1-9][0-9]* \. [0-9]* | [0]? \. [0-9]+ | [0] \. ) (?: [eE] -? [1-9][0-9]* ) $ >x; | 
| 1829 |  |  |  |  |  |  | } | 
| 1830 |  |  |  |  |  |  |  | 
| 1831 |  |  |  |  |  |  | # If the data type is something we don't recognize, don't do any check. | 
| 1832 |  |  |  |  |  |  |  | 
| 1833 |  |  |  |  |  |  | else | 
| 1834 |  |  |  |  |  |  | { | 
| 1835 | 0 |  |  |  |  |  | return; | 
| 1836 |  |  |  |  |  |  | } | 
| 1837 |  |  |  |  |  |  |  | 
| 1838 |  |  |  |  |  |  | # If we get here, then the value failed the test.  If we were given a | 
| 1839 |  |  |  |  |  |  | # replacement value, substitute it.  Otherwise, just delete the field. | 
| 1840 |  |  |  |  |  |  |  | 
| 1841 | 0 | 0 |  |  |  |  | if ( defined $subst ) | 
| 1842 |  |  |  |  |  |  | { | 
| 1843 | 0 |  |  |  |  |  | $record->{$field} = $subst; | 
| 1844 |  |  |  |  |  |  | } | 
| 1845 |  |  |  |  |  |  |  | 
| 1846 |  |  |  |  |  |  | else | 
| 1847 |  |  |  |  |  |  | { | 
| 1848 | 0 |  |  |  |  |  | delete $record->{$field}; | 
| 1849 |  |  |  |  |  |  | } | 
| 1850 |  |  |  |  |  |  | } | 
| 1851 |  |  |  |  |  |  |  | 
| 1852 |  |  |  |  |  |  |  | 
| 1853 |  |  |  |  |  |  | # _generate_single_result ( request ) | 
| 1854 |  |  |  |  |  |  | # | 
| 1855 |  |  |  |  |  |  | # This function is called after an operation is executed and returns a single | 
| 1856 |  |  |  |  |  |  | # record.  Return this record formatted as a single string according to the | 
| 1857 |  |  |  |  |  |  | # specified output format. | 
| 1858 |  |  |  |  |  |  |  | 
| 1859 |  |  |  |  |  |  | sub _generate_single_result { | 
| 1860 |  |  |  |  |  |  |  | 
| 1861 | 0 |  |  | 0 |  |  | my ($ds, $request) = @_; | 
| 1862 |  |  |  |  |  |  |  | 
| 1863 |  |  |  |  |  |  | # Determine the output format and figure out which class implements it. | 
| 1864 |  |  |  |  |  |  |  | 
| 1865 | 0 |  |  |  |  |  | my $format = $request->output_format; | 
| 1866 | 0 |  |  |  |  |  | my $format_class = $ds->{format}{$format}{package}; | 
| 1867 |  |  |  |  |  |  |  | 
| 1868 | 0 | 0 |  |  |  |  | die "could not generate a result in format '$format': no implementing module was found" | 
| 1869 |  |  |  |  |  |  | unless $format_class; | 
| 1870 |  |  |  |  |  |  |  | 
| 1871 | 0 |  |  |  |  |  | my $path = $request->node_path; | 
| 1872 | 0 |  | 0 |  |  |  | my $serial_hook = $ds->{hook_enabled}{after_serialize_hook} && $ds->node_attr($path, 'after_serialize_hook'); | 
| 1873 |  |  |  |  |  |  |  | 
| 1874 |  |  |  |  |  |  | # Set the result count to 1, in case the client asked for it. | 
| 1875 |  |  |  |  |  |  |  | 
| 1876 | 0 |  |  |  |  |  | $request->{result_count} = 1; | 
| 1877 |  |  |  |  |  |  |  | 
| 1878 |  |  |  |  |  |  | # Get the lists that specify how to process each record and which fields | 
| 1879 |  |  |  |  |  |  | # to output. | 
| 1880 |  |  |  |  |  |  |  | 
| 1881 | 0 |  |  |  |  |  | my $proc_list = $request->{proc_list}; | 
| 1882 | 0 |  |  |  |  |  | my $field_list = $request->{field_list}; | 
| 1883 |  |  |  |  |  |  |  | 
| 1884 |  |  |  |  |  |  | # Make sure we have at least one field to output. | 
| 1885 |  |  |  |  |  |  |  | 
| 1886 | 0 | 0 | 0 |  |  |  | unless ( ref $field_list && @$field_list ) | 
| 1887 |  |  |  |  |  |  | { | 
| 1888 | 0 |  |  |  |  |  | $request->add_warning("No output fields were defined for this request."); | 
| 1889 |  |  |  |  |  |  | } | 
| 1890 |  |  |  |  |  |  |  | 
| 1891 |  |  |  |  |  |  | # If there are any processing steps to do, then do them. | 
| 1892 |  |  |  |  |  |  |  | 
| 1893 | 0 |  |  |  |  |  | $ds->process_record($request, $request->{main_record}, $proc_list); | 
| 1894 |  |  |  |  |  |  |  | 
| 1895 |  |  |  |  |  |  | # If there is a before_record_hook defined for this path, call it now. For a single result, | 
| 1896 |  |  |  |  |  |  | # calls to 'skip_output_record' are not allowed. | 
| 1897 |  |  |  |  |  |  |  | 
| 1898 | 0 | 0 |  |  |  |  | if ( $ds->{hook_enabled}{before_record_hook} ) | 
| 1899 |  |  |  |  |  |  | { | 
| 1900 |  |  |  |  |  |  | $ds->_call_hooks($path, 'before_record_hook', $request, $request->{main_record}) | 
| 1901 | 0 |  |  |  |  |  | } | 
| 1902 |  |  |  |  |  |  |  | 
| 1903 |  |  |  |  |  |  | # Generate the initial part of the output, before the first record. | 
| 1904 |  |  |  |  |  |  |  | 
| 1905 | 0 |  |  |  |  |  | my $header = $format_class->emit_header($request, $field_list); | 
| 1906 |  |  |  |  |  |  |  | 
| 1907 |  |  |  |  |  |  | # Generate the output corresponding to our single record. | 
| 1908 |  |  |  |  |  |  |  | 
| 1909 | 0 |  |  |  |  |  | my $record = $format_class->emit_record($request, $request->{main_record}, $field_list); | 
| 1910 |  |  |  |  |  |  |  | 
| 1911 |  |  |  |  |  |  | # Generate the final part of the output, after the last record. | 
| 1912 |  |  |  |  |  |  |  | 
| 1913 | 0 |  |  |  |  |  | my $footer = $format_class->emit_footer($request, $field_list); | 
| 1914 |  |  |  |  |  |  |  | 
| 1915 |  |  |  |  |  |  | # If an after_serialize_hook is defined for this path, call it. | 
| 1916 |  |  |  |  |  |  |  | 
| 1917 | 0 | 0 |  |  |  |  | if ( $serial_hook ) | 
| 1918 |  |  |  |  |  |  | { | 
| 1919 | 0 |  |  |  |  |  | my $rs = ''; | 
| 1920 |  |  |  |  |  |  |  | 
| 1921 | 0 |  |  |  |  |  | $ds->_call_hook_list($serial_hook, $request, 'header', \$header); | 
| 1922 | 0 |  |  |  |  |  | $ds->_call_hook_list($serial_hook, $request, 'record', \$rs, \$record); | 
| 1923 | 0 |  |  |  |  |  | $ds->_call_hook_list($serial_hook, $request, 'footer', \$footer); | 
| 1924 |  |  |  |  |  |  | } | 
| 1925 |  |  |  |  |  |  |  | 
| 1926 | 0 |  |  |  |  |  | return $header . $record . $footer; | 
| 1927 |  |  |  |  |  |  | } | 
| 1928 |  |  |  |  |  |  |  | 
| 1929 |  |  |  |  |  |  |  | 
| 1930 |  |  |  |  |  |  | # _generate_compound_result ( request ) | 
| 1931 |  |  |  |  |  |  | # | 
| 1932 |  |  |  |  |  |  | # This function is called after an operation is executed and returns a result | 
| 1933 |  |  |  |  |  |  | # set, provided that the entire result set does not need to be processed | 
| 1934 |  |  |  |  |  |  | # before output.  It serializes each result record according to the specified output | 
| 1935 |  |  |  |  |  |  | # format and returns the resulting string.  If $streaming_threshold is | 
| 1936 |  |  |  |  |  |  | # specified, and if the size of the output exceeds this threshold, this | 
| 1937 |  |  |  |  |  |  | # routine then sets up to stream the rest of the output. | 
| 1938 |  |  |  |  |  |  |  | 
| 1939 |  |  |  |  |  |  | sub _generate_compound_result { | 
| 1940 |  |  |  |  |  |  |  | 
| 1941 | 0 |  |  | 0 |  |  | my ($ds, $request, $streaming_threshold) = @_; | 
| 1942 |  |  |  |  |  |  |  | 
| 1943 |  |  |  |  |  |  | # Determine the output format and figure out which class implements it. | 
| 1944 |  |  |  |  |  |  |  | 
| 1945 | 0 |  |  |  |  |  | my $format = $request->output_format; | 
| 1946 | 0 |  |  |  |  |  | my $format_class = $ds->{format}{$format}{package}; | 
| 1947 |  |  |  |  |  |  |  | 
| 1948 | 0 | 0 |  |  |  |  | die "could not generate a result in format '$format': no implementing module was found" | 
| 1949 |  |  |  |  |  |  | unless $format_class; | 
| 1950 |  |  |  |  |  |  |  | 
| 1951 | 0 |  |  |  |  |  | my $path = $request->node_path; | 
| 1952 | 0 |  | 0 |  |  |  | my $serial_hook = $ds->{hook_enabled}{after_serialize_hook} && $ds->node_attr($path, 'after_serialize_hook'); | 
| 1953 |  |  |  |  |  |  |  | 
| 1954 |  |  |  |  |  |  | # Get the lists that specify how to process each record and which fields | 
| 1955 |  |  |  |  |  |  | # to output. | 
| 1956 |  |  |  |  |  |  |  | 
| 1957 | 0 |  |  |  |  |  | my $proc_list = $request->{proc_list}; | 
| 1958 | 0 |  |  |  |  |  | my $field_list = $request->{field_list}; | 
| 1959 |  |  |  |  |  |  |  | 
| 1960 |  |  |  |  |  |  | # If we have an explicit result list, then we know the count. | 
| 1961 |  |  |  |  |  |  |  | 
| 1962 | 0 |  |  |  |  |  | $request->{result_count} = scalar(@{$request->{main_result}}) | 
| 1963 | 0 | 0 |  |  |  |  | if ref $request->{main_result}; | 
| 1964 |  |  |  |  |  |  |  | 
| 1965 |  |  |  |  |  |  | # Generate the initial part of the output, before the first record. | 
| 1966 |  |  |  |  |  |  |  | 
| 1967 | 0 |  |  |  |  |  | my $output = $format_class->emit_header($request, $field_list); | 
| 1968 |  |  |  |  |  |  |  | 
| 1969 | 0 | 0 |  |  |  |  | if ( $serial_hook ) | 
| 1970 |  |  |  |  |  |  | { | 
| 1971 | 0 |  |  |  |  |  | $ds->_call_hook_list($serial_hook, $request, 'header', \$output); | 
| 1972 |  |  |  |  |  |  | } | 
| 1973 |  |  |  |  |  |  |  | 
| 1974 |  |  |  |  |  |  | # A record separator is emitted before every record except the first.  If | 
| 1975 |  |  |  |  |  |  | # this format class does not define a record separator, use the empty | 
| 1976 |  |  |  |  |  |  | # string. | 
| 1977 |  |  |  |  |  |  |  | 
| 1978 | 0 | 0 |  |  |  |  | $request->{rs} = $format_class->can('emit_separator') ? | 
| 1979 |  |  |  |  |  |  | $format_class->emit_separator($request) : ''; | 
| 1980 |  |  |  |  |  |  |  | 
| 1981 | 0 |  |  |  |  |  | my $emit_rs = 0; | 
| 1982 |  |  |  |  |  |  |  | 
| 1983 | 0 |  |  |  |  |  | $request->{actual_count} = 0; | 
| 1984 |  |  |  |  |  |  |  | 
| 1985 |  |  |  |  |  |  | # If we have a result limit of 0, just output the header and footer and | 
| 1986 |  |  |  |  |  |  | # don't bother about the records. | 
| 1987 |  |  |  |  |  |  |  | 
| 1988 | 0 | 0 | 0 |  |  |  | if ( defined $request->{result_limit} && $request->{result_limit} eq '0' ) | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
| 1989 |  |  |  |  |  |  | { | 
| 1990 | 0 |  |  |  |  |  | $request->{limit_zero} = 1; | 
| 1991 |  |  |  |  |  |  | } | 
| 1992 |  |  |  |  |  |  |  | 
| 1993 |  |  |  |  |  |  | # Otherwise, if an offset was specified and the result method didn't | 
| 1994 |  |  |  |  |  |  | # handle this itself, then skip the specified number of records. | 
| 1995 |  |  |  |  |  |  |  | 
| 1996 |  |  |  |  |  |  | elsif ( defined $request->{result_offset} && $request->{result_offset} > 0 | 
| 1997 |  |  |  |  |  |  | && ! $request->{offset_handled} ) | 
| 1998 |  |  |  |  |  |  | { | 
| 1999 | 0 |  |  |  |  |  | foreach (1..$request->{result_offset}) | 
| 2000 |  |  |  |  |  |  | { | 
| 2001 | 0 | 0 |  |  |  |  | $ds->_next_record($request) or last; | 
| 2002 |  |  |  |  |  |  | } | 
| 2003 |  |  |  |  |  |  | } | 
| 2004 |  |  |  |  |  |  |  | 
| 2005 |  |  |  |  |  |  | # Now fetch and process each output record in turn.  If output streaming is | 
| 2006 |  |  |  |  |  |  | # available and our total output size exceeds the threshold, switch over | 
| 2007 |  |  |  |  |  |  | # to streaming. | 
| 2008 |  |  |  |  |  |  |  | 
| 2009 |  |  |  |  |  |  | RECORD: | 
| 2010 | 0 |  |  |  |  |  | while ( my $record = $ds->_next_record($request) ) | 
| 2011 |  |  |  |  |  |  | { | 
| 2012 |  |  |  |  |  |  | # If there are any processing steps to do, then process this record. | 
| 2013 |  |  |  |  |  |  |  | 
| 2014 | 0 |  |  |  |  |  | $ds->process_record($request, $record, $proc_list); | 
| 2015 |  |  |  |  |  |  |  | 
| 2016 |  |  |  |  |  |  | # If there is a before_record_hook defined for this path, call it now. | 
| 2017 |  |  |  |  |  |  | # If it calls 'skip_output_record', then do not output this record. | 
| 2018 |  |  |  |  |  |  |  | 
| 2019 | 0 | 0 |  |  |  |  | if ( $ds->{hook_enabled}{before_record_hook} ) | 
| 2020 |  |  |  |  |  |  | { | 
| 2021 | 0 |  |  |  |  |  | delete $request->{_skip_record}; | 
| 2022 | 0 |  |  |  |  |  | $ds->_call_hooks('before_record_hook', $request, $record); | 
| 2023 | 0 | 0 |  |  |  |  | next RECORD if $request->{_skip_record}; | 
| 2024 |  |  |  |  |  |  | } | 
| 2025 |  |  |  |  |  |  |  | 
| 2026 |  |  |  |  |  |  | # Generate the output for this record, preceded by a record separator if | 
| 2027 |  |  |  |  |  |  | # it is not the first record. | 
| 2028 |  |  |  |  |  |  |  | 
| 2029 | 0 | 0 |  |  |  |  | my $outrs = $emit_rs ? $request->{rs} : ''; $emit_rs = 1; | 
|  | 0 |  |  |  |  |  |  | 
| 2030 | 0 |  |  |  |  |  | my $outrec = $format_class->emit_record($request, $record, $field_list); | 
| 2031 |  |  |  |  |  |  |  | 
| 2032 | 0 | 0 |  |  |  |  | if ( $serial_hook ) | 
| 2033 |  |  |  |  |  |  | { | 
| 2034 | 0 |  |  |  |  |  | $ds->_call_hook_list($serial_hook, $request, 'record', \$outrs, \$outrec); | 
| 2035 |  |  |  |  |  |  | } | 
| 2036 |  |  |  |  |  |  |  | 
| 2037 | 0 |  |  |  |  |  | $output .= $outrs . $outrec; | 
| 2038 |  |  |  |  |  |  |  | 
| 2039 |  |  |  |  |  |  | # Keep count of the output records, and stop if we have exceeded the | 
| 2040 |  |  |  |  |  |  | # limit. | 
| 2041 |  |  |  |  |  |  |  | 
| 2042 | 0 |  |  |  |  |  | $request->{actual_count}++; | 
| 2043 |  |  |  |  |  |  |  | 
| 2044 | 0 | 0 | 0 |  |  |  | if ( defined $request->{result_limit} && $request->{result_limit} ne 'all' ) | 
| 2045 |  |  |  |  |  |  | { | 
| 2046 | 0 | 0 |  |  |  |  | last if $request->{actual_count} >= $request->{result_limit}; | 
| 2047 |  |  |  |  |  |  | } | 
| 2048 |  |  |  |  |  |  |  | 
| 2049 |  |  |  |  |  |  | # If streaming is a possibility, check whether we have passed the | 
| 2050 |  |  |  |  |  |  | # threshold for result size.  If so, then we need to immediately | 
| 2051 |  |  |  |  |  |  | # stash the output generated so far and call stream_data.  Doing that | 
| 2052 |  |  |  |  |  |  | # will cause the current function to be aborted, followed by an | 
| 2053 |  |  |  |  |  |  | # automatic call to &stream_result (defined below). | 
| 2054 |  |  |  |  |  |  |  | 
| 2055 | 0 | 0 | 0 |  |  |  | if ( defined $streaming_threshold && length($output) > $streaming_threshold ) | 
| 2056 |  |  |  |  |  |  | { | 
| 2057 | 0 |  |  |  |  |  | $request->{stashed_output} = $output; | 
| 2058 | 0 |  |  |  |  |  | Dancer::Plugin::StreamData::stream_data($request, &_stream_compound_result); | 
| 2059 |  |  |  |  |  |  | } | 
| 2060 |  |  |  |  |  |  | } | 
| 2061 |  |  |  |  |  |  |  | 
| 2062 |  |  |  |  |  |  | # If we get here, then we did not initiate streaming.  So add the | 
| 2063 |  |  |  |  |  |  | # footer and return the output data. | 
| 2064 |  |  |  |  |  |  |  | 
| 2065 |  |  |  |  |  |  | # If we didn't output any records, give the formatter a chance to indicate | 
| 2066 |  |  |  |  |  |  | # this. | 
| 2067 |  |  |  |  |  |  |  | 
| 2068 | 0 | 0 |  |  |  |  | unless ( $request->{actual_count} ) | 
| 2069 |  |  |  |  |  |  | { | 
| 2070 | 0 |  |  |  |  |  | my $empty = $format_class->emit_empty($request); | 
| 2071 |  |  |  |  |  |  |  | 
| 2072 | 0 | 0 |  |  |  |  | if ( $serial_hook ) | 
| 2073 |  |  |  |  |  |  | { | 
| 2074 | 0 |  |  |  |  |  | $ds->_call_hook_list($serial_hook, $request, 'empty', \$empty); | 
| 2075 |  |  |  |  |  |  | } | 
| 2076 |  |  |  |  |  |  |  | 
| 2077 | 0 |  |  |  |  |  | $output .= $empty; | 
| 2078 |  |  |  |  |  |  | } | 
| 2079 |  |  |  |  |  |  |  | 
| 2080 |  |  |  |  |  |  | # Generate the final part of the output, after the last record. | 
| 2081 |  |  |  |  |  |  |  | 
| 2082 | 0 |  |  |  |  |  | my $footer = $format_class->emit_footer($request, $field_list); | 
| 2083 |  |  |  |  |  |  |  | 
| 2084 | 0 | 0 |  |  |  |  | if ( $serial_hook ) | 
| 2085 |  |  |  |  |  |  | { | 
| 2086 | 0 |  |  |  |  |  | $ds->_call_hook_list($serial_hook, $request, 'footer', \$footer); | 
| 2087 |  |  |  |  |  |  | } | 
| 2088 |  |  |  |  |  |  |  | 
| 2089 | 0 |  |  |  |  |  | $output .= $footer; | 
| 2090 |  |  |  |  |  |  |  | 
| 2091 |  |  |  |  |  |  | # Determine if we need to encode the output into the proper character set. | 
| 2092 |  |  |  |  |  |  | # Usually Dancer does this for us, but only if it recognizes the content | 
| 2093 |  |  |  |  |  |  | # type as text.  For these formats, the definition should set the | 
| 2094 |  |  |  |  |  |  | # attribute 'encode_as_text' to true. | 
| 2095 |  |  |  |  |  |  |  | 
| 2096 | 0 |  |  |  |  |  | my $output_charset = $ds->{_config}{charset}; | 
| 2097 | 0 |  |  |  |  |  | my $must_encode; | 
| 2098 |  |  |  |  |  |  |  | 
| 2099 | 0 | 0 | 0 |  |  |  | if ( $output_charset | 
|  |  |  | 0 |  |  |  |  | 
| 2100 |  |  |  |  |  |  | && $ds->{format}{$format}{encode_as_text} | 
| 2101 |  |  |  |  |  |  | && ! $request->{content_type_is_text} ) | 
| 2102 |  |  |  |  |  |  | { | 
| 2103 | 0 |  |  |  |  |  | $must_encode = 1; | 
| 2104 |  |  |  |  |  |  | } | 
| 2105 |  |  |  |  |  |  |  | 
| 2106 | 0 | 0 |  |  |  |  | return $must_encode ? encode($output_charset, $output) : $output; | 
| 2107 |  |  |  |  |  |  | } | 
| 2108 |  |  |  |  |  |  |  | 
| 2109 |  |  |  |  |  |  |  | 
| 2110 |  |  |  |  |  |  | # _generate_processed_result ( request ) | 
| 2111 |  |  |  |  |  |  | # | 
| 2112 |  |  |  |  |  |  | # This function is called if the result set needs to be processed in its | 
| 2113 |  |  |  |  |  |  | # entirety before being output.  It processes the entire result set and | 
| 2114 |  |  |  |  |  |  | # collects a list of processed records, and then serializes each result record | 
| 2115 |  |  |  |  |  |  | # according to the specified output format.  If $streaming_threshold is | 
| 2116 |  |  |  |  |  |  | # specified, and if the size of the output exceeds this threshold, this | 
| 2117 |  |  |  |  |  |  | # routine then sets up to stream the rest of the output. | 
| 2118 |  |  |  |  |  |  |  | 
| 2119 |  |  |  |  |  |  | sub _generate_processed_result { | 
| 2120 |  |  |  |  |  |  |  | 
| 2121 | 0 |  |  | 0 |  |  | my ($ds, $request, $streaming_threshold) = @_; | 
| 2122 |  |  |  |  |  |  |  | 
| 2123 |  |  |  |  |  |  | # Determine the output format and figure out which class implements it. | 
| 2124 |  |  |  |  |  |  |  | 
| 2125 | 0 |  |  |  |  |  | my $format = $request->output_format; | 
| 2126 | 0 |  |  |  |  |  | my $format_class = $ds->{format}{$format}{package}; | 
| 2127 |  |  |  |  |  |  |  | 
| 2128 | 0 | 0 |  |  |  |  | die "could not generate a result in format '$format': no implementing module was found" | 
| 2129 |  |  |  |  |  |  | unless $format_class; | 
| 2130 |  |  |  |  |  |  |  | 
| 2131 | 0 |  |  |  |  |  | $ds->debug_line("Processing result set before output."); | 
| 2132 |  |  |  |  |  |  |  | 
| 2133 | 0 |  |  |  |  |  | my $path = $request->node_path; | 
| 2134 | 0 |  | 0 |  |  |  | my $serial_hook = $ds->{hook_enabled}{after_serialize_hook} && $ds->node_attr($path, 'after_serialize_hook'); | 
| 2135 |  |  |  |  |  |  |  | 
| 2136 |  |  |  |  |  |  | # Get the lists that specify how to process each record and which fields | 
| 2137 |  |  |  |  |  |  | # to output. | 
| 2138 |  |  |  |  |  |  |  | 
| 2139 | 0 |  |  |  |  |  | my $proc_list = $request->{proc_list}; | 
| 2140 | 0 |  |  |  |  |  | my $field_list = $request->{field_list}; | 
| 2141 |  |  |  |  |  |  |  | 
| 2142 |  |  |  |  |  |  | # Now fetch and process each output record in turn.  Collect up all of the | 
| 2143 |  |  |  |  |  |  | # records that pass the processing phase in a list. | 
| 2144 |  |  |  |  |  |  |  | 
| 2145 | 0 |  |  |  |  |  | my @results; | 
| 2146 |  |  |  |  |  |  |  | 
| 2147 |  |  |  |  |  |  | RECORD: | 
| 2148 | 0 |  |  |  |  |  | while ( my $record = $ds->_next_record($request) ) | 
| 2149 |  |  |  |  |  |  | { | 
| 2150 |  |  |  |  |  |  | # If there are any processing steps to do, then process this record. | 
| 2151 |  |  |  |  |  |  | # If the return value is not true, skip the record. | 
| 2152 |  |  |  |  |  |  |  | 
| 2153 | 0 | 0 |  |  |  |  | $ds->process_record($request, $record, $proc_list) or next RECORD; | 
| 2154 |  |  |  |  |  |  |  | 
| 2155 |  |  |  |  |  |  | # If there is a before_record_hook defined for this path, call it now. | 
| 2156 |  |  |  |  |  |  | # If it calls 'skip_output_record', then do not output this record. | 
| 2157 |  |  |  |  |  |  |  | 
| 2158 | 0 | 0 |  |  |  |  | if ( $ds->{hook_enabled}{before_record_hook} ) | 
| 2159 |  |  |  |  |  |  | { | 
| 2160 | 0 |  |  |  |  |  | delete $request->{_skip_record}; | 
| 2161 | 0 |  |  |  |  |  | $ds->_call_hooks('before_record_hook', $request, $record); | 
| 2162 | 0 | 0 |  |  |  |  | next RECORD if $request->{_skip_record}; | 
| 2163 |  |  |  |  |  |  | } | 
| 2164 |  |  |  |  |  |  |  | 
| 2165 |  |  |  |  |  |  | # Add the record to the list. | 
| 2166 |  |  |  |  |  |  |  | 
| 2167 | 0 |  |  |  |  |  | push @results, $record; | 
| 2168 |  |  |  |  |  |  | } | 
| 2169 |  |  |  |  |  |  |  | 
| 2170 |  |  |  |  |  |  | # We now know the result count. | 
| 2171 |  |  |  |  |  |  |  | 
| 2172 | 0 |  |  |  |  |  | $request->{result_count} = scalar(@results); | 
| 2173 |  |  |  |  |  |  |  | 
| 2174 |  |  |  |  |  |  | # At this point, we can generate the output.  We start with the header. | 
| 2175 |  |  |  |  |  |  |  | 
| 2176 | 0 |  |  |  |  |  | my $output = $format_class->emit_header($request, $field_list); | 
| 2177 |  |  |  |  |  |  |  | 
| 2178 | 0 | 0 |  |  |  |  | if ( $serial_hook ) | 
| 2179 |  |  |  |  |  |  | { | 
| 2180 | 0 |  |  |  |  |  | $ds->_call_hook_list($serial_hook, $request, 'header', \$output); | 
| 2181 |  |  |  |  |  |  | } | 
| 2182 |  |  |  |  |  |  |  | 
| 2183 |  |  |  |  |  |  | # A record separator is emitted before every record except the first.  If | 
| 2184 |  |  |  |  |  |  | # this format class does not define a record separator, use the empty | 
| 2185 |  |  |  |  |  |  | # string. | 
| 2186 |  |  |  |  |  |  |  | 
| 2187 | 0 | 0 |  |  |  |  | $request->{rs} = $format_class->can('emit_separator') ? | 
| 2188 |  |  |  |  |  |  | $format_class->emit_separator($request) : ''; | 
| 2189 |  |  |  |  |  |  |  | 
| 2190 | 0 |  |  |  |  |  | my $emit_rs = 0; | 
| 2191 |  |  |  |  |  |  |  | 
| 2192 | 0 |  |  |  |  |  | $request->{actual_count} = 0; | 
| 2193 |  |  |  |  |  |  |  | 
| 2194 |  |  |  |  |  |  | # If an offset was specified and the result method didn't handle this | 
| 2195 |  |  |  |  |  |  | # itself, then skip the specified number of records. | 
| 2196 |  |  |  |  |  |  |  | 
| 2197 | 0 | 0 | 0 |  |  |  | if ( defined $request->{result_offset} && $request->{result_offset} > 0 | 
|  |  |  | 0 |  |  |  |  | 
| 2198 |  |  |  |  |  |  | && ! $request->{offset_handled} ) | 
| 2199 |  |  |  |  |  |  | { | 
| 2200 | 0 |  |  |  |  |  | splice(@results, 0, $request->{result_offset}); | 
| 2201 |  |  |  |  |  |  | } | 
| 2202 |  |  |  |  |  |  |  | 
| 2203 |  |  |  |  |  |  | # If the result limit is zero, we can ignore all records. | 
| 2204 |  |  |  |  |  |  |  | 
| 2205 | 0 | 0 | 0 |  |  |  | if ( defined $request->{result_limit} && $request->{result_limit} eq '0' ) | 
| 2206 |  |  |  |  |  |  | { | 
| 2207 | 0 |  |  |  |  |  | @results = (); | 
| 2208 |  |  |  |  |  |  | } | 
| 2209 |  |  |  |  |  |  |  | 
| 2210 |  |  |  |  |  |  | # Otherwise iterate over all of the remaining records. | 
| 2211 |  |  |  |  |  |  |  | 
| 2212 |  |  |  |  |  |  | OUTPUT: | 
| 2213 | 0 |  |  |  |  |  | while ( @results ) | 
| 2214 |  |  |  |  |  |  | { | 
| 2215 | 0 |  |  |  |  |  | my $record = shift @results; | 
| 2216 |  |  |  |  |  |  |  | 
| 2217 |  |  |  |  |  |  | # Generate the output for this record, preceded by a record separator if | 
| 2218 |  |  |  |  |  |  | # it is not the first record. | 
| 2219 |  |  |  |  |  |  |  | 
| 2220 | 0 | 0 |  |  |  |  | my $outrs = $emit_rs ? $request->{rs} : ''; $emit_rs = 1; | 
|  | 0 |  |  |  |  |  |  | 
| 2221 | 0 |  |  |  |  |  | my $outrec = $format_class->emit_record($request, $record, $field_list); | 
| 2222 |  |  |  |  |  |  |  | 
| 2223 | 0 | 0 |  |  |  |  | if ( $serial_hook ) | 
| 2224 |  |  |  |  |  |  | { | 
| 2225 | 0 |  |  |  |  |  | $ds->_call_hook_list($serial_hook, $request, 'record', \$outrs, \$outrec); | 
| 2226 |  |  |  |  |  |  | } | 
| 2227 |  |  |  |  |  |  |  | 
| 2228 | 0 |  |  |  |  |  | $output .= $outrs . $outrec; | 
| 2229 |  |  |  |  |  |  |  | 
| 2230 |  |  |  |  |  |  | # Keep count of the output records, and stop if we have exceeded the | 
| 2231 |  |  |  |  |  |  | # limit. | 
| 2232 |  |  |  |  |  |  |  | 
| 2233 | 0 |  |  |  |  |  | $request->{actual_count}++; | 
| 2234 |  |  |  |  |  |  |  | 
| 2235 | 0 | 0 | 0 |  |  |  | if ( defined $request->{result_limit} && $request->{result_limit} ne 'all' ) | 
| 2236 |  |  |  |  |  |  | { | 
| 2237 | 0 | 0 |  |  |  |  | last if $request->{actual_count} >= $request->{result_limit}; | 
| 2238 |  |  |  |  |  |  | } | 
| 2239 |  |  |  |  |  |  |  | 
| 2240 |  |  |  |  |  |  | # If streaming is a possibility, check whether we have passed the | 
| 2241 |  |  |  |  |  |  | # threshold for result size.  If so, then we need to immediately | 
| 2242 |  |  |  |  |  |  | # stash the output generated so far and call stream_data.  Doing that | 
| 2243 |  |  |  |  |  |  | # will cause the current function to be aborted, followed by an | 
| 2244 |  |  |  |  |  |  | # automatic call to &stream_result (defined below). | 
| 2245 |  |  |  |  |  |  |  | 
| 2246 | 0 | 0 | 0 |  |  |  | if ( defined $streaming_threshold && length($output) > $streaming_threshold ) | 
| 2247 |  |  |  |  |  |  | { | 
| 2248 | 0 |  |  |  |  |  | $request->{stashed_output} = $output; | 
| 2249 | 0 |  |  |  |  |  | $request->{stashed_results} = \@results; | 
| 2250 | 0 |  |  |  |  |  | $request->{processing_complete} = 1; | 
| 2251 | 0 |  |  |  |  |  | Dancer::Plugin::StreamData::stream_data($request, &_stream_compound_result); | 
| 2252 |  |  |  |  |  |  | } | 
| 2253 |  |  |  |  |  |  | } | 
| 2254 |  |  |  |  |  |  |  | 
| 2255 |  |  |  |  |  |  | # If we get here, then we did not initiate streaming.  So add the | 
| 2256 |  |  |  |  |  |  | # footer and return the output data. | 
| 2257 |  |  |  |  |  |  |  | 
| 2258 |  |  |  |  |  |  | # If we didn't output any records, give the formatter a chance to indicate | 
| 2259 |  |  |  |  |  |  | # this. | 
| 2260 |  |  |  |  |  |  |  | 
| 2261 | 0 | 0 |  |  |  |  | unless ( $request->{actual_count} ) | 
| 2262 |  |  |  |  |  |  | { | 
| 2263 | 0 |  |  |  |  |  | my $empty = $format_class->emit_empty($request); | 
| 2264 |  |  |  |  |  |  |  | 
| 2265 | 0 | 0 |  |  |  |  | if ( $serial_hook ) | 
| 2266 |  |  |  |  |  |  | { | 
| 2267 | 0 |  |  |  |  |  | $ds->_call_hook_list($serial_hook, $request, 'empty', \$empty); | 
| 2268 |  |  |  |  |  |  | } | 
| 2269 |  |  |  |  |  |  |  | 
| 2270 | 0 |  |  |  |  |  | $output .= $empty; | 
| 2271 |  |  |  |  |  |  | } | 
| 2272 |  |  |  |  |  |  |  | 
| 2273 |  |  |  |  |  |  | # Generate the final part of the output, after the last record. | 
| 2274 |  |  |  |  |  |  |  | 
| 2275 | 0 |  |  |  |  |  | my $footer = $format_class->emit_footer($request, $field_list); | 
| 2276 |  |  |  |  |  |  |  | 
| 2277 | 0 | 0 |  |  |  |  | if ( $serial_hook ) | 
| 2278 |  |  |  |  |  |  | { | 
| 2279 | 0 |  |  |  |  |  | $ds->_call_hook_list($serial_hook, $request, 'footer', \$footer); | 
| 2280 |  |  |  |  |  |  | } | 
| 2281 |  |  |  |  |  |  |  | 
| 2282 | 0 |  |  |  |  |  | $output .= $footer; | 
| 2283 |  |  |  |  |  |  |  | 
| 2284 |  |  |  |  |  |  | # Determine if we need to encode the output into the proper character set. | 
| 2285 |  |  |  |  |  |  | # Usually Dancer does this for us, but only if it recognizes the content | 
| 2286 |  |  |  |  |  |  | # type as text.  For these formats, the definition should set the | 
| 2287 |  |  |  |  |  |  | # attribute 'encode_as_text' to true. | 
| 2288 |  |  |  |  |  |  |  | 
| 2289 | 0 |  |  |  |  |  | my $output_charset = $ds->{_config}{charset}; | 
| 2290 | 0 |  |  |  |  |  | my $must_encode; | 
| 2291 |  |  |  |  |  |  |  | 
| 2292 | 0 | 0 | 0 |  |  |  | if ( $output_charset | 
|  |  |  | 0 |  |  |  |  | 
| 2293 |  |  |  |  |  |  | && $ds->{format}{$format}{encode_as_text} | 
| 2294 |  |  |  |  |  |  | && ! $request->{content_type_is_text} ) | 
| 2295 |  |  |  |  |  |  | { | 
| 2296 | 0 |  |  |  |  |  | $must_encode = 1; | 
| 2297 |  |  |  |  |  |  | } | 
| 2298 |  |  |  |  |  |  |  | 
| 2299 | 0 | 0 |  |  |  |  | return $must_encode ? encode($output_charset, $output) : $output; | 
| 2300 |  |  |  |  |  |  | } | 
| 2301 |  |  |  |  |  |  |  | 
| 2302 |  |  |  |  |  |  |  | 
| 2303 |  |  |  |  |  |  | # _stream_compound_result ( ) | 
| 2304 |  |  |  |  |  |  | # | 
| 2305 |  |  |  |  |  |  | # Continue to generate a compound query result from where | 
| 2306 |  |  |  |  |  |  | # generate_compound_result() left off, and stream it to the client | 
| 2307 |  |  |  |  |  |  | # record-by-record. | 
| 2308 |  |  |  |  |  |  | # | 
| 2309 |  |  |  |  |  |  | # This routine must be passed a Plack 'writer' object, to which will be | 
| 2310 |  |  |  |  |  |  | # written in turn the stashed output from generate_compound_result(), each | 
| 2311 |  |  |  |  |  |  | # subsequent record, and then the footer.  Each of these chunks of data will | 
| 2312 |  |  |  |  |  |  | # be immediately sent off to the client, instead of being marshalled together | 
| 2313 |  |  |  |  |  |  | # in memory.  This allows the server to send results up to hundreds of | 
| 2314 |  |  |  |  |  |  | # megabytes in length without bogging down. | 
| 2315 |  |  |  |  |  |  |  | 
| 2316 |  |  |  |  |  |  | sub _stream_compound_result { | 
| 2317 |  |  |  |  |  |  |  | 
| 2318 | 0 |  |  | 0 |  |  | my ($request, $writer) = @_; | 
| 2319 |  |  |  |  |  |  |  | 
| 2320 | 0 |  |  |  |  |  | my $ds = $request->{ds}; | 
| 2321 |  |  |  |  |  |  |  | 
| 2322 |  |  |  |  |  |  | # Determine the output format and figure out which class implements it. | 
| 2323 |  |  |  |  |  |  |  | 
| 2324 | 0 |  |  |  |  |  | my $format = $request->output_format; | 
| 2325 | 0 |  |  |  |  |  | my $format_class = $ds->{format}{$format}{package}; | 
| 2326 | 0 |  |  |  |  |  | my $format_is_text = $ds->{format}{$format}{is_text}; | 
| 2327 |  |  |  |  |  |  |  | 
| 2328 | 0 | 0 |  |  |  |  | croak "could not generate a result in format '$format': no implementing class" | 
| 2329 |  |  |  |  |  |  | unless $format_class; | 
| 2330 |  |  |  |  |  |  |  | 
| 2331 | 0 |  |  |  |  |  | my $path = $request->node_path; | 
| 2332 | 0 |  | 0 |  |  |  | my $serial_hook = $ds->{hook_enabled}{after_serialize_hook} && $ds->node_attr($path, 'after_serialize_hook'); | 
| 2333 |  |  |  |  |  |  |  | 
| 2334 |  |  |  |  |  |  | # Get the lists that specify how to process each record and which fields | 
| 2335 |  |  |  |  |  |  | # to output. | 
| 2336 |  |  |  |  |  |  |  | 
| 2337 | 0 |  |  |  |  |  | my $proc_list = $request->{proc_list}; | 
| 2338 | 0 |  |  |  |  |  | my $field_list = $request->{field_list}; | 
| 2339 |  |  |  |  |  |  |  | 
| 2340 |  |  |  |  |  |  | # Determine the output character set, because we will need to encode text | 
| 2341 |  |  |  |  |  |  | # responses in it. | 
| 2342 |  |  |  |  |  |  |  | 
| 2343 | 0 |  |  |  |  |  | my $output_charset = $ds->{_config}{charset}; | 
| 2344 |  |  |  |  |  |  |  | 
| 2345 |  |  |  |  |  |  | #return $must_encode ? encode($output_charset, $output) : $output; | 
| 2346 |  |  |  |  |  |  |  | 
| 2347 |  |  |  |  |  |  | # First send out the partial output previously stashed by | 
| 2348 |  |  |  |  |  |  | # generate_compound_result(). | 
| 2349 |  |  |  |  |  |  |  | 
| 2350 | 0 | 0 | 0 |  |  |  | if ( $output_charset && $format_is_text ) | 
| 2351 |  |  |  |  |  |  | { | 
| 2352 | 0 |  |  |  |  |  | $writer->write( encode($output_charset, $ds->{stashed_output}) ); | 
| 2353 |  |  |  |  |  |  | } | 
| 2354 |  |  |  |  |  |  |  | 
| 2355 |  |  |  |  |  |  | else | 
| 2356 |  |  |  |  |  |  | { | 
| 2357 | 0 |  |  |  |  |  | $writer->write( $ds->{stashed_output} ); | 
| 2358 |  |  |  |  |  |  | } | 
| 2359 |  |  |  |  |  |  |  | 
| 2360 |  |  |  |  |  |  | # Then process the remaining rows. | 
| 2361 |  |  |  |  |  |  |  | 
| 2362 |  |  |  |  |  |  | RECORD: | 
| 2363 | 0 |  |  |  |  |  | while ( my $record = $ds->_next_record($request) ) | 
| 2364 |  |  |  |  |  |  | { | 
| 2365 |  |  |  |  |  |  | # If there are any processing steps to do, then process this record. But skip this if this | 
| 2366 |  |  |  |  |  |  | # subroutine was called from '_generate_processed_result'. | 
| 2367 |  |  |  |  |  |  |  | 
| 2368 | 0 | 0 |  |  |  |  | unless ( $request->{processing_complete} ) | 
| 2369 |  |  |  |  |  |  | { | 
| 2370 | 0 |  |  |  |  |  | $ds->process_record($request, $record, $proc_list); | 
| 2371 |  |  |  |  |  |  |  | 
| 2372 |  |  |  |  |  |  | # If there is a before_record_hook defined for this path, call it now. | 
| 2373 |  |  |  |  |  |  | # If it calls 'skip_output_record', then do not output this record. | 
| 2374 |  |  |  |  |  |  |  | 
| 2375 | 0 | 0 |  |  |  |  | if ( $ds->{hook_enabled}{before_record_hook} ) | 
| 2376 |  |  |  |  |  |  | { | 
| 2377 | 0 |  |  |  |  |  | delete $request->{_skip_record}; | 
| 2378 | 0 |  |  |  |  |  | $ds->_call_hooks('before_record_hook', $request, $record); | 
| 2379 | 0 | 0 |  |  |  |  | next RECORD if $request->{_skip_record}; | 
| 2380 |  |  |  |  |  |  | } | 
| 2381 |  |  |  |  |  |  | } | 
| 2382 |  |  |  |  |  |  |  | 
| 2383 |  |  |  |  |  |  | # Generate the output for this record, preceded by a record separator if | 
| 2384 |  |  |  |  |  |  | # it is not the first record. | 
| 2385 |  |  |  |  |  |  |  | 
| 2386 | 0 |  |  |  |  |  | my $outrs = $request->{rs}; | 
| 2387 | 0 |  |  |  |  |  | my $outrec = $format_class->emit_record($request, $record, $field_list); | 
| 2388 |  |  |  |  |  |  |  | 
| 2389 | 0 | 0 |  |  |  |  | if ( $serial_hook ) | 
| 2390 |  |  |  |  |  |  | { | 
| 2391 | 0 |  |  |  |  |  | $ds->_call_hook_list($serial_hook, $request, 'record', \$outrs, \$outrec); | 
| 2392 |  |  |  |  |  |  | } | 
| 2393 |  |  |  |  |  |  |  | 
| 2394 | 0 |  |  |  |  |  | my $output .= $outrs . $outrec; | 
| 2395 |  |  |  |  |  |  |  | 
| 2396 | 0 | 0 | 0 |  |  |  | if ( ! defined $output or $output eq '' ) | 
|  |  | 0 | 0 |  |  |  |  | 
| 2397 |  |  |  |  |  |  | { | 
| 2398 |  |  |  |  |  |  | # do nothing | 
| 2399 |  |  |  |  |  |  | } | 
| 2400 |  |  |  |  |  |  |  | 
| 2401 |  |  |  |  |  |  | elsif ( $output_charset && $format_is_text ) | 
| 2402 |  |  |  |  |  |  | { | 
| 2403 | 0 |  |  |  |  |  | $writer->write( encode($output_charset, $output) ); | 
| 2404 |  |  |  |  |  |  | } | 
| 2405 |  |  |  |  |  |  |  | 
| 2406 |  |  |  |  |  |  | else | 
| 2407 |  |  |  |  |  |  | { | 
| 2408 | 0 |  |  |  |  |  | $writer->write( $output ); | 
| 2409 |  |  |  |  |  |  | } | 
| 2410 |  |  |  |  |  |  |  | 
| 2411 |  |  |  |  |  |  | # Keep count of the output records, and stop if we have exceeded the | 
| 2412 |  |  |  |  |  |  | # limit. | 
| 2413 |  |  |  |  |  |  |  | 
| 2414 |  |  |  |  |  |  | last if $request->{result_limit} ne 'all' && | 
| 2415 | 0 | 0 | 0 |  |  |  | ++$request->{actual_count} >= $request->{result_limit}; | 
| 2416 |  |  |  |  |  |  | } | 
| 2417 |  |  |  |  |  |  |  | 
| 2418 |  |  |  |  |  |  | # finish output... | 
| 2419 |  |  |  |  |  |  |  | 
| 2420 |  |  |  |  |  |  | # my $final = $ds->finishOutput(); | 
| 2421 |  |  |  |  |  |  | # $writer->write( encode_utf8($final) ) if defined $final and $final ne ''; | 
| 2422 |  |  |  |  |  |  |  | 
| 2423 |  |  |  |  |  |  | # Finally, send out the footer and then close the writer object. | 
| 2424 |  |  |  |  |  |  |  | 
| 2425 |  |  |  |  |  |  | # Generate the final part of the output, after the last record. | 
| 2426 |  |  |  |  |  |  |  | 
| 2427 | 0 |  |  |  |  |  | my $footer = $format_class->emit_footer($request, $field_list); | 
| 2428 |  |  |  |  |  |  |  | 
| 2429 | 0 | 0 |  |  |  |  | if ( $serial_hook ) | 
| 2430 |  |  |  |  |  |  | { | 
| 2431 | 0 |  |  |  |  |  | $ds->_call_hook_list($serial_hook, $request, 'footer', \$footer); | 
| 2432 |  |  |  |  |  |  | } | 
| 2433 |  |  |  |  |  |  |  | 
| 2434 | 0 | 0 | 0 |  |  |  | if ( ! defined $footer or $footer eq '' ) | 
|  |  | 0 | 0 |  |  |  |  | 
| 2435 |  |  |  |  |  |  | { | 
| 2436 |  |  |  |  |  |  | # do nothing | 
| 2437 |  |  |  |  |  |  | } | 
| 2438 |  |  |  |  |  |  |  | 
| 2439 |  |  |  |  |  |  | elsif ( $output_charset && $format_is_text ) | 
| 2440 |  |  |  |  |  |  | { | 
| 2441 | 0 |  |  |  |  |  | $writer->write( encode($output_charset, $footer) ); | 
| 2442 |  |  |  |  |  |  | } | 
| 2443 |  |  |  |  |  |  |  | 
| 2444 |  |  |  |  |  |  | else | 
| 2445 |  |  |  |  |  |  | { | 
| 2446 | 0 |  |  |  |  |  | $writer->write( $footer ); | 
| 2447 |  |  |  |  |  |  | } | 
| 2448 |  |  |  |  |  |  |  | 
| 2449 | 0 |  |  |  |  |  | $writer->close(); | 
| 2450 |  |  |  |  |  |  | } | 
| 2451 |  |  |  |  |  |  |  | 
| 2452 |  |  |  |  |  |  |  | 
| 2453 |  |  |  |  |  |  | # _next_record ( request ) | 
| 2454 |  |  |  |  |  |  | # | 
| 2455 |  |  |  |  |  |  | # Return the next record to be output for the given request.  If | 
| 2456 |  |  |  |  |  |  | # $ds->{main_result} is set, use that first.  Once that is exhausted (or if | 
| 2457 |  |  |  |  |  |  | # it was never set) then if $result->{main_sth} is set then read records from | 
| 2458 |  |  |  |  |  |  | # it until exhausted. | 
| 2459 |  |  |  |  |  |  |  | 
| 2460 |  |  |  |  |  |  | sub _next_record { | 
| 2461 |  |  |  |  |  |  |  | 
| 2462 | 0 |  |  | 0 |  |  | my ($ds, $request) = @_; | 
| 2463 |  |  |  |  |  |  |  | 
| 2464 |  |  |  |  |  |  | # If the request has a zero limit, and no processing needs to be done on | 
| 2465 |  |  |  |  |  |  | # the result set, then no records need to be returned. | 
| 2466 |  |  |  |  |  |  |  | 
| 2467 | 0 | 0 |  |  |  |  | return if $request->{limit_zero}; | 
| 2468 |  |  |  |  |  |  |  | 
| 2469 |  |  |  |  |  |  | # If we have a stashed result list, return the next item in it. | 
| 2470 |  |  |  |  |  |  |  | 
| 2471 | 0 | 0 | 0 |  |  |  | if ( ref $request->{stashed_results} eq 'ARRAY' ) | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 2472 |  |  |  |  |  |  | { | 
| 2473 | 0 |  |  |  |  |  | return shift @{$request->{stashed_results}}; | 
|  | 0 |  |  |  |  |  |  | 
| 2474 |  |  |  |  |  |  | } | 
| 2475 |  |  |  |  |  |  |  | 
| 2476 |  |  |  |  |  |  | # If we have a 'main_result' array with something in it, return the next | 
| 2477 |  |  |  |  |  |  | # item in it. | 
| 2478 |  |  |  |  |  |  |  | 
| 2479 | 0 |  |  |  |  |  | elsif ( ref $request->{main_result} eq 'ARRAY' and @{$request->{main_result}} ) | 
| 2480 |  |  |  |  |  |  | { | 
| 2481 | 0 |  |  |  |  |  | return shift @{$request->{main_result}}; | 
|  | 0 |  |  |  |  |  |  | 
| 2482 |  |  |  |  |  |  | } | 
| 2483 |  |  |  |  |  |  |  | 
| 2484 |  |  |  |  |  |  | # Otherwise, if we have a 'main_sth' statement handle, read the next item | 
| 2485 |  |  |  |  |  |  | # from it. | 
| 2486 |  |  |  |  |  |  |  | 
| 2487 |  |  |  |  |  |  | elsif ( ref $request->{main_sth} ) | 
| 2488 |  |  |  |  |  |  | { | 
| 2489 |  |  |  |  |  |  | return $request->{main_sth}->fetchrow_hashref | 
| 2490 | 0 |  |  |  |  |  | } | 
| 2491 |  |  |  |  |  |  |  | 
| 2492 |  |  |  |  |  |  | else | 
| 2493 |  |  |  |  |  |  | { | 
| 2494 | 0 |  |  |  |  |  | return; | 
| 2495 |  |  |  |  |  |  | } | 
| 2496 |  |  |  |  |  |  | } | 
| 2497 |  |  |  |  |  |  |  | 
| 2498 |  |  |  |  |  |  |  | 
| 2499 |  |  |  |  |  |  | # _generate_empty_result ( request ) | 
| 2500 |  |  |  |  |  |  | # | 
| 2501 |  |  |  |  |  |  | # This function is called after an operation is executed and returns no results | 
| 2502 |  |  |  |  |  |  | # at all.  Return the header and footer only. | 
| 2503 |  |  |  |  |  |  |  | 
| 2504 |  |  |  |  |  |  | sub _generate_empty_result { | 
| 2505 |  |  |  |  |  |  |  | 
| 2506 | 0 |  |  | 0 |  |  | my ($ds, $request) = @_; | 
| 2507 |  |  |  |  |  |  |  | 
| 2508 |  |  |  |  |  |  | # Determine the output format and figure out which class implements it. | 
| 2509 |  |  |  |  |  |  |  | 
| 2510 | 0 |  |  |  |  |  | my $format = $request->output_format; | 
| 2511 | 0 |  |  |  |  |  | my $format_class = $ds->{format}{$format}{package}; | 
| 2512 |  |  |  |  |  |  |  | 
| 2513 | 0 | 0 |  |  |  |  | croak "could not generate a result in format '$format': no implementing class" | 
| 2514 |  |  |  |  |  |  | unless $format_class; | 
| 2515 |  |  |  |  |  |  |  | 
| 2516 |  |  |  |  |  |  | # Call the appropriate methods from this class to generate the header, | 
| 2517 |  |  |  |  |  |  | # and footer. | 
| 2518 |  |  |  |  |  |  |  | 
| 2519 | 0 |  |  |  |  |  | my $output = $format_class->emit_header($request); | 
| 2520 |  |  |  |  |  |  |  | 
| 2521 | 0 |  |  |  |  |  | $output .= $format_class->emit_empty($request); | 
| 2522 | 0 |  |  |  |  |  | $output .= $format_class->emit_footer($request); | 
| 2523 |  |  |  |  |  |  |  | 
| 2524 | 0 |  |  |  |  |  | return $output; | 
| 2525 |  |  |  |  |  |  | } | 
| 2526 |  |  |  |  |  |  |  | 
| 2527 |  |  |  |  |  |  |  | 
| 2528 |  |  |  |  |  |  | 1; |