001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source 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, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: *
023: * Free Software Foundation, Inc.
024: * 59 Temple Place, Suite 330
025: * Boston, MA 02111-1307 USA
026: *
027: * @author Scott Ferguson
028: */
029:
030: package com.caucho.xsl;
031:
032: import com.caucho.java.LineMap;
033: import com.caucho.log.Log;
034: import com.caucho.util.CharBuffer;
035: import com.caucho.util.IntArray;
036: import com.caucho.vfs.Path;
037: import com.caucho.xml.CauchoNode;
038: import com.caucho.xml.QAbstractNode;
039: import com.caucho.xml.QElement;
040: import com.caucho.xml.XMLWriter;
041: import com.caucho.xml.XmlChar;
042: import com.caucho.xml.XmlUtil;
043: import com.caucho.xpath.Env;
044: import com.caucho.xpath.Expr;
045: import com.caucho.xpath.StylesheetEnv;
046: import com.caucho.xpath.XPath;
047: import com.caucho.xpath.XPathException;
048: import com.caucho.xpath.XPathFun;
049: import com.caucho.xpath.pattern.AbstractPattern;
050: import com.caucho.xpath.pattern.NodeIterator;
051: import com.caucho.xsl.fun.DocumentFun;
052: import com.caucho.xsl.fun.ExtensionElementFun;
053: import com.caucho.xsl.fun.ExtensionFunctionFun;
054: import com.caucho.xsl.fun.SystemPropertyFun;
055: import com.caucho.xsl.fun.UnparsedEntityFun;
056:
057: import org.w3c.dom.Document;
058: import org.w3c.dom.Element;
059: import org.w3c.dom.NamedNodeMap;
060: import org.w3c.dom.Node;
061: import org.w3c.dom.Text;
062: import org.xml.sax.SAXException;
063:
064: import javax.xml.transform.TransformerException;
065: import java.io.IOException;
066: import java.util.ArrayList;
067: import java.util.Comparator;
068: import java.util.HashMap;
069: import java.util.Iterator;
070: import java.util.Locale;
071: import java.util.logging.Logger;
072:
073: /**
074: * Implementation base class for stylesheets. It is made public only
075: * because generated Java and JavaScript classes need to access these
076: * routines.
077: */
078: public class StylesheetImpl extends AbstractStylesheet {
079: static final Logger log = Log.open(StylesheetImpl.class);
080:
081: public char[] text; // static buffer of the text nodes
082:
083: protected HashMap templates;
084: HashMap<String, XPathFun> _funs = new HashMap<String, XPathFun>();
085:
086: private HashMap<String, String> _preserve;
087: private HashMap<String, String> _strip;
088: private HashMap<String, String> _preservePrefix;
089: private HashMap<String, String> _stripPrefix;
090: private HashMap<String, Object> _properties = new HashMap<String, Object>();
091:
092: boolean isCacheable = true;
093: protected boolean _defaultDisableEscaping;
094: Path cachePath;
095: long lastModified;
096:
097: boolean _generateLocation;
098:
099: LineMap lineMap;
100:
101: protected void copy(AbstractStylesheet stylesheet) {
102: super .copy(stylesheet);
103:
104: StylesheetImpl stylesheetImpl = (StylesheetImpl) stylesheet;
105:
106: stylesheetImpl.text = text;
107: stylesheetImpl.templates = templates;
108: stylesheetImpl._preserve = _preserve;
109: stylesheetImpl._strip = _strip;
110: stylesheetImpl._preservePrefix = _preservePrefix;
111: stylesheetImpl._stripPrefix = _stripPrefix;
112: stylesheetImpl.lineMap = lineMap;
113: stylesheetImpl._properties = _properties;
114: stylesheetImpl._defaultDisableEscaping = _defaultDisableEscaping;
115: }
116:
117: public OutputFormat getOutputFormat() {
118: return new OutputFormat();
119: }
120:
121: public void setOutputFormat(OutputFormat output) {
122: }
123:
124: protected void setSpaces(HashMap<String, String> preserve,
125: HashMap<String, String> preservePrefix,
126: HashMap<String, String> strip,
127: HashMap<String, String> stripPrefix) {
128: _preserve = preserve;
129: _strip = strip;
130: _preservePrefix = preservePrefix;
131: _stripPrefix = stripPrefix;
132: }
133:
134: public void setProperty(String name, Object value) {
135: _properties.put(name, value);
136: }
137:
138: public void setGenerateLocation(boolean generateLocation) {
139: _generateLocation = generateLocation;
140: }
141:
142: public boolean getGenerateLocation() {
143: return _generateLocation;
144: }
145:
146: public Object getProperty(String name) {
147: Object value = _properties.get(name);
148: if (value != null)
149: return value;
150:
151: return super .getProperty(name);
152: }
153:
154: protected void addFunction(String name, XPathFun fun) {
155: _funs.put(name, fun);
156: }
157:
158: public void init(Path path) throws Exception {
159: super .init(path);
160:
161: addFunction("system-property", new SystemPropertyFun());
162: addFunction("element-available", new ExtensionElementFun());
163: addFunction("function-available", new ExtensionFunctionFun());
164: addFunction("unparsed-entity-uri", new UnparsedEntityFun());
165: }
166:
167: /**
168: * Transforms the input node to the output writer
169: *
170: * @param xml the input node to be transformed
171: * @param writer output writer receiving the output
172: * @param transformer the transformer to be used
173: */
174: public void transform(Node xml, XMLWriter writer,
175: TransformerImpl transformer) throws SAXException,
176: IOException, TransformerException {
177: if (xml == null)
178: throw new NullPointerException("can't transform null node");
179:
180: XslWriter out = new XslWriter(null, this , transformer);
181: out.init(writer);
182:
183: if (_funs == null)
184: _funs = (HashMap) ((StylesheetImpl) _stylesheet)._funs
185: .clone();
186: else
187: _funs
188: .putAll((HashMap) ((StylesheetImpl) _stylesheet)._funs);
189:
190: addFunction("document", new DocumentFun(transformer));
191: DocumentFun docFun = new DocumentFun(transformer);
192: docFun.setHtml(true);
193: addFunction("html_document", docFun);
194:
195: Env env = XPath.createEnv();
196: env.setFunctions(_funs);
197: StylesheetEnv ssEnv = new StylesheetEnv();
198: ssEnv.setPath(getPath());
199: env.setStylesheetEnv(ssEnv);
200:
201: out.disableEscaping(_defaultDisableEscaping);
202:
203: if (_strip != null && !_strip.isEmpty()) {
204: stripSpaces(xml);
205: }
206:
207: try {
208: _xsl_init(out, xml, env);
209:
210: applyNode(out, xml, env, 0, Integer.MAX_VALUE);
211: } catch (TransformerException e) {
212: throw e;
213: } catch (IOException e) {
214: throw e;
215: } catch (SAXException e) {
216: throw e;
217: } catch (Exception e) {
218: throw new XslException(e, lineMap);
219: }
220:
221: out.close();
222:
223: XPath.freeEnv(env);
224:
225: // funs = null;
226: }
227:
228: protected void _xsl_init(XslWriter out, Node context, Env env)
229: throws Exception {
230: }
231:
232: protected Document ownerDocument(Node node) {
233: Document owner = node.getOwnerDocument();
234:
235: if (owner != null)
236: return owner;
237: else
238: return (Document) node;
239: }
240:
241: public void applyNode(XslWriter out, Node node, Env env)
242: throws Exception {
243: applyNode(out, node, env, Integer.MIN_VALUE, Integer.MAX_VALUE);
244: }
245:
246: protected void applyNode(XslWriter out, Node node, Env env,
247: int min, int max) throws Exception {
248: if (node == null)
249: return;
250:
251: switch (node.getNodeType()) {
252: case Node.DOCUMENT_NODE:
253: case Node.DOCUMENT_FRAGMENT_NODE:
254: for (Node child = node.getFirstChild(); child != null; child = child
255: .getNextSibling()) {
256: applyNode(out, child, env, 0, 2147483647);
257: }
258: break;
259:
260: case Node.ELEMENT_NODE:
261: out.pushCopy(node);
262: if (node instanceof QElement) {
263: for (Node child = ((QElement) node).getFirstAttribute(); child != null; child = child
264: .getNextSibling()) {
265: applyNode(out, child, env, 0, 2147483647);
266: }
267: } else {
268: NamedNodeMap attributeMap = ((Element) node)
269: .getAttributes();
270: int size = attributeMap.getLength();
271: for (int i = 0; i < size; i++) {
272: Node child = attributeMap.item(i);
273:
274: applyNode(out, child, env, 0, 2147483647);
275: }
276: }
277:
278: for (Node child = node.getFirstChild(); child != null; child = child
279: .getNextSibling()) {
280: applyNode(out, child, env, 0, 2147483647);
281: }
282: out.popCopy(node);
283: break;
284:
285: case Node.TEXT_NODE:
286: case Node.CDATA_SECTION_NODE:
287: String value = node.getNodeValue();
288: out.print(value);
289: return;
290:
291: case Node.ATTRIBUTE_NODE:
292: out.pushCopy(node);
293: out.popCopy(node);
294: break;
295:
296: case Node.ENTITY_REFERENCE_NODE:
297: out.pushCopy(node);
298: out.popCopy(node);
299: break;
300: }
301: }
302:
303: /**
304: * Gets a template.
305: *
306: * Only those templates with importance between min and max are considered.
307: * For apply-templates, min = 0, and max = Infinity,
308: *
309: * @param min minimum allowed importance
310: * @param max maximum allowed importance
311: */
312: protected Template getTemplate(HashMap templates, Node node,
313: Env env, int min, int max) throws XPathException {
314: Template template = null;
315:
316: Template[] templateList = (Template[]) templates.get(node
317: .getNodeName());
318: if (templateList == null)
319: templateList = (Template[]) templates.get("*");
320:
321: for (int i = 0; templateList != null && i < templateList.length; i++) {
322: Template subtemplate = templateList[i];
323:
324: if (min <= subtemplate.maxImportance
325: && subtemplate.maxImportance <= max
326: && subtemplate.pattern.match(node, env)) {
327: return subtemplate;
328: }
329: }
330:
331: return null;
332: }
333:
334: /**
335: * The default rule when no templates match. By default, it
336: * calls apply-template on element children and copies text. All
337: * other nodes are stripped.
338: *
339: * @param out the current writer.
340: * @param node the current node.
341: * @param env the xpath environment.
342: */
343: protected void applyNodeDefault(XslWriter out, Node node, Env env)
344: throws Exception {
345: switch (node.getNodeType()) {
346: case Node.TEXT_NODE:
347: case Node.CDATA_SECTION_NODE:
348: if (_generateLocation && node instanceof QAbstractNode)
349: out.setLocation(((QAbstractNode) node).getBaseURI(),
350: ((QAbstractNode) node).getFilename(),
351: ((QAbstractNode) node).getLine());
352: String value = node.getNodeValue();
353: out.print(value);
354: return;
355:
356: case Node.ATTRIBUTE_NODE:
357: case Node.ENTITY_REFERENCE_NODE:
358: out.print(node.getNodeValue());
359: break;
360:
361: case Node.ELEMENT_NODE:
362: case Node.DOCUMENT_NODE:
363: throw new RuntimeException();
364: }
365: }
366:
367: public void printValue(XslWriter out, Node node) throws IOException {
368: if (node != null)
369: out.print(getNodeValue(node));
370: }
371:
372: public String getNodeValue(Node node) {
373: CharBuffer cb = new CharBuffer();
374:
375: nodeValue(cb, node);
376:
377: return cb.toString();
378: }
379:
380: private void nodeValue(CharBuffer cb, Node node) {
381: if (node == null)
382: return;
383:
384: switch (node.getNodeType()) {
385: case Node.ELEMENT_NODE:
386: for (Node child = node.getFirstChild(); child != null; child = child
387: .getNextSibling()) {
388: switch (child.getNodeType()) {
389: case Node.ELEMENT_NODE:
390: case Node.TEXT_NODE:
391: case Node.CDATA_SECTION_NODE:
392: case Node.ENTITY_REFERENCE_NODE:
393: nodeValue(cb, child);
394: break;
395: }
396: }
397: break;
398:
399: case Node.ENTITY_REFERENCE_NODE:
400: cb.append('&');
401: cb.append(node.getNodeName());
402: cb.append(';');
403: break;
404:
405: case Node.DOCUMENT_NODE:
406: Document doc = (Document) node;
407: nodeValue(cb, doc.getDocumentElement());
408: break;
409:
410: case Node.TEXT_NODE:
411: case Node.CDATA_SECTION_NODE:
412: String value = node.getNodeValue();
413: cb.append(value);
414: break;
415:
416: default:
417: cb.append(node.getNodeValue());
418: break;
419: }
420: }
421:
422: protected ArrayList xslSort(Node node, Env env,
423: AbstractPattern pattern, Sort[] sortList) throws Exception {
424: ArrayList<Node> sortKeys = new ArrayList<Node>();
425:
426: Iterator<Node> sortIter;
427: NodeIterator iter = pattern.select(node, env);
428:
429: while (iter.hasNext()) {
430: Node child = iter.next();
431: sortKeys.add(child);
432: }
433:
434: int[] map = new int[sortKeys.size()];
435: for (int i = map.length - 1; i >= 0; i--)
436: map[i] = i;
437:
438: int[] workMap = new int[map.length];
439:
440: Object[] values = new Object[map.length * sortList.length];
441:
442: int size = map.length;
443: for (int i = 0; i < size; i++) {
444: Node child = (Node) sortKeys.get(i);
445:
446: env.setPosition(i + 1);
447: // XXX: set last() as well
448:
449: for (int j = 0; j < sortList.length; j++) {
450: Sort sort = sortList[j];
451: Object value = sort.sortValue(child, env);
452:
453: values[i * sortList.length + j] = value;
454: }
455: }
456:
457: boolean[] ascendingList = new boolean[sortList.length];
458:
459: for (int i = 0; i < ascendingList.length; i++) {
460: Expr isAscending = sortList[i].getAscending();
461: if (isAscending == null
462: || isAscending.evalBoolean(node, env))
463: ascendingList[i] = true;
464: }
465:
466: Comparator[] comparatorList = new Comparator[sortList.length];
467:
468: for (int i = 0; i < comparatorList.length; i++) {
469: Expr langExpr = sortList[i].getLang();
470: String lang = null;
471:
472: if (langExpr != null) {
473: lang = langExpr.evalString(node, env);
474: }
475:
476: if (lang != null)
477: comparatorList[i] = getComparator(lang);
478: }
479:
480: int[] caseOrderList = new int[sortList.length];
481:
482: for (int i = 0; i < caseOrderList.length; i++) {
483: Expr caseOrder = sortList[i].getCaseOrder();
484: if (caseOrder == null)
485: caseOrderList[i] = Sort.NO_CASE_ORDER;
486: else if (caseOrder.evalBoolean(node, env))
487: caseOrderList[i] = Sort.UPPER_FIRST;
488: else
489: caseOrderList[i] = Sort.LOWER_FIRST;
490: }
491:
492: sort(values, sortList, comparatorList, ascendingList,
493: caseOrderList, 0, map.length, map, workMap);
494:
495: ArrayList sortedKeys = new ArrayList();
496:
497: for (int i = 0; i < map.length; i++)
498: sortedKeys.add(sortKeys.get(map[i]));
499:
500: return sortedKeys;
501: }
502:
503: /**
504: * Returns the comparator for the language.
505: */
506: private Comparator getComparator(String lang) {
507: Locale locale = getLocale(lang);
508:
509: return java.text.Collator.getInstance(locale);
510: }
511:
512: /**
513: * Returns the locale for the language.
514: */
515: private Locale getLocale(String lang) {
516: int p = lang.indexOf('-');
517: Locale locale = null;
518:
519: if (p < 0) {
520: locale = new Locale(lang, "");
521: } else {
522: String language = lang.substring(0, p);
523:
524: int q = lang.indexOf(p + 1, '-');
525:
526: if (q < 0) {
527: String country = lang.substring(p + 1);
528:
529: locale = new Locale(language, country);
530: } else {
531: String country = lang.substring(p + 1, q);
532: String variant = lang.substring(q);
533:
534: locale = new Locale(language, country, variant);
535: }
536: }
537:
538: return locale;
539: }
540:
541: /**
542: * Sorts a subsequence.
543: *
544: * @param head the start of the subsequence
545: * @param tail the tail of the subsequence
546: */
547: private void sort(Object[] values, Sort[] sortList,
548: Comparator[] comparatorList, boolean[] ascendingList,
549: int[] caseOrder, int head, int tail, int map[],
550: int[] workMap) {
551: int length = tail - head;
552: if (length <= 1)
553: return;
554:
555: // shortcut when only have two items
556: if (length == 2) {
557: int a = map[head];
558: int b = map[head + 1];
559:
560: if (lessThan(values, sortList, comparatorList,
561: ascendingList, caseOrder, b, a)) {
562: map[head] = b;
563: map[head + 1] = a;
564: }
565: return;
566: }
567: // shortcut when only have three items
568: else if (length == 3) {
569: int a = map[head];
570: int b = map[head + 1];
571: int c = map[head + 2];
572:
573: if (lessThan(values, sortList, comparatorList,
574: ascendingList, caseOrder, b, a)) {
575: map[head] = b;
576: map[head + 1] = a;
577: a = map[head];
578: b = map[head + 1];
579: }
580:
581: if (!lessThan(values, sortList, comparatorList,
582: ascendingList, caseOrder, c, b)) {
583: } else if (lessThan(values, sortList, comparatorList,
584: ascendingList, caseOrder, c, a)) {
585: map[head] = c;
586: map[head + 1] = a;
587: map[head + 2] = b;
588: } else {
589: map[head + 1] = c;
590: map[head + 2] = b;
591: }
592:
593: return;
594: }
595:
596: int pivotIndex = (head + tail) / 2;
597: int pivot = map[pivotIndex];
598: int top = tail;
599:
600: // values greater than the pivot value are put in the work map
601: for (int i = tail - 1; i >= head; i--) {
602: if (lessThan(values, sortList, comparatorList,
603: ascendingList, caseOrder, pivot, map[i])) {
604: workMap[--top] = map[i];
605: map[i] = -1;
606: }
607: }
608:
609: // if the pivot is the max, need to shift equals
610: if (top == tail) {
611: // values greater than the pivot value are put in the work map
612: for (int i = tail - 1; i >= head; i--) {
613: if (!lessThan(values, sortList, comparatorList,
614: ascendingList, caseOrder, map[i], pivot)) {
615: workMap[--top] = map[i];
616: map[i] = -1;
617: }
618: }
619:
620: // If all entries are equal to the pivot, we're done
621: if (top == head) {
622: for (int i = head; i < tail; i++)
623: map[i] = workMap[i];
624: return;
625: }
626: }
627:
628: // shift down the values less than the pivot
629: int center = head;
630: for (int i = head; i < tail; i++) {
631: if (map[i] >= 0)
632: map[center++] = map[i];
633: }
634:
635: for (int i = center; i < tail; i++)
636: map[i] = workMap[i];
637:
638: sort(values, sortList, comparatorList, ascendingList,
639: caseOrder, head, center, map, workMap);
640: sort(values, sortList, comparatorList, ascendingList,
641: caseOrder, center, tail, map, workMap);
642: }
643:
644: /**
645: * Swaps two items in the map.
646: */
647: private void swap(int[] map, int a, int b) {
648: int ka = map[a];
649: int kb = map[b];
650:
651: map[b] = ka;
652: map[a] = kb;
653: }
654:
655: /**
656: * Returns true if the first value is strictly less than the second.
657: */
658: private boolean lessThan(Object[] values, Sort[] sortList,
659: Comparator[] comparatorList, boolean[] ascendingList,
660: int[] caseOrder, int ai, int bi) {
661: int len = sortList.length;
662:
663: for (int i = 0; i < len; i++) {
664: Object a = values[len * ai + i];
665: Object b = values[len * bi + i];
666:
667: int cmp = sortList[i].cmp(a, b, comparatorList[i],
668: ascendingList[i], caseOrder[i]);
669: if (cmp < 0)
670: return true;
671: else if (cmp > 0)
672: return false;
673: }
674:
675: return false;
676: }
677:
678: public void singleNumber(XslWriter out, Node node, Env env,
679: AbstractPattern countPattern, AbstractPattern fromPattern,
680: XslNumberFormat format) throws Exception {
681: if (countPattern == null)
682: countPattern = XPath.parseMatch(node.getNodeName())
683: .getPattern();
684:
685: IntArray numbers = new IntArray();
686: for (; node != null; node = node.getParentNode()) {
687: if (countPattern.match(node, env)) {
688: numbers.add(countPreviousSiblings(node, env,
689: countPattern));
690: break;
691: }
692: if (fromPattern != null && fromPattern.match(node, env))
693: break;
694: }
695: if (fromPattern != null
696: && !findFromAncestor(node, env, fromPattern))
697: numbers.clear();
698:
699: format.format(out, numbers);
700: }
701:
702: public void multiNumber(XslWriter out, Node node, Env env,
703: AbstractPattern countPattern, AbstractPattern fromPattern,
704: XslNumberFormat format) throws Exception {
705: if (countPattern == null)
706: countPattern = XPath.parseMatch(node.getNodeName())
707: .getPattern();
708:
709: IntArray numbers = new IntArray();
710: for (; node != null; node = node.getParentNode()) {
711: if (countPattern.match(node, env))
712: numbers.add(countPreviousSiblings(node, env,
713: countPattern));
714:
715: if (fromPattern != null && fromPattern.match(node, env))
716: break;
717: }
718: if (fromPattern != null
719: && !findFromAncestor(node, env, fromPattern))
720: numbers.clear();
721:
722: format.format(out, numbers);
723: }
724:
725: public void anyNumber(XslWriter out, Node node, Env env,
726: AbstractPattern countPattern, AbstractPattern fromPattern,
727: XslNumberFormat format) throws Exception {
728: if (countPattern == null)
729: countPattern = XPath.parseMatch(node.getNodeName())
730: .getPattern();
731:
732: IntArray numbers = new IntArray();
733: int count = 0;
734: for (; node != null; node = XmlUtil.getPrevious(node)) {
735: if (countPattern.match(node, env))
736: count++;
737:
738: if (fromPattern != null && fromPattern.match(node, env))
739: break;
740: }
741: numbers.add(count);
742: if (fromPattern != null
743: && !findFromAncestor(node, env, fromPattern))
744: numbers.clear();
745:
746: format.format(out, numbers);
747: }
748:
749: public void exprNumber(XslWriter out, Node node, Env env,
750: Expr expr, XslNumberFormat format) throws Exception {
751: IntArray numbers = new IntArray();
752: numbers.add((int) expr.evalNumber(node, env));
753:
754: format.format(out, numbers);
755: }
756:
757: private int countPreviousSiblings(Node node, Env env, String name) {
758: int count = 1;
759: for (node = node.getPreviousSibling(); node != null; node = node
760: .getPreviousSibling()) {
761: if (node.getNodeType() == node.ELEMENT_NODE
762: && node.getNodeName().equals(name))
763: count++;
764: }
765:
766: return count;
767: }
768:
769: private int countPreviousSiblings(Node node, Env env,
770: AbstractPattern pattern) throws XPathException {
771: int count = 1;
772: for (node = node.getPreviousSibling(); node != null; node = node
773: .getPreviousSibling()) {
774: if (pattern.match(node, env))
775: count++;
776: }
777:
778: return count;
779: }
780:
781: private boolean findFromAncestor(Node node, Env env,
782: AbstractPattern pattern) throws XPathException {
783: for (; node != null; node = node.getParentNode())
784: if (pattern.match(node, env))
785: return true;
786:
787: return false;
788: }
789:
790: /**
791: * Strips the spaces from a tree.
792: */
793: void stripSpaces(Node node) {
794: Node child = node.getFirstChild();
795:
796: while (child != null) {
797: Node next = child.getNextSibling();
798:
799: if (child instanceof Element) {
800: stripSpaces(child);
801: } else if (child instanceof Text) {
802: String data = ((Text) child).getData();
803:
804: boolean hasContent = false;
805: for (int i = data.length() - 1; i >= 0; i--) {
806: char ch = data.charAt(i);
807:
808: if (!XmlChar.isWhitespace(ch)) {
809: hasContent = true;
810: break;
811: }
812: }
813:
814: if (!hasContent && isStripSpaces(node)) {
815: node.removeChild(child);
816: }
817: }
818:
819: child = next;
820: }
821: }
822:
823: /**
824: * Returns true if the node is a pure whitespace text node.
825: */
826: boolean isStripSpaces(Node node) {
827: if (_strip == null)
828: return false;
829:
830: for (Node ptr = node; ptr != null; ptr = ptr.getParentNode()) {
831: if (ptr instanceof Element) {
832: Element elt = (Element) ptr;
833: String space = elt.getAttribute("xml:space");
834: if (space != null && space.equals("preserve"))
835: return false;
836: else if (space != null)
837: break;
838: }
839: }
840: String name = node.getNodeName();
841: if (_preserve.get(node.getNodeName()) != null)
842: return false;
843: else if (_strip.get(node.getNodeName()) != null)
844: return true;
845:
846: CauchoNode cnode = (CauchoNode) node;
847: String nsStar = cnode.getPrefix();
848: if (_preservePrefix.get(nsStar) != null)
849: return false;
850: else if (_stripPrefix.get(nsStar) != null)
851: return true;
852:
853: return _strip.get("*") != null;
854: }
855:
856: /**
857: * Merges two template arrays into the final one.
858: */
859: protected static Template[] mergeTemplates(Template[] star,
860: Template[] templates) {
861: Template[] merged = new Template[star.length + templates.length];
862:
863: int i = 0;
864: int j = 0;
865: int k = 0;
866:
867: while (i < star.length && j < templates.length) {
868: if (star[i].compareTo(templates[j]) > 0)
869: merged[k++] = star[i++];
870: else
871: merged[k++] = templates[j++];
872: }
873:
874: for (; i < star.length; i++)
875: merged[k++] = star[i];
876:
877: for (; j < templates.length; j++)
878: merged[k++] = templates[j];
879:
880: return merged;
881: }
882: }
|