Clover coverage report - Clover results for XOM 1.2d1
Coverage timestamp: Wed Feb 8 2006 08:31:33 EST
file stats: LOC: 373   Methods: 22
NCLOC: 245   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
XSLTHandler.java 92.9% 92.9% 59.1% 88.9%
coverage coverage
 1    /* Copyright 2002-2005 Elliotte Rusty Harold
 2   
 3    This library is free software; you can redistribute it and/or modify
 4    it under the terms of version 2.1 of the GNU Lesser General Public
 5    License as published by the Free Software Foundation.
 6   
 7    This library is distributed in the hope that it will be useful,
 8    but WITHOUT ANY WARRANTY; without even the implied warranty of
 9    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 10    GNU Lesser General Public License for more details.
 11   
 12    You should have received a copy of the GNU Lesser General Public
 13    License along with this library; if not, write to the
 14    Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 15    Boston, MA 02111-1307 USA
 16   
 17    You can contact Elliotte Rusty Harold by sending e-mail to
 18    elharo@metalab.unc.edu. Please include the word "XOM" in the
 19    subject line. The XOM home page is located at http://www.xom.nu/
 20    */
 21   
 22    package nu.xom.xslt;
 23   
 24    import java.util.ArrayList;
 25   
 26    import nu.xom.Attribute;
 27    import nu.xom.Element;
 28    import nu.xom.NamespaceConflictException;
 29    import nu.xom.Node;
 30    import nu.xom.NodeFactory;
 31    import nu.xom.Nodes;
 32    import nu.xom.ParentNode;
 33    import nu.xom.XMLException;
 34   
 35    import org.xml.sax.Attributes;
 36    import org.xml.sax.ContentHandler;
 37    import org.xml.sax.Locator;
 38    import org.xml.sax.SAXException;
 39    import org.xml.sax.ext.LexicalHandler;
 40    import org.xml.sax.helpers.AttributesImpl;
 41   
 42    /**
 43    * <p>
 44    * As currently designed this class is non-public and never
 45    * reused. A new XSLTHandler is used for each call to transform().
 46    * Therefore we do not actually need to reset. This is important
 47    * because some XSLT processors call startDocument() and
 48    * endDocument() and some don't, especially when the output
 49    * of a transform is a document fragment.
 50    * </p>
 51    *
 52    * @author Elliotte Rusty Harold
 53    * @version 1.2b1
 54    *
 55    */
 56    class XSLTHandler
 57    implements ContentHandler, LexicalHandler {
 58   
 59    private final Nodes result;
 60    private final ArrayList parents;
 61    private final NodeFactory factory;
 62    private StringBuffer buffer;
 63   
 64   
 65  1405 XSLTHandler(NodeFactory factory) {
 66  1405 this.factory = factory;
 67  1405 result = new Nodes();
 68  1405 parents = new ArrayList();
 69  1405 buffer = new StringBuffer();
 70    }
 71   
 72   
 73  407 Nodes getResult() {
 74  407 flushText(); // to handle case where there's no endDocument
 75  407 return result;
 76    }
 77   
 78   
 79  0 public void setDocumentLocator(Locator locator) {}
 80  424 public void startDocument() {}
 81  407 public void endDocument() {}
 82   
 83    private Element current;
 84   
 85  105440 public void startElement(String namespaceURI, String localName,
 86    String qualifiedName, Attributes attributes) {
 87   
 88  105440 flushText();
 89   
 90    // mix namespaceDeclarations into attributes
 91  105440 int length = attributes.getLength();
 92  105440 for (int i = 0; i < length; i++) {
 93  598 namespaceDeclarations.addAttribute(
 94    attributes.getURI(i),
 95    attributes.getLocalName(i),
 96    attributes.getQName(i),
 97    attributes.getType(i),
 98    attributes.getValue(i)
 99    );
 100    }
 101  105440 attributes = namespaceDeclarations;
 102   
 103  105440 Element element
 104    = factory.startMakingElement(qualifiedName, namespaceURI);
 105   
 106  105438 if (parents.isEmpty()) {
 107    // won't append until finishMakingElement()
 108  1156 current = element;
 109    }
 110    else {
 111  104282 ParentNode parent = (ParentNode) parents.get(parents.size()-1);
 112  104282 parent.appendChild(element);
 113    }
 114  105438 parents.add(element);
 115   
 116    // Attach the attributes
 117  105438 length = attributes.getLength();
 118  105438 for (int i = 0; i < length; i++) {
 119  721 String attributeName = attributes.getQName(i);
 120    // handle namespaces later
 121  721 if (attributeName.equals("xmlns")
 122    || attributeName.startsWith("xmlns:")) {
 123  248 continue;
 124    }
 125  473 String namespace = attributes.getURI(i);
 126  473 String value = attributes.getValue(i);
 127   
 128  473 Nodes nodes = factory.makeAttribute(
 129    attributeName,
 130    namespace,
 131    value,
 132    Attribute.Type.UNDECLARED
 133    );
 134  468 int size = nodes.size();
 135  468 for (int j=0; j < size; j++) {
 136  468 Node node = nodes.get(j);
 137  468 if (node instanceof Attribute) {
 138  465 Attribute attribute = (Attribute) node;
 139  465 while (true) {
 140  469 try {
 141  469 element.addAttribute(attribute);
 142  465 break;
 143    }
 144    catch (NamespaceConflictException ex) {
 145    // According to section 7.1.3 of XSLT spec we
 146    // need to remap the prefix here; ideally the
 147    // XSLT processor should do this but many don't
 148    // for instance, see
 149    // http://nagoya.apache.org/bugzilla/show_bug.cgi?id=5389
 150  4 attribute.setNamespace(
 151    "p"+attribute.getNamespacePrefix(),
 152    attribute.getNamespaceURI()
 153    );
 154    }
 155    }
 156    }
 157    else {
 158  3 element.appendChild(node);
 159    }
 160    }
 161    }
 162   
 163    // Attach any additional namespaces
 164  105433 for (int i = 0; i < length; i++) {
 165  706 String qName = attributes.getQName(i);
 166  706 if (qName.startsWith("xmlns:")) {
 167  208 String namespaceName = attributes.getValue(i);
 168  208 String namespacePrefix = qName.substring(6);
 169  208 String currentValue
 170    = element.getNamespaceURI(namespacePrefix);
 171  208 if (!namespaceName.equals(currentValue)) {
 172  87 try {
 173  87 element.addNamespaceDeclaration(
 174    namespacePrefix, namespaceName);
 175    }
 176    catch (NamespaceConflictException ex) {
 177    // skip it; see attribset40 test case;
 178    // This should only happen if an attribute's
 179    // namespace conflicts with the element's
 180    // namespace; in which case we already remapped
 181    // the prefix when adding the attribute
 182    }
 183    }
 184    }
 185  498 else if (qName.equals("xmlns")) {
 186  30 String namespaceName = attributes.getValue(i);
 187  30 if (namespaceName == null) { // Work around a Xalan bug
 188  2 namespaceName = "";
 189    }
 190  30 String namespacePrefix = "";
 191  30 String currentValue
 192    = element.getNamespaceURI(namespacePrefix);
 193  30 if (!namespaceName.equals(currentValue)) {
 194  1 try {
 195  1 element.addNamespaceDeclaration(namespacePrefix,
 196    namespaceName);
 197    }
 198    catch (NamespaceConflictException ex) {
 199    // work around Bug 27937 in Xalan
 200    // http://nagoya.apache.org/bugzilla/show_bug.cgi?id=27937
 201    // Xalan sometimes use the XML namespace
 202    // http://www.w3.org/XML/1998/namespace where it
 203    // should use the empty string
 204  0 if ("http://www.w3.org/XML/1998/namespace".equals(namespaceName)
 205    && "".equals(namespacePrefix)) {
 206  0 element.addNamespaceDeclaration("", "");
 207    }
 208    }
 209    }
 210    }
 211    }
 212   
 213    // reset namespaceDeclarations
 214  105433 namespaceDeclarations = new AttributesImpl();
 215   
 216    }
 217   
 218   
 219  105430 public void endElement(String namespaceURI, String localName,
 220    String qualifiedName) {
 221   
 222  105430 flushText();
 223  105430 Element element = (Element) parents.remove(parents.size()-1);
 224  105430 if (parents.isEmpty()) {
 225  1150 Nodes nodes = factory.finishMakingElement(current);
 226  1150 for (int i = 0; i < nodes.size(); i++) {
 227  1151 result.append(nodes.get(i));
 228    }
 229  1150 current = null;
 230    }
 231    else {
 232  104280 Nodes nodes = factory.finishMakingElement(element);
 233  104280 ParentNode parent = element.getParent();
 234  104280 element.detach();
 235  104280 for (int i = 0; i < nodes.size(); i++) {
 236  104282 Node node = nodes.get(i);
 237  104282 if (node instanceof Attribute) {
 238  2 ((Element) parent).addAttribute((Attribute) node);
 239    }
 240    else {
 241  104280 parent.appendChild(node);
 242    }
 243    }
 244    }
 245   
 246    }
 247   
 248   
 249  86355 public void characters(char[] text, int start, int length) {
 250  86355 buffer.append(text, start, length);
 251    }
 252   
 253   
 254    // accumulate all text that's in the buffer into a text node
 255  211357 private void flushText() {
 256  211357 if (buffer.length() > 0) {
 257  61738 Nodes text = factory.makeText(buffer.toString());
 258  61738 addToResultTree(text);
 259  61738 buffer = new StringBuffer();
 260    }
 261    }
 262   
 263   
 264  0 public void ignorableWhitespace(char[] text, int start, int length) {
 265  0 characters(text, start, length);
 266    }
 267   
 268   
 269  39 public void processingInstruction(String target, String data)
 270    throws SAXException {
 271   
 272    // See http://saxon.sourceforge.net/saxon6.5.2/extensibility.html#Writing-output-filters
 273    // to understand why we need to work around Saxon here
 274  39 if ("saxon:warning".equals(target)) {
 275  0 throw new SAXException("continue");
 276    }
 277  39 else if ("javax.xml.transform.disable-output-escaping".equals(target)
 278    || "javax.xml.transform.enable-output-escaping".equals(target)) {
 279    // Xalan workaround
 280  14 return;
 281    }
 282   
 283  25 flushText();
 284    // Xalan fails to split the ?> before passing such data to
 285    // this method, so we have to do it
 286  25 int position = data.indexOf("?>");
 287  25 while (position != -1) {
 288  0 data = data.substring(0, position) + "? >" + data.substring(position+2);
 289  0 position = data.indexOf("?>");
 290    }
 291  25 Nodes nodes = factory.makeProcessingInstruction(target, data);
 292  23 addToResultTree(nodes);
 293   
 294    }
 295   
 296   
 297  61816 private void addToResultTree(Nodes nodes) {
 298   
 299  61816 if (parents.isEmpty()) {
 300  409 for (int i = 0; i < nodes.size(); i++) {
 301  384 result.append(nodes.get(i));
 302    }
 303    }
 304    else {
 305  61407 ParentNode parent = (ParentNode) parents.get(parents.size()-1);
 306  61407 for (int i = 0; i < nodes.size(); i++) {
 307  61169 Node node = nodes.get(i);
 308  61169 if (node instanceof Attribute) {
 309  1 ((Element) parent).addAttribute((Attribute) node);
 310    }
 311    else {
 312  61168 parent.appendChild(node);
 313    }
 314    }
 315    }
 316   
 317    }
 318   
 319   
 320  119 public void endPrefixMapping(String prefix) {}
 321   
 322   
 323    private AttributesImpl namespaceDeclarations = new AttributesImpl();
 324   
 325  127 public void startPrefixMapping(String prefix, String uri) {
 326   
 327  127 if ("".equals(prefix)) {
 328  18 namespaceDeclarations.addAttribute("", "xmlns", "xmlns", "CDATA", uri);
 329    }
 330    else {
 331  109 namespaceDeclarations.addAttribute("", "xmlns:" + prefix, "xmlns:" + prefix, "CDATA", uri);
 332    }
 333   
 334    }
 335   
 336   
 337  0 public void skippedEntity(String name) {
 338  0 flushText();
 339  0 throw new XMLException("Could not resolve entity " + name);
 340    }
 341   
 342   
 343    // LexicalHandler events
 344  0 public void startCDATA() {}
 345  0 public void endCDATA() {}
 346    // ???? For Bill Pugh, would this method be called if xsl:output
 347    // specifies a Doctype? If it is, then we coudl add a DOCTYPE to the result tree.
 348  0 public void startDTD(String name, String publicID, String systemID) {}
 349  0 public void endDTD() {}
 350  0 public void startEntity(String name) {}
 351  0 public void endEntity(String name) {}
 352   
 353   
 354  55 public void comment(char[] text, int start, int length) {
 355   
 356  55 flushText();
 357   
 358  55 String data = new String(text, start, length);
 359    // Xalan should add spaces as necessary to split up double hyphens
 360    // in comments but it doesn't
 361  55 int position = data.indexOf("--");
 362  55 while (position != -1) {
 363  14 data = data.substring(0, position) + "- -" + data.substring(position+2);
 364  14 position = data.indexOf("--");
 365    }
 366  5 if (data.endsWith("-")) data += ' ';
 367   
 368  55 addToResultTree(factory.makeComment(data));
 369   
 370    }
 371   
 372   
 373    }