@@ -21,9 +21,11 @@ use Scalar::Util qw(reftype);
21
21
# Packages we invoke by hand
22
22
require XML::Axk::Language;
23
23
require Exporter;
24
+
25
+ # Names the axk script will have access to
24
26
our @EXPORT = qw(
25
27
pre_all pre_file post_file post_all perform
26
- always never xpath sel on run) ;
28
+ always never xpath sel on run entering leaving whenever ) ;
27
29
our @EXPORT_OK = qw( @SP_names ) ;
28
30
29
31
# Helpers ======================================================== {{{1
@@ -64,36 +66,36 @@ sub post_all :prototype(&) {
64
66
# }}}1
65
67
# Definers for node actions ====================================== {{{1
66
68
67
- # # @function public perform (&action[, pattern [, when] ])
69
+ # # @function private add_to_worklist (&action, matcher [, when])
68
70
# # The main way to define pattern/action pairs. This takes the action first
69
71
# # since that's how Perl's prototypes are set up the cleanest (block first).
70
72
# # @params required &action A block to execute when the pattern matches
71
- # # @params required pattern The pattern
73
+ # # @params required matcher An object that defines test(%CPs) to determine
74
+ # # whether the element described in
75
+ # # core-parameter hash %CPs matches.
76
+ # # @params optional when If provided, when to run the action:
77
+ # # HI, BYE, or CIAO. Default is HI.
72
78
sub add_to_worklist {
79
+ my $sandbox = _sandbox or croak " Can't find sandbox" ;
73
80
# say "add_to_worklist args: ", Dumper(\@_);
74
81
my ($drAction , $refPattern , $when ) = @_ ;
75
- # say "perform(): ", Dumper(\@_);
76
82
$when = $when // HI; # only on entry, by default
77
-
78
83
$refPattern = \( my $temp = $refPattern ) unless ref ($refPattern );
79
84
80
- # TODO? support Regexp, scalar patterns in some sensible way
81
-
82
- my $sandbox = _sandbox or croak(" Can't find sandbox in perform" );
83
85
push @{$sandbox -> worklist}, [$refPattern , $drAction , $when ];
84
- } # perform ()
86
+ } # add_to_worklist ()
85
87
86
88
# User-facing alias for add_to_worklist
87
89
sub perform :prototype(&@) {
88
90
goto &add_to_worklist; # Need goto so that _sandbox() can use caller(1)
89
91
}
90
92
91
- # run { action } [optional <when>] - syntactic sugar for sub {}, when
93
+ # run { action } [optional <when>] - syntactic sugar for ` sub {}, when`
92
94
sub run :prototype(&;$) {
93
95
return @_ ;
94
96
} # run()
95
97
96
- # pattern-first style - on {} run {} [when];
98
+ # pattern-first style - on {} run {} [when (default HI) ];
97
99
sub on :prototype(&@) {
98
100
my ($drMakeMatcher , $drAction , $when ) = @_ ;
99
101
@@ -105,6 +107,32 @@ sub on :prototype(&@) {
105
107
goto &add_to_worklist;
106
108
} # on()
107
109
110
+ # pattern-first style, sugar for symmetry with whenever() and leaving()
111
+ sub entering :prototype(&@) {
112
+ goto &on
113
+ } # entering()
114
+
115
+ # pattern-first style, common implementation for BYE and CIAO
116
+ sub _leaving_whenever_impl {
117
+ croak " Too many arguments" if $#_ >2;
118
+ my ($when , $drMakeMatcher , $drAction ) = @_ ;
119
+ my $matcher = &$drMakeMatcher ;
120
+ @_ =($drAction , $matcher , $when );
121
+ goto &add_to_worklist;
122
+ } # _leaving_whenever_impl()
123
+
124
+ # pattern-first style, specific to leaving nodes (BYE) - leaving {} run {};
125
+ sub leaving :prototype(&@) {
126
+ unshift @_ , BYE;
127
+ goto &_leaving_whenever_impl;
128
+ } # leaving()
129
+
130
+ # pattern-first style, specific to hitting nodes (CIAO) - whenever {} run {};
131
+ sub whenever :prototype(&@) {
132
+ unshift @_ , CIAO;
133
+ goto &_leaving_whenever_impl;
134
+ } # whenever()
135
+
108
136
# }}}1
109
137
# Definers for matchers ========================================== {{{1
110
138
@@ -158,14 +186,13 @@ our @SP_names = qw($C @F $D $E $NOW);
158
186
159
187
sub update {
160
188
# say "L1::update: ", Dumper(\@_);
161
- my $hrSP = shift or croak(" No hrSP" );
162
- my %opts = @_ ;
163
-
164
- $hrSP -> {' $D' } = $opts {document } or croak(" No document" );
165
- $hrSP -> {' $E' } = $opts {record } or croak(" No record" );
166
- croak(" You are in a timeless maze" ) unless defined $opts {NOW };
167
- $hrSP -> {' $NOW' } = now_names $opts {NOW };
168
- # while (my ($key, $value) = each %new_sps) { }
189
+ my $hrSP = shift or croak(" Invalid call - No hrSP" );
190
+ my %CPs = @_ ;
191
+
192
+ $hrSP -> {' $D' } = $CPs {document } or croak(" No document" );
193
+ $hrSP -> {' $E' } = $CPs {record } or croak(" No record" );
194
+ croak(" You are in a timeless maze" ) unless defined $CPs {NOW };
195
+ $hrSP -> {' $NOW' } = now_names $CPs {NOW };
169
196
} # update()
170
197
171
198
# }}}1
@@ -174,19 +201,157 @@ sub update {
174
201
175
202
sub import {
176
203
# say "update: ",ref \&update, Dumper(\&update);
177
- my $target = caller ;
178
204
# say "XAL1 run from $target:\n", Devel::StackTrace->new->as_string;
179
205
XML::Axk::Language-> import (
180
- target => $target ,
206
+ target => caller ,
181
207
sp => \@SP_names ,
182
208
updater => \&update
183
209
);
184
210
# By doing this here rather than in the `use` statement,
185
- # we get $target and don't have to walk the stack to find the
211
+ # we get `caller` and don't have to walk the stack to find the
186
212
# axk script.
187
213
goto &Exporter::import ; # for @EXPORT &c. @_ is what it was on entry.
188
214
} # import()
189
215
190
216
# }}}1
191
217
1;
218
+ # === Documentation ===================================================== {{{1
219
+
220
+ =pod
221
+
222
+ =encoding UTF-8
223
+
224
+ =head1 NAME
225
+
226
+ XML::Axk::Core::L1 - ack-like XML processor, language 1
227
+
228
+ =head1 EXAMPLE
229
+
230
+ L1
231
+ on { xpath(q<//item>) } run {say "$NOW: " . $E->getTagName}, CIAO
232
+ # "CIAO" can also be "HI" or "BYE" (default HI).
233
+ # "entering" is a synonym for "on" with no HI/BYE/CIAO.
234
+ whenever { xpath(q<//item>) } run {say "$NOW: " . $E->getTagName};
235
+ # the same as the "on ... CIAO" line
236
+ leaving { xpath(q<//item>) } run {say "$NOW: " . $E->getTagName};
237
+
238
+ =head1 PATTERNS AND ACTIONS
239
+
240
+ =head2 C<< on {<matcher>} run {<action>} [, <when>] >>
241
+
242
+ Whenever C<< <matcher> >> says that a node matches, run C<< <action> >>.
243
+ The optional C<< <when> >> parameter says when in the course of processing to
244
+ run C<< <action> >>:
245
+
246
+ =over
247
+
248
+ =item C<HI >
249
+
250
+ When the node is first reached, before any of its children are processed
251
+
252
+ =item C<BYE >
253
+
254
+ After all of the node's children have been processed.
255
+
256
+ =item C<CIAO >
257
+
258
+ Both C<HI > and C<BYE > . Suggestions for alternative terminology are welcome.
259
+
260
+ =back
261
+
262
+ =head2 C<entering > , C<whenever > C<leaving >
263
+
264
+ entering {<matcher>} run {<action>}
265
+ whenever {<matcher>} run {<action>}
266
+ leaving {<matcher>} run {<action>}
267
+
268
+ The same as C<on {} run {} > , with C<when > set to C<HI > , C<CIAO > , or C<BYE >
269
+ respectively.
270
+
271
+ =head2 C<< perform { <action> } <matcher> [, <when>] >>
272
+
273
+ If you prefer RPN, or you want to save some characters, you can put the
274
+ C<< <matcher> >> after the C<< <action> >> using C<perform > . For example,
275
+ the following two lines have exactly the same effect:
276
+
277
+ on { xpath(q<//item>) } run {say "$NOW: " . $E->getTagName}, CIAO
278
+ perform {say "$NOW: " . $E->getTagName} xpath(q<//item>), CIAO
279
+
280
+ =head1 VARIABLES
281
+
282
+ When an C<< <action> >> is running, it has access to predefined variables
283
+ that hold the state of the element being matched. This is similar to C<$0 > ,
284
+ C<$1 > , ... in awk.
285
+
286
+ =over
287
+
288
+ =item B<$D >
289
+
290
+ The current XML document
291
+
292
+ =item B<$E >
293
+
294
+ The XML element that was matched
295
+
296
+ =item B<$NOW >
297
+
298
+ The current phase, as a human-readable string: C<entering > for C<HI > ,
299
+ C<leaving > for C<BYE > , and C<BOTH > for C<CIAO > .
300
+
301
+ =back
302
+
303
+ =head1 MATCHERS
304
+
305
+ =head2 C<< xpath('xpath expression') >>
306
+
307
+ Match nodes that match the given XPath expression. Remember that Perl will
308
+ interpolate C<@name > in double-quotes, so single-quote or C<q{} > your XPath
309
+ expressions.
310
+
311
+ =head2 C<< sel('selector') >>
312
+
313
+ Match nodes that match the given selector.
314
+
315
+ =head2 C<always > , C<never >
316
+
317
+ Always or never match, respectively.
318
+
319
+ =head1 SPECIAL ACTIONS
320
+
321
+ =head2 C<< pre_all {<block>} >>
322
+
323
+ Run C<< <block> >> before any file is processed.
324
+
325
+ =head2 C<< pre_file {<block>} >>
326
+
327
+ Run C<< <block>($filename) >> before each file is processed.
328
+
329
+ =head2 C<< post_file {<block>} >>
330
+
331
+ Run C<< <block>($filename) >> after each file is processed.
332
+
333
+ =head2 C<< post_all {<block>} >>
334
+
335
+ Run C<< <block> >> after all files have been processed.
336
+
337
+ =head1 AUTHOR
338
+
339
+ Christopher White, C<cxwembedded at gmail.com >
340
+
341
+ =head1 CONTACT
342
+
343
+ For any bug reports, feature requests, or questions, please see the
344
+ information in L<XML::Axk> .
345
+
346
+ =head1 LICENSE AND COPYRIGHT
347
+
348
+ Copyright (c) 2018 Christopher White. All rights reserved.
349
+
350
+ This program is free software; you can redistribute it and/or modify it
351
+ under the terms of the the Artistic License (2.0). Details are in the LICENSE
352
+ file accompanying this distribution.
353
+
354
+ =cut
355
+
356
+ # }}}1
192
357
# vi: set ts=4 sts=4 sw=4 et ai fo-=ro foldmethod=marker: #
0 commit comments