001: /*
002: Copyright (C) 2002-2007 MySQL AB
003:
004: This program is free software; you can redistribute it and/or modify
005: it under the terms of version 2 of the GNU General Public License as
006: published by the Free Software Foundation.
007:
008: There are special exceptions to the terms and conditions of the GPL
009: as it is applied to this software. View the full text of the
010: exception in file EXCEPTIONS-CONNECTOR-J in the directory of this
011: software distribution.
012:
013: This program is distributed in the hope that it will be useful,
014: but WITHOUT ANY WARRANTY; without even the implied warranty of
015: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: GNU General Public License for more details.
017:
018: You should have received a copy of the GNU General Public License
019: along with this program; if not, write to the Free Software
020: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
021:
022: */
023:
024: package com.mysql.jdbc;
025:
026: import java.io.ByteArrayInputStream;
027: import java.io.ByteArrayOutputStream;
028: import java.io.InputStream;
029: import java.io.IOException;
030: import java.io.OutputStream;
031: import java.io.Reader;
032: import java.io.StringReader;
033: import java.io.StringWriter;
034: import java.io.UnsupportedEncodingException;
035: import java.io.Writer;
036: import java.sql.SQLException;
037: import java.sql.SQLFeatureNotSupportedException;
038: import java.sql.SQLXML;
039:
040: import javax.xml.transform.Transformer;
041: import javax.xml.transform.TransformerFactory;
042: import javax.xml.parsers.DocumentBuilder;
043: import javax.xml.parsers.DocumentBuilderFactory;
044: import javax.xml.parsers.FactoryConfigurationError;
045: import javax.xml.parsers.ParserConfigurationException;
046: import javax.xml.parsers.SAXParser;
047: import javax.xml.parsers.SAXParserFactory;
048: import javax.xml.stream.XMLInputFactory;
049: import javax.xml.stream.XMLOutputFactory;
050: import javax.xml.stream.XMLStreamException;
051: import javax.xml.stream.XMLStreamReader;
052: import javax.xml.transform.Result;
053: import javax.xml.transform.Source;
054: import javax.xml.transform.dom.DOMResult;
055: import javax.xml.transform.dom.DOMSource;
056: import javax.xml.transform.sax.SAXResult;
057: import javax.xml.transform.sax.SAXSource;
058: import javax.xml.transform.stax.StAXResult;
059: import javax.xml.transform.stax.StAXSource;
060: import javax.xml.transform.stream.StreamResult;
061: import javax.xml.transform.stream.StreamSource;
062:
063: import org.w3c.dom.DOMException;
064: import org.w3c.dom.Document;
065: import org.xml.sax.Attributes;
066: import org.xml.sax.InputSource;
067: import org.xml.sax.helpers.DefaultHandler;
068: import org.xml.sax.SAXException;
069:
070: import com.mysql.jdbc.exceptions.NotYetImplementedException;
071:
072: public class JDBC4MysqlSQLXML implements SQLXML {
073:
074: private XMLInputFactory inputFactory;
075:
076: private XMLOutputFactory outputFactory;
077:
078: private String stringRep;
079:
080: private ResultSetInternalMethods owningResultSet;
081:
082: private int columnIndexOfXml;
083:
084: private boolean fromResultSet;
085:
086: private boolean isClosed = false;
087:
088: private boolean workingWithResult;
089:
090: private DOMResult asDOMResult;
091:
092: private SAXResult asSAXResult;
093:
094: private SimpleSaxToReader saxToReaderConverter;
095:
096: private StringWriter asStringWriter;
097:
098: private ByteArrayOutputStream asByteArrayOutputStream;
099:
100: protected JDBC4MysqlSQLXML(ResultSetInternalMethods owner, int index) {
101: this .owningResultSet = owner;
102: this .columnIndexOfXml = index;
103: this .fromResultSet = true;
104: }
105:
106: protected JDBC4MysqlSQLXML() {
107: this .fromResultSet = false;
108: }
109:
110: public synchronized void free() throws SQLException {
111: this .stringRep = null;
112: this .asDOMResult = null;
113: this .asSAXResult = null;
114: this .inputFactory = null;
115: this .outputFactory = null;
116: this .owningResultSet = null;
117: this .workingWithResult = false;
118: this .isClosed = true;
119:
120: }
121:
122: public synchronized String getString() throws SQLException {
123: checkClosed();
124: checkWorkingWithResult();
125:
126: if (this .fromResultSet) {
127: return this .owningResultSet
128: .getString(this .columnIndexOfXml);
129: }
130:
131: return this .stringRep;
132: }
133:
134: private synchronized void checkClosed() throws SQLException {
135: if (this .isClosed) {
136: throw SQLError
137: .createSQLException("SQLXMLInstance has been free()d");
138: }
139: }
140:
141: private synchronized void checkWorkingWithResult()
142: throws SQLException {
143: if (this .workingWithResult) {
144: throw SQLError
145: .createSQLException(
146: "Can't perform requested operation after getResult() has been called to write XML data",
147: SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
148: }
149: }
150:
151: /**
152: * Sets the XML value designated by this SQLXML instance to the given String
153: * representation. The format of this String is defined by
154: * org.xml.sax.InputSource, where the characters in the stream represent the
155: * unicode code points for XML according to section 2 and appendix B of the
156: * XML 1.0 specification. Although an encoding declaration other than
157: * unicode may be present, the encoding of the String is unicode. The
158: * behavior of this method is the same as ResultSet.updateString() when the
159: * designated column of the ResultSet has a type java.sql.Types of SQLXML.
160: * <p>
161: * The SQL XML object becomes not writeable when this method is called and
162: * may also become not readable depending on implementation.
163: *
164: * @param value
165: * the XML value
166: * @throws SQLException
167: * if there is an error processing the XML value. The getCause()
168: * method of the exception may provide a more detailed
169: * exception, for example, if the stream does not contain valid
170: * characters. An exception is thrown if the state is not
171: * writable.
172: * @exception SQLFeatureNotSupportedException
173: * if the JDBC driver does not support this method
174: * @since 1.6
175: */
176:
177: public synchronized void setString(String str) throws SQLException {
178: checkClosed();
179: checkWorkingWithResult();
180:
181: this .stringRep = str;
182: this .fromResultSet = false;
183: }
184:
185: public synchronized boolean isEmpty() throws SQLException {
186: checkClosed();
187: checkWorkingWithResult();
188:
189: if (!this .fromResultSet) {
190: return this .stringRep == null
191: || this .stringRep.length() == 0;
192: }
193:
194: return false;
195: }
196:
197: public synchronized InputStream getBinaryStream()
198: throws SQLException {
199: checkClosed();
200: checkWorkingWithResult();
201:
202: return this .owningResultSet
203: .getBinaryStream(this .columnIndexOfXml);
204: }
205:
206: /**
207: * Retrieves the XML value designated by this SQLXML instance as a
208: * java.io.Reader object. The format of this stream is defined by
209: * org.xml.sax.InputSource, where the characters in the stream represent the
210: * unicode code points for XML according to section 2 and appendix B of the
211: * XML 1.0 specification. Although an encoding declaration other than
212: * unicode may be present, the encoding of the stream is unicode. The
213: * behavior of this method is the same as ResultSet.getCharacterStream()
214: * when the designated column of the ResultSet has a type java.sql.Types of
215: * SQLXML.
216: * <p>
217: * The SQL XML object becomes not readable when this method is called and
218: * may also become not writable depending on implementation.
219: *
220: * @return a stream containing the XML data.
221: * @throws SQLException
222: * if there is an error processing the XML value. The getCause()
223: * method of the exception may provide a more detailed
224: * exception, for example, if the stream does not contain valid
225: * characters. An exception is thrown if the state is not
226: * readable.
227: * @exception SQLFeatureNotSupportedException
228: * if the JDBC driver does not support this method
229: * @since 1.6
230: */
231: public synchronized Reader getCharacterStream() throws SQLException {
232: checkClosed();
233: checkWorkingWithResult();
234:
235: return this .owningResultSet
236: .getCharacterStream(this .columnIndexOfXml);
237: }
238:
239: /**
240: * Returns a Source for reading the XML value designated by this SQLXML
241: * instance. Sources are used as inputs to XML parsers and XSLT
242: * transformers.
243: * <p>
244: * Sources for XML parsers will have namespace processing on by default. The
245: * systemID of the Source is implementation dependent.
246: * <p>
247: * The SQL XML object becomes not readable when this method is called and
248: * may also become not writable depending on implementation.
249: * <p>
250: * Note that SAX is a callback architecture, so a returned SAXSource should
251: * then be set with a content handler that will receive the SAX events from
252: * parsing. The content handler will receive callbacks based on the contents
253: * of the XML.
254: *
255: * <pre>
256: * SAXSource saxSource = sqlxml.getSource(SAXSource.class);
257: * XMLReader xmlReader = saxSource.getXMLReader();
258: * xmlReader.setContentHandler(myHandler);
259: * xmlReader.parse(saxSource.getInputSource());
260: * </pre>
261: *
262: * @param sourceClass
263: * The class of the source, or null. If the class is null, a
264: * vendor specifc Source implementation will be returned. The
265: * following classes are supported at a minimum:
266: *
267: * (MySQL returns a SAXSource if sourceClass == null)
268: *
269: * <pre>
270: * javax.xml.transform.dom.DOMSource - returns a DOMSource
271: * javax.xml.transform.sax.SAXSource - returns a SAXSource
272: * javax.xml.transform.stax.StAXSource - returns a StAXSource
273: * javax.xml.transform.stream.StreamSource - returns a StreamSource
274: * </pre>
275: *
276: * @return a Source for reading the XML value.
277: * @throws SQLException
278: * if there is an error processing the XML value or if this
279: * feature is not supported. The getCause() method of the
280: * exception may provide a more detailed exception, for example,
281: * if an XML parser exception occurs. An exception is thrown if
282: * the state is not readable.
283: * @exception SQLFeatureNotSupportedException
284: * if the JDBC driver does not support this method
285: * @since 1.6
286: */
287: public synchronized Source getSource(Class clazz)
288: throws SQLException {
289: checkClosed();
290: checkWorkingWithResult();
291:
292: // Note that we try and use streams here wherever possible
293: // for the day that the server actually supports streaming
294: // from server -> client (futureproofing)
295:
296: if (clazz == null || clazz.equals(SAXSource.class)) {
297:
298: InputSource inputSource = null;
299:
300: if (this .fromResultSet) {
301: inputSource = new InputSource(this .owningResultSet
302: .getCharacterStream(this .columnIndexOfXml));
303: } else {
304: inputSource = new InputSource(new StringReader(
305: this .stringRep));
306: }
307:
308: return new SAXSource(inputSource);
309: } else if (clazz.equals(DOMSource.class)) {
310: try {
311: DocumentBuilderFactory builderFactory = DocumentBuilderFactory
312: .newInstance();
313: builderFactory.setNamespaceAware(true);
314: DocumentBuilder builder = builderFactory
315: .newDocumentBuilder();
316:
317: InputSource inputSource = null;
318:
319: if (this .fromResultSet) {
320: inputSource = new InputSource(this .owningResultSet
321: .getCharacterStream(this .columnIndexOfXml));
322: } else {
323: inputSource = new InputSource(new StringReader(
324: this .stringRep));
325: }
326:
327: return new DOMSource(builder.parse(inputSource));
328: } catch (Throwable t) {
329: SQLException sqlEx = SQLError.createSQLException(t
330: .getMessage(),
331: SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
332: sqlEx.initCause(t);
333:
334: throw sqlEx;
335: }
336:
337: } else if (clazz.equals(StreamSource.class)) {
338: Reader reader = null;
339:
340: if (this .fromResultSet) {
341: reader = this .owningResultSet
342: .getCharacterStream(this .columnIndexOfXml);
343: } else {
344: reader = new StringReader(this .stringRep);
345: }
346:
347: return new StreamSource(reader);
348: } else if (clazz.equals(StAXSource.class)) {
349: try {
350: Reader reader = null;
351:
352: if (this .fromResultSet) {
353: reader = this .owningResultSet
354: .getCharacterStream(this .columnIndexOfXml);
355: } else {
356: reader = new StringReader(this .stringRep);
357: }
358:
359: return new StAXSource(this .inputFactory
360: .createXMLStreamReader(reader));
361: } catch (XMLStreamException ex) {
362: SQLException sqlEx = SQLError.createSQLException(ex
363: .getMessage(),
364: SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
365: sqlEx.initCause(ex);
366:
367: throw sqlEx;
368: }
369: } else {
370: throw SQLError.createSQLException("XML Source of type \""
371: + clazz.toString() + "\" Not supported.",
372: SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
373: }
374: }
375:
376: /**
377: * Retrieves a stream that can be used to write the XML value that this
378: * SQLXML instance represents. The stream begins at position 0. The bytes of
379: * the stream are interpreted according to appendix F of the XML 1.0
380: * specification The behavior of this method is the same as
381: * ResultSet.updateBinaryStream() when the designated column of the
382: * ResultSet has a type java.sql.Types of SQLXML.
383: * <p>
384: * The SQL XML object becomes not writeable when this method is called and
385: * may also become not readable depending on implementation.
386: *
387: * @return a stream to which data can be written.
388: * @throws SQLException
389: * if there is an error processing the XML value. An exception
390: * is thrown if the state is not writable.
391: * @exception SQLFeatureNotSupportedException
392: * if the JDBC driver does not support this method
393: * @since 1.6
394: */
395: public synchronized OutputStream setBinaryStream()
396: throws SQLException {
397: checkClosed();
398: checkWorkingWithResult();
399:
400: this .workingWithResult = true;
401:
402: return setBinaryStreamInternal();
403: }
404:
405: private synchronized OutputStream setBinaryStreamInternal()
406: throws SQLException {
407: this .asByteArrayOutputStream = new ByteArrayOutputStream();
408:
409: return this .asByteArrayOutputStream;
410: }
411:
412: /**
413: * Retrieves a stream to be used to write the XML value that this SQLXML
414: * instance represents. The format of this stream is defined by
415: * org.xml.sax.InputSource, where the characters in the stream represent the
416: * unicode code points for XML according to section 2 and appendix B of the
417: * XML 1.0 specification. Although an encoding declaration other than
418: * unicode may be present, the encoding of the stream is unicode. The
419: * behavior of this method is the same as ResultSet.updateCharacterStream()
420: * when the designated column of the ResultSet has a type java.sql.Types of
421: * SQLXML.
422: * <p>
423: * The SQL XML object becomes not writeable when this method is called and
424: * may also become not readable depending on implementation.
425: *
426: * @return a stream to which data can be written.
427: * @throws SQLException
428: * if there is an error processing the XML value. The getCause()
429: * method of the exception may provide a more detailed
430: * exception, for example, if the stream does not contain valid
431: * characters. An exception is thrown if the state is not
432: * writable.
433: * @exception SQLFeatureNotSupportedException
434: * if the JDBC driver does not support this method
435: * @since 1.6
436: */
437: public synchronized Writer setCharacterStream() throws SQLException {
438: checkClosed();
439: checkWorkingWithResult();
440:
441: this .workingWithResult = true;
442:
443: return setCharacterStreamInternal();
444: }
445:
446: private synchronized Writer setCharacterStreamInternal()
447: throws SQLException {
448: this .asStringWriter = new StringWriter();
449:
450: return this .asStringWriter;
451: }
452:
453: /**
454: * Returns a Result for setting the XML value designated by this SQLXML
455: * instance.
456: * <p>
457: * The systemID of the Result is implementation dependent.
458: * <p>
459: * The SQL XML object becomes not writeable when this method is called and
460: * may also become not readable depending on implementation.
461: * <p>
462: * Note that SAX is a callback architecture and the returned SAXResult has a
463: * content handler assigned that will receive the SAX events based on the
464: * contents of the XML. Call the content handler with the contents of the
465: * XML document to assign the values.
466: *
467: * <pre>
468: * SAXResult saxResult = sqlxml.setResult(SAXResult.class);
469: * ContentHandler contentHandler = saxResult.getXMLReader().getContentHandler();
470: * contentHandler.startDocument();
471: * // set the XML elements and attributes into the result
472: * contentHandler.endDocument();
473: * </pre>
474: *
475: * @param resultClass
476: * The class of the result, or null. If resultClass is null, a
477: * vendor specific Result implementation will be returned. The
478: * following classes are supported at a minimum:
479: *
480: * <pre>
481: * javax.xml.transform.dom.DOMResult - returns a DOMResult
482: * javax.xml.transform.sax.SAXResult - returns a SAXResult
483: * javax.xml.transform.stax.StAXResult - returns a StAXResult
484: * javax.xml.transform.stream.StreamResult - returns a StreamResult
485: * </pre>
486: *
487: * @return Returns a Result for setting the XML value.
488: * @throws SQLException
489: * if there is an error processing the XML value or if this
490: * feature is not supported. The getCause() method of the
491: * exception may provide a more detailed exception, for example,
492: * if an XML parser exception occurs. An exception is thrown if
493: * the state is not writable.
494: * @exception SQLFeatureNotSupportedException
495: * if the JDBC driver does not support this method
496: * @since 1.6
497: */
498: public synchronized Result setResult(Class clazz)
499: throws SQLException {
500: checkClosed();
501: checkWorkingWithResult();
502:
503: this .workingWithResult = true;
504: this .asDOMResult = null;
505: this .asSAXResult = null;
506: this .saxToReaderConverter = null;
507: this .stringRep = null;
508: this .asStringWriter = null;
509: this .asByteArrayOutputStream = null;
510:
511: if (clazz == null || clazz.equals(SAXResult.class)) {
512: this .saxToReaderConverter = new SimpleSaxToReader();
513:
514: this .asSAXResult = new SAXResult(this .saxToReaderConverter);
515:
516: return this .asSAXResult;
517: } else if (clazz.equals(DOMResult.class)) {
518:
519: this .asDOMResult = new DOMResult();
520: return this .asDOMResult;
521:
522: } else if (clazz.equals(StreamResult.class)) {
523: return new StreamResult(setCharacterStreamInternal());
524: } else if (clazz.equals(StAXResult.class)) {
525: try {
526: if (this .outputFactory == null) {
527: this .outputFactory = XMLOutputFactory.newInstance();
528: }
529:
530: return new StAXResult(
531: this .outputFactory
532: .createXMLEventWriter(setCharacterStreamInternal()));
533: } catch (XMLStreamException ex) {
534: SQLException sqlEx = SQLError.createSQLException(ex
535: .getMessage(),
536: SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
537: sqlEx.initCause(ex);
538:
539: throw sqlEx;
540: }
541: } else {
542: throw SQLError.createSQLException("XML Result of type \""
543: + clazz.toString() + "\" Not supported.",
544: SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
545: }
546: }
547:
548: private Reader binaryInputStreamStreamToReader(
549: ByteArrayOutputStream out) {
550:
551: try {
552: // There's got to be an easier way to do this, but
553: // I don't feel like coding up Appendix F of the XML Spec
554: // myself, when there's a reusable way to do it, and we
555: // can warn folks away from BINARY xml streams that have
556: // to be parsed to determine the character encoding :P
557:
558: String encoding = "UTF-8";
559:
560: try {
561: ByteArrayInputStream bIn = new ByteArrayInputStream(out
562: .toByteArray());
563: XMLStreamReader reader = this .inputFactory
564: .createXMLStreamReader(bIn);
565:
566: int eventType = 0;
567:
568: while ((eventType = reader.next()) != XMLStreamReader.END_DOCUMENT) {
569: if (eventType == XMLStreamReader.START_DOCUMENT) {
570: String possibleEncoding = reader.getEncoding();
571:
572: if (possibleEncoding != null) {
573: encoding = possibleEncoding;
574: }
575:
576: break;
577: }
578: }
579: } catch (Throwable t) {
580: // ignore, dealt with later when the string can't be parsed
581: // into valid XML
582: }
583:
584: return new StringReader(new String(out.toByteArray(),
585: encoding));
586: } catch (UnsupportedEncodingException badEnc) {
587: throw new RuntimeException(badEnc);
588: }
589: }
590:
591: protected String readerToString(Reader reader) throws SQLException {
592: StringBuffer buf = new StringBuffer();
593:
594: int charsRead = 0;
595:
596: char[] charBuf = new char[512];
597:
598: try {
599: while ((charsRead = reader.read(charBuf)) != -1) {
600: buf.append(charBuf, 0, charsRead);
601: }
602: } catch (IOException ioEx) {
603: SQLException sqlEx = SQLError.createSQLException(ioEx
604: .getMessage(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
605: sqlEx.initCause(ioEx);
606:
607: throw sqlEx;
608: }
609:
610: return buf.toString();
611: }
612:
613: protected synchronized Reader serializeAsCharacterStream()
614: throws SQLException {
615: checkClosed();
616: if (this .workingWithResult) {
617: // figure out what kind of result
618: if (this .stringRep != null) {
619: return new StringReader(this .stringRep);
620: }
621:
622: if (this .asDOMResult != null) {
623: return new StringReader(domSourceToString());
624: }
625:
626: if (this .asStringWriter != null) { // stax result
627: return new StringReader(this .asStringWriter.toString());
628: }
629:
630: if (this .asSAXResult != null) {
631: return this .saxToReaderConverter.toReader();
632: }
633:
634: if (this .asByteArrayOutputStream != null) {
635: return binaryInputStreamStreamToReader(this .asByteArrayOutputStream);
636: }
637: }
638:
639: return this .owningResultSet
640: .getCharacterStream(this .columnIndexOfXml);
641: }
642:
643: protected String domSourceToString() throws SQLException {
644: try {
645: DOMSource source = new DOMSource(this .asDOMResult.getNode());
646: Transformer identity = TransformerFactory.newInstance()
647: .newTransformer();
648: StringWriter stringOut = new StringWriter();
649: Result result = new StreamResult(stringOut);
650: identity.transform(source, result);
651:
652: return stringOut.toString();
653: } catch (Throwable t) {
654: SQLException sqlEx = SQLError.createSQLException(t
655: .getMessage(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
656: sqlEx.initCause(t);
657:
658: throw sqlEx;
659: }
660: }
661:
662: protected synchronized String serializeAsString()
663: throws SQLException {
664: checkClosed();
665: if (this .workingWithResult) {
666: // figure out what kind of result
667: if (this .stringRep != null) {
668: return this .stringRep;
669: }
670:
671: if (this .asDOMResult != null) {
672: return domSourceToString();
673: }
674:
675: if (this .asStringWriter != null) { // stax result
676: return this .asStringWriter.toString();
677: }
678:
679: if (this .asSAXResult != null) {
680: return readerToString(this .saxToReaderConverter
681: .toReader());
682: }
683:
684: if (this .asByteArrayOutputStream != null) {
685: return readerToString(binaryInputStreamStreamToReader(this .asByteArrayOutputStream));
686: }
687: }
688:
689: return this .owningResultSet.getString(this .columnIndexOfXml);
690: }
691:
692: /*
693: * The SimpleSaxToReader class is an adaptation of the SAX "Writer"
694: * example from the Apache XercesJ-2 Project. The license for this
695: * code is as follows:
696: *
697: * Licensed to the Apache Software Foundation (ASF) under one or more
698: * contributor license agreements. See the NOTICE file distributed with
699: * this work for additional information regarding copyright ownership.
700: * The ASF licenses this file to You under the Apache License, Version 2.0
701: * (the "License"); you may not use this file except in compliance with
702: * the License. You may obtain a copy of the License at
703: *
704: * http://www.apache.org/licenses/LICENSE-2.0
705: *
706: * Unless required by applicable law or agreed to in writing, software
707: * distributed under the License is distributed on an "AS IS" BASIS,
708: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
709: * See the License for the specific language governing permissions and
710: * limitations under the License.
711: */
712:
713: class SimpleSaxToReader extends DefaultHandler {
714: StringBuffer buf = new StringBuffer();
715:
716: public void startDocument() throws SAXException {
717: buf.append("<?xml version='1.0' encoding='UTF-8'?>");
718: }
719:
720: public void endDocument() throws SAXException {
721: // Do we need to override this?
722: }
723:
724: public void startElement(String namespaceURI, String sName,
725: String qName, Attributes attrs) throws SAXException {
726:
727: this .buf.append("<");
728: this .buf.append(qName);
729:
730: if (attrs != null) {
731: for (int i = 0; i < attrs.getLength(); i++) {
732: this .buf.append(" ");
733: this .buf.append(attrs.getQName(i)).append("=\"");
734: escapeCharsForXml(attrs.getValue(i), true);
735: this .buf.append("\"");
736: }
737: }
738:
739: this .buf.append(">");
740: }
741:
742: public void characters(char buf[], int offset, int len)
743: throws SAXException {
744: if (!this .inCDATA) {
745: escapeCharsForXml(buf, offset, len, false);
746: } else {
747: this .buf.append(buf, offset, len);
748: }
749: }
750:
751: public void ignorableWhitespace(char ch[], int start, int length)
752: throws SAXException {
753: characters(ch, start, length);
754: }
755:
756: private boolean inCDATA = false;
757:
758: public void startCDATA() throws SAXException {
759: this .buf.append("<![CDATA[");
760: this .inCDATA = true;
761: }
762:
763: public void endCDATA() throws SAXException {
764: this .inCDATA = false;
765: this .buf.append("]]>");
766: }
767:
768: public void comment(char ch[], int start, int length)
769: throws SAXException {
770: // if (!fCanonical && fElementDepth > 0) {
771: this .buf.append("<!--");
772: for (int i = 0; i < length; ++i) {
773: this .buf.append(ch[start + i]);
774: }
775: this .buf.append("-->");
776: // }
777: }
778:
779: Reader toReader() {
780: return new StringReader(this .buf.toString());
781: }
782:
783: private void escapeCharsForXml(String str,
784: boolean isAttributeData) {
785: if (str == null) {
786: return;
787: }
788:
789: int strLen = str.length();
790:
791: for (int i = 0; i < strLen; i++) {
792: escapeCharsForXml(str.charAt(i), isAttributeData);
793: }
794: }
795:
796: private void escapeCharsForXml(char[] buf, int offset, int len,
797: boolean isAttributeData) {
798:
799: if (buf == null) {
800: return;
801: }
802:
803: for (int i = 0; i < len; i++) {
804: escapeCharsForXml(buf[offset + i], isAttributeData);
805: }
806: }
807:
808: private void escapeCharsForXml(char c, boolean isAttributeData) {
809: switch (c) {
810: case '<':
811: this .buf.append("<");
812: break;
813:
814: case '>':
815: this .buf.append(">");
816: break;
817:
818: case '&':
819: this .buf.append("&");
820: break;
821:
822: case '"':
823:
824: if (!isAttributeData) {
825: this .buf.append("\"");
826: } else {
827: this .buf.append(""");
828: }
829:
830: break;
831:
832: case '\r':
833: this .buf.append("
");
834: break;
835:
836: default:
837:
838: if (((c >= 0x01 && c <= 0x1F && c != 0x09 && c != 0x0A)
839: || (c >= 0x7F && c <= 0x9F) || c == 0x2028)
840: || isAttributeData && (c == 0x09 || c == 0x0A)) {
841: this .buf.append("&#x");
842: this .buf.append(Integer.toHexString(c)
843: .toUpperCase());
844: this .buf.append(";");
845: } else {
846: this.buf.append(c);
847: }
848: }
849: }
850: }
851: }
|