001: package net.sf.saxon.trans;
002:
003: import net.sf.saxon.Configuration;
004: import net.sf.saxon.functions.NormalizeSpace;
005: import net.sf.saxon.expr.XPathContext;
006: import net.sf.saxon.expr.XPathContextMajor;
007: import net.sf.saxon.om.Navigator;
008: import net.sf.saxon.om.NodeInfo;
009: import net.sf.saxon.pattern.NoNodeTest;
010: import net.sf.saxon.pattern.Pattern;
011: import net.sf.saxon.type.Type;
012:
013: import java.io.Serializable;
014:
015: /**
016: * A Mode is a collection of rules; the selection of a rule to apply to a given element
017: * is determined by a Pattern.
018: *
019: * @author Michael H. Kay
020: */
021:
022: public class Mode implements Serializable {
023:
024: public static final int DEFAULT_MODE = -1;
025: public static final int ALL_MODES = -2;
026: public static final int NAMED_MODE = -3;
027: public static final int STRIPPER_MODE = -4;
028:
029: private Rule[] ruleDict = new Rule[101 + Type.MAX_NODE_TYPE];
030: private int sequence = 0; // sequence number for the rules in this Mode
031: private boolean isDefault;
032: private boolean isStripper;
033:
034: /**
035: * Default constructor - creates a Mode containing no rules
036: * @param usage one of {@link #DEFAULT_MODE}, {@link #NAMED_MODE}, {@link #STRIPPER_MODE}
037: */
038:
039: public Mode(int usage) {
040: this .isDefault = (usage == DEFAULT_MODE);
041: this .isStripper = (usage == STRIPPER_MODE);
042: }
043:
044: /**
045: * Construct a new Mode, copying the contents of an existing Mode
046: *
047: * @param omniMode the existing mode. May be null, in which case it is not copied
048: */
049:
050: public Mode(Mode omniMode) {
051: isDefault = false;
052: isStripper = false;
053: if (omniMode != null) {
054: for (int i = 0; i < this .ruleDict.length; i++) {
055: if (omniMode.ruleDict[i] != null) {
056: this .ruleDict[i] = new Rule(omniMode.ruleDict[i]);
057: }
058: }
059: this .sequence = omniMode.sequence;
060: }
061: }
062:
063: /**
064: * Determine if this is the default mode
065: */
066:
067: public boolean isDefaultMode() {
068: return isDefault;
069: }
070:
071: /**
072: * Add a rule to the Mode. <br>
073: * The rule effectively replaces any other rule for the same pattern/mode at the same or a lower
074: * priority.
075: *
076: * @param p a Pattern
077: * @param obj the Object to return from getRule() when the supplied node matches this Pattern
078: * @param precedence the import precedence of the rule
079: * @param priority the explicit or implicit priority of the rule
080: */
081:
082: public void addRule(Pattern p, Object obj, int precedence,
083: double priority) {
084:
085: // System.err.println("Add rule, pattern = " + p.toString() + " class " + p.getClass() + ", priority=" + priority);
086:
087: // Ignore a pattern that will never match, e.g. "@comment"
088:
089: if (p.getNodeTest() instanceof NoNodeTest) {
090: return;
091: }
092:
093: // for fast lookup, we maintain one list for each element name for patterns that can only
094: // match elements of a given name, one list for each node type for patterns that can only
095: // match one kind of non-element node, and one generic list.
096: // Each list is sorted in precedence/priority order so we find the highest-priority rule first
097:
098: int fingerprint = p.getFingerprint();
099: int type = p.getNodeKind();
100:
101: int key = getList(fingerprint, type);
102: // System.err.println("Fingerprint " + fingerprint + " key " + key + " type " + type);
103:
104: Rule newRule = new Rule(p, obj, precedence, priority,
105: sequence++);
106:
107: Rule rule = ruleDict[key];
108: if (rule == null) {
109: ruleDict[key] = newRule;
110: return;
111: }
112:
113: // insert the new rule into this list before others of the same precedence/priority
114:
115: Rule prev = null;
116: while (rule != null) {
117: if ((rule.precedence < precedence)
118: || (rule.precedence == precedence && rule.priority <= priority)) {
119: newRule.next = rule;
120: if (prev == null) {
121: ruleDict[key] = newRule;
122: } else {
123: prev.next = newRule;
124: }
125: break;
126: } else {
127: prev = rule;
128: rule = rule.next;
129: }
130: }
131: if (rule == null) {
132: prev.next = newRule;
133: newRule.next = null;
134: }
135: }
136:
137: /**
138: * Determine which list to use for a given pattern (we must also search the generic list)
139: */
140:
141: public int getList(int fingerprint, int type) {
142:
143: if (type == Type.ELEMENT) {
144: if (fingerprint == -1) {
145: return Type.NODE; // the generic list
146: } else {
147: return Type.MAX_NODE_TYPE + (fingerprint % 101);
148: }
149: } else {
150: return type;
151: }
152: }
153:
154: /**
155: * Get the rule corresponding to a given Node, by finding the best Pattern match.
156: *
157: * @param node the NodeInfo referring to the node to be matched
158: * @return the object (e.g. a NodeHandler) registered for that element, if any (otherwise null).
159: */
160:
161: public Object getRule(NodeInfo node, XPathContext context)
162: throws XPathException {
163: int fingerprint = node.getFingerprint();
164: // This is inefficient with wrapped object models (DOM, XOM, JDOM),
165: // but there's not much we can do about it
166: int type = node.getNodeKind();
167: int key = getList(fingerprint, type);
168: int policy = context.getController().getRecoveryPolicy();
169:
170: // If there are match patterns in the stylesheet that use local variables, we need to allocate
171: // a new stack frame for evaluating the match patterns. We base this on the match pattern with
172: // the highest number of range variables, so we can reuse the same stack frame for all rules
173: // that we test against. If no patterns use range variables, we don't bother allocating a new
174: // stack frame.
175:
176: context = perhapsMakeNewContext(context);
177:
178: Rule specificRule = null;
179: Rule generalRule = null;
180: int specificPrecedence = -1;
181: double specificPriority = Double.NEGATIVE_INFINITY;
182:
183: // search the specific list for this node type / node name
184:
185: if (key != Type.NODE) {
186: Rule r = ruleDict[key];
187: while (r != null) {
188: // if we already have a match, and the precedence or priority of this
189: // rule is lower, quit the search for a second match
190: if (specificRule != null) {
191: if (r.precedence < specificPrecedence
192: || (r.precedence == specificPrecedence && r.priority < specificPriority)) {
193: break;
194: }
195: }
196: if (r.pattern.matches(node, context)) {
197:
198: // is this a second match?
199: if (specificRule != null) {
200: if (r.precedence == specificPrecedence
201: && r.priority == specificPriority) {
202: reportAmbiguity(node, specificRule, r,
203: context);
204: }
205: break;
206: }
207: specificRule = r;
208: specificPrecedence = r.precedence;
209: specificPriority = r.priority;
210: if (policy == Configuration.RECOVER_SILENTLY) {
211: break; // find the first; they are in priority order
212: }
213: }
214: r = r.next;
215: }
216: }
217:
218: // search the general list
219:
220: Rule r2 = ruleDict[Type.NODE];
221: while (r2 != null) {
222: if (r2.precedence < specificPrecedence
223: || (r2.precedence == specificPrecedence && r2.priority < specificPriority)) {
224: break; // no point in looking at a lower priority rule than the one we've got
225: }
226: if (r2.pattern.matches(node, context)) {
227: // is it a second match?
228: if (generalRule != null) {
229: if (r2.precedence == generalRule.precedence
230: && r2.priority == generalRule.priority) {
231: reportAmbiguity(node, r2, generalRule, context);
232: }
233: break;
234: } else {
235: generalRule = r2;
236: if (policy == Configuration.RECOVER_SILENTLY) {
237: break; // find only the first; they are in priority order
238: }
239: }
240: }
241: r2 = r2.next;
242: }
243:
244: if (specificRule != null && generalRule == null) {
245: return specificRule.object;
246: }
247: if (specificRule == null && generalRule != null) {
248: return generalRule.object;
249: }
250: if (specificRule != null && generalRule != null) {
251: if (specificRule.precedence == generalRule.precedence
252: && specificRule.priority == generalRule.priority) {
253: // This situation is exceptional: we have a "specific" pattern and
254: // a "general" pattern with the same priority. We have to select
255: // the one that was added last
256: // (Bug reported by Norman Walsh Jan 2002)
257: Object result = (specificRule.sequence > generalRule.sequence ? specificRule.object
258: : generalRule.object);
259:
260: if (policy != Configuration.RECOVER_SILENTLY) {
261: reportAmbiguity(node, specificRule, generalRule,
262: context);
263: }
264: return result;
265: }
266: if (specificRule.precedence > generalRule.precedence
267: || (specificRule.precedence == generalRule.precedence && specificRule.priority >= generalRule.priority)) {
268: return specificRule.object;
269: } else {
270: return generalRule.object;
271: }
272: }
273: return null;
274: }
275:
276: /**
277: * Make a new XPath context for evaluating patterns if there is any possibility that the
278: * pattern uses local variables
279: *
280: * @param context The existing XPath context
281: * @return a new XPath context (or the existing context if no new context was created)
282: */
283:
284: private XPathContext perhapsMakeNewContext(XPathContext context) {
285: int patternLocals = context.getController().getExecutable()
286: .getLargestPatternStackFrame();
287: if (patternLocals > 0) {
288: context = context.newContext();
289: context.setOrigin(context.getController());
290: ((XPathContextMajor) context).openStackFrame(patternLocals);
291: }
292: return context;
293: }
294:
295: /**
296: * Get the rule corresponding to a given Node, by finding the best Pattern match, subject to a minimum
297: * and maximum precedence. (This supports xsl:apply-imports)
298: *
299: * @param node the NodeInfo referring to the node to be matched
300: * @return the object (e.g. a NodeHandler) registered for that element, if any (otherwise null).
301: */
302:
303: public Object getRule(NodeInfo node, int min, int max,
304: XPathContext context) throws XPathException {
305: int fing = node.getFingerprint();
306: int type = node.getNodeKind();
307: int key = getList(fing, type);
308:
309: Rule specificRule = null;
310: Rule generalRule = null;
311:
312: context = perhapsMakeNewContext(context);
313:
314: // search the the specific list for this node type / name
315:
316: if (key != Type.NODE) {
317: Rule r = ruleDict[key];
318: while (r != null) {
319: if (r.precedence >= min && r.precedence <= max
320: && r.pattern.matches(node, context)) {
321: specificRule = r;
322: break; // find the first; they are in priority order
323: }
324: r = r.next;
325: }
326: }
327:
328: // search the generic list
329:
330: Rule r2 = ruleDict[Type.NODE];
331: while (r2 != null) {
332: if (r2.precedence >= min && r2.precedence <= max
333: && r2.pattern.matches(node, context)) {
334: generalRule = r2;
335: break; // find only the first; they are in priority order
336: }
337: r2 = r2.next;
338: }
339: if (specificRule != null && generalRule == null) {
340: return specificRule.object;
341: }
342: if (specificRule == null && generalRule != null) {
343: return generalRule.object;
344: }
345: if (specificRule != null && generalRule != null) {
346: if (specificRule.precedence > generalRule.precedence
347: || (specificRule.precedence == generalRule.precedence && specificRule.priority >= generalRule.priority)) {
348: return specificRule.object;
349: } else {
350: return generalRule.object;
351: }
352: }
353: return null;
354: }
355:
356: /**
357: * Get the rule corresponding to a given Node, by finding the next-best Pattern match
358: * after the specified object.
359: *
360: * @param node the NodeInfo referring to the node to be matched
361: * @return the object (e.g. a NodeHandler) registered for that element, if any (otherwise null).
362: */
363:
364: public Object getNextMatchRule(NodeInfo node,
365: Object currentHandler, XPathContext context)
366: throws XPathException {
367: int fingerprint = node.getFingerprint();
368: int type = node.getNodeKind();
369: int key = getList(fingerprint, type);
370: int policy = context.getController().getRecoveryPolicy();
371:
372: int currentPrecedence = -1;
373: double currentPriority = -1.0;
374: int currentSequence = -1;
375:
376: context = perhapsMakeNewContext(context);
377:
378: // First find the Rule object corresponding to the current handler
379:
380: Rule r = ruleDict[key];
381: while (r != null) {
382: if (r.object == currentHandler) {
383: currentPrecedence = r.precedence;
384: currentPriority = r.priority;
385: currentSequence = r.sequence;
386: break;
387: } else {
388: r = r.next;
389: }
390: }
391: if (r == null) {
392: r = ruleDict[Type.NODE];
393: while (r != null) {
394: if (r.object == currentHandler) {
395: currentPrecedence = r.precedence;
396: currentPriority = r.priority;
397: currentSequence = r.sequence;
398: break;
399: } else {
400: r = r.next;
401: }
402: }
403: if (r == null) {
404: DynamicError de = new DynamicError(
405: "Internal error: current template doesn't match current node");
406: de.setXPathContext(context);
407: de.setErrorCode("SAXON:0000");
408: throw de;
409: }
410: }
411:
412: Rule specificRule = null;
413: Rule generalRule = null;
414: int specificPrecedence = -1;
415: double specificPriority = Double.NEGATIVE_INFINITY;
416:
417: // search the specific list for this node type / node name
418:
419: // System.err.println("Hash key = " + key);
420:
421: if (key != Type.NODE) {
422: r = ruleDict[key];
423: while (r != null) {
424: // skip this rule if its template is the current template. (There can be more than
425: // one rule for the same template in the case of a union pattern.)
426: if (r.object == currentHandler) {
427: // skip this rule
428: } else
429: // skip this rule unless it's "below" the current rule in search order
430: if ((r.precedence > currentPrecedence)
431: || (r.precedence == currentPrecedence && (r.priority > currentPriority || (r.priority == currentPriority && r.sequence >= currentSequence)))) {
432: // skip this rule
433: } else {
434: // quit the search on finding the second (recoverable error) match
435: if (specificRule != null) {
436: if (r.precedence < specificPrecedence
437: || (r.precedence == specificPrecedence && r.priority < specificPriority)) {
438: break;
439: }
440: }
441: //System.err.println("Testing " + Navigator.getPath(node) + " against " + r.pattern);
442: if (r.pattern.matches(node, context)) {
443: //System.err.println("Matches");
444:
445: // is this a second match?
446: if (specificRule != null) {
447: if (r.precedence == specificPrecedence
448: && r.priority == specificPriority) {
449: reportAmbiguity(node, specificRule, r,
450: context);
451: }
452: break;
453: }
454: specificRule = r;
455: specificPrecedence = r.precedence;
456: specificPriority = r.priority;
457: if (policy == Configuration.RECOVER_SILENTLY) {
458: break; // find the first; they are in priority order
459: }
460: }
461: }
462: r = r.next;
463: }
464: }
465:
466: // search the general list
467:
468: Rule r2 = ruleDict[Type.NODE];
469: while (r2 != null) {
470: // skip this rule if the template is the current template
471: if (r2.object == currentHandler) {
472: // skip this rule
473: } else
474: // skip this rule unless it's "after" the current rule in search order
475: if ((r2.precedence > currentPrecedence)
476: || (r2.precedence == currentPrecedence && (r2.priority > currentPriority || (r2.priority == currentPriority && r2.sequence >= currentSequence)))) {
477: // skip this rule
478: } else {
479: if (r2.precedence < specificPrecedence
480: || (r2.precedence == specificPrecedence && r2.priority < specificPriority)) {
481: break; // no point in looking at a lower priority rule than the one we've got
482: }
483: if (r2.pattern.matches(node, context)) {
484: // is it a second match?
485: if (generalRule != null) {
486: if (r2.precedence == generalRule.precedence
487: && r2.priority == generalRule.priority) {
488: reportAmbiguity(node, r2, generalRule,
489: context);
490: }
491: break;
492: } else {
493: generalRule = r2;
494: if (policy == Configuration.RECOVER_SILENTLY) {
495: break; // find only the first; they are in priority order
496: }
497: }
498: }
499: }
500: r2 = r2.next;
501: }
502:
503: if (specificRule != null && generalRule == null) {
504: return specificRule.object;
505: }
506: if (specificRule == null && generalRule != null) {
507: return generalRule.object;
508: }
509: if (specificRule != null && generalRule != null) {
510: if (specificRule.precedence == generalRule.precedence
511: && specificRule.priority == generalRule.priority) {
512: // This situation is exceptional: we have a "specific" pattern and
513: // a "general" pattern with the same priority. We have to select
514: // the one that was added last
515: // (Bug reported by Norman Walsh Jan 2002)
516: Object result = (specificRule.sequence > generalRule.sequence ? specificRule.object
517: : generalRule.object);
518:
519: if (policy != Configuration.RECOVER_SILENTLY) {
520: reportAmbiguity(node, specificRule, generalRule,
521: context);
522: }
523: return result;
524: }
525: if (specificRule.precedence > generalRule.precedence
526: || (specificRule.precedence == generalRule.precedence && specificRule.priority >= generalRule.priority)) {
527: return specificRule.object;
528: } else {
529: return generalRule.object;
530: }
531: }
532: return null;
533: }
534:
535: /**
536: * Report an ambiguity, that is, the situation where two rules of the same
537: * precedence and priority match the same node
538: *
539: * @param node The node that matches two or more rules
540: * @param r1 The first rule that the node matches
541: * @param r2 The second rule that the node matches
542: * @param c The controller for the transformation
543: */
544:
545: private void reportAmbiguity(NodeInfo node, Rule r1, Rule r2,
546: XPathContext c) throws XPathException {
547: // don't report an error if the conflict is between two branches of the same
548: // Union pattern
549: if (r1.object == r2.object) {
550: return;
551: }
552: String path;
553: String errorCode = "XTRE0540";
554:
555: if (isStripper) {
556: // don't report an error if the conflict is between strip-space and strip-space, or
557: // preserve-space and preserve-space
558: if (r1.object.equals(r2.object)) {
559: return;
560: }
561: errorCode = "XTRE0270";
562: path = "xsl:strip-space";
563: } else {
564: path = Navigator.getPath(node);
565: }
566:
567: Pattern pat1 = r1.pattern;
568: Pattern pat2 = r2.pattern;
569:
570: DynamicError err = new DynamicError("Ambiguous rule match for "
571: + path + '\n' + "Matches both \"" + showPattern(pat1)
572: + "\" on line " + pat1.getLineNumber() + " of "
573: + pat1.getSystemId() + "\nand \"" + showPattern(pat2)
574: + "\" on line " + pat2.getLineNumber() + " of "
575: + pat2.getSystemId());
576: err.setErrorCode(errorCode);
577: err.setLocator(c.getOrigin().getInstructionInfo());
578: c.getController().recoverableError(err);
579: }
580:
581: private static String showPattern(Pattern p) {
582: // Complex patterns can be laid out with lots of whitespace, which looks messy in the error message
583: return NormalizeSpace.normalize(p.toString()).toString();
584: }
585:
586: /**
587: * Inner class Rule used to support the implementation
588: */
589:
590: public static class Rule implements Serializable {
591: public Pattern pattern;
592: public Object object;
593: public int precedence;
594: public double priority;
595: public int sequence;
596: public Rule next;
597:
598: /**
599: * Create a Rule
600: *
601: * @param p the pattern that this rule matches
602: * @param o the object invoked by this rule (usually a Template)
603: * @param prec the precedence of the rule
604: * @param prio the priority of the rule
605: * @param seq a sequence number for ordering of rules
606: */
607:
608: public Rule(Pattern p, Object o, int prec, double prio, int seq) {
609: pattern = p;
610: object = o;
611: precedence = prec;
612: priority = prio;
613: next = null;
614: sequence = seq;
615: }
616:
617: /**
618: * Copy a rule, including the chain of rules linked to it
619: *
620: * @param r
621: */
622:
623: public Rule(Rule r) {
624: this .pattern = r.pattern;
625: this .object = r.object;
626: this .precedence = r.precedence;
627: this .priority = r.priority;
628: this .sequence = r.sequence;
629: if (r.next == null) {
630: this .next = null;
631: } else {
632: this .next = new Rule(r.next);
633: }
634: }
635:
636: }
637:
638: }
639:
640: //
641: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
642: // you may not use this file except in compliance with the License. You may obtain a copy of the
643: // License at http://www.mozilla.org/MPL/
644: //
645: // Software distributed under the License is distributed on an "AS IS" basis,
646: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
647: // See the License for the specific language governing rights and limitations under the License.
648: //
649: // The Original Code is: all this file.
650: //
651: // The Initial Developer of the Original Code is Michael H. Kay.
652: //
653: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
654: //
655: // Contributor(s): none.
656: //
|