001: /*
002: * BSLTemplate.java
003: *
004: * Brazil project web application Framework,
005: * export version: 1.1
006: * Copyright (c) 1999-2001 Sun Microsystems, Inc.
007: *
008: * Sun Public License Notice
009: *
010: * The contents of this file are subject to the Sun Public License Version
011: * 1.0 (the "License"). You may not use this file except in compliance with
012: * the License. A copy of the License is included as the file "license.terms",
013: * and also available at http://www.sun.com/
014: *
015: * The Original Code is from:
016: * Brazil project web application Framework release 1.1.
017: * The Initial Developer of the Original Code is: cstevens.
018: * Portions created by cstevens are Copyright (C) Sun Microsystems, Inc.
019: * All Rights Reserved.
020: *
021: * Contributor(s): cstevens, suhler.
022: *
023: * Version: 1.24
024: * Created by cstevens on 99/10/21
025: * Last modified by suhler on 01/01/14 14:51:42
026: */
027:
028: package sunlabs.brazil.template;
029:
030: import sunlabs.brazil.util.Glob;
031: import sunlabs.brazil.util.Sort;
032: import sunlabs.brazil.util.Format;
033: import sunlabs.brazil.util.regexp.Regexp;
034:
035: import java.util.Enumeration;
036: import java.util.Properties;
037: import java.util.StringTokenizer;
038: import java.util.Vector;
039: import java.io.Serializable;
040:
041: /**
042: * The <code>BSLTemplate</code> takes an HTML document with embedded "BSL"
043: * markup tags in it and evaluates those special tags to produce a
044: * standard HTML document.
045: * <p>
046: * BSL stands for Brazil Scripting Language. BSL can be used to substitute
047: * data from the request properties into the resultant document. However,
048: * rather than simple property substitution as is provided by the
049: * <code>PropsTemplate</code>, this class provides the ability to iterate
050: * over and choose amongst the values substituted with a set of simple
051: * flow-control constructs.
052: * <p>
053: * BSL uses the following special tags as its language constructs: <ul>
054: * <li> <code><foreach></code>
055: * <li> <code><if></code>
056: * </ul>
057: * <p>
058: * This template recursively evalutes the bodies/clauses of the BSL commands,
059: * meaning that they may contain nested BSL and/or other tags defined by
060: * other templates.
061: * <p>
062: * The following configuration parameter is used to initialize this
063: * template.
064: * <dl class=props>
065: * <dt> debug
066: * <dd> If this configuration parameter is present, this class replaces
067: * the BSL tags with comments, so the user can keep track of where
068: * the dynamically generated content is coming from by examining the
069: * comments in the resultant HTML document. By default, the BSL tags
070: * are completely eliminated from the HTML document rather than changed
071: * into comments.
072: * </dl>
073: * <hr>
074: * <h2><foreach> TAG</h2>
075: * The <code><foreach></code> tag repeatedly evaluates its body a
076: * selected number of times. Each time the body is evaluated, the provided
077: * named property is set to the next word in the provided list of words.
078: * The body is terminated by the <code></foreach></code> tag.
079: * This tag is especially useful for dynamically producing lists and tables.
080: * <ul>
081: * <li><pre>
082: * <foreach name=<i>var</i> list="<i>value1 value2 ...</i>" [delim=x]>
083: * <property <i>var</i>>
084: * </foreach></pre>
085: *
086: * Iterate over the set of values "<i>value1 value2 ...</i>". The named
087: * property <i>var</i> is assigned each value in turn. If the optional
088: * parameter <code>delim</code> is specified, its value is used as
089: * the list delimeter. By default, whitespace is used.
090: *
091: * <p><li><pre>
092: * <foreach name=<i>var</i> property=<i>property</i> [delim=x]>
093: * <property <i>var</i>>
094: * </foreach> </pre>
095:
096: * Iterate over the values in the other <i>property</i>. The value
097: * of the other property is broken up using the <code>StringTokenizer</code>
098: * and each piece is assigned to the named property <i>var</i> in turn.
099: * If the optional
100: * parameter <code>delim</code> is specified, its value is used as
101: * the list delimeter. By default, whitespace is used.
102: *
103: * <p><li><pre>
104: * <foreach name=<i>var</i> property=<i>property</i> [delim=x]>
105: * <property <i>var</i>>
106: * </foreach> </pre>
107:
108: * Iterate over the values in the other <i>property</i>. The value
109: * of the other property is broken up using the <code>StringTokenizer</code>
110: * and each piece is assigned to the named property <i>var</i> in turn.
111: *
112: * <p><li><pre>
113: * <foreach name=<i>var</i> glob=<i>pattern</i>>
114: * <property <i>var</i>.name>
115: * <property <i>var</i>.value>
116: *
117: * <property <i>var</i>.name.1>
118: * <property <i>var</i>.name.2>
119: * </foreach> </pre>
120: *
121: * Iterate over all the properties whose name matches the
122: * {@link sunlabs.brazil.util.Glob glob} <i>pattern</i>. In turn, the
123: * following properties are set:
124: * <br>
125: * <code><i>var</i>.name</code> is the name of the property.
126: * <br>
127: * <code><i>var</i>.value</code> is the value of the property.
128: * <br>
129: * <code><i>var</i>.name.1</code>, <code><i>var</i>.name.2</code>, ... are
130: * the substrings matching the wildcard characters in the pattern, if any.
131: *
132: *
133: * <p><li><pre>
134: * <foreach name=<i>var</i> glob=<i>pattern</i>>
135: * <property <i>var</i>.name>
136: * <property <i>var</i>.value>
137: *
138: * <property <i>var</i>.name.1>
139: * <property <i>var</i>.name.2>
140: * </foreach> </pre>
141: *
142: * Iterate over all the properties whose name matches the
143: * {@link sunlabs.brazil.util.Glob glob} <i>pattern</i>. In turn, the
144: * following properties are set:
145: * <br>
146: * <code><i>var</i>.name</code> is the name of the property.
147: * <br>
148: * <code><i>var</i>.value</code> is the value of the property.
149: * <br>
150: * <code><i>var</i>.name.1</code>, <code><i>var</i>.name.2</code>, ... are
151: * the substrings matching the wildcard characters in the pattern, if any.
152: *
153: * <p><li><pre>
154: * <foreach name=<i>var</i> match=<i>pattern</i>>
155: * <property <i>var</i>.name>
156: * <property <i>var</i>.value>
157: *
158: * <property <i>var</i>.name.0>
159: * <property <i>var</i>.name.1>
160: * <property <i>var</i>.name.2>
161: * </foreach> </pre>
162: *
163: * Iterate over all the properties whose name matches the
164: * {@link sunlabs.brazil.util.regexp.Regexp regular expression}
165: * <i>pattern</i>. In turn, the following properties are set:
166: * <br>
167: * <code><i>var</i>.name</code> is the name of the property.
168: * <br>
169: * <code><i>var</i>.value</code> is the value of the property.
170: * <br>
171: * <code><i>var</i>.name.0</code> is the substring that matched the whole
172: * pattern.
173: * <br>
174: * <code><i>var</i>.name.1</code>, <code><i>var</i>.name.2</code>, ... are
175: * the substrings matching the parenthesized subexpressions, if any.
176: * </ul>
177: * <h2>Sorting using <code>foreach</code></h2>
178: * The <foreach> tag contains an (experimantal) feature to
179: * change the order of iteration. This facility is intended for
180: * common sorting operations. For general purpose manipulation of
181: * the iteration order, the order should be defined either in another
182: * handler, or by using the
183: * {@link sunlabs.brazil.tcl.TclServerTemplate <server>}
184: * directive.
185: * <p>
186: * The three additional parameters used to control sorting are:
187: * <dl>
188: * <dt> reverse
189: * <dd> The list of items is iterated in the reverse order.
190: *
191: * <dt> numeric
192: * <dd> When used in conjunction with the <code>sort</code> parameter, it
193: * causes the sort keys to be interpreted as numbers (or zero if the
194: * key is an invalid number).
195: *
196: * <dt> sort[=key]
197: * <dd> The items to be iterated over are sorted. If no key is supplied, the
198: * items are sorted by the property name. If a key is supplied, its
199: * value is used as the sort key for the iteration. For this to be
200: * meaningful, the key should contain one or more variable substitutions
201: * (e.g. ${...}, see * {@link sunlabs.brazil.util.Format#getProperty
202: * getProperty}). Before iteration commences, the value for the key is
203: * computed for each item to be iterated over, by performing all of the
204: * '${...}' substitutions in <i>key</i> for each value, then sorting the
205: * items by the result.
206: * </dl>
207: * <hr>
208: * <h2><if> TAG</h2>
209: * The <code><if></code> tag evaluates one of its clauses dependant upon
210: * the value of the provided named property(ies). The other clauses are not
211: * evaluated and do not appear in the resultant HTML document.
212: * The general format of the <code><if></code> tag is as follows:
213: * <pre>
214: * <if <i>...condition...</i>>
215: * <i>...clause...</i>
216: * <elseif <i>...condition...</i>>
217: * <i>...clause...</i>
218: * <else>
219: * <i>...clause...</i>
220: * </if> </pre>
221: *
222: * The <code><elseif></code> and <code><else></code> tags are
223: * optional, and multiple <code><elseif></code> tags may be present.
224: * <p>
225: * Following are the formats of the "<i>...condition...</i>": <dl>
226: *
227: * <dt> <code><if <i>var</i>></code>
228: * <dd> Test if the value of property <i>var</i> is set and is not "",
229: * "false", "no", "off", or the number 0.
230: * <p>
231: * <dt> <code><if name=<i>var</i>></code>
232: * <dd> Same as above. <p>
233: * <p>
234: * <dt> <code><if name=<i>var</i> value=<i>string</i>></code>
235: * <dd> Test if the value of property <i>var</i> is equal to the
236: * given <i>string</i>.
237: * <p>
238: * <dt> <code><if name=<i>var</i> glob=<i>pattern</i>></code>
239: * <dd> Test if the value of property <i>var</i> matches the given
240: * {@link sunlabs.brazil.util.Glob glob} <i>pattern</i>.
241: * <p>
242: * <dt> <code><if name=<i>var</i> match=<i>pattern</i>></code>
243: * <dd> Test if the value of property <i>var</i> matches the given
244: * {@link sunlabs.brazil.util.regexp.Regexp regular expression}
245: * <i>pattern</i>.
246: * </dl>
247: * <p>
248: * Anytime a variable name is specified, variable substitution as
249: * described in {@link sunlabs.brazil.util.Format#getProperty} may be used.
250: * @see <a href=/bsl.html>a sample HTML page that contains some BSL
251: * markup</a>
252: */
253:
254: public class BSLTemplate extends Template implements Serializable {
255: private static final String DEBUG = "debug";
256:
257: transient boolean debug;
258:
259: /**
260: * Called at the beginning of each HTML document that this
261: * <code>BSLTemplate</code> is asked to process.
262: * <p>
263: * Checks the "debug" configuation parameter.
264: *
265: * @param hr
266: * The request and associated HTML document that will be
267: * processed.
268: *
269: * @returns <code>true</code> always, indicating success.
270: */
271: public boolean init(RewriteContext hr) {
272: debug = (hr.request.props.getProperty(hr.prefix + DEBUG) != null);
273: return true;
274: }
275:
276: private static String substProp(RewriteContext hr, String name) {
277: return Format.subst(hr.request.props, hr.get(name));
278: }
279:
280: /**
281: * Handles the "foreach" tag.
282: */
283: public void tag_foreach(RewriteContext hr) {
284: String name = substProp(hr, "name");
285: if (name == null) {
286: return;
287: }
288:
289: hr.killToken();
290: if (debug) {
291: hr.append("<!-- " + hr.getBody() + " -->");
292: }
293:
294: String arg;
295: boolean done = false;
296:
297: if ((arg = substProp(hr, "list")) != null) {
298: done = foreach_list(hr, name, arg, substProp(hr, "delim"));
299: } else if ((arg = substProp(hr, "glob")) != null) {
300: done = foreach_match(hr, name, arg, true);
301: } else if ((arg = substProp(hr, "match")) != null) {
302: done = foreach_match(hr, name, arg, false);
303: } else if ((arg = hr.get("property")) != null) {
304: String list = Format.getProperty(hr.request.props, arg, "");
305: done = foreach_list(hr, name, list, substProp(hr, "delim"));
306: }
307:
308: if (done == false) {
309: hr.accumulate(false);
310: skipTo(hr, "/foreach");
311: hr.accumulate(true);
312: }
313:
314: if (debug) {
315: hr.append("<!-- " + hr.getBody() + " -->");
316: }
317: }
318:
319: private static class SortThing {
320: String index;
321: String s;
322: double d;
323:
324: public SortThing(String index, String key) {
325: this .index = index;
326: this .s = key;
327: }
328:
329: public SortThing(String index, double d) {
330: this .index = index;
331: this .d = d;
332: }
333: }
334:
335: private static class Compare implements Sort.Compare {
336: boolean numeric;
337: boolean reverse;
338:
339: Compare(boolean numeric, boolean reverse) {
340: this .numeric = numeric;
341: this .reverse = reverse;
342: }
343:
344: public int compare(Object array, int index1, int index2) {
345: SortThing[] sa = (SortThing[]) array;
346: SortThing s1 = sa[index1];
347: SortThing s2 = sa[index2];
348:
349: int result = 0;
350: if (numeric) {
351: if (s1.d > s2.d) {
352: result = 1;
353: } else if (s1.d == s2.d) {
354: result = 0;
355: } else {
356: result = -1;
357: }
358: } else {
359: result = s1.s.compareTo(s2.s);
360: }
361:
362: if (reverse) {
363: result = -result;
364: }
365: return result;
366: }
367: }
368:
369: private boolean foreach_list(RewriteContext hr, String name,
370: String list, String delim) {
371: Properties props = hr.request.props;
372:
373: Object old = props.get(name);
374:
375: StringTokenizer st;
376: if (delim == null) {
377: st = new StringTokenizer(list);
378: } else {
379: st = new StringTokenizer(list, delim);
380: }
381: String rest = hr.lex.rest();
382: boolean any = false;
383:
384: String sort = hr.get("sort");
385: boolean reverse = (substProp(hr, "reverse") != null);
386:
387: if ((sort == null) && (reverse == false)) {
388: while (st.hasMoreElements()) {
389: props.put(name, st.nextElement());
390: processTo(hr, rest, "/foreach");
391: any = true;
392: }
393: } else if ((sort == null) && (reverse == true)) {
394: Vector v = new Vector();
395: while (st.hasMoreTokens()) {
396: v.addElement(st.nextToken());
397: }
398: for (int i = v.size(); --i >= 0;) {
399: props.put(name, v.elementAt(i));
400: processTo(hr, rest, "/foreach");
401: any = true;
402: }
403: } else {
404: boolean numeric = (substProp(hr, "numeric") != null);
405:
406: if (sort.length() == 0) {
407: sort = null;
408: }
409:
410: Vector v = new Vector();
411: while (st.hasMoreTokens()) {
412: String value = st.nextToken();
413: String key = value;
414: if (sort != null) {
415: props.put(name, value);
416: key = Format.subst(props, sort);
417: }
418: if (numeric) {
419: double d;
420: try {
421: d = Double.valueOf(key).doubleValue();
422: } catch (Exception e) {
423: d = 0;
424: }
425: v.addElement(new SortThing(value, d));
426: } else {
427: v.addElement(new SortThing(value, key));
428: }
429: }
430: SortThing[] array = new SortThing[v.size()];
431: v.copyInto(array);
432:
433: Sort.qsort(array, new Compare(numeric, reverse));
434:
435: for (int i = 0; i < array.length; i++) {
436: props.put(name, array[i].index);
437: processTo(hr, rest, "/foreach");
438: any = true;
439: }
440: }
441:
442: restore(props, name, old);
443: return any;
444: }
445:
446: private static class Bag {
447: Properties props;
448: Regexp r;
449: int subspecs;
450: String pat;
451: String nameProp;
452: String valueProp;
453: String[] sub;
454: String[] subName;
455: }
456:
457: private boolean foreach_match(RewriteContext hr, String name,
458: String pat, boolean glob) {
459: Properties props = hr.request.props;
460:
461: Bag bag = new Bag();
462: bag.pat = pat;
463: bag.props = props;
464: bag.nameProp = name + ".name";
465: bag.valueProp = name + ".value";
466:
467: Object oldName = props.get(bag.nameProp);
468: Object oldValue = props.get(bag.valueProp);
469:
470: bag.sub = new String[20];
471: bag.subName = new String[bag.sub.length];
472: Object[] oldSub = new Object[bag.sub.length];
473: for (int i = 0; i < bag.sub.length; i++) {
474: bag.subName[i] = bag.nameProp + "." + i;
475: oldSub[i] = props.get(bag.subName[i]);
476: }
477:
478: bag.r = null;
479: int subspecs = 0;
480: if (glob == false) {
481: try {
482: bag.r = new Regexp(pat);
483: } catch (Exception e) {
484: bag.r = new Regexp("^$x"); // unmatchable
485: }
486: bag.subspecs = Math.min(bag.sub.length, bag.r.subspecs());
487: }
488:
489: Enumeration names = props.propertyNames();
490: String rest = hr.lex.rest();
491: boolean any = false;
492:
493: String sort = hr.get("sort");
494: boolean reverse = (substProp(hr, "reverse") != null);
495:
496: if ((sort == null) && (reverse == false)) {
497: while (names.hasMoreElements()) {
498: String value = (String) names.nextElement();
499: if (matches(bag, true, value)) {
500: processTo(hr, rest, "/foreach");
501: any = true;
502: }
503: }
504: } else if ((sort == null) && (reverse == true)) {
505: Vector v = new Vector();
506: while (names.hasMoreElements()) {
507: String value = (String) names.nextElement();
508: if (matches(bag, false, value)) {
509: v.addElement(value);
510: }
511: }
512: for (int i = v.size(); --i >= 0;) {
513: String value = (String) v.elementAt(i);
514: matches(bag, true, value);
515: processTo(hr, rest, "/foreach");
516: any = true;
517: }
518: } else {
519: boolean setProps = (sort.length() > 0);
520:
521: Vector v = new Vector();
522: while (names.hasMoreElements()) {
523: String value = (String) names.nextElement();
524: if (matches(bag, setProps, value)) {
525: String key = value;
526: if (setProps) {
527: key = Format.subst(props, sort);
528: }
529: v.addElement(new SortThing(value, key));
530: }
531: }
532:
533: SortThing[] array = new SortThing[v.size()];
534: v.copyInto(array);
535: Sort.qsort(array, new Compare(false, reverse));
536:
537: for (int i = 0; i < array.length; i++) {
538: String value = array[i].index;
539: matches(bag, true, value);
540: processTo(hr, rest, "/foreach");
541: any = true;
542: }
543: }
544:
545: restore(props, bag.nameProp, oldName);
546: restore(props, bag.valueProp, oldValue);
547: for (int i = 0; i < bag.sub.length; i++) {
548: restore(props, bag.subName[i], oldSub[i]);
549: }
550:
551: return any;
552: }
553:
554: private boolean matches(Bag bag, boolean setProps, String name) {
555: boolean match;
556:
557: if (bag.r == null) {
558: match = Glob.match(bag.pat, name, bag.sub);
559: if (match && setProps) {
560: try {
561: for (int i = 0; bag.sub[i] != null; i++) {
562: bag.props.put(bag.subName[i + 1], bag.sub[i]);
563: }
564: } catch (Exception e) {
565: }
566: }
567: } else {
568: match = bag.r.match(name, bag.sub);
569: if (match && setProps) {
570: for (int i = 0; i < bag.subspecs; i++) {
571: if (bag.sub[i] == null) {
572: bag.sub[i] = "";
573: }
574: bag.props.put(bag.subName[i], bag.sub[i]);
575: }
576: }
577: }
578: if (match && setProps) {
579: bag.props.put(bag.nameProp, name);
580: bag.props.put(bag.valueProp, bag.props.getProperty(name));
581: }
582: return match;
583: }
584:
585: private static void restore(Properties props, String name,
586: Object value) {
587: if (value == null) {
588: props.remove(name);
589: } else {
590: props.put(name, value);
591: }
592: }
593:
594: /**
595: * Handles the "if" tag.
596: */
597: public void tag_if(RewriteContext hr) {
598: hr.killToken();
599:
600: while (true) {
601: if (debug) {
602: hr.append("<!-- " + hr.getBody() + " -->");
603: }
604: if (isTrue(hr)) {
605: processClause(hr);
606: break;
607: } else {
608: hr.accumulate(false);
609: boolean elseif = false;
610: while (hr.nextTag()) {
611: String tag = hr.getTag();
612: if (tag.equals("if")) {
613: skipTo(hr, "/if");
614: } else if (tag.equals("/if")) {
615: break;
616: } else if (tag.equals("else")) {
617: hr.accumulate(true);
618: if (debug) {
619: hr.append("<!-- " + hr.getBody() + " -->");
620: }
621: processClause(hr);
622: break;
623: } else if (tag.equals("elseif")) {
624: hr.accumulate(true);
625: elseif = true;
626: break;
627: }
628: }
629: if (elseif == false) {
630: break;
631: }
632: }
633: }
634: if (debug) {
635: hr.append("<!-- " + hr.getBody() + " -->");
636: }
637: hr.accumulate(true);
638: }
639:
640: private static boolean isTrue(RewriteContext hr) {
641: String name = hr.get("name");
642: if (name == null) {
643: name = hr.getArgs();
644: }
645: String value = Format.getProperty(hr.request.props, name, "");
646:
647: String arg;
648: if ((arg = substProp(hr, "value")) != null) {
649: return arg.equals(value);
650: } else if ((arg = substProp(hr, "match")) != null) {
651: try {
652: return (new Regexp(arg).match(value) != null);
653: } catch (Exception e) {
654: return false;
655: }
656: } else if ((arg = substProp(hr, "glob")) != null) {
657: return Glob.match(arg, value);
658: } else {
659: /*
660: * Unknown or no argument specified, treat value as boolean: if
661: * named property exists and is not "", "false", "no", "off", or
662: * 0, then return true.
663: */
664:
665: if ((value.length() == 0) || value.equals("false")
666: || value.equals("no") || value.equals("off")) {
667: return false;
668: } else {
669: try {
670: return Integer.decode(value).intValue() != 0;
671: } catch (Exception e) {
672: return true;
673: }
674: }
675: }
676: }
677:
678: private static void processClause(RewriteContext hr) {
679: while (hr.nextTag()) {
680: String tag = hr.getTag();
681: if (tag.equals("/if")) {
682: break;
683: } else if (tag.equals("else") || tag.equals("elseif")) {
684: hr.accumulate(false);
685: skipTo(hr, "/if");
686: break;
687: } else {
688: hr.process();
689: }
690: }
691: }
692:
693: private static void skipTo(RewriteContext hr, String endTag) {
694: String startTag = endTag.substring(1);
695: while (hr.nextTag()) {
696: String tag = hr.getTag();
697: if (tag.equals(startTag)) {
698: skipTo(hr, endTag);
699: } else if (tag.equals(endTag)) {
700: break;
701: }
702: }
703: }
704:
705: private static void processTo(RewriteContext hr, String source,
706: String tag) {
707: hr.lex.replace(source);
708: while (hr.nextTag()) {
709: if (hr.getTag().equals(tag)) {
710: hr.killToken();
711: break;
712: }
713: hr.process();
714: }
715: }
716:
717: /**
718: * Handle the [experimental] "extract" tag.
719: * This permits parts of a property's value to be extracted into
720: * additional properties, based on either glob or regular expression
721: * patterns.
722: * <br>
723: * <extract name= prepend= glob= match=>
724: * <dl>
725: * <dt>name <dd>The name of the property to extract
726: * <dt>prepend<dd>The base name for all extracted properties
727: * (defaults to "name"). If it doesn't end with a ".",
728: * one is added.
729: * <dt>glob <dd>The glob pattern to use for extraction. The text
730: * matching each wildcard in the pattern is extracted.
731: * <dt>match <dd>The regular expression pattern to use for extraction.
732: * The text matching each sub-expression is extracted. If
733: * "glob" is specified, then "match" is ignored.
734: * </dl>
735: */
736: public void tag_extract(RewriteContext hr) {
737: String name = hr.get("name");
738: String prefix = substProp(hr, "prepend");
739: String glob = substProp(hr, "glob");
740: String match = substProp(hr, "match");
741:
742: if (name == null) {
743: return;
744: }
745: String value = Format.getProperty(hr.request.props, name, "");
746:
747: hr.killToken();
748: if (debug) {
749: hr.append("<!-- " + hr.getBody() + " -->");
750: }
751:
752: if (prefix == null) {
753: prefix = name;
754: }
755: if (prefix.endsWith(".") == false) {
756: prefix += ".";
757: }
758:
759: Properties props = hr.request.props;
760:
761: try {
762: if (glob != null) {
763: String[] sub = new String[20];
764: if (Glob.match(glob, value, sub)) {
765: for (int i = 0; sub[i] != null; i++) {
766: props.put(prefix + (i + 1), sub[i]);
767: }
768: }
769: } else if (match != null) {
770: Regexp r = new Regexp(match);
771: String[] sub = new String[r.subspecs()];
772: if (r.match(value, sub)) {
773: for (int i = 0; i < sub.length; i++) {
774: if (sub[i] == null) {
775: sub[i] = "";
776: }
777: props.put(prefix + i, sub[i]);
778: }
779: }
780: }
781: } catch (Exception e) {
782: }
783: }
784: }
|