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 }