|
1 |
<?xml version="1.0"?>
|
|
2 |
|
|
3 |
<!--
|
|
4 |
/*
|
|
5 |
** Module : com_origin.rules
|
|
6 |
** Abstract : associates every COM/OCX referent with the control or object it originated from
|
|
7 |
**
|
|
8 |
** Copyright (c) 2026, Golden Code Development Corporation.
|
|
9 |
**
|
|
10 |
** _#_ _I_ __Date__ _______________________________________Description_______________________________________
|
|
11 |
** 001 HC 20260831 Created initial version. Annotates each COM-HANDLE declaration with the identity of the
|
|
12 |
** control or automation object it was loaded from or assigned from, so that the analytics
|
|
13 |
** reports can attribute every method and property access to its originating control.
|
|
14 |
** Refs #11808.
|
|
15 |
*/
|
|
16 |
-->
|
|
17 |
|
|
18 |
<!--
|
|
19 |
** This program is free software: you can redistribute it and/or modify
|
|
20 |
** it under the terms of the GNU Affero General Public License as
|
|
21 |
** published by the Free Software Foundation, either version 3 of the
|
|
22 |
** License, or (at your option) any later version.
|
|
23 |
**
|
|
24 |
** This program is distributed in the hope that it will be useful,
|
|
25 |
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
26 |
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
27 |
** GNU Affero General Public License for more details.
|
|
28 |
**
|
|
29 |
** You may find a copy of the GNU Affero GPL version 3 at the following
|
|
30 |
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
|
|
31 |
**
|
|
32 |
** Additional terms under GNU Affero GPL version 3 section 7:
|
|
33 |
**
|
|
34 |
** Under Section 7 of the GNU Affero GPL version 3, the following additional
|
|
35 |
** terms apply to the works covered under the License. These additional terms
|
|
36 |
** are non-permissive additional terms allowed under Section 7 of the GNU
|
|
37 |
** Affero GPL version 3 and may not be removed by you.
|
|
38 |
**
|
|
39 |
** 0. Attribution Requirement.
|
|
40 |
**
|
|
41 |
** You must preserve all legal notices or author attributions in the covered
|
|
42 |
** work or Appropriate Legal Notices displayed by works containing the covered
|
|
43 |
** work. You may not remove from the covered work any author or developer
|
|
44 |
** credit already included within the covered work.
|
|
45 |
**
|
|
46 |
** 1. No License To Use Trademarks.
|
|
47 |
**
|
|
48 |
** This license does not grant any license or rights to use the trademarks
|
|
49 |
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
|
|
50 |
** of Golden Code Development Corporation. You are not authorized to use the
|
|
51 |
** name Golden Code, FWD, or the names of any author or contributor, for
|
|
52 |
** publicity purposes without written authorization.
|
|
53 |
**
|
|
54 |
** 2. No Misrepresentation of Affiliation.
|
|
55 |
**
|
|
56 |
** You may not represent yourself as Golden Code Development Corporation or FWD.
|
|
57 |
**
|
|
58 |
** You may not represent yourself for publicity purposes as associated with
|
|
59 |
** Golden Code Development Corporation, FWD, or any author or contributor to
|
|
60 |
** the covered work, without written authorization.
|
|
61 |
**
|
|
62 |
** 3. No Misrepresentation of Source or Origin.
|
|
63 |
**
|
|
64 |
** You may not represent the covered work as solely your work. All modified
|
|
65 |
** versions of the covered work must be marked in a reasonable way to make it
|
|
66 |
** clear that the modified work is not originating from Golden Code Development
|
|
67 |
** Corporation or FWD. All modified versions must contain the notices of
|
|
68 |
** attribution required in this license.
|
|
69 |
-->
|
|
70 |
|
|
71 |
<!--
|
|
72 |
** Every COM or OCX method/property access in 4GL is made through a COM-HANDLE referent. The
|
|
73 |
** referent alone does not say WHICH control or automation object is being driven; that is
|
|
74 |
** established elsewhere, at the point the handle was created, loaded or assigned:
|
|
75 |
**
|
|
76 |
** CREATE "Excel.Application" chExcel. COM automation, identity is the ProgID
|
|
77 |
** chFrame = CtrlFrame:COM-HANDLE. OCX, identity begins with the control frame
|
|
78 |
** chFrame:LoadControls("x.wrx", "Section"). OCX, identity gains the .wrx file and section
|
|
79 |
** chCtl = chFrame:ListView. OCX, identity gains the named control
|
|
80 |
** chBook = chExcel:Workbooks:Add(). identity is inherited from the chain root
|
|
81 |
**
|
|
82 |
** This rule set records that identity on the DECLARATION node of each COM-HANDLE (the node that
|
|
83 |
** every reference points at through its "refid" annotation), under two annotations:
|
|
84 |
**
|
|
85 |
** com-origin the control/object identity string
|
|
86 |
** com-origin-kind com-automation | ocx-control | ocx-control-frame | com-derived
|
|
87 |
**
|
|
88 |
** A declaration that carries no com-origin annotation after this pass could not be resolved
|
|
89 |
** statically (the handle arrives as a parameter, through a shared variable, from a computed
|
|
90 |
** ProgID, or from another compilation unit). That is a legitimate and expected outcome; the
|
|
91 |
** reports treat a missing annotation as an unresolved origin and count it separately rather
|
|
92 |
** than dropping the access.
|
|
93 |
**
|
|
94 |
** This runs as part of the F2 front end (annotations/early_annotations), NOT the CB back end,
|
|
95 |
** because the analytics pipeline only ever executes the front end. The annotations are persisted
|
|
96 |
** into the .ast artifacts by BinaryAstCodec and are read back, read-only, by ReportDriver in a
|
|
97 |
** separate JVM. Since the report is a later pass over persisted trees, the annotation only has
|
|
98 |
** to be correct by the end of this file's walk: origins are therefore written as soon as they
|
|
99 |
** are discovered and refined in place as more becomes known, and no deferred application pass
|
|
100 |
** is needed.
|
|
101 |
-->
|
|
102 |
|
|
103 |
<rule-set input="tree">
|
|
104 |
|
|
105 |
<!-- register worker objects -->
|
|
106 |
<worker class="com.goldencode.p2j.uast.ProgressPatternWorker" namespace="prog" />
|
|
107 |
<worker class="com.goldencode.p2j.convert.ExpressionConversionWorker" namespace="ecw" />
|
|
108 |
|
|
109 |
<!-- node scratch -->
|
|
110 |
<variable name="href" type="com.goldencode.ast.Aast" />
|
|
111 |
<variable name="enode" type="com.goldencode.ast.Aast" />
|
|
112 |
<variable name="lref" type="com.goldencode.ast.Aast" />
|
|
113 |
<variable name="rref" type="com.goldencode.ast.Aast" />
|
|
114 |
<variable name="cmeth" type="com.goldencode.ast.Aast" />
|
|
115 |
<variable name="pnode" type="com.goldencode.ast.Aast" />
|
|
116 |
|
|
117 |
<!-- value scratch -->
|
|
118 |
<variable name="declId" type="java.lang.Long" />
|
|
119 |
<variable name="srcId" type="java.lang.Long" />
|
|
120 |
<variable name="ident" type="java.lang.String" />
|
|
121 |
<variable name="skind" type="java.lang.String" />
|
|
122 |
<variable name="frame" type="java.lang.String" />
|
|
123 |
<variable name="wrx" type="java.lang.String" />
|
|
124 |
<variable name="sect" type="java.lang.String" />
|
|
125 |
|
|
126 |
<!-- per-file accumulated knowledge, keyed by declaration node id -->
|
|
127 |
<variable name="originOf" type="java.util.Map" />
|
|
128 |
<variable name="kindOf" type="java.util.Map" />
|
|
129 |
<variable name="frameOf" type="java.util.Map" />
|
|
130 |
<variable name="wrxOf" type="java.util.Map" />
|
|
131 |
<variable name="sectOf" type="java.util.Map" />
|
|
132 |
|
|
133 |
<!-- expression libraries -->
|
|
134 |
<include name="common-progress" />
|
|
135 |
|
|
136 |
<func-library access="private">
|
|
137 |
|
|
138 |
<!-- True only for a genuine COM-HANDLE typed node. The shared com_type() library function
|
|
139 |
also accepts plain widget HANDLE types, which is too broad here: a widget handle is
|
|
140 |
not a COM referent and must not be given a com-origin. -->
|
|
141 |
<function name="com_handle_type">
|
|
142 |
<parameter name="ttype" type="java.lang.Integer" />
|
|
143 |
ttype == prog.var_com_handle or ttype == prog.field_com_handle or
|
|
144 |
ttype == prog.func_com_handle or ttype == prog.attr_com_handle or
|
|
145 |
ttype == prog.meth_com_handle or ttype == prog.oo_meth_com_handle
|
|
146 |
</function>
|
|
147 |
|
|
148 |
<!-- The declaration node id a reference resolves to, or the node's own id when it IS the
|
|
149 |
declaration. Returns null when neither is available. -->
|
|
150 |
<function name="decl_id">
|
|
151 |
<parameter name="target" type="com.goldencode.ast.Aast" />
|
|
152 |
<return name="rid" type="java.lang.Long" />
|
|
153 |
|
|
154 |
<rule>rid = null</rule>
|
|
155 |
|
|
156 |
<rule>target != null
|
|
157 |
<rule>target.isAnnotation("refid")
|
|
158 |
<action>rid = #(java.lang.Long) target.getAnnotation("refid")</action>
|
|
159 |
<action on="false">rid = target.getId()</action>
|
|
160 |
</rule>
|
|
161 |
</rule>
|
|
162 |
</function>
|
|
163 |
|
|
164 |
<!-- Descend the left edge of a COM invocation/attribute chain to the referent it is rooted
|
|
165 |
at. For chExcel:Workbooks:Add() this yields the chExcel reference. -->
|
|
166 |
<function name="com_root_ref">
|
|
167 |
<parameter name="target" type="com.goldencode.ast.Aast" />
|
|
168 |
<return name="ref" type="com.goldencode.ast.Aast" />
|
|
169 |
|
|
170 |
<rule>ref = target</rule>
|
|
171 |
|
|
172 |
<while>ref != null and
|
|
173 |
(ref.type == prog.com_invocation or
|
|
174 |
ref.type == prog.colon)
|
|
175 |
<action>ref = ref.getChildAt(0)</action>
|
|
176 |
</while>
|
|
177 |
</function>
|
|
178 |
|
|
179 |
<!-- Literal text of a string node, unquoted and normalized, or null when the node is not a
|
|
180 |
string literal (a computed expression, a variable, a function call). -->
|
|
181 |
<function name="literal_text">
|
|
182 |
<parameter name="target" type="com.goldencode.ast.Aast" />
|
|
183 |
<return name="txt" type="java.lang.String" />
|
|
184 |
|
|
185 |
<rule>txt = null</rule>
|
|
186 |
|
|
187 |
<rule>target != null and target.type == prog.string
|
|
188 |
<action>txt = ecw.progressToJavaString(target.text)</action>
|
|
189 |
</rule>
|
|
190 |
</function>
|
|
191 |
|
|
192 |
<!-- The single expression payload under an EXPRESSION wrapper (or the node itself when it
|
|
193 |
is not wrapped). -->
|
|
194 |
<function name="expr_payload">
|
|
195 |
<parameter name="target" type="com.goldencode.ast.Aast" />
|
|
196 |
<return name="ref" type="com.goldencode.ast.Aast" />
|
|
197 |
|
|
198 |
<rule>ref = target</rule>
|
|
199 |
|
|
200 |
<while>ref != null and ref.type == prog.expression
|
|
201 |
<action>ref = ref.getChildAt(0)</action>
|
|
202 |
</while>
|
|
203 |
</function>
|
|
204 |
|
|
205 |
<!-- Compose an OCX control identity from the pieces known so far. The .wrx file name is
|
|
206 |
frequently passed to LoadControls() as a variable rather than a literal, so the
|
|
207 |
identity degrades gracefully: wrx#section/control, section/control, or frame. -->
|
|
208 |
<function name="compose_ocx">
|
|
209 |
<parameter name="fname" type="java.lang.String" />
|
|
210 |
<parameter name="wname" type="java.lang.String" />
|
|
211 |
<parameter name="sname" type="java.lang.String" />
|
|
212 |
<parameter name="cname" type="java.lang.String" />
|
|
213 |
<return name="txt" type="java.lang.String" />
|
|
214 |
|
|
215 |
<!-- least specific fallback: the control frame's own name -->
|
|
216 |
<rule>txt = fname</rule>
|
|
217 |
|
|
218 |
<rule>sname != null
|
|
219 |
<action>txt = sname</action>
|
|
220 |
|
|
221 |
<rule>wname != null
|
|
222 |
<action>txt = sprintf("%s#%s", wname, sname)</action>
|
|
223 |
</rule>
|
|
224 |
</rule>
|
|
225 |
|
|
226 |
<rule>cname != null
|
|
227 |
<action>txt = sprintf("%s/%s", txt, cname)</action>
|
|
228 |
</rule>
|
|
229 |
</function>
|
|
230 |
|
|
231 |
</func-library>
|
|
232 |
|
|
233 |
<init-rules>
|
|
234 |
<rule>originOf = create("java.util.HashMap")</rule>
|
|
235 |
<rule>kindOf = create("java.util.HashMap")</rule>
|
|
236 |
<rule>frameOf = create("java.util.HashMap")</rule>
|
|
237 |
<rule>wrxOf = create("java.util.HashMap")</rule>
|
|
238 |
<rule>sectOf = create("java.util.HashMap")</rule>
|
|
239 |
</init-rules>
|
|
240 |
|
|
241 |
<walk-rules>
|
|
242 |
|
|
243 |
<!-- ================================================================================
|
|
244 |
COM automation object creation: CREATE "Excel.Application" chExcel.
|
|
245 |
Matched on the COM-HANDLE operand rather than on the CREATE_OBJECT node itself, so
|
|
246 |
that no assumption is made about the order of the statement's children.
|
|
247 |
================================================================================ -->
|
|
248 |
<rule>evalLib("com_handle_type", type) and
|
|
249 |
parent.type == prog.create_object
|
|
250 |
|
|
251 |
<action>declId = execLib("decl_id", this)</action>
|
|
252 |
<action>enode = parent.getImmediateChild(prog.expression, null)</action>
|
|
253 |
<action>ident = execLib("literal_text", execLib("expr_payload", enode))</action>
|
|
254 |
|
|
255 |
<rule>declId != null
|
|
256 |
|
|
257 |
<!-- A non-literal ProgID (CREATE VALUE(expr) h, or a variable) cannot be resolved
|
|
258 |
statically. Leave the declaration unannotated so it is counted as unresolved
|
|
259 |
rather than attributed to the wrong object. -->
|
|
260 |
<rule>ident != null
|
|
261 |
<action>originOf.put(declId, ident)</action>
|
|
262 |
<action>kindOf.put(declId, "com-automation")</action>
|
|
263 |
<action>putReferenceNote(declId, "com-origin", ident)</action>
|
|
264 |
<action>putReferenceNote(declId, "com-origin-kind", "com-automation")</action>
|
|
265 |
</rule>
|
|
266 |
</rule>
|
|
267 |
</rule>
|
|
268 |
|
|
269 |
<!-- ================================================================================
|
|
270 |
Control frame binding: chFrame = CtrlFrame:COM-HANDLE.
|
|
271 |
The COM-HANDLE attribute node's parent chain gives the control frame widget on the
|
|
272 |
left; the enclosing assignment gives the COM-HANDLE variable being bound to it.
|
|
273 |
================================================================================ -->
|
|
274 |
<rule>type == prog.attr_com_handle and
|
|
275 |
parent != null and
|
|
276 |
(parent.type == prog.colon or
|
|
277 |
parent.type == prog.com_invocation)
|
|
278 |
|
|
279 |
<action>href = parent.getChildAt(0)</action>
|
|
280 |
<action>frame = null</action>
|
|
281 |
|
|
282 |
<rule>href != null
|
|
283 |
<action>frame = href.text</action>
|
|
284 |
</rule>
|
|
285 |
|
|
286 |
<!-- locate the assignment target holding this expression -->
|
|
287 |
<action>pnode = parent.parent</action>
|
|
288 |
<action>lref = null</action>
|
|
289 |
|
|
290 |
<rule>pnode != null and
|
|
291 |
(pnode.type == prog.assignment or
|
|
292 |
pnode.type == prog.assign)
|
|
293 |
<action>lref = pnode.getChildAt(0)</action>
|
|
294 |
</rule>
|
|
295 |
|
|
296 |
<rule>lref != null and frame != null and evalLib("com_handle_type", lref.type)
|
|
297 |
<action>declId = execLib("decl_id", lref)</action>
|
|
298 |
|
|
299 |
<rule>declId != null
|
|
300 |
<action>frameOf.put(declId, frame)</action>
|
|
301 |
<action>ident = execLib("compose_ocx",
|
|
302 |
frame,
|
|
303 |
#(java.lang.String) wrxOf.get(declId),
|
|
304 |
#(java.lang.String) sectOf.get(declId),
|
|
305 |
null)</action>
|
|
306 |
<action>originOf.put(declId, ident)</action>
|
|
307 |
<action>kindOf.put(declId, "ocx-control-frame")</action>
|
|
308 |
<action>putReferenceNote(declId, "com-origin", ident)</action>
|
|
309 |
<action>putReferenceNote(declId, "com-origin-kind", "ocx-control-frame")</action>
|
|
310 |
</rule>
|
|
311 |
</rule>
|
|
312 |
</rule>
|
|
313 |
|
|
314 |
<!-- ================================================================================
|
|
315 |
Control loading: chFrame:LoadControls("controls.wrx", "SectionName").
|
|
316 |
ocx_control_load() matches the second argument of the call, which is the .wrx section
|
|
317 |
name. The first argument is the .wrx file, commonly a variable rather than a literal.
|
|
318 |
================================================================================ -->
|
|
319 |
<rule>evalLib("ocx_control_load", this)
|
|
320 |
|
|
321 |
<action>cmeth = parent.parent</action>
|
|
322 |
<action>sect = execLib("literal_text", this)</action>
|
|
323 |
<action>wrx = null</action>
|
|
324 |
<action>href = null</action>
|
|
325 |
|
|
326 |
<rule>cmeth != null
|
|
327 |
<!-- first argument: the .wrx file -->
|
|
328 |
<action>pnode = cmeth.getChildAt(0)</action>
|
|
329 |
|
|
330 |
<rule>pnode != null
|
|
331 |
<action>wrx = execLib("literal_text", execLib("expr_payload", pnode.getChildAt(0)))</action>
|
|
332 |
</rule>
|
|
333 |
|
|
334 |
<!-- the referent the call was made on: the control frame's COM-HANDLE -->
|
|
335 |
<rule>cmeth.parent != null and cmeth.parent.type == prog.com_invocation
|
|
336 |
<action>href = execLib("com_root_ref", cmeth.parent.getChildAt(0))</action>
|
|
337 |
</rule>
|
|
338 |
</rule>
|
|
339 |
|
|
340 |
<rule>href != null and evalLib("com_handle_type", href.type)
|
|
341 |
<action>declId = execLib("decl_id", href)</action>
|
|
342 |
|
|
343 |
<rule>declId != null
|
|
344 |
<rule>wrx != null
|
|
345 |
<action>wrxOf.put(declId, wrx)</action>
|
|
346 |
</rule>
|
|
347 |
<rule>sect != null
|
|
348 |
<action>sectOf.put(declId, sect)</action>
|
|
349 |
</rule>
|
|
350 |
|
|
351 |
<action>ident = execLib("compose_ocx",
|
|
352 |
#(java.lang.String) frameOf.get(declId),
|
|
353 |
#(java.lang.String) wrxOf.get(declId),
|
|
354 |
#(java.lang.String) sectOf.get(declId),
|
|
355 |
null)</action>
|
|
356 |
|
|
357 |
<rule>ident != null
|
|
358 |
<action>originOf.put(declId, ident)</action>
|
|
359 |
<action>kindOf.put(declId, "ocx-control-frame")</action>
|
|
360 |
<action>putReferenceNote(declId, "com-origin", ident)</action>
|
|
361 |
<action>putReferenceNote(declId, "com-origin-kind", "ocx-control-frame")</action>
|
|
362 |
</rule>
|
|
363 |
</rule>
|
|
364 |
</rule>
|
|
365 |
</rule>
|
|
366 |
|
|
367 |
<!-- ================================================================================
|
|
368 |
Origin inheritance through assignment:
|
|
369 |
chCtl = chFrame:ListView. (named OCX control off a control frame)
|
|
370 |
chBook = chExcel:Workbooks:Add(). (object returned from a COM chain)
|
|
371 |
chSheet = chBook:Worksheets(5). (transitively, from an inherited origin)
|
|
372 |
The assigned handle takes the identity of the referent the chain is rooted at. For a
|
|
373 |
control frame the member name is the control's name and refines the identity; for an
|
|
374 |
automation object the identity is inherited unchanged, since the intermediate objects
|
|
375 |
a chain walks through have no control identity of their own.
|
|
376 |
================================================================================ -->
|
|
377 |
<rule>(type == prog.assignment or type == prog.assign) and
|
|
378 |
this.getChildAt(0) != null and
|
|
379 |
this.getChildAt(1) != null
|
|
380 |
|
|
381 |
<action>lref = execLib("expr_payload", this.getChildAt(0))</action>
|
|
382 |
<action>rref = execLib("expr_payload", this.getChildAt(1))</action>
|
|
383 |
|
|
384 |
<rule>lref != null and
|
|
385 |
rref != null and
|
|
386 |
evalLib("com_handle_type", lref.type) and
|
|
387 |
(rref.type == prog.com_invocation or
|
|
388 |
rref.type == prog.colon)
|
|
389 |
|
|
390 |
<action>href = execLib("com_root_ref", rref)</action>
|
|
391 |
|
|
392 |
<rule>href != null and evalLib("com_handle_type", href.type)
|
|
393 |
<action>srcId = execLib("decl_id", href)</action>
|
|
394 |
<action>declId = execLib("decl_id", lref)</action>
|
|
395 |
|
|
396 |
<rule>srcId != null and declId != null and declId != srcId
|
|
397 |
<action>ident = #(java.lang.String) originOf.get(srcId)</action>
|
|
398 |
|
|
399 |
<action>pnode = rref.getChildAt(1)</action>
|
|
400 |
<action>skind = #(java.lang.String) kindOf.get(srcId)</action>
|
|
401 |
|
|
402 |
<rule>ident != null
|
|
403 |
|
|
404 |
<!-- A control frame yields a NAMED control, so the member name is the
|
|
405 |
control's name and refines the identity. Stated as two mutually
|
|
406 |
exclusive sibling rules rather than one rule with an on="false" branch,
|
|
407 |
because the branch would otherwise have to sit after a nested rule and
|
|
408 |
its scope becomes ambiguous. -->
|
|
409 |
<rule>skind != null and
|
|
410 |
skind.equals("ocx-control-frame") and
|
|
411 |
pnode != null
|
|
412 |
<action>ident = execLib("compose_ocx",
|
|
413 |
#(java.lang.String) frameOf.get(srcId),
|
|
414 |
#(java.lang.String) wrxOf.get(srcId),
|
|
415 |
#(java.lang.String) sectOf.get(srcId),
|
|
416 |
pnode.text)</action>
|
|
417 |
<action>originOf.put(declId, ident)</action>
|
|
418 |
<action>kindOf.put(declId, "ocx-control")</action>
|
|
419 |
<action>putReferenceNote(declId, "com-origin", ident)</action>
|
|
420 |
<action>putReferenceNote(declId, "com-origin-kind", "ocx-control")</action>
|
|
421 |
</rule>
|
|
422 |
|
|
423 |
<!-- otherwise the origin is inherited verbatim from the chain root, since the
|
|
424 |
intermediate objects a chain walks through have no identity of their own -->
|
|
425 |
<rule>skind == null or
|
|
426 |
!skind.equals("ocx-control-frame") or
|
|
427 |
pnode == null
|
|
428 |
<action>originOf.put(declId, ident)</action>
|
|
429 |
<action>kindOf.put(declId, "com-derived")</action>
|
|
430 |
<action>putReferenceNote(declId, "com-origin", ident)</action>
|
|
431 |
<action>putReferenceNote(declId, "com-origin-kind", "com-derived")</action>
|
|
432 |
</rule>
|
|
433 |
</rule>
|
|
434 |
</rule>
|
|
435 |
</rule>
|
|
436 |
</rule>
|
|
437 |
</rule>
|
|
438 |
|
|
439 |
</walk-rules>
|
|
440 |
|
|
441 |
</rule-set>
|