1 16 package org.apache.cocoon.serialization; 17 18 import org.apache.avalon.framework.configuration.Configurable; 19 import org.apache.avalon.framework.configuration.Configuration; 20 import org.apache.avalon.framework.configuration.ConfigurationException; 21 import org.apache.avalon.framework.context.Context; 22 import org.apache.avalon.framework.context.ContextException; 23 import org.apache.avalon.framework.context.Contextualizable; 24 import org.apache.cocoon.Constants; 25 import org.apache.cocoon.caching.CacheableProcessingComponent; 26 import org.apache.cocoon.util.ClassUtils; 27 import org.apache.cocoon.util.TraxErrorHandler; 28 import org.apache.cocoon.xml.AbstractXMLPipe; 29 import org.apache.cocoon.xml.XMLConsumer; 30 import org.apache.cocoon.xml.XMLUtils; 31 32 import org.apache.commons.lang.BooleanUtils; 33 import org.apache.excalibur.source.SourceValidity; 34 import org.apache.excalibur.source.impl.validity.NOPValidity; 35 import org.xml.sax.Attributes ; 36 import org.xml.sax.ContentHandler ; 37 import org.xml.sax.SAXException ; 38 import org.xml.sax.ext.LexicalHandler ; 39 import org.xml.sax.helpers.AttributesImpl ; 40 41 import javax.xml.transform.OutputKeys ; 42 import javax.xml.transform.TransformerFactory ; 43 import javax.xml.transform.TransformerException ; 44 import javax.xml.transform.sax.SAXTransformerFactory ; 45 import javax.xml.transform.sax.TransformerHandler ; 46 import javax.xml.transform.stream.StreamResult ; 47 import java.io.IOException ; 48 import java.io.OutputStream ; 49 import java.io.StringWriter ; 50 import java.util.ArrayList ; 51 import java.util.HashMap ; 52 import java.util.List ; 53 import java.util.Map ; 54 import java.util.Properties ; 55 56 63 public abstract class AbstractTextSerializer extends AbstractSerializer 64 implements Configurable, CacheableProcessingComponent, Contextualizable { 65 66 70 private static final Map needsNamespaceCache = new HashMap (); 71 72 75 private SAXTransformerFactory tfactory; 76 77 80 protected final Properties format = new Properties (); 81 82 85 private NamespaceAsAttributes namespacePipe; 86 87 90 private String cachingKey = "1"; 91 92 95 public void setConsumer(XMLConsumer consumer) { 96 if (this.namespacePipe == null) { 97 super.setConsumer(consumer); 98 } else { 99 this.namespacePipe.setConsumer(consumer); 100 super.setConsumer(this.namespacePipe); 101 } 102 } 103 104 107 public void setContentHandler(ContentHandler handler) { 108 if (this.namespacePipe == null) { 109 super.setContentHandler(handler); 110 } else { 111 this.namespacePipe.setContentHandler(handler); 112 super.setContentHandler(this.namespacePipe); 113 } 114 } 115 116 119 public void setLexicalHandler(LexicalHandler handler) { 120 if (this.namespacePipe == null) { 121 super.setLexicalHandler(handler); 122 } else { 123 this.namespacePipe.setLexicalHandler(handler); 124 super.setLexicalHandler(this.namespacePipe); 125 } 126 } 127 128 131 protected SAXTransformerFactory getTransformerFactory() { 132 return tfactory; 133 } 134 135 138 protected TransformerHandler getTransformerHandler() throws TransformerException { 139 return this.getTransformerFactory().newTransformerHandler(); 140 } 141 142 146 public void setOutputStream(OutputStream out) throws IOException { 147 153 super.setOutputStream(out); 158 } 160 161 164 public void contextualize(Context context) throws ContextException { 165 String defaultEncoding = (String )context.get(Constants.CONTEXT_DEFAULT_ENCODING); 166 if (defaultEncoding != null) { 167 this.format.setProperty(OutputKeys.ENCODING, defaultEncoding); 168 } 169 } 170 171 174 public void configure(Configuration conf) throws ConfigurationException { 175 180 String cdataSectionElements = conf.getChild("cdata-section-elements").getValue(null); 182 String dtPublic = conf.getChild("doctype-public").getValue(null); 183 String dtSystem = conf.getChild("doctype-system").getValue(null); 184 String encoding = conf.getChild("encoding").getValue(null); 185 String indent = conf.getChild("indent").getValue(null); 186 String mediaType = conf.getChild("media-type").getValue(null); 187 String method = conf.getChild("method").getValue(null); 188 String omitXMLDeclaration = conf.getChild("omit-xml-declaration").getValue(null); 189 String standAlone = conf.getChild("standalone").getValue(null); 190 String version = conf.getChild("version").getValue(null); 191 192 final StringBuffer buffer = new StringBuffer (); 193 194 if (cdataSectionElements != null) { 195 format.put(OutputKeys.CDATA_SECTION_ELEMENTS, cdataSectionElements); 196 buffer.append(";cdata-section-elements=").append(cdataSectionElements); 197 } 198 if (dtPublic != null) { 199 format.put(OutputKeys.DOCTYPE_PUBLIC, dtPublic); 200 buffer.append(";doctype-public=").append(dtPublic); 201 } 202 if (dtSystem != null) { 203 format.put(OutputKeys.DOCTYPE_SYSTEM, dtSystem); 204 buffer.append(";doctype-system=").append(dtSystem); 205 } 206 if (encoding != null) { 207 format.put(OutputKeys.ENCODING, encoding); 208 buffer.append(";encoding=").append(encoding); 209 } 210 if (indent != null) { 211 format.put(OutputKeys.INDENT, indent); 212 buffer.append(";indent=").append(indent); 213 } 214 if (mediaType != null) { 215 format.put(OutputKeys.MEDIA_TYPE, mediaType); 216 buffer.append(";media-type=").append(mediaType); 217 } 218 if (method != null) { 219 format.put(OutputKeys.METHOD, method); 220 buffer.append(";method=").append(method); 221 } 222 if (omitXMLDeclaration != null) { 223 format.put(OutputKeys.OMIT_XML_DECLARATION, omitXMLDeclaration); 224 buffer.append(";omit-xml-declaration=").append(omitXMLDeclaration); 225 } 226 if (standAlone != null) { 227 format.put(OutputKeys.STANDALONE, standAlone); 228 buffer.append(";standalone=").append(standAlone); 229 } 230 if (version != null) { 231 format.put(OutputKeys.VERSION, version); 232 buffer.append(";version=").append(version); 233 } 234 235 if ( buffer.length() > 0 ) { 236 this.cachingKey = buffer.toString(); 237 } 238 239 String tFactoryClass = conf.getChild("transformer-factory").getValue(null); 240 if (tFactoryClass != null) { 241 try { 242 this.tfactory = (SAXTransformerFactory ) ClassUtils.newInstance(tFactoryClass); 243 if (getLogger().isDebugEnabled()) { 244 getLogger().debug("Using transformer factory " + tFactoryClass); 245 } 246 } catch (Exception e) { 247 throw new ConfigurationException("Cannot load transformer factory " + tFactoryClass, e); 248 } 249 } else { 250 this.tfactory = (SAXTransformerFactory ) TransformerFactory.newInstance(); 252 } 253 tfactory.setErrorListener(new TraxErrorHandler(getLogger())); 254 255 try { 257 if (needsNamespacesAsAttributes()) { 258 this.namespacePipe = new NamespaceAsAttributes(); 260 this.namespacePipe.enableLogging(getLogger()); 261 } 262 } catch (Exception e) { 263 getLogger().warn("Cannot know if transformer needs namespaces attributes - assuming NO.", e); 264 } 265 } 266 267 270 public void recycle() { 271 super.recycle(); 272 273 if (this.namespacePipe != null) { 274 this.namespacePipe.recycle(); 275 } 276 } 277 278 286 public java.io.Serializable getKey() { 287 return this.cachingKey; 288 } 289 290 298 public SourceValidity getValidity() { 299 return NOPValidity.SHARED_INSTANCE; 300 } 301 302 310 protected boolean needsNamespacesAsAttributes() throws Exception { 311 312 SAXTransformerFactory factory = getTransformerFactory(); 313 314 Boolean cacheValue = (Boolean ) needsNamespaceCache.get(factory.getClass().getName()); 315 if (cacheValue != null) { 316 return cacheValue.booleanValue(); 317 } else { 318 StringWriter writer = new StringWriter (); 320 321 String uri = "namespaceuri"; 322 String prefix = "nsp"; 323 String check = "xmlns:" + prefix + "='" + uri + "'"; 324 325 TransformerHandler handler = this.getTransformerHandler(); 326 327 handler.getTransformer().setOutputProperties(format); 328 handler.setResult(new StreamResult (writer)); 329 330 handler.startDocument(); 332 handler.startPrefixMapping(prefix, uri); 333 handler.startElement(uri, "element", "", XMLUtils.EMPTY_ATTRIBUTES); 334 handler.endPrefixMapping(prefix); 335 handler.endDocument(); 336 337 String text = writer.toString(); 338 339 boolean needsIt = (text.replace('"', '\'').indexOf(check) == -1); 341 342 String msg = needsIt ? " needs namespace attributes (will be slower)." : " handles correctly namespaces."; 343 344 getLogger().debug("Trax handler " + handler.getClass().getName() + msg); 345 346 needsNamespaceCache.put(factory.getClass().getName(), BooleanUtils.toBooleanObject(needsIt)); 347 348 return needsIt; 349 } 350 } 351 352 354 359 public static class NamespaceAsAttributes extends AbstractXMLPipe { 360 361 364 private List prefixList = new ArrayList (); 365 366 369 private List uriList = new ArrayList (); 370 371 375 private Map uriToPrefixMap = new HashMap (); 376 private Map prefixToUriMap = new HashMap (); 377 378 381 private boolean hasMappings = false; 382 383 public void startDocument() throws SAXException { 384 this.uriToPrefixMap.clear(); 386 this.prefixToUriMap.clear(); 387 clearMappings(); 388 super.startDocument(); 389 } 390 391 395 public void startPrefixMapping(String prefix, String uri) throws SAXException { 396 if (uri != null && !prefix.startsWith("xml")) { 400 this.hasMappings = true; 401 this.prefixList.add(prefix); 402 this.uriList.add(uri); 403 404 if (prefix.length() > 0) { 407 this.uriToPrefixMap.put(uri, prefix + ":"); 408 } else { 409 this.uriToPrefixMap.put(uri, prefix); 410 } 411 412 this.prefixToUriMap.put(prefix, uri); 413 } 414 super.startPrefixMapping(prefix, uri); 415 } 416 417 423 public void startElement(String eltUri, String eltLocalName, String eltQName, Attributes attrs) 424 throws SAXException { 425 426 if (null != eltUri && eltUri.length() != 0 && this.uriToPrefixMap.containsKey(eltUri)) { 428 eltQName = this.uriToPrefixMap.get(eltUri) + eltLocalName; 429 } 430 if (this.hasMappings) { 431 433 AttributesImpl newAttrs = null; 435 436 int mappingCount = this.prefixList.size(); 437 int attrCount = attrs.getLength(); 438 439 for (int mapping = 0; mapping < mappingCount; mapping++) { 440 441 String uri = (String ) this.uriList.get(mapping); 443 String prefix = (String ) this.prefixList.get(mapping); 444 String qName = prefix.equals("") ? "xmlns" : ("xmlns:" + prefix); 445 446 boolean found = false; 448 for (int attr = 0; attr < attrCount; attr++) { 449 if (qName.equals(attrs.getQName(attr))) { 450 if (!uri.equals(attrs.getValue(attr))) { 452 getLogger().error("URI in prefix mapping and attribute do not match : '" 453 + uri + "' - '" + attrs.getURI(attr) + "'"); 454 throw new SAXException ("URI in prefix mapping and attribute do not match"); 455 } 456 found = true; 457 break; 458 } 459 } 460 461 if (!found) { 462 if (newAttrs == null) { 464 if (attrCount == 0) { 467 newAttrs = new AttributesImpl (); 468 } else { 469 newAttrs = new AttributesImpl (attrs); 470 } 471 } 472 473 if (prefix.equals("")) { 474 newAttrs.addAttribute(Constants.XML_NAMESPACE_URI, "xmlns", "xmlns", "CDATA", uri); 475 } else { 476 newAttrs.addAttribute(Constants.XML_NAMESPACE_URI, prefix, qName, "CDATA", uri); 477 } 478 } 479 } 481 clearMappings(); 483 484 super.startElement(eltUri, eltLocalName, eltQName, newAttrs == null ? attrs : newAttrs); 486 } else { 487 super.startElement(eltUri, eltLocalName, eltQName, attrs); 489 } 490 } 491 492 493 497 public void endElement(String eltUri, String eltLocalName, String eltQName) throws SAXException { 498 if (null != eltUri && eltUri.length() != 0 && this.uriToPrefixMap.containsKey(eltUri)) { 500 eltQName = this.uriToPrefixMap.get(eltUri) + eltLocalName; 501 } 502 super.endElement(eltUri, eltLocalName, eltQName); 503 } 504 505 509 public void endPrefixMapping(String prefix) throws SAXException { 510 if (this.prefixToUriMap.containsKey(prefix)) { 514 this.uriToPrefixMap.remove(this.prefixToUriMap.get(prefix)); 515 this.prefixToUriMap.remove(prefix); 516 } 517 518 if (hasMappings) { 519 int pos = prefixList.lastIndexOf(prefix); 526 if (pos != -1) { 527 prefixList.remove(pos); 528 uriList.remove(pos); 529 } 530 } 531 532 super.endPrefixMapping(prefix); 533 } 534 535 538 public void endDocument() throws SAXException { 539 this.uriToPrefixMap.clear(); 541 this.prefixToUriMap.clear(); 542 clearMappings(); 543 super.endDocument(); 544 } 545 546 private void clearMappings() { 547 this.hasMappings = false; 548 this.prefixList.clear(); 549 this.uriList.clear(); 550 } 551 } 552 553 556 public void endDocument() throws SAXException { 557 super.endDocument(); 558 559 } 566 567 } 568 | Popular Tags |