1. /*
  2. * Copyright 2001-2004 The Apache Software Foundation.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. /*
  17. * $Id: Whitespace.java,v 1.10 2004/02/16 22:25:10 minchau Exp $
  18. */
  19. package com.sun.org.apache.xalan.internal.xsltc.compiler;
  20. import java.util.StringTokenizer;
  21. import java.util.Vector;
  22. import com.sun.org.apache.bcel.internal.generic.ALOAD;
  23. import com.sun.org.apache.bcel.internal.generic.BranchHandle;
  24. import com.sun.org.apache.bcel.internal.generic.ConstantPoolGen;
  25. import com.sun.org.apache.bcel.internal.generic.IF_ICMPEQ;
  26. import com.sun.org.apache.bcel.internal.generic.ILOAD;
  27. import com.sun.org.apache.bcel.internal.generic.INVOKEINTERFACE;
  28. import com.sun.org.apache.bcel.internal.generic.INVOKEVIRTUAL;
  29. import com.sun.org.apache.bcel.internal.generic.InstructionHandle;
  30. import com.sun.org.apache.bcel.internal.generic.InstructionList;
  31. import com.sun.org.apache.bcel.internal.generic.PUSH;
  32. import com.sun.org.apache.xalan.internal.xsltc.compiler.util.ClassGenerator;
  33. import com.sun.org.apache.xalan.internal.xsltc.compiler.util.ErrorMsg;
  34. import com.sun.org.apache.xalan.internal.xsltc.compiler.util.MethodGenerator;
  35. import com.sun.org.apache.xalan.internal.xsltc.compiler.util.Type;
  36. import com.sun.org.apache.xalan.internal.xsltc.compiler.util.TypeCheckError;
  37. import com.sun.org.apache.xalan.internal.xsltc.compiler.util.Util;
  38. /**
  39. * @author Morten Jorgensen
  40. */
  41. final class Whitespace extends TopLevelElement {
  42. // Three possible actions for the translet:
  43. public static final int USE_PREDICATE = 0;
  44. public static final int STRIP_SPACE = 1;
  45. public static final int PRESERVE_SPACE = 2;
  46. // The 3 different categories of strip/preserve rules (order important)
  47. public static final int RULE_NONE = 0;
  48. public static final int RULE_ELEMENT = 1; // priority 0
  49. public static final int RULE_NAMESPACE = 2; // priority -1/4
  50. public static final int RULE_ALL = 3; // priority -1/2
  51. private String _elementList;
  52. private int _action;
  53. private int _importPrecedence;
  54. /**
  55. * Auxillary class for encapsulating a single strip/preserve rule
  56. */
  57. private final static class WhitespaceRule {
  58. private final int _action;
  59. private String _namespace; // Should be replaced by NS type (int)
  60. private String _element; // Should be replaced by node type (int)
  61. private int _type;
  62. private int _priority;
  63. /**
  64. * Strip/preserve rule constructor
  65. */
  66. public WhitespaceRule(int action, String element, int precedence) {
  67. // Determine the action (strip or preserve) for this rule
  68. _action = action;
  69. // Get the namespace and element name for this rule
  70. final int colon = element.indexOf(':');
  71. if (colon >= 0) {
  72. _namespace = element.substring(0,colon);
  73. _element = element.substring(colon+1,element.length());
  74. }
  75. else {
  76. _namespace = Constants.EMPTYSTRING;
  77. _element = element;
  78. }
  79. // Determine the initial priority for this rule
  80. _priority = precedence << 2;
  81. // Get the strip/preserve type; either "NS:EL", "NS:*" or "*"
  82. if (_element.equals("*")) {
  83. if (_namespace == Constants.EMPTYSTRING) {
  84. _type = RULE_ALL; // Strip/preserve _all_ elements
  85. _priority += 2; // Lowest priority
  86. }
  87. else {
  88. _type = RULE_NAMESPACE; // Strip/reserve elements within NS
  89. _priority += 1; // Medium priority
  90. }
  91. }
  92. else {
  93. _type = RULE_ELEMENT; // Strip/preserve single element
  94. }
  95. }
  96. /**
  97. * For sorting rules depending on priority
  98. */
  99. public int compareTo(WhitespaceRule other) {
  100. return _priority < other._priority
  101. ? -1
  102. : _priority > other._priority ? 1 : 0;
  103. }
  104. public int getAction() { return _action; }
  105. public int getStrength() { return _type; }
  106. public int getPriority() { return _priority; }
  107. public String getElement() { return _element; }
  108. public String getNamespace() { return _namespace; }
  109. }
  110. /**
  111. * Parse the attributes of the xsl:strip/preserve-space element.
  112. * The element should have not contents (ignored if any).
  113. */
  114. public void parseContents(Parser parser) {
  115. // Determine if this is an xsl:strip- or preserve-space element
  116. _action = _qname.getLocalPart().endsWith("strip-space")
  117. ? STRIP_SPACE : PRESERVE_SPACE;
  118. // Determine the import precedence
  119. _importPrecedence = parser.getCurrentImportPrecedence();
  120. // Get the list of elements to strip/preserve
  121. _elementList = getAttribute("elements");
  122. if (_elementList == null || _elementList.length() == 0) {
  123. reportError(this, parser, ErrorMsg.REQUIRED_ATTR_ERR, "elements");
  124. return;
  125. }
  126. final SymbolTable stable = parser.getSymbolTable();
  127. StringTokenizer list = new StringTokenizer(_elementList);
  128. StringBuffer elements = new StringBuffer(Constants.EMPTYSTRING);
  129. while (list.hasMoreElements()) {
  130. String token = list.nextToken();
  131. String prefix;
  132. String namespace;
  133. int col;
  134. if ((col = token.indexOf(':')) != -1) {
  135. prefix = token.substring(0,col);
  136. }
  137. else {
  138. prefix = Constants.EMPTYSTRING;
  139. }
  140. namespace = lookupNamespace(prefix);
  141. if (namespace != null)
  142. elements.append(namespace+":"+
  143. token.substring(col+1,token.length()));
  144. else
  145. elements.append(token);
  146. if (list.hasMoreElements())
  147. elements.append(" ");
  148. }
  149. _elementList = elements.toString();
  150. }
  151. /**
  152. * De-tokenize the elements listed in the 'elements' attribute and
  153. * instanciate a set of strip/preserve rules.
  154. */
  155. public Vector getRules() {
  156. final Vector rules = new Vector();
  157. // Go through each element and instanciate strip/preserve-object
  158. final StringTokenizer list = new StringTokenizer(_elementList);
  159. while (list.hasMoreElements()) {
  160. rules.add(new WhitespaceRule(_action,
  161. list.nextToken(),
  162. _importPrecedence));
  163. }
  164. return rules;
  165. }
  166. /**
  167. * Scans through the rules vector and looks for a rule of higher
  168. * priority that contradicts the current rule.
  169. */
  170. private static WhitespaceRule findContradictingRule(Vector rules,
  171. WhitespaceRule rule) {
  172. for (int i = 0; i < rules.size(); i++) {
  173. // Get the next rule in the prioritized list
  174. WhitespaceRule currentRule = (WhitespaceRule)rules.elementAt(i);
  175. // We only consider rules with higher priority
  176. if (currentRule == rule) {
  177. return null;
  178. }
  179. /*
  180. * See if there is a contradicting rule with higher priority.
  181. * If the rules has the same action then this rule is redundant,
  182. * if they have different action then this rule will never win.
  183. */
  184. switch (currentRule.getStrength()) {
  185. case RULE_ALL:
  186. return currentRule;
  187. case RULE_ELEMENT:
  188. if (!rule.getElement().equals(currentRule.getElement())) {
  189. break;
  190. }
  191. // intentional fall-through
  192. case RULE_NAMESPACE:
  193. if (rule.getNamespace().equals(currentRule.getNamespace())) {
  194. return currentRule;
  195. }
  196. break;
  197. }
  198. }
  199. return null;
  200. }
  201. /**
  202. * Orders a set or rules by priority, removes redundant rules and rules
  203. * that are shadowed by stronger, contradicting rules.
  204. */
  205. private static int prioritizeRules(Vector rules) {
  206. WhitespaceRule currentRule;
  207. int defaultAction = PRESERVE_SPACE;
  208. // Sort all rules with regard to priority
  209. quicksort(rules, 0, rules.size()-1);
  210. // Check if there are any "xsl:strip-space" elements at all.
  211. // If there are no xsl:strip elements we can ignore all xsl:preserve
  212. // elements and signal that all whitespaces should be preserved
  213. boolean strip = false;
  214. for (int i = 0; i < rules.size(); i++) {
  215. currentRule = (WhitespaceRule)rules.elementAt(i);
  216. if (currentRule.getAction() == STRIP_SPACE) {
  217. strip = true;
  218. }
  219. }
  220. // Return with default action: PRESERVE_SPACE
  221. if (!strip) {
  222. rules.removeAllElements();
  223. return PRESERVE_SPACE;
  224. }
  225. // Remove all rules that are contradicted by rules with higher priority
  226. for (int idx = 0; idx < rules.size(); ) {
  227. currentRule = (WhitespaceRule)rules.elementAt(idx);
  228. // Remove this single rule if it has no purpose
  229. if (findContradictingRule(rules,currentRule) != null) {
  230. rules.remove(idx);
  231. }
  232. else {
  233. // Remove all following rules if this one overrides all
  234. if (currentRule.getStrength() == RULE_ALL) {
  235. defaultAction = currentRule.getAction();
  236. for (int i = idx; i < rules.size(); i++) {
  237. rules.removeElementAt(i);
  238. }
  239. }
  240. // Skip to next rule (there might not be any)...
  241. idx++;
  242. }
  243. }
  244. // The rules vector could be empty if first rule has strength RULE_ALL
  245. if (rules.size() == 0) {
  246. return defaultAction;
  247. }
  248. // Now work backwards and strip away all rules that have the same
  249. // action as the default rule (no reason the check them at the end).
  250. do {
  251. currentRule = (WhitespaceRule)rules.lastElement();
  252. if (currentRule.getAction() == defaultAction) {
  253. rules.removeElementAt(rules.size() - 1);
  254. }
  255. else {
  256. break;
  257. }
  258. } while (rules.size() > 0);
  259. // Signal that whitespace detection predicate must be used.
  260. return defaultAction;
  261. }
  262. public static void compileStripSpace(BranchHandle strip[],
  263. int sCount,
  264. InstructionList il) {
  265. final InstructionHandle target = il.append(ICONST_1);
  266. il.append(IRETURN);
  267. for (int i = 0; i < sCount; i++) {
  268. strip[i].setTarget(target);
  269. }
  270. }
  271. public static void compilePreserveSpace(BranchHandle preserve[],
  272. int pCount,
  273. InstructionList il) {
  274. final InstructionHandle target = il.append(ICONST_0);
  275. il.append(IRETURN);
  276. for (int i = 0; i < pCount; i++) {
  277. preserve[i].setTarget(target);
  278. }
  279. }
  280. /*
  281. private static void compileDebug(ClassGenerator classGen,
  282. InstructionList il) {
  283. final ConstantPoolGen cpg = classGen.getConstantPool();
  284. final int prt = cpg.addMethodref("java/lang/System/out",
  285. "println",
  286. "(Ljava/lang/String;)V");
  287. il.append(DUP);
  288. il.append(new INVOKESTATIC(prt));
  289. }
  290. */
  291. /**
  292. * Compiles the predicate method
  293. */
  294. private static void compilePredicate(Vector rules,
  295. int defaultAction,
  296. ClassGenerator classGen) {
  297. final ConstantPoolGen cpg = classGen.getConstantPool();
  298. final InstructionList il = new InstructionList();
  299. final XSLTC xsltc = classGen.getParser().getXSLTC();
  300. // private boolean Translet.stripSpace(int type) - cannot be static
  301. final MethodGenerator stripSpace =
  302. new MethodGenerator(ACC_PUBLIC | ACC_FINAL ,
  303. com.sun.org.apache.bcel.internal.generic.Type.BOOLEAN,
  304. new com.sun.org.apache.bcel.internal.generic.Type[] {
  305. Util.getJCRefType(DOM_INTF_SIG),
  306. com.sun.org.apache.bcel.internal.generic.Type.INT,
  307. com.sun.org.apache.bcel.internal.generic.Type.INT
  308. },
  309. new String[] { "dom","node","type" },
  310. "stripSpace",classGen.getClassName(),il,cpg);
  311. classGen.addInterface("com/sun/org/apache/xalan/internal/xsltc/StripFilter");
  312. final int paramDom = stripSpace.getLocalIndex("dom");
  313. final int paramCurrent = stripSpace.getLocalIndex("node");
  314. final int paramType = stripSpace.getLocalIndex("type");
  315. BranchHandle strip[] = new BranchHandle[rules.size()];
  316. BranchHandle preserve[] = new BranchHandle[rules.size()];
  317. int sCount = 0;
  318. int pCount = 0;
  319. // Traverse all strip/preserve rules
  320. for (int i = 0; i<rules.size(); i++) {
  321. // Get the next rule in the prioritised list
  322. WhitespaceRule rule = (WhitespaceRule)rules.elementAt(i);
  323. // Returns the namespace for a node in the DOM
  324. final int gns = cpg.addInterfaceMethodref(DOM_INTF,
  325. "getNamespaceName",
  326. "(I)Ljava/lang/String;");
  327. final int strcmp = cpg.addMethodref("java/lang/String",
  328. "compareTo",
  329. "(Ljava/lang/String;)I");
  330. // Handle elements="ns:*" type rule
  331. if (rule.getStrength() == RULE_NAMESPACE) {
  332. il.append(new ALOAD(paramDom));
  333. il.append(new ILOAD(paramCurrent));
  334. il.append(new INVOKEINTERFACE(gns,2));
  335. il.append(new PUSH(cpg, rule.getNamespace()));
  336. il.append(new INVOKEVIRTUAL(strcmp));
  337. il.append(ICONST_0);
  338. if (rule.getAction() == STRIP_SPACE) {
  339. strip[sCount++] = il.append(new IF_ICMPEQ(null));
  340. }
  341. else {
  342. preserve[pCount++] = il.append(new IF_ICMPEQ(null));
  343. }
  344. }
  345. // Handle elements="ns:el" type rule
  346. else if (rule.getStrength() == RULE_ELEMENT) {
  347. // Create the QName for the element
  348. final Parser parser = classGen.getParser();
  349. QName qname;
  350. if (rule.getNamespace() != Constants.EMPTYSTRING )
  351. qname = parser.getQName(rule.getNamespace(), null,
  352. rule.getElement());
  353. else
  354. qname = parser.getQName(rule.getElement());
  355. // Register the element.
  356. final int elementType = xsltc.registerElement(qname);
  357. il.append(new ILOAD(paramType));
  358. il.append(new PUSH(cpg, elementType));
  359. // Compare current node type with wanted element type
  360. if (rule.getAction() == STRIP_SPACE)
  361. strip[sCount++] = il.append(new IF_ICMPEQ(null));
  362. else
  363. preserve[pCount++] = il.append(new IF_ICMPEQ(null));
  364. }
  365. }
  366. if (defaultAction == STRIP_SPACE) {
  367. compileStripSpace(strip, sCount, il);
  368. compilePreserveSpace(preserve, pCount, il);
  369. }
  370. else {
  371. compilePreserveSpace(preserve, pCount, il);
  372. compileStripSpace(strip, sCount, il);
  373. }
  374. stripSpace.stripAttributes(true);
  375. stripSpace.setMaxLocals();
  376. stripSpace.setMaxStack();
  377. stripSpace.removeNOPs();
  378. classGen.addMethod(stripSpace.getMethod());
  379. }
  380. /**
  381. * Compiles the predicate method
  382. */
  383. private static void compileDefault(int defaultAction,
  384. ClassGenerator classGen) {
  385. final ConstantPoolGen cpg = classGen.getConstantPool();
  386. final InstructionList il = new InstructionList();
  387. final XSLTC xsltc = classGen.getParser().getXSLTC();
  388. // private boolean Translet.stripSpace(int type) - cannot be static
  389. final MethodGenerator stripSpace =
  390. new MethodGenerator(ACC_PUBLIC | ACC_FINAL ,
  391. com.sun.org.apache.bcel.internal.generic.Type.BOOLEAN,
  392. new com.sun.org.apache.bcel.internal.generic.Type[] {
  393. Util.getJCRefType(DOM_INTF_SIG),
  394. com.sun.org.apache.bcel.internal.generic.Type.INT,
  395. com.sun.org.apache.bcel.internal.generic.Type.INT
  396. },
  397. new String[] { "dom","node","type" },
  398. "stripSpace",classGen.getClassName(),il,cpg);
  399. classGen.addInterface("com/sun/org/apache/xalan/internal/xsltc/StripFilter");
  400. if (defaultAction == STRIP_SPACE)
  401. il.append(ICONST_1);
  402. else
  403. il.append(ICONST_0);
  404. il.append(IRETURN);
  405. stripSpace.stripAttributes(true);
  406. stripSpace.setMaxLocals();
  407. stripSpace.setMaxStack();
  408. stripSpace.removeNOPs();
  409. classGen.addMethod(stripSpace.getMethod());
  410. }
  411. /**
  412. * Takes a vector of WhitespaceRule objects and generates a predicate
  413. * method. This method returns the translets default action for handling
  414. * whitespace text-nodes:
  415. * - USE_PREDICATE (run the method generated by this method)
  416. * - STRIP_SPACE (always strip whitespace text-nodes)
  417. * - PRESERVE_SPACE (always preserve whitespace text-nodes)
  418. */
  419. public static int translateRules(Vector rules,
  420. ClassGenerator classGen) {
  421. // Get the core rules in prioritized order
  422. final int defaultAction = prioritizeRules(rules);
  423. // The rules vector may be empty after prioritising
  424. if (rules.size() == 0) {
  425. compileDefault(defaultAction,classGen);
  426. return defaultAction;
  427. }
  428. // Now - create a predicate method and sequence through rules...
  429. compilePredicate(rules, defaultAction, classGen);
  430. // Return with the translets required action (
  431. return USE_PREDICATE;
  432. }
  433. /**
  434. * Sorts a range of rules with regard to PRIORITY only
  435. */
  436. private static void quicksort(Vector rules, int p, int r) {
  437. while (p < r) {
  438. final int q = partition(rules, p, r);
  439. quicksort(rules, p, q);
  440. p = q + 1;
  441. }
  442. }
  443. /**
  444. * Used with quicksort method above
  445. */
  446. private static int partition(Vector rules, int p, int r) {
  447. final WhitespaceRule x = (WhitespaceRule)rules.elementAt((p+r) >>> 1);
  448. int i = p - 1, j = r + 1;
  449. while (true) {
  450. while (x.compareTo((WhitespaceRule)rules.elementAt(--j)) < 0) {
  451. }
  452. while (x.compareTo((WhitespaceRule)rules.elementAt(++i)) > 0) {
  453. }
  454. if (i < j) {
  455. final WhitespaceRule tmp = (WhitespaceRule)rules.elementAt(i);
  456. rules.setElementAt(rules.elementAt(j), i);
  457. rules.setElementAt(tmp, j);
  458. }
  459. else {
  460. return j;
  461. }
  462. }
  463. }
  464. /**
  465. * Type-check contents/attributes - nothing to do...
  466. */
  467. public Type typeCheck(SymbolTable stable) throws TypeCheckError {
  468. return Type.Void; // We don't return anything.
  469. }
  470. /**
  471. * This method should not produce any code
  472. */
  473. public void translate(ClassGenerator classGen, MethodGenerator methodGen) {
  474. }
  475. }