001: /*
002: * TokenMarker.java - Tokenizes lines of text
003: * :tabSize=8:indentSize=8:noTabs=false:
004: * :folding=explicit:collapseFolds=1:
005: *
006: * Copyright (C) 1998, 2003 Slava Pestov
007: * Copyright (C) 1999, 2000 mike dillon
008: *
009: * This program is free software; you can redistribute it and/or
010: * modify it under the terms of the GNU General Public License
011: * as published by the Free Software Foundation; either version 2
012: * of the License, or any later version.
013: *
014: * This program is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
017: * GNU General Public License for more details.
018: *
019: * You should have received a copy of the GNU General Public License
020: * along with this program; if not, write to the Free Software
021: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
022: */
023:
024: package org.gjt.sp.jedit.syntax;
025:
026: //{{{ Imports
027: import javax.swing.text.Segment;
028: import java.util.*;
029: import java.util.regex.Matcher;
030: import java.util.regex.Pattern;
031: import org.gjt.sp.jedit.TextUtilities;
032: import org.gjt.sp.util.SegmentCharSequence;
033: import org.gjt.sp.util.StandardUtilities;
034:
035: //}}}
036:
037: /**
038: * A token marker splits lines of text into tokens. Each token carries
039: * a length field and an identification tag that can be mapped to a color
040: * or font style for painting that token.
041: *
042: * @author Slava Pestov, mike dillon
043: * @version $Id: TokenMarker.java 11079 2007-11-16 03:20:00Z vanza $
044: *
045: * @see org.gjt.sp.jedit.syntax.Token
046: * @see org.gjt.sp.jedit.syntax.TokenHandler
047: */
048: public class TokenMarker {
049: //{{{ TokenMarker constructor
050: public TokenMarker() {
051: } //}}}
052:
053: //{{{ addRuleSet() method
054: public void addRuleSet(ParserRuleSet rules) {
055: ruleSets.put(rules.getSetName(), rules);
056:
057: if (rules.getSetName().equals("MAIN"))
058: mainRuleSet = rules;
059: } //}}}
060:
061: //{{{ getMainRuleSet() method
062: public ParserRuleSet getMainRuleSet() {
063: return mainRuleSet;
064: } //}}}
065:
066: //{{{ getRuleSet() method
067: public ParserRuleSet getRuleSet(String setName) {
068: return ruleSets.get(setName);
069: } //}}}
070:
071: //{{{ getRuleSets() method
072: /**
073: * @since jEdit 4.2pre3
074: */
075: public ParserRuleSet[] getRuleSets() {
076: return ruleSets.values().toArray(
077: new ParserRuleSet[ruleSets.size()]);
078: } //}}}
079:
080: //{{{ markTokens() method
081: /**
082: * Do not call this method directly; call Buffer.markTokens() instead.
083: *
084: * @param prevContext the context of the previous line, it can be null
085: * @param tokenHandler the token handler
086: * @param line a segment containing the content of the line
087: */
088: public LineContext markTokens(LineContext prevContext,
089: TokenHandler tokenHandler, Segment line) {
090: //{{{ Set up some instance variables
091: // this is to avoid having to pass around lots and lots of
092: // parameters.
093: this .tokenHandler = tokenHandler;
094: this .line = line;
095:
096: lastOffset = line.offset;
097: lineLength = line.count + line.offset;
098:
099: context = new LineContext();
100:
101: if (prevContext == null) {
102: context.rules = getMainRuleSet();
103: context.escapeRule = context.rules.getEscapeRule();
104: } else {
105: context.parent = prevContext.parent;
106: context.setInRule(prevContext.inRule);
107: context.rules = prevContext.rules;
108: context.spanEndSubst = prevContext.spanEndSubst;
109: }
110:
111: keywords = context.rules.getKeywords();
112:
113: seenWhitespaceEnd = false;
114: whitespaceEnd = line.offset;
115: //}}}
116:
117: //{{{ Main parser loop
118: int terminateChar = context.rules.getTerminateChar();
119: boolean terminated = false;
120: main_loop: for (pos = line.offset; pos < lineLength; pos++) {
121: //{{{ check if we have to stop parsing (happens if the terminateChar has been exceeded)
122: if (terminateChar >= 0
123: && pos - line.offset >= terminateChar
124: && !terminated) {
125: terminated = true;
126: context = new LineContext(
127: ParserRuleSet.getStandardRuleSet(context.rules
128: .getDefault()), context);
129: keywords = context.rules.getKeywords();
130: } //}}}
131:
132: //{{{ Check for the escape rule before anything else.
133: if (context.escapeRule != null
134: && handleRule(context.escapeRule, false)) {
135: continue main_loop;
136: } //}}}
137:
138: //{{{ check for end of delegate
139: if (context.parent != null && context.parent.inRule != null
140: && checkDelegateEnd(context.parent.inRule)) {
141: seenWhitespaceEnd = true;
142: continue main_loop;
143: } //}}}
144:
145: //{{{ check every rule
146: Character ch = Character.valueOf(line.array[pos]);
147: List<ParserRule> rules = context.rules.getRules(ch);
148: for (ParserRule rule : rules) {
149: // stop checking rules if there was a match
150: if (handleRule(rule, false)) {
151: seenWhitespaceEnd = true;
152: continue main_loop;
153: }
154: } //}}}
155:
156: //{{{ check if current character is a word separator
157: if (Character.isWhitespace(ch)) {
158: if (!seenWhitespaceEnd)
159: whitespaceEnd = pos + 1;
160:
161: if (context.inRule != null)
162: handleRule(context.inRule, true);
163:
164: handleNoWordBreak();
165:
166: markKeyword(false);
167:
168: if (lastOffset != pos) {
169: tokenHandler.handleToken(line, context.rules
170: .getDefault(), lastOffset - line.offset,
171: pos - lastOffset, context);
172: }
173:
174: tokenHandler.handleToken(line, context.rules
175: .getDefault(), pos - line.offset, 1, context);
176: lastOffset = pos + 1;
177: } else {
178: if (keywords != null
179: || context.rules.getRuleCount() != 0) {
180: String noWordSep = context.rules.getNoWordSep();
181:
182: if (!Character.isLetterOrDigit(ch)
183: && noWordSep.indexOf(ch) == -1) {
184: if (context.inRule != null)
185: handleRule(context.inRule, true);
186:
187: handleNoWordBreak();
188:
189: markKeyword(true);
190:
191: tokenHandler.handleToken(line, context.rules
192: .getDefault(),
193: lastOffset - line.offset, 1, context);
194: lastOffset = pos + 1;
195: }
196: }
197:
198: seenWhitespaceEnd = true;
199: } //}}}
200: } //}}}
201:
202: //{{{ Mark all remaining characters
203: pos = lineLength;
204:
205: if (context.inRule != null)
206: handleRule(context.inRule, true);
207:
208: handleNoWordBreak();
209: markKeyword(true);
210: //}}}
211:
212: //{{{ Unwind any NO_LINE_BREAK parent delegates
213: unwind: while (context.parent != null) {
214: ParserRule rule = context.parent.inRule;
215: if ((rule != null && (rule.action & ParserRule.NO_LINE_BREAK) == ParserRule.NO_LINE_BREAK)
216: || terminated) {
217: context = context.parent;
218: keywords = context.rules.getKeywords();
219: context.setInRule(null);
220: } else
221: break unwind;
222: } //}}}
223:
224: tokenHandler.handleToken(line, Token.END, pos - line.offset, 0,
225: context);
226:
227: context = context.intern();
228: tokenHandler.setLineContext(context);
229:
230: /* for GC. */
231: this .line = null;
232:
233: return context;
234: } //}}}
235:
236: //{{{ Private members
237:
238: //{{{ Instance variables
239: private final Map<String, ParserRuleSet> ruleSets = new Hashtable<String, ParserRuleSet>(
240: 64);
241: private ParserRuleSet mainRuleSet;
242:
243: // Instead of passing these around to each method, we just store them
244: // as instance variables. Note that this is not thread-safe.
245: private TokenHandler tokenHandler;
246: /** The line from which we will mark the tokens. */
247: private Segment line;
248: /** The context of the current line. */
249: private LineContext context;
250: private KeywordMap keywords;
251: private final Segment pattern = new Segment();
252: private int lastOffset;
253: private int lineLength;
254: private int pos;
255:
256: private int whitespaceEnd;
257: private boolean seenWhitespaceEnd;
258:
259: //}}}
260:
261: //{{{ checkDelegateEnd() method
262: private boolean checkDelegateEnd(ParserRule rule) {
263: if (rule.end == null)
264: return false;
265:
266: LineContext tempContext = context;
267: context = context.parent;
268: keywords = context.rules.getKeywords();
269: boolean handled = handleRule(rule, true);
270: context = tempContext;
271: keywords = context.rules.getKeywords();
272:
273: if (handled) {
274: if (context.inRule != null)
275: handleRule(context.inRule, true);
276:
277: markKeyword(true);
278:
279: context = (LineContext) context.parent.clone();
280:
281: tokenHandler.handleToken(line, matchToken(context.inRule,
282: context.inRule, context), pos - line.offset,
283: pattern.count, context);
284:
285: keywords = context.rules.getKeywords();
286: context.setInRule(null);
287: lastOffset = pos + pattern.count;
288:
289: // move pos to last character of match sequence
290: pos += pattern.count - 1;
291:
292: return true;
293: }
294:
295: return false;
296: } //}}}
297:
298: //{{{ handleRule() method
299: /**
300: * Checks if the rule matches the line at the current position
301: * and handles the rule if it does match
302: */
303: private boolean handleRule(ParserRule checkRule, boolean end) {
304: //{{{ Some rules can only match in certain locations
305: if (!end) {
306: if (null == checkRule.upHashChars) {
307: if (checkRule.upHashChar != null
308: && (pos + checkRule.upHashChar.length() < line.array.length)
309: && !checkHashString(checkRule)) {
310: return false;
311: }
312: } else {
313: if (-1 == Arrays.binarySearch(checkRule.upHashChars,
314: Character.toUpperCase(line.array[pos]))) {
315: return false;
316: }
317: }
318: }
319:
320: int offset = (checkRule.action & ParserRule.MARK_PREVIOUS) != 0 ? lastOffset
321: : pos;
322: int posMatch = end ? checkRule.endPosMatch
323: : checkRule.startPosMatch;
324:
325: if ((posMatch & ParserRule.AT_LINE_START) == ParserRule.AT_LINE_START) {
326: if (offset != line.offset) {
327: return false;
328: }
329: } else if ((posMatch & ParserRule.AT_WHITESPACE_END) == ParserRule.AT_WHITESPACE_END) {
330: if (offset != whitespaceEnd) {
331: return false;
332: }
333: } else if ((posMatch & ParserRule.AT_WORD_START) == ParserRule.AT_WORD_START) {
334: if (offset != lastOffset) {
335: return false;
336: }
337: } //}}}
338:
339: int matchedChars = 1;
340: CharSequence charSeq = null;
341: Matcher match = null;
342:
343: //{{{ See if the rule's start or end sequence matches here
344: if (!end || (checkRule.action & ParserRule.MARK_FOLLOWING) == 0) {
345: // the end cannot be a regular expression
346: if ((checkRule.action & ParserRule.REGEXP) == 0 || end) {
347: if (end) {
348: if (context.spanEndSubst != null)
349: pattern.array = context.spanEndSubst;
350: else
351: pattern.array = checkRule.end;
352: } else
353: pattern.array = checkRule.start;
354: pattern.offset = 0;
355: pattern.count = pattern.array.length;
356: matchedChars = pattern.count;
357:
358: if (!SyntaxUtilities.regionMatches(context.rules
359: .getIgnoreCase(), line, pos, pattern.array)) {
360: return false;
361: }
362: } else {
363: // note that all regexps start with \A so they only
364: // match the start of the string
365: //int matchStart = pos - line.offset;
366: charSeq = new SegmentCharSequence(line, pos
367: - line.offset, line.count - (pos - line.offset));
368: match = checkRule.startRegexp.matcher(charSeq);
369: if (!match.lookingAt()) {
370: return false;
371: } else if (match.start() != 0) {
372: throw new InternalError("Can't happen");
373: } else {
374: matchedChars = match.end();
375: /* workaround for hang if match was
376: * zero-width. not sure if there is
377: * a better way to handle this */
378: if (matchedChars == 0)
379: matchedChars = 1;
380: }
381: }
382: } //}}}
383: //{{{ Check for an escape sequence
384: if ((checkRule.action & ParserRule.IS_ESCAPE) == ParserRule.IS_ESCAPE) {
385: pos += pattern.count;
386: } //}}}
387: //{{{ Handle start of rule
388: else if (!end) {
389: if (context.inRule != null)
390: handleRule(context.inRule, true);
391:
392: markKeyword((checkRule.action & ParserRule.MARK_PREVIOUS) != ParserRule.MARK_PREVIOUS);
393:
394: switch (checkRule.action & ParserRule.MAJOR_ACTIONS) {
395: //{{{ SEQ
396: case ParserRule.SEQ:
397: context.spanEndSubst = null;
398:
399: if ((checkRule.action & ParserRule.REGEXP) != 0) {
400: handleTokenWithSpaces(tokenHandler,
401: checkRule.token, pos - line.offset,
402: matchedChars, context);
403: } else {
404: tokenHandler.handleToken(line, checkRule.token, pos
405: - line.offset, matchedChars, context);
406: }
407:
408: // a DELEGATE attribute on a SEQ changes the
409: // ruleset from the end of the SEQ onwards
410: if (checkRule.delegate != null) {
411: context = new LineContext(checkRule.delegate,
412: context.parent);
413: keywords = context.rules.getKeywords();
414: }
415: break;
416: //}}}
417: //{{{ SPAN, EOL_SPAN
418: case ParserRule.SPAN:
419: case ParserRule.EOL_SPAN:
420: context.setInRule(checkRule);
421:
422: byte tokenType = matchToken(checkRule, context.inRule,
423: context);
424:
425: if ((checkRule.action & ParserRule.REGEXP) != 0) {
426: handleTokenWithSpaces(tokenHandler, tokenType, pos
427: - line.offset, matchedChars, context);
428: } else {
429: tokenHandler.handleToken(line, tokenType, pos
430: - line.offset, matchedChars, context);
431: }
432:
433: char[] spanEndSubst = null;
434: /* substitute result of matching the rule start
435: * into the end string.
436: *
437: * eg, in shell script mode, <<\s*(\w+) is
438: * matched into \<$1\> to construct rules for
439: * highlighting read-ins like this <<EOF
440: * ...
441: * EOF
442: */
443: if (charSeq != null && checkRule.end != null) {
444: spanEndSubst = substitute(match, checkRule.end);
445: }
446:
447: context.spanEndSubst = spanEndSubst;
448: context = new LineContext(checkRule.delegate, context);
449: keywords = context.rules.getKeywords();
450:
451: break;
452: //}}}
453: //{{{ MARK_FOLLOWING
454: case ParserRule.MARK_FOLLOWING:
455: tokenHandler.handleToken(line, matchToken(checkRule,
456: checkRule, context), pos - line.offset,
457: pattern.count, context);
458:
459: context.spanEndSubst = null;
460: context.setInRule(checkRule);
461: break;
462: //}}}
463: //{{{ MARK_PREVIOUS
464: case ParserRule.MARK_PREVIOUS:
465: context.spanEndSubst = null;
466:
467: if (pos != lastOffset) {
468: tokenHandler.handleToken(line, checkRule.token,
469: lastOffset - line.offset, pos - lastOffset,
470: context);
471: }
472:
473: tokenHandler.handleToken(line, matchToken(checkRule,
474: checkRule, context), pos - line.offset,
475: pattern.count, context);
476:
477: break;
478: //}}}
479: default:
480: throw new InternalError("Unhandled major action");
481: }
482:
483: // move pos to last character of match sequence
484: pos += matchedChars - 1;
485: lastOffset = pos + 1;
486:
487: // break out of inner for loop to check next char
488: } //}}}
489: //{{{ Handle end of MARK_FOLLOWING
490: else if ((context.inRule.action & ParserRule.MARK_FOLLOWING) != 0) {
491: if (pos != lastOffset) {
492: tokenHandler.handleToken(line, context.inRule.token,
493: lastOffset - line.offset, pos - lastOffset,
494: context);
495: }
496:
497: lastOffset = pos;
498: context.setInRule(null);
499: } //}}}
500:
501: return true;
502: } //}}}
503:
504: //{{{ handleNoWordBreak() method
505: private void handleNoWordBreak() {
506: if (context.parent != null) {
507: ParserRule rule = context.parent.inRule;
508: if (rule != null
509: && (context.parent.inRule.action & ParserRule.NO_WORD_BREAK) != 0) {
510: if (pos != lastOffset) {
511: tokenHandler.handleToken(line, rule.token,
512: lastOffset - line.offset, pos - lastOffset,
513: context);
514: }
515:
516: lastOffset = pos;
517: context = context.parent;
518: keywords = context.rules.getKeywords();
519: context.setInRule(null);
520: }
521: }
522: } //}}}
523:
524: //{{{ handleTokenWithSpaces() method
525: private void handleTokenWithSpaces(TokenHandler tokenHandler,
526: byte tokenType, int start, int len, LineContext context) {
527: int last = start;
528: int end = start + len;
529:
530: for (int i = start; i < end; i++) {
531: if (Character.isWhitespace(line.array[i + line.offset])) {
532: if (last != i) {
533: tokenHandler.handleToken(line, tokenType, last, i
534: - last, context);
535: }
536: tokenHandler
537: .handleToken(line, tokenType, i, 1, context);
538: last = i + 1;
539: }
540: }
541:
542: if (last != end) {
543: tokenHandler.handleToken(line, tokenType, last, end - last,
544: context);
545: }
546: } //}}}
547:
548: //{{{ markKeyword() method
549: private void markKeyword(boolean addRemaining) {
550: int len = pos - lastOffset;
551: if (len == 0)
552: return;
553:
554: //{{{ Do digits
555: if (context.rules.getHighlightDigits()) {
556: boolean digit = false;
557: boolean mixed = false;
558:
559: for (int i = lastOffset; i < pos; i++) {
560: char ch = line.array[i];
561: if (Character.isDigit(ch))
562: digit = true;
563: else
564: mixed = true;
565: }
566:
567: if (mixed) {
568: Pattern digitRE = context.rules.getDigitRegexp();
569:
570: // only match against regexp if its not all
571: // digits; if all digits, no point matching
572: if (digit) {
573: if (digitRE == null) {
574: // mixed digit/alpha keyword,
575: // and no regexp... don't
576: // highlight as DIGIT
577: digit = false;
578: } else {
579: int oldCount = line.count;
580: int oldOffset = line.offset;
581: line.offset = lastOffset;
582: line.count = len;
583: CharSequence seq = new SegmentCharSequence(line);
584: digit = digitRE.matcher(seq).matches();
585: line.offset = oldOffset;
586: line.count = oldCount;
587: }
588: }
589: }
590:
591: if (digit) {
592: tokenHandler.handleToken(line, Token.DIGIT, lastOffset
593: - line.offset, len, context);
594: lastOffset = pos;
595:
596: return;
597: }
598: } //}}}
599:
600: //{{{ Do keywords
601: if (keywords != null) {
602: byte id = keywords.lookup(line, lastOffset, len);
603:
604: if (id != Token.NULL) {
605: tokenHandler.handleToken(line, id, lastOffset
606: - line.offset, len, context);
607: lastOffset = pos;
608: return;
609: }
610: } //}}}
611:
612: //{{{ Handle any remaining crud
613: if (addRemaining) {
614: tokenHandler.handleToken(line, context.rules.getDefault(),
615: lastOffset - line.offset, len, context);
616: lastOffset = pos;
617: } //}}}
618: } //}}}
619:
620: //{{{ substitute() method
621: private static char[] substitute(Matcher match, char[] end) {
622: StringBuilder buf = new StringBuilder();
623: for (int i = 0; i < end.length; i++) {
624: char ch = end[i];
625: if (ch == '$' || ch == '~') {
626: if (i == end.length - 1)
627: buf.append(ch);
628: else {
629: char digit = end[i + 1];
630: if (!Character.isDigit(digit))
631: buf.append(ch);
632: else if (ch == '$') {
633: buf.append(match.group(digit - '0'));
634: i++;
635: } else {
636: String s = match.group(digit - '0');
637: if (s.length() == 1) {
638: char b = TextUtilities
639: .getComplementaryBracket(s
640: .charAt(0), null);
641: if (b == '\0')
642: b = s.charAt(0);
643: buf.append(b);
644: } else
645: buf.append(ch);
646: i++;
647: }
648: }
649: } else
650: buf.append(ch);
651: }
652:
653: char[] returnValue = new char[buf.length()];
654: buf.getChars(0, buf.length(), returnValue, 0);
655: return returnValue;
656: } //}}}
657:
658: //{{{ matchToken() method
659: private byte matchToken(ParserRule rule, ParserRule base,
660: LineContext ctx) {
661: switch (rule.matchType) {
662: case ParserRule.MATCH_TYPE_RULE:
663: return base.token;
664:
665: case ParserRule.MATCH_TYPE_CONTEXT:
666: return context.rules.getDefault();
667:
668: default:
669: return rule.matchType;
670: }
671: } //}}}
672:
673: //{{{ checkHashString() method
674: private boolean checkHashString(ParserRule rule) {
675: for (int i = 0; i < rule.upHashChar.length(); i++) {
676: if (Character.toUpperCase(line.array[pos + i]) != rule.upHashChar
677: .charAt(i)) {
678: return false;
679: }
680: }
681: return true;
682: } //}}}
683:
684: //}}}
685:
686: //{{{ LineContext class
687: /**
688: * Stores persistent per-line syntax parser state.
689: */
690: public static class LineContext {
691: private static final Map<LineContext, LineContext> intern = new HashMap<LineContext, LineContext>();
692:
693: public LineContext parent;
694: public ParserRule inRule;
695: public ParserRuleSet rules;
696: // used for SPAN_REGEXP rules; otherwise null
697: public char[] spanEndSubst;
698: public ParserRule escapeRule;
699:
700: //{{{ LineContext constructor
701: public LineContext(ParserRuleSet rs, LineContext lc) {
702: rules = rs;
703: parent = (lc == null ? null : (LineContext) lc.clone());
704: /*
705: * SPANs with no delegate need to propagate the
706: * escape rule to the child context, so this is
707: * needed.
708: */
709: if (rs.getModeName() != null)
710: escapeRule = rules.getEscapeRule();
711: else
712: escapeRule = lc.escapeRule;
713: } //}}}
714:
715: //{{{ LineContext constructor
716: public LineContext() {
717: } //}}}
718:
719: //{{{ intern() method
720: public LineContext intern() {
721: LineContext obj = intern.get(this );
722: if (obj == null) {
723: intern.put(this , this );
724: return this ;
725: } else
726: return obj;
727: } //}}}
728:
729: //{{{ hashCode() method
730: public int hashCode() {
731: if (inRule != null)
732: return inRule.hashCode();
733: else if (rules != null)
734: return rules.hashCode();
735: else
736: return 0;
737: } //}}}
738:
739: //{{{ equals() method
740: public boolean equals(Object obj) {
741: if (obj instanceof LineContext) {
742: LineContext lc = (LineContext) obj;
743: return lc.inRule == inRule
744: && lc.rules == rules
745: && StandardUtilities.objectsEqual(parent,
746: lc.parent)
747: && charArraysEqual(spanEndSubst,
748: lc.spanEndSubst);
749: } else
750: return false;
751: } //}}}
752:
753: //{{{ clone() method
754: public Object clone() {
755: LineContext lc = new LineContext();
756: lc.inRule = inRule;
757: lc.rules = rules;
758: lc.parent = (parent == null) ? null : (LineContext) parent
759: .clone();
760: lc.spanEndSubst = spanEndSubst;
761: lc.escapeRule = escapeRule;
762:
763: return lc;
764: } //}}}
765:
766: //{{{ charArraysEqual() method
767: private static boolean charArraysEqual(char[] c1, char[] c2) {
768: if (c1 == null)
769: return c2 == null;
770:
771: // c1 is not null
772: if (c2 == null)
773: return false;
774:
775: if (c1.length != c2.length)
776: return false;
777:
778: for (int i = 0; i < c1.length; i++) {
779: if (c1[i] != c2[i])
780: return false;
781: }
782:
783: return true;
784: } //}}}
785:
786: //{{{ setInRule() method
787: /**
788: * Sets the current rule being processed and adjusts the
789: * escape rule for the context based on the rule.
790: */
791: public void setInRule(ParserRule rule) {
792: inRule = rule;
793: if (rule != null && rule.escapeRule != null)
794: escapeRule = rule.escapeRule;
795: else if (rules != null && rules.getName() != null)
796: escapeRule = rules.getEscapeRule();
797: else if (parent != null)
798: escapeRule = parent.escapeRule;
799: else
800: escapeRule = null;
801: } //}}}
802:
803: } //}}}
804: }
|