001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 /*
019 * $Id: Whitespace.java 468650 2006-10-28 07:03:30Z minchau $
020 */
021
022 package org.apache.xalan.xsltc.compiler;
023
024 import java.util.StringTokenizer;
025 import java.util.Vector;
026
027 import org.apache.bcel.generic.ALOAD;
028 import org.apache.bcel.generic.BranchHandle;
029 import org.apache.bcel.generic.ConstantPoolGen;
030 import org.apache.bcel.generic.IF_ICMPEQ;
031 import org.apache.bcel.generic.ILOAD;
032 import org.apache.bcel.generic.INVOKEINTERFACE;
033 import org.apache.bcel.generic.INVOKEVIRTUAL;
034 import org.apache.bcel.generic.InstructionHandle;
035 import org.apache.bcel.generic.InstructionList;
036 import org.apache.bcel.generic.PUSH;
037 import org.apache.xalan.xsltc.compiler.util.ClassGenerator;
038 import org.apache.xalan.xsltc.compiler.util.ErrorMsg;
039 import org.apache.xalan.xsltc.compiler.util.MethodGenerator;
040 import org.apache.xalan.xsltc.compiler.util.Type;
041 import org.apache.xalan.xsltc.compiler.util.TypeCheckError;
042 import org.apache.xalan.xsltc.compiler.util.Util;
043
044 /**
045 * @author Morten Jorgensen
046 */
047 final class Whitespace extends TopLevelElement {
048 // Three possible actions for the translet:
049 public static final int USE_PREDICATE = 0;
050 public static final int STRIP_SPACE = 1;
051 public static final int PRESERVE_SPACE = 2;
052
053 // The 3 different categories of strip/preserve rules (order important)
054 public static final int RULE_NONE = 0;
055 public static final int RULE_ELEMENT = 1; // priority 0
056 public static final int RULE_NAMESPACE = 2; // priority -1/4
057 public static final int RULE_ALL = 3; // priority -1/2
058
059 private String _elementList;
060 private int _action;
061 private int _importPrecedence;
062
063 /**
064 * Auxillary class for encapsulating a single strip/preserve rule
065 */
066 private final static class WhitespaceRule {
067 private final int _action;
068 private String _namespace; // Should be replaced by NS type (int)
069 private String _element; // Should be replaced by node type (int)
070 private int _type;
071 private int _priority;
072
073 /**
074 * Strip/preserve rule constructor
075 */
076 public WhitespaceRule(int action, String element, int precedence) {
077 // Determine the action (strip or preserve) for this rule
078 _action = action;
079
080 // Get the namespace and element name for this rule
081 final int colon = element.lastIndexOf(':');
082 if (colon >= 0) {
083 _namespace = element.substring(0,colon);
084 _element = element.substring(colon+1,element.length());
085 }
086 else {
087 _namespace = Constants.EMPTYSTRING;
088 _element = element;
089 }
090
091 // Determine the initial priority for this rule
092 _priority = precedence << 2;
093
094 // Get the strip/preserve type; either "NS:EL", "NS:*" or "*"
095 if (_element.equals("*")) {
096 if (_namespace == Constants.EMPTYSTRING) {
097 _type = RULE_ALL; // Strip/preserve _all_ elements
098 _priority += 2; // Lowest priority
099 }
100 else {
101 _type = RULE_NAMESPACE; // Strip/reserve elements within NS
102 _priority += 1; // Medium priority
103 }
104 }
105 else {
106 _type = RULE_ELEMENT; // Strip/preserve single element
107 }
108 }
109
110 /**
111 * For sorting rules depending on priority
112 */
113 public int compareTo(WhitespaceRule other) {
114 return _priority < other._priority
115 ? -1
116 : _priority > other._priority ? 1 : 0;
117 }
118
119 public int getAction() { return _action; }
120 public int getStrength() { return _type; }
121 public int getPriority() { return _priority; }
122 public String getElement() { return _element; }
123 public String getNamespace() { return _namespace; }
124 }
125
126 /**
127 * Parse the attributes of the xsl:strip/preserve-space element.
128 * The element should have not contents (ignored if any).
129 */
130 public void parseContents(Parser parser) {
131 // Determine if this is an xsl:strip- or preserve-space element
132 _action = _qname.getLocalPart().endsWith("strip-space")
133 ? STRIP_SPACE : PRESERVE_SPACE;
134
135 // Determine the import precedence
136 _importPrecedence = parser.getCurrentImportPrecedence();
137
138 // Get the list of elements to strip/preserve
139 _elementList = getAttribute("elements");
140 if (_elementList == null || _elementList.length() == 0) {
141 reportError(this, parser, ErrorMsg.REQUIRED_ATTR_ERR, "elements");
142 return;
143 }
144
145 final SymbolTable stable = parser.getSymbolTable();
146 StringTokenizer list = new StringTokenizer(_elementList);
147 StringBuffer elements = new StringBuffer(Constants.EMPTYSTRING);
148
149 while (list.hasMoreElements()) {
150 String token = list.nextToken();
151 String prefix;
152 String namespace;
153 int col = token.indexOf(':');
154
155 if (col != -1) {
156 namespace = lookupNamespace(token.substring(0,col));
157 if (namespace != null) {
158 elements.append(namespace+":"+
159 token.substring(col+1,token.length()));
160 } else {
161 elements.append(token);
162 }
163 } else {
164 elements.append(token);
165 }
166
167 if (list.hasMoreElements())
168 elements.append(" ");
169 }
170 _elementList = elements.toString();
171 }
172
173
174 /**
175 * De-tokenize the elements listed in the 'elements' attribute and
176 * instanciate a set of strip/preserve rules.
177 */
178 public Vector getRules() {
179 final Vector rules = new Vector();
180 // Go through each element and instanciate strip/preserve-object
181 final StringTokenizer list = new StringTokenizer(_elementList);
182 while (list.hasMoreElements()) {
183 rules.add(new WhitespaceRule(_action,
184 list.nextToken(),
185 _importPrecedence));
186 }
187 return rules;
188 }
189
190
191 /**
192 * Scans through the rules vector and looks for a rule of higher
193 * priority that contradicts the current rule.
194 */
195 private static WhitespaceRule findContradictingRule(Vector rules,
196 WhitespaceRule rule) {
197 for (int i = 0; i < rules.size(); i++) {
198 // Get the next rule in the prioritized list
199 WhitespaceRule currentRule = (WhitespaceRule)rules.elementAt(i);
200 // We only consider rules with higher priority
201 if (currentRule == rule) {
202 return null;
203 }
204
205 /*
206 * See if there is a contradicting rule with higher priority.
207 * If the rules has the same action then this rule is redundant,
208 * if they have different action then this rule will never win.
209 */
210 switch (currentRule.getStrength()) {
211 case RULE_ALL:
212 return currentRule;
213
214 case RULE_ELEMENT:
215 if (!rule.getElement().equals(currentRule.getElement())) {
216 break;
217 }
218 // intentional fall-through
219 case RULE_NAMESPACE:
220 if (rule.getNamespace().equals(currentRule.getNamespace())) {
221 return currentRule;
222 }
223 break;
224 }
225 }
226 return null;
227 }
228
229
230 /**
231 * Orders a set or rules by priority, removes redundant rules and rules
232 * that are shadowed by stronger, contradicting rules.
233 */
234 private static int prioritizeRules(Vector rules) {
235 WhitespaceRule currentRule;
236 int defaultAction = PRESERVE_SPACE;
237
238 // Sort all rules with regard to priority
239 quicksort(rules, 0, rules.size()-1);
240
241 // Check if there are any "xsl:strip-space" elements at all.
242 // If there are no xsl:strip elements we can ignore all xsl:preserve
243 // elements and signal that all whitespaces should be preserved
244 boolean strip = false;
245 for (int i = 0; i < rules.size(); i++) {
246 currentRule = (WhitespaceRule)rules.elementAt(i);
247 if (currentRule.getAction() == STRIP_SPACE) {
248 strip = true;
249 }
250 }
251 // Return with default action: PRESERVE_SPACE
252 if (!strip) {
253 rules.removeAllElements();
254 return PRESERVE_SPACE;
255 }
256
257 // Remove all rules that are contradicted by rules with higher priority
258 for (int idx = 0; idx < rules.size(); ) {
259 currentRule = (WhitespaceRule)rules.elementAt(idx);
260
261 // Remove this single rule if it has no purpose
262 if (findContradictingRule(rules,currentRule) != null) {
263 rules.remove(idx);
264 }
265 else {
266 // Remove all following rules if this one overrides all
267 if (currentRule.getStrength() == RULE_ALL) {
268 defaultAction = currentRule.getAction();
269 for (int i = idx; i < rules.size(); i++) {
270 rules.removeElementAt(i);
271 }
272 }
273 // Skip to next rule (there might not be any)...
274 idx++;
275 }
276 }
277
278 // The rules vector could be empty if first rule has strength RULE_ALL
279 if (rules.size() == 0) {
280 return defaultAction;
281 }
282
283 // Now work backwards and strip away all rules that have the same
284 // action as the default rule (no reason the check them at the end).
285 do {
286 currentRule = (WhitespaceRule)rules.lastElement();
287 if (currentRule.getAction() == defaultAction) {
288 rules.removeElementAt(rules.size() - 1);
289 }
290 else {
291 break;
292 }
293 } while (rules.size() > 0);
294
295 // Signal that whitespace detection predicate must be used.
296 return defaultAction;
297 }
298
299 public static void compileStripSpace(BranchHandle strip[],
300 int sCount,
301 InstructionList il) {
302 final InstructionHandle target = il.append(ICONST_1);
303 il.append(IRETURN);
304 for (int i = 0; i < sCount; i++) {
305 strip[i].setTarget(target);
306 }
307 }
308
309 public static void compilePreserveSpace(BranchHandle preserve[],
310 int pCount,
311 InstructionList il) {
312 final InstructionHandle target = il.append(ICONST_0);
313 il.append(IRETURN);
314 for (int i = 0; i < pCount; i++) {
315 preserve[i].setTarget(target);
316 }
317 }
318
319 /*
320 private static void compileDebug(ClassGenerator classGen,
321 InstructionList il) {
322 final ConstantPoolGen cpg = classGen.getConstantPool();
323 final int prt = cpg.addMethodref("java/lang/System/out",
324 "println",
325 "(Ljava/lang/String;)V");
326 il.append(DUP);
327 il.append(new INVOKESTATIC(prt));
328 }
329 */
330
331 /**
332 * Compiles the predicate method
333 */
334 private static void compilePredicate(Vector rules,
335 int defaultAction,
336 ClassGenerator classGen) {
337 final ConstantPoolGen cpg = classGen.getConstantPool();
338 final InstructionList il = new InstructionList();
339 final XSLTC xsltc = classGen.getParser().getXSLTC();
340
341 // private boolean Translet.stripSpace(int type) - cannot be static
342 final MethodGenerator stripSpace =
343 new MethodGenerator(ACC_PUBLIC | ACC_FINAL ,
344 org.apache.bcel.generic.Type.BOOLEAN,
345 new org.apache.bcel.generic.Type[] {
346 Util.getJCRefType(DOM_INTF_SIG),
347 org.apache.bcel.generic.Type.INT,
348 org.apache.bcel.generic.Type.INT
349 },
350 new String[] { "dom","node","type" },
351 "stripSpace",classGen.getClassName(),il,cpg);
352
353 classGen.addInterface("org/apache/xalan/xsltc/StripFilter");
354
355 final int paramDom = stripSpace.getLocalIndex("dom");
356 final int paramCurrent = stripSpace.getLocalIndex("node");
357 final int paramType = stripSpace.getLocalIndex("type");
358
359 BranchHandle strip[] = new BranchHandle[rules.size()];
360 BranchHandle preserve[] = new BranchHandle[rules.size()];
361 int sCount = 0;
362 int pCount = 0;
363
364 // Traverse all strip/preserve rules
365 for (int i = 0; i<rules.size(); i++) {
366 // Get the next rule in the prioritised list
367 WhitespaceRule rule = (WhitespaceRule)rules.elementAt(i);
368
369 // Returns the namespace for a node in the DOM
370 final int gns = cpg.addInterfaceMethodref(DOM_INTF,
371 "getNamespaceName",
372 "(I)Ljava/lang/String;");
373
374 final int strcmp = cpg.addMethodref("java/lang/String",
375 "compareTo",
376 "(Ljava/lang/String;)I");
377
378 // Handle elements="ns:*" type rule
379 if (rule.getStrength() == RULE_NAMESPACE) {
380 il.append(new ALOAD(paramDom));
381 il.append(new ILOAD(paramCurrent));
382 il.append(new INVOKEINTERFACE(gns,2));
383 il.append(new PUSH(cpg, rule.getNamespace()));
384 il.append(new INVOKEVIRTUAL(strcmp));
385 il.append(ICONST_0);
386
387 if (rule.getAction() == STRIP_SPACE) {
388 strip[sCount++] = il.append(new IF_ICMPEQ(null));
389 }
390 else {
391 preserve[pCount++] = il.append(new IF_ICMPEQ(null));
392 }
393 }
394 // Handle elements="ns:el" type rule
395 else if (rule.getStrength() == RULE_ELEMENT) {
396 // Create the QName for the element
397 final Parser parser = classGen.getParser();
398 QName qname;
399 if (rule.getNamespace() != Constants.EMPTYSTRING )
400 qname = parser.getQName(rule.getNamespace(), null,
401 rule.getElement());
402 else
403 qname = parser.getQName(rule.getElement());
404
405 // Register the element.
406 final int elementType = xsltc.registerElement(qname);
407 il.append(new ILOAD(paramType));
408 il.append(new PUSH(cpg, elementType));
409
410 // Compare current node type with wanted element type
411 if (rule.getAction() == STRIP_SPACE)
412 strip[sCount++] = il.append(new IF_ICMPEQ(null));
413 else
414 preserve[pCount++] = il.append(new IF_ICMPEQ(null));
415 }
416 }
417
418 if (defaultAction == STRIP_SPACE) {
419 compileStripSpace(strip, sCount, il);
420 compilePreserveSpace(preserve, pCount, il);
421 }
422 else {
423 compilePreserveSpace(preserve, pCount, il);
424 compileStripSpace(strip, sCount, il);
425 }
426
427 classGen.addMethod(stripSpace);
428 }
429
430 /**
431 * Compiles the predicate method
432 */
433 private static void compileDefault(int defaultAction,
434 ClassGenerator classGen) {
435 final ConstantPoolGen cpg = classGen.getConstantPool();
436 final InstructionList il = new InstructionList();
437 final XSLTC xsltc = classGen.getParser().getXSLTC();
438
439 // private boolean Translet.stripSpace(int type) - cannot be static
440 final MethodGenerator stripSpace =
441 new MethodGenerator(ACC_PUBLIC | ACC_FINAL ,
442 org.apache.bcel.generic.Type.BOOLEAN,
443 new org.apache.bcel.generic.Type[] {
444 Util.getJCRefType(DOM_INTF_SIG),
445 org.apache.bcel.generic.Type.INT,
446 org.apache.bcel.generic.Type.INT
447 },
448 new String[] { "dom","node","type" },
449 "stripSpace",classGen.getClassName(),il,cpg);
450
451 classGen.addInterface("org/apache/xalan/xsltc/StripFilter");
452
453 if (defaultAction == STRIP_SPACE)
454 il.append(ICONST_1);
455 else
456 il.append(ICONST_0);
457 il.append(IRETURN);
458
459 classGen.addMethod(stripSpace);
460 }
461
462
463 /**
464 * Takes a vector of WhitespaceRule objects and generates a predicate
465 * method. This method returns the translets default action for handling
466 * whitespace text-nodes:
467 * - USE_PREDICATE (run the method generated by this method)
468 * - STRIP_SPACE (always strip whitespace text-nodes)
469 * - PRESERVE_SPACE (always preserve whitespace text-nodes)
470 */
471 public static int translateRules(Vector rules,
472 ClassGenerator classGen) {
473 // Get the core rules in prioritized order
474 final int defaultAction = prioritizeRules(rules);
475 // The rules vector may be empty after prioritising
476 if (rules.size() == 0) {
477 compileDefault(defaultAction,classGen);
478 return defaultAction;
479 }
480 // Now - create a predicate method and sequence through rules...
481 compilePredicate(rules, defaultAction, classGen);
482 // Return with the translets required action (
483 return USE_PREDICATE;
484 }
485
486 /**
487 * Sorts a range of rules with regard to PRIORITY only
488 */
489 private static void quicksort(Vector rules, int p, int r) {
490 while (p < r) {
491 final int q = partition(rules, p, r);
492 quicksort(rules, p, q);
493 p = q + 1;
494 }
495 }
496
497 /**
498 * Used with quicksort method above
499 */
500 private static int partition(Vector rules, int p, int r) {
501 final WhitespaceRule x = (WhitespaceRule)rules.elementAt((p+r) >>> 1);
502 int i = p - 1, j = r + 1;
503 while (true) {
504 while (x.compareTo((WhitespaceRule)rules.elementAt(--j)) < 0) {
505 }
506 while (x.compareTo((WhitespaceRule)rules.elementAt(++i)) > 0) {
507 }
508 if (i < j) {
509 final WhitespaceRule tmp = (WhitespaceRule)rules.elementAt(i);
510 rules.setElementAt(rules.elementAt(j), i);
511 rules.setElementAt(tmp, j);
512 }
513 else {
514 return j;
515 }
516 }
517 }
518
519 /**
520 * Type-check contents/attributes - nothing to do...
521 */
522 public Type typeCheck(SymbolTable stable) throws TypeCheckError {
523 return Type.Void; // We don't return anything.
524 }
525
526 /**
527 * This method should not produce any code
528 */
529 public void translate(ClassGenerator classGen, MethodGenerator methodGen) {
530 }
531 }