001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.xml;
018:
019: import java.io.StringWriter;
020: import java.util.ArrayList;
021: import java.util.Collection;
022: import java.util.Enumeration;
023: import java.util.Iterator;
024: import java.util.Properties;
025:
026: import javax.xml.transform.OutputKeys;
027: import javax.xml.transform.TransformerException;
028: import javax.xml.transform.TransformerFactory;
029: import javax.xml.transform.sax.SAXTransformerFactory;
030: import javax.xml.transform.sax.TransformerHandler;
031: import javax.xml.transform.stream.StreamResult;
032:
033: import org.apache.cocoon.ProcessingException;
034: import org.apache.cocoon.xml.dom.DOMStreamer;
035: import org.w3c.dom.Attr;
036: import org.w3c.dom.Document;
037: import org.w3c.dom.Element;
038: import org.w3c.dom.NamedNodeMap;
039: import org.w3c.dom.Node;
040: import org.xml.sax.Attributes;
041: import org.xml.sax.ContentHandler;
042: import org.xml.sax.SAXException;
043: import org.xml.sax.ext.LexicalHandler;
044:
045: /**
046: * XML utility methods.
047: *
048: * @author <a href="mailto:barozzi@nicolaken.com">Nicola Ken Barozzi</a>
049: * @author <a href="mailto:sylvain@apache.org">Sylvain Wallez</a>
050: * @author <a href="mailto:cziegeler@s-und-n.de">Carsten Ziegeler</a>
051: * @version $Id: XMLUtils.java 433543 2006-08-22 06:22:54Z crossley $
052: */
053: public class XMLUtils {
054:
055: /**
056: * Empty attributes immutable object.
057: */
058: public static final Attributes EMPTY_ATTRIBUTES = new ImmutableAttributesImpl();
059:
060: private static final SAXTransformerFactory FACTORY = (SAXTransformerFactory) TransformerFactory
061: .newInstance();
062: private static final Properties XML_FORMAT = createDefaultPropertiesForXML(false);
063: private static final Properties XML_FORMAT_NODECL = createDefaultPropertiesForXML(true);
064:
065: // FIXME: parent parameter not used anymore
066: // Using parent because some dom implementations like jtidy are bugged,
067: // cannot get parent or delete child
068: public static void stripDuplicateAttributes(Node node, Node parent) {
069:
070: // The output depends on the type of the node
071: switch (node.getNodeType()) {
072: case Node.DOCUMENT_NODE: {
073: Document doc = (Document) node;
074: Node child = doc.getFirstChild();
075: while (child != null) {
076: stripDuplicateAttributes(child, node);
077: child = child.getNextSibling();
078: }
079: break;
080: }
081:
082: case Node.ELEMENT_NODE: {
083: Element elt = (Element) node;
084: NamedNodeMap attrs = elt.getAttributes();
085:
086: ArrayList nodesToRemove = new ArrayList();
087: int nodesToRemoveNum = 0;
088:
089: for (int i = 0; i < attrs.getLength(); i++) {
090: final Node a = attrs.item(i);
091:
092: for (int j = 0; j < attrs.getLength(); j++) {
093: final Node b = attrs.item(j);
094:
095: //if there are two attributes with same name
096: if (i != j
097: && (a.getNodeName().equals(b.getNodeName()))) {
098: nodesToRemove.add(b);
099: nodesToRemoveNum++;
100: }
101: }
102: }
103:
104: for (int i = 0; i < nodesToRemoveNum; i++) {
105: Attr nodeToDelete = (Attr) nodesToRemove.get(i);
106: Element nodeToDeleteParent = (Element) node; //nodeToDelete.getParentNode();
107: nodeToDeleteParent.removeAttributeNode(nodeToDelete);
108: }
109:
110: nodesToRemove.clear();
111:
112: Node child = elt.getFirstChild();
113: while (child != null) {
114: stripDuplicateAttributes(child, node);
115: child = child.getNextSibling();
116: }
117:
118: break;
119: }
120:
121: default:
122: //do nothing
123: break;
124: }
125: }
126:
127: /**
128: * Get an <code>XMLConsumer</code> from a <code>ContentHandler</code> and
129: * a <code>LexicalHandler</code>. If the content handler is already an
130: * <code>XMLConsumer</code>, it is returned as is, otherwise it is wrapped
131: * in an <code>XMLConsumer</code> with the lexical handler.
132: *
133: * @param ch the content handler, which should not be <code>null</code>
134: * @param lh the lexical handler, which can be <code>null</code>
135: * @return an <code>XMLConsumer</code> for <code>ch</code> an <code>lh</code>
136: */
137: public static XMLConsumer getConsumer(ContentHandler ch,
138: LexicalHandler lh) {
139: if (ch instanceof XMLConsumer) {
140: return (XMLConsumer) ch;
141: } else {
142: if (lh == null && ch instanceof LexicalHandler) {
143: lh = (LexicalHandler) ch;
144: }
145: return new ContentHandlerWrapper(ch, lh);
146: }
147: }
148:
149: /**
150: * Get an <code>XMLConsumer</code> from <code>ContentHandler</code>. If the
151: * content handler is already an <code>XMLConsumer</code>, it is returned as
152: * is, otherwise it is wrapped in an <code>XMLConsumer</code>.
153: *
154: * @param ch the content handler, which should not be <code>null</code>
155: * @return an <code>XMLConsumer</code> for <code>ch</code>
156: */
157: public static XMLConsumer getConsumer(ContentHandler ch) {
158: return getConsumer(ch, null);
159: }
160:
161: /**
162: * Serialize a DOM node to a String.
163: * The defaultSerializeToXMLFormat() is used to format the serialized xml.
164: * @deprecated use serializeNode(Node, Properties) instead
165: */
166: public static String serializeNodeToXML(Node node)
167: throws ProcessingException {
168: return serializeNode(node, XMLUtils
169: .defaultSerializeToXMLFormat());
170: }
171:
172: /**
173: * This is the default properties set used to serialize xml.
174: * It is used by the serializeNodeToXML() method.
175: * The format is as follows:
176: * Method: xml
177: * Encoding: ISO-8859-1
178: * Omit xml declaration: no
179: * Indent: yes
180: * @deprecated Use createPropertiesForXML(false) instead and add the encoding
181: */
182: public static Properties defaultSerializeToXMLFormat() {
183: return defaultSerializeToXMLFormat(false);
184: }
185:
186: /**
187: * This is the default properties set used to serialize xml.
188: * It is used by the serializeNodeToXML() method.
189: * The omit xml declaration property can be controlled by the flag.
190: * Method: xml
191: * Encoding: ISO-8859-1
192: * Omit xml declaration: according to the flag
193: * Indent: yes
194: * @deprecated Use createPropertiesForXML(boolean) instead and add the encoding
195: */
196: public static Properties defaultSerializeToXMLFormat(
197: boolean omitXMLDeclaration) {
198: final Properties format = createPropertiesForXML(omitXMLDeclaration);
199: format.put(OutputKeys.ENCODING, "ISO-8859-1");
200: return format;
201: }
202:
203: /**
204: * Method for static initializer
205: */
206: private static Properties createDefaultPropertiesForXML(
207: boolean omitXMLDeclaration) {
208: final Properties format = new Properties();
209: format.put(OutputKeys.METHOD, "xml");
210: format.put(OutputKeys.OMIT_XML_DECLARATION,
211: (omitXMLDeclaration ? "yes" : "no"));
212: format.put(OutputKeys.INDENT, "yes");
213: return format;
214: }
215:
216: /**
217: * Create a new properties set for serializing xml.
218: * The omit xml declaration property can be controlled by the flag.
219: *
220: * <ul>
221: * <li>Method: xml
222: * <li>Omit xml declaration: according to the flag
223: * <li>Indent: yes
224: * </ul>
225: */
226: public static Properties createPropertiesForXML(
227: boolean omitXMLDeclaration) {
228: /* Properties passed as parameters to the Properties constructor become "default properties".
229: But Xalan does not use the default properties, so they are lost.
230: Therefore, we must promote them to "set properties".
231: */
232: Properties propertiesForXML = new Properties(
233: omitXMLDeclaration ? XML_FORMAT_NODECL : XML_FORMAT);
234: for (Enumeration e = propertiesForXML.propertyNames(); e
235: .hasMoreElements();) {
236: String propertyName = (String) e.nextElement();
237: propertiesForXML.setProperty(propertyName, propertiesForXML
238: .getProperty(propertyName, ""));
239: }
240: return propertiesForXML;
241: }
242:
243: /**
244: * Serialize a DOM node into a string using format created by
245: * <code>createPropertiesForXML(false)</code>.
246: *
247: * @see #createPropertiesForXML
248: */
249: public static String serializeNode(Node node)
250: throws ProcessingException {
251: // Don't create new properties as we do not intend to modify defaults.
252: return serializeNode(node, XML_FORMAT);
253: }
254:
255: /**
256: * Serialize a DOM node into a string.
257: * If the node is null the empty string is returned.
258: *
259: * @param format The format of the output to be used by SAX transformer.
260: * @see OutputKeys
261: */
262: public static String serializeNode(Node node, Properties format)
263: throws ProcessingException {
264:
265: try {
266: if (node == null) {
267: return "";
268: }
269:
270: StringWriter writer = new StringWriter();
271: TransformerHandler transformerHandler;
272: transformerHandler = FACTORY.newTransformerHandler();
273: transformerHandler.getTransformer().setOutputProperties(
274: format);
275: transformerHandler.setResult(new StreamResult(writer));
276: if (node.getNodeType() != Node.DOCUMENT_NODE) {
277: transformerHandler.startDocument();
278: }
279: DOMStreamer domStreamer = new DOMStreamer(
280: transformerHandler, transformerHandler);
281: domStreamer.stream(node);
282: if (node.getNodeType() != Node.DOCUMENT_NODE) {
283: transformerHandler.endDocument();
284: }
285:
286: return writer.toString();
287: } catch (TransformerException e) {
288: throw new ProcessingException("TransformerException: " + e,
289: e);
290: } catch (SAXException e) {
291: throw new ProcessingException(
292: "SAXException while streaming DOM node to SAX: "
293: + e, e);
294: }
295: }
296:
297: /**
298: * Serialize a XMLizable into a string.
299: * If the object is null the empty string is returned.
300: *
301: * @param format The format of the output to be used by SAX transformer.
302: * @see OutputKeys
303: */
304: public static String serialize(
305: org.apache.excalibur.xml.sax.XMLizable xml,
306: Properties format) throws ProcessingException {
307:
308: try {
309: if (xml == null) {
310: return "";
311: }
312:
313: StringWriter writer = new StringWriter();
314: TransformerHandler transformerHandler;
315: transformerHandler = FACTORY.newTransformerHandler();
316: transformerHandler.getTransformer().setOutputProperties(
317: format);
318: transformerHandler.setResult(new StreamResult(writer));
319: transformerHandler.startDocument();
320: xml.toSAX(new EmbeddedXMLPipe(transformerHandler));
321: transformerHandler.endDocument();
322:
323: return writer.toString();
324: } catch (TransformerException e) {
325: throw new ProcessingException("TransformerException: " + e,
326: e);
327: } catch (SAXException e) {
328: throw new ProcessingException(
329: "SAXException while streaming DOM node to SAX: "
330: + e, e);
331: }
332: }
333:
334: /**
335: * Add string data
336: *
337: * @param contentHandler The SAX content handler
338: * @param data The string data
339: */
340: public static void data(ContentHandler contentHandler, String data)
341: throws SAXException {
342:
343: contentHandler.characters(data.toCharArray(), 0, data.length());
344: }
345:
346: /**
347: * Implementation of <xsp:expr> for <code>String</code> :
348: * outputs characters representing the value.
349: *
350: * @param contentHandler the SAX content handler
351: * @param text the value
352: */
353: public static void valueOf(ContentHandler contentHandler,
354: String text) throws SAXException {
355:
356: if (text != null) {
357: data(contentHandler, text);
358: }
359: }
360:
361: /**
362: * Implementation of <xsp:expr> for <code>XMLizable</code> :
363: * outputs the value by calling <code>v.toSax(contentHandler)</code>.
364: *
365: * @param contentHandler the SAX content handler
366: * @param v the XML fragment
367: */
368: public static void valueOf(ContentHandler contentHandler,
369: org.apache.excalibur.xml.sax.XMLizable v)
370: throws SAXException {
371:
372: if (v != null) {
373: v.toSAX(contentHandler);
374: }
375: }
376:
377: /**
378: * Implementation of <xsp:expr> for <code>org.w3c.dom.Node</code> :
379: * converts the Node to a SAX event stream.
380: *
381: * @param contentHandler the SAX content handler
382: * @param v the value
383: */
384: public static void valueOf(ContentHandler contentHandler, Node v)
385: throws SAXException {
386:
387: if (v != null) {
388: DOMStreamer streamer = new DOMStreamer(contentHandler);
389: if (contentHandler instanceof LexicalHandler) {
390: streamer
391: .setLexicalHandler((LexicalHandler) contentHandler);
392: }
393: streamer.stream(v);
394: }
395: }
396:
397: /**
398: * Implementation of <xsp:expr> for <code>java.util.Collection</code> :
399: * outputs the value by calling <code>xspExpr()</code> on each element of the
400: * collection.
401: *
402: * @param contentHandler the SAX content handler
403: * @param v the XML fragment
404: */
405: public static void valueOf(ContentHandler contentHandler,
406: Collection v) throws SAXException {
407:
408: if (v != null) {
409: Iterator iterator = v.iterator();
410: while (iterator.hasNext()) {
411: valueOf(contentHandler, iterator.next());
412: }
413: }
414: }
415:
416: /**
417: * Implementation of <xsp:expr> for <code>Object</code> depending on its class :
418: * <ul>
419: * <li>if it's an array, call <code>xspExpr()</code> on all its elements,</li>
420: * <li>if it's class has a specific <code>xspExpr()</code>implementation, use it,</li>
421: * <li>else, output it's string representation.</li>
422: * </ul>
423: *
424: * @param contentHandler the SAX content handler
425: * @param v the value
426: */
427: public static void valueOf(ContentHandler contentHandler, Object v)
428: throws SAXException {
429:
430: if (v == null) {
431: return;
432: }
433:
434: // Array: recurse over each element
435: if (v.getClass().isArray()) {
436: Object[] elements = (Object[]) v;
437:
438: for (int i = 0; i < elements.length; i++) {
439: valueOf(contentHandler, elements[i]);
440: }
441: return;
442: }
443:
444: // Check handled object types in case they were not typed in the XSP
445:
446: // XMLizable
447: if (v instanceof org.apache.excalibur.xml.sax.XMLizable) {
448: valueOf(contentHandler,
449: (org.apache.excalibur.xml.sax.XMLizable) v);
450: return;
451: }
452:
453: // Node
454: if (v instanceof Node) {
455: valueOf(contentHandler, (Node) v);
456: return;
457: }
458:
459: // Collection
460: if (v instanceof Collection) {
461: valueOf(contentHandler, (Collection) v);
462: return;
463: }
464:
465: // Give up: hope it's a string or has a meaningful string representation
466: data(contentHandler, String.valueOf(v));
467: }
468:
469: /**
470: * Create a start and endElement with a empty Namespace and without Attributes
471: *
472: * @param localName The local name (without prefix)
473: * @exception org.xml.sax.SAXException Any SAX exception, possibly
474: * wrapping another exception.
475: * @see #endElement(ContentHandler, String)
476: */
477: public static void createElement(ContentHandler contentHandler,
478: String localName) throws SAXException {
479:
480: startElement(contentHandler, localName);
481: endElement(contentHandler, localName);
482: }
483:
484: /**
485: * Create a start and endElement with a empty Namespace and without Attributes
486: * The content of the Element is set to the stringValue parameter
487: *
488: * @param localName The local name (without prefix)
489: * @param stringValue The content of the Element
490: * @exception org.xml.sax.SAXException Any SAX exception, possibly
491: * wrapping another exception.
492: * @see #endElement(ContentHandler, String)
493: */
494: public static void createElement(ContentHandler contentHandler,
495: String localName, String stringValue) throws SAXException {
496:
497: startElement(contentHandler, localName);
498: data(contentHandler, stringValue);
499: endElement(contentHandler, localName);
500: }
501:
502: /**
503: * Create a start and endElement with a empty Namespace
504: *
505: * @param localName The local name (without prefix)
506: * @param atts The attributes attached to the element. If
507: * there are no attributes, it shall be an empty
508: * Attributes object.
509: * @exception org.xml.sax.SAXException Any SAX exception, possibly
510: * wrapping another exception.
511: * @see #endElement(ContentHandler, String)
512: * @see org.xml.sax.Attributes
513: */
514: public static void createElement(ContentHandler contentHandler,
515: String localName, Attributes atts) throws SAXException {
516:
517: startElement(contentHandler, localName, atts);
518: endElement(contentHandler, localName);
519: }
520:
521: /**
522: * Create a start and endElement with a empty Namespace
523: * The content of the Element is set to the stringValue parameter
524: *
525: * @param localName The local name (without prefix)
526: * @param atts The attributes attached to the element. If
527: * there are no attributes, it shall be an empty
528: * Attributes object.
529: * @param stringValue The content of the Element
530: * @exception org.xml.sax.SAXException Any SAX exception, possibly
531: * wrapping another exception.
532: * @see #endElement(ContentHandler, String)
533: * @see org.xml.sax.Attributes
534: */
535: public static void createElement(ContentHandler contentHandler,
536: String localName, Attributes atts, String stringValue)
537: throws SAXException {
538:
539: startElement(contentHandler, localName, atts);
540: data(contentHandler, stringValue);
541: endElement(contentHandler, localName);
542: }
543:
544: /**
545: * Create a start and endElement without Attributes
546: *
547: * @param localName The local name (without prefix)
548: * @exception org.xml.sax.SAXException Any SAX exception, possibly
549: * wrapping another exception.
550: * @see #endElement(ContentHandler, String)
551: */
552: public static void createElementNS(ContentHandler contentHandler,
553: String namespaceURI, String localName) throws SAXException {
554:
555: startElement(contentHandler, namespaceURI, localName);
556: endElement(contentHandler, namespaceURI, localName);
557: }
558:
559: /**
560: * Create a start and endElement without Attributes
561: * The content of the Element is set to the stringValue parameter
562: *
563: * @param localName The local name (without prefix)
564: * @param stringValue The content of the Element
565: * @exception org.xml.sax.SAXException Any SAX exception, possibly
566: * wrapping another exception.
567: * @see #endElement(ContentHandler, String)
568: */
569: public static void createElementNS(ContentHandler contentHandler,
570: String namespaceURI, String localName, String stringValue)
571: throws SAXException {
572:
573: startElement(contentHandler, namespaceURI, localName);
574: data(contentHandler, stringValue);
575: endElement(contentHandler, namespaceURI, localName);
576: }
577:
578: /**
579: * Create a start and endElement
580: *
581: * @param localName The local name (without prefix)
582: * @param atts The attributes attached to the element. If
583: * there are no attributes, it shall be an empty
584: * Attributes object.
585: * @exception org.xml.sax.SAXException Any SAX exception, possibly
586: * wrapping another exception.
587: * @see #endElement(ContentHandler, String)
588: * @see org.xml.sax.Attributes
589: */
590: public static void createElementNS(ContentHandler contentHandler,
591: String namespaceURI, String localName, Attributes atts)
592: throws SAXException {
593:
594: startElement(contentHandler, namespaceURI, localName, atts);
595: endElement(contentHandler, namespaceURI, localName);
596: }
597:
598: /**
599: * Create a start and endElement with a empty Namespace
600: * The content of the Element is set to the stringValue parameter
601: *
602: * @param localName The local name (without prefix)
603: * @param atts The attributes attached to the element. If
604: * there are no attributes, it shall be an empty
605: * Attributes object.
606: * @param stringValue The content of the Element
607: * @exception org.xml.sax.SAXException Any SAX exception, possibly
608: * wrapping another exception.
609: * @see #endElement(ContentHandler, String)
610: * @see org.xml.sax.Attributes
611: */
612: public static void createElementNS(ContentHandler contentHandler,
613: String namespaceURI, String localName, Attributes atts,
614: String stringValue) throws SAXException {
615:
616: startElement(contentHandler, namespaceURI, localName, atts);
617: data(contentHandler, stringValue);
618: endElement(contentHandler, namespaceURI, localName);
619: }
620:
621: /**
622: * Create endElement with empty Namespace
623: *
624: * <p>For information on the names, see startElement.</p>
625: *
626: * @param localName The local name (without prefix)
627: * @exception org.xml.sax.SAXException Any SAX exception, possibly
628: * wrapping another exception.
629: */
630: public static void endElement(ContentHandler contentHandler,
631: String localName) throws SAXException {
632:
633: contentHandler.endElement("", localName, localName);
634: }
635:
636: /**
637: * Create endElement
638: * Prefix must be mapped to empty String
639: *
640: * <p>For information on the names, see startElement.</p>
641: *
642: * @param localName The local name (without prefix)
643: * @exception org.xml.sax.SAXException Any SAX exception, possibly
644: * wrapping another exception.
645: */
646: public static void endElement(ContentHandler contentHandler,
647: String namespaceURI, String localName) throws SAXException {
648:
649: contentHandler.endElement(namespaceURI, localName, localName);
650: }
651:
652: /**
653: * Create a startElement with a empty Namespace and without Attributes
654: *
655: * @param localName The local name (without prefix)
656: * @exception org.xml.sax.SAXException Any SAX exception, possibly
657: * wrapping another exception.
658: * @see #endElement(ContentHandler, String)
659: */
660: public static void startElement(ContentHandler contentHandler,
661: String localName) throws SAXException {
662:
663: contentHandler.startElement("", localName, localName,
664: EMPTY_ATTRIBUTES);
665: }
666:
667: /**
668: * Create a startElement without Attributes
669: * Prefix must be mapped to empty String
670: *
671: * @param namespaceURI The Namespace URI
672: * @param localName The local name (without prefix)
673: * @exception org.xml.sax.SAXException Any SAX exception, possibly
674: * wrapping another exception.
675: * @see #endElement(ContentHandler, String)
676: */
677: public static void startElement(ContentHandler contentHandler,
678: String namespaceURI, String localName) throws SAXException {
679:
680: contentHandler.startElement(namespaceURI, localName, localName,
681: EMPTY_ATTRIBUTES);
682: }
683:
684: /**
685: * Create a startElement with a empty Namespace
686: *
687: * @param localName The local name (without prefix)
688: * @param atts The attributes attached to the element. If
689: * there are no attributes, it shall be an empty
690: * Attributes object.
691: * @exception org.xml.sax.SAXException Any SAX exception, possibly
692: * wrapping another exception.
693: * @see #endElement(ContentHandler, String)
694: * @see org.xml.sax.Attributes
695: */
696: public static void startElement(ContentHandler contentHandler,
697: String localName, Attributes atts) throws SAXException {
698:
699: contentHandler.startElement("", localName, localName, atts);
700: }
701:
702: /**
703: * Create a startElement with a empty Namespace
704: * Prefix must be mapped to empty String
705: *
706: * @param namespaceURI The Namespace URI
707: * @param localName The local name (without prefix)
708: * @param atts The attributes attached to the element. If
709: * there are no attributes, it shall be an empty
710: * Attributes object.
711: * @exception org.xml.sax.SAXException Any SAX exception, possibly
712: * wrapping another exception.
713: * @see #endElement(ContentHandler, String)
714: * @see org.xml.sax.Attributes
715: */
716: public static void startElement(ContentHandler contentHandler,
717: String namespaceURI, String localName, Attributes atts)
718: throws SAXException {
719:
720: contentHandler.startElement(namespaceURI, localName, localName,
721: atts);
722: }
723: }
|