001: /*
002: * The Apache Software License, Version 1.1
003: *
004: *
005: * Copyright (c) 1999-2001 The Apache Software Foundation. All rights
006: * reserved.
007: *
008: * Redistribution and use in source and binary forms, with or without
009: * modification, are permitted provided that the following conditions
010: * are met:
011: *
012: * 1. Redistributions of source code must retain the above copyright
013: * notice, this list of conditions and the following disclaimer.
014: *
015: * 2. Redistributions in binary form must reproduce the above copyright
016: * notice, this list of conditions and the following disclaimer in
017: * the documentation and/or other materials provided with the
018: * distribution.
019: *
020: * 3. The end-user documentation included with the redistribution,
021: * if any, must include the following acknowledgment:
022: * "This product includes software developed by the
023: * Apache Software Foundation (http://www.apache.org/)."
024: * Alternately, this acknowledgment may appear in the software itself,
025: * if and wherever such third-party acknowledgments normally appear.
026: *
027: * 4. The names "Xerces" and "Apache Software Foundation" must
028: * not be used to endorse or promote products derived from this
029: * software without prior written permission. For written
030: * permission, please contact apache@apache.org.
031: *
032: * 5. Products derived from this software may not be called "Apache",
033: * nor may "Apache" appear in their name, without prior written
034: * permission of the Apache Software Foundation.
035: *
036: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
037: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
038: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
039: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
040: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
041: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
042: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
043: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
044: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
045: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
046: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
047: * SUCH DAMAGE.
048: * ====================================================================
049: *
050: * This software consists of voluntary contributions made by many
051: * individuals on behalf of the Apache Software Foundation and was
052: * originally based on software copyright (c) 1999, International
053: * Business Machines, Inc., http://www.apache.org. For more
054: * information on the Apache Software Foundation, please see
055: * <http://www.apache.org/>.
056: */
057:
058: package org.apache.xerces.dom;
059:
060: import org.w3c.dom.DOMException;
061: import org.w3c.dom.Node;
062: import org.w3c.dom.NodeList;
063: import org.w3c.dom.Text;
064:
065: /**
066: * CharacterData is an abstract Node that can carry character data as its
067: * Value. It provides shared behavior for Text, CData, and
068: * possibly other node types. All offsets are 0-based.
069: * <p>
070: * Since ProcessingInstructionImpl inherits from this class to reuse the
071: * setNodeValue method, this class isn't declared as implementing the interface
072: * CharacterData. This is done by relevant subclasses (TexImpl, CommentImpl).
073: * <p>
074: * This class doesn't directly support mutation events, however, it notifies
075: * the document when mutations are performed so that the document class do so.
076: *
077: * @version
078: * @since PR-DOM-Level-1-19980818.
079: */
080: public abstract class CharacterDataImpl extends ChildNode {
081:
082: //
083: // Constants
084: //
085:
086: /** Serialization version. */
087: static final long serialVersionUID = 7931170150428474230L;
088:
089: //
090: // Data
091: //
092:
093: protected String data;
094:
095: /** Empty child nodes. */
096: private static transient NodeList singletonNodeList = new NodeList() {
097: public Node item(int index) {
098: return null;
099: }
100:
101: public int getLength() {
102: return 0;
103: }
104: };
105:
106: //
107: // Constructors
108: //
109:
110: /** Factory constructor. */
111: protected CharacterDataImpl(CoreDocumentImpl ownerDocument,
112: String data) {
113: super (ownerDocument);
114: this .data = data;
115: }
116:
117: //
118: // Node methods
119: //
120:
121: /** Returns an empty node list. */
122: public NodeList getChildNodes() {
123: return singletonNodeList;
124: }
125:
126: /*
127: * returns the content of this node
128: */
129: public String getNodeValue() {
130: if (needsSyncData()) {
131: synchronizeData();
132: }
133: return data;
134: }
135:
136: /** This function added so that we can distinguish whether
137: * setNodeValue has been called from some other DOM functions.
138: * or by the client.<p>
139: * This is important, because we do one type of Range fix-up,
140: * from the high-level functions in CharacterData, and another
141: * type if the client simply calls setNodeValue(value).
142: */
143: protected void setNodeValueInternal(String value) {
144: if (isReadOnly())
145: throw new DOMException(
146: DOMException.NO_MODIFICATION_ALLOWED_ERR,
147: "DOM001 Modification not allowed");
148: // revisit: may want to set the value in ownerDocument.
149: // Default behavior, overridden in some subclasses
150: if (needsSyncData()) {
151: synchronizeData();
152: }
153:
154: // keep old value for document notification
155: String oldvalue = this .data;
156:
157: CoreDocumentImpl ownerDocument = ownerDocument();
158:
159: // notify document
160: ownerDocument.modifyingCharacterData(this );
161:
162: this .data = value;
163:
164: // notify document
165: ownerDocument.modifiedCharacterData(this , oldvalue, value);
166: }
167:
168: /**
169: * Sets the content, possibly firing related events,
170: * and updating ranges (via notification to the document)
171: */
172: public void setNodeValue(String value) {
173:
174: setNodeValueInternal(value);
175:
176: // notify document
177: ownerDocument().replacedText(this );
178: }
179:
180: //
181: // CharacterData methods
182: //
183:
184: /**
185: * Retrieve character data currently stored in this node.
186: *
187: * @throws DOMExcpetion(DOMSTRING_SIZE_ERR) In some implementations,
188: * the stored data may exceed the permitted length of strings. If so,
189: * getData() will throw this DOMException advising the user to
190: * instead retrieve the data in chunks via the substring() operation.
191: */
192: public String getData() {
193: if (needsSyncData()) {
194: synchronizeData();
195: }
196: return data;
197: }
198:
199: /**
200: * Report number of characters currently stored in this node's
201: * data. It may be 0, meaning that the value is an empty string.
202: */
203: public int getLength() {
204: if (needsSyncData()) {
205: synchronizeData();
206: }
207: return data.length();
208: }
209:
210: /**
211: * Concatenate additional characters onto the end of the data
212: * stored in this node. Note that this, and insert(), are the paths
213: * by which a DOM could wind up accumulating more data than the
214: * language's strings can easily handle. (See above discussion.)
215: *
216: * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if node is readonly.
217: */
218: public void appendData(String data) {
219:
220: if (isReadOnly()) {
221: throw new DOMException(
222: DOMException.NO_MODIFICATION_ALLOWED_ERR,
223: "DOM001 Modification not allowed");
224: }
225:
226: if (needsSyncData()) {
227: synchronizeData();
228: }
229:
230: setNodeValue(this .data + data);
231:
232: } // appendData(String)
233:
234: /**
235: * Remove a range of characters from the node's value. Throws a
236: * DOMException if the offset is beyond the end of the
237: * string. However, a deletion _count_ that exceeds the available
238: * data is accepted as a delete-to-end request.
239: *
240: * @throws DOMException(INDEX_SIZE_ERR) if offset is negative or
241: * greater than length, or if count is negative.
242: *
243: * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if node is
244: * readonly.
245: */
246: public void deleteData(int offset, int count) throws DOMException {
247:
248: if (isReadOnly()) {
249: throw new DOMException(
250: DOMException.NO_MODIFICATION_ALLOWED_ERR,
251: "DOM001 Modification not allowed");
252: }
253:
254: if (count < 0) {
255: throw new DOMException(DOMException.INDEX_SIZE_ERR,
256: "DOM004 Index out of bounds");
257: }
258:
259: if (needsSyncData()) {
260: synchronizeData();
261: }
262: int tailLength = Math.max(data.length() - count - offset, 0);
263: try {
264: String value = data.substring(0, offset)
265: + (tailLength > 0 ? data.substring(offset + count,
266: offset + count + tailLength) : "");
267:
268: setNodeValueInternal(value);
269:
270: // notify document
271: ownerDocument().deletedText(this , offset, count);
272: } catch (StringIndexOutOfBoundsException e) {
273: throw new DOMException(DOMException.INDEX_SIZE_ERR,
274: "DOM004 Index out of bounds");
275: }
276:
277: } // deleteData(int,int)
278:
279: /**
280: * Insert additional characters into the data stored in this node,
281: * at the offset specified.
282: *
283: * @throws DOMException(INDEX_SIZE_ERR) if offset is negative or
284: * greater than length.
285: *
286: * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if node is readonly.
287: */
288: public void insertData(int offset, String data) throws DOMException {
289:
290: if (isReadOnly()) {
291: throw new DOMException(
292: DOMException.NO_MODIFICATION_ALLOWED_ERR,
293: "DOM001 Modification not allowed");
294: }
295:
296: if (needsSyncData()) {
297: synchronizeData();
298: }
299: try {
300: String value = new StringBuffer(this .data).insert(offset,
301: data).toString();
302:
303: setNodeValueInternal(value);
304:
305: // notify document
306: ownerDocument().insertedText(this , offset, data.length());
307: } catch (StringIndexOutOfBoundsException e) {
308: throw new DOMException(DOMException.INDEX_SIZE_ERR,
309: "DOM004 Index out of bounds");
310: }
311:
312: } // insertData(int,int)
313:
314: /**
315: * Replace a series of characters at the specified (zero-based)
316: * offset with a new string, NOT necessarily of the same
317: * length. Convenience method, equivalent to a delete followed by an
318: * insert. Throws a DOMException if the specified offset is beyond
319: * the end of the existing data.
320: *
321: * @param offset The offset at which to begin replacing.
322: *
323: * @param count The number of characters to remove,
324: * interpreted as in the delete() method.
325: *
326: * @param data The new string to be inserted at offset in place of
327: * the removed data. Note that the entire string will
328: * be inserted -- the count parameter does not affect
329: * insertion, and the new data may be longer or shorter
330: * than the substring it replaces.
331: *
332: * @throws DOMException(INDEX_SIZE_ERR) if offset is negative or
333: * greater than length, or if count is negative.
334: *
335: * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if node is
336: * readonly.
337: */
338: public void replaceData(int offset, int count, String data)
339: throws DOMException {
340:
341: // The read-only check is done by deleteData()
342: // ***** This could be more efficient w/r/t Mutation Events,
343: // specifically by aggregating DOMAttrModified and
344: // DOMSubtreeModified. But mutation events are
345: // underspecified; I don't feel compelled
346: // to deal with it right now.
347: deleteData(offset, count);
348: insertData(offset, data);
349:
350: } // replaceData(int,int,String)
351:
352: /**
353: * Store character data into this node.
354: *
355: * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if node is readonly.
356: */
357: public void setData(String value) throws DOMException {
358: setNodeValue(value);
359: }
360:
361: /**
362: * Substring is more than a convenience function. In some
363: * implementations of the DOM, where the stored data may exceed the
364: * length that can be returned in a single string, the only way to
365: * read it all is to extract it in chunks via this method.
366: *
367: * @param offset Zero-based offset of first character to retrieve.
368: * @param count Number of characters to retrieve.
369: *
370: * If the sum of offset and count exceeds the length, all characters
371: * to end of data are returned.
372: *
373: * @throws DOMException(INDEX_SIZE_ERR) if offset is negative or
374: * greater than length, or if count is negative.
375: *
376: * @throws DOMException(WSTRING_SIZE_ERR) In some implementations,
377: * count may exceed the permitted length of strings. If so,
378: * substring() will throw this DOMException advising the user to
379: * instead retrieve the data in smaller chunks.
380: */
381: public String substringData(int offset, int count)
382: throws DOMException {
383:
384: if (needsSyncData()) {
385: synchronizeData();
386: }
387:
388: int length = data.length();
389: if (count < 0 || offset < 0 || offset > length - 1) {
390: throw new DOMException(DOMException.INDEX_SIZE_ERR,
391: "DOM004 Index out of bounds");
392: }
393:
394: int tailIndex = Math.min(offset + count, length);
395:
396: return data.substring(offset, tailIndex);
397:
398: } // substringData(int,int):String
399:
400: } // class CharacterDataImpl
|