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:
018: package org.apache.xerces.dom;
019:
020: import org.w3c.dom.DOMException;
021: import org.w3c.dom.Node;
022: import org.w3c.dom.NodeList;
023:
024: /**
025: * CharacterData is an abstract Node that can carry character data as its
026: * Value. It provides shared behavior for Text, CData, and
027: * possibly other node types. All offsets are 0-based.
028: * <p>
029: * Since ProcessingInstructionImpl inherits from this class to reuse the
030: * setNodeValue method, this class isn't declared as implementing the interface
031: * CharacterData. This is done by relevant subclasses (TexImpl, CommentImpl).
032: * <p>
033: * This class doesn't directly support mutation events, however, it notifies
034: * the document when mutations are performed so that the document class do so.
035: *
036: * @xerces.internal
037: *
038: * @version $Id: CharacterDataImpl.java 447266 2006-09-18 05:57:49Z mrglavas $
039: * @since PR-DOM-Level-1-19980818.
040: */
041: public abstract class CharacterDataImpl extends ChildNode {
042:
043: //
044: // Constants
045: //
046:
047: /** Serialization version. */
048: static final long serialVersionUID = 7931170150428474230L;
049:
050: //
051: // Data
052: //
053:
054: protected String data;
055:
056: /** Empty child nodes. */
057: private static transient NodeList singletonNodeList = new NodeList() {
058: public Node item(int index) {
059: return null;
060: }
061:
062: public int getLength() {
063: return 0;
064: }
065: };
066:
067: //
068: // Constructors
069: //
070:
071: public CharacterDataImpl() {
072: }
073:
074: /** Factory constructor. */
075: protected CharacterDataImpl(CoreDocumentImpl ownerDocument,
076: String data) {
077: super (ownerDocument);
078: this .data = data;
079: }
080:
081: //
082: // Node methods
083: //
084:
085: /** Returns an empty node list. */
086: public NodeList getChildNodes() {
087: return singletonNodeList;
088: }
089:
090: /*
091: * returns the content of this node
092: */
093: public String getNodeValue() {
094: if (needsSyncData()) {
095: synchronizeData();
096: }
097: return data;
098: }
099:
100: /** Convenience wrapper for calling setNodeValueInternal when
101: * we are not performing a replacement operation
102: */
103: protected void setNodeValueInternal(String value) {
104: setNodeValueInternal(value, false);
105: }
106:
107: /** This function added so that we can distinguish whether
108: * setNodeValue has been called from some other DOM functions.
109: * or by the client.<p>
110: * This is important, because we do one type of Range fix-up,
111: * from the high-level functions in CharacterData, and another
112: * type if the client simply calls setNodeValue(value).
113: */
114: protected void setNodeValueInternal(String value, boolean replace) {
115:
116: CoreDocumentImpl ownerDocument = ownerDocument();
117:
118: if (ownerDocument.errorChecking && isReadOnly()) {
119: String msg = DOMMessageFormatter.formatMessage(
120: DOMMessageFormatter.DOM_DOMAIN,
121: "NO_MODIFICATION_ALLOWED_ERR", null);
122: throw new DOMException(
123: DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
124: }
125:
126: // revisit: may want to set the value in ownerDocument.
127: // Default behavior, overridden in some subclasses
128: if (needsSyncData()) {
129: synchronizeData();
130: }
131:
132: // keep old value for document notification
133: String oldvalue = this .data;
134:
135: // notify document
136: ownerDocument.modifyingCharacterData(this , replace);
137:
138: this .data = value;
139:
140: // notify document
141: ownerDocument.modifiedCharacterData(this , oldvalue, value,
142: replace);
143: }
144:
145: /**
146: * Sets the content, possibly firing related events,
147: * and updating ranges (via notification to the document)
148: */
149: public void setNodeValue(String value) {
150:
151: setNodeValueInternal(value);
152:
153: // notify document
154: ownerDocument().replacedText(this );
155: }
156:
157: //
158: // CharacterData methods
159: //
160:
161: /**
162: * Retrieve character data currently stored in this node.
163: *
164: * @throws DOMExcpetion(DOMSTRING_SIZE_ERR) In some implementations,
165: * the stored data may exceed the permitted length of strings. If so,
166: * getData() will throw this DOMException advising the user to
167: * instead retrieve the data in chunks via the substring() operation.
168: */
169: public String getData() {
170: if (needsSyncData()) {
171: synchronizeData();
172: }
173: return data;
174: }
175:
176: /**
177: * Report number of characters currently stored in this node's
178: * data. It may be 0, meaning that the value is an empty string.
179: */
180: public int getLength() {
181: if (needsSyncData()) {
182: synchronizeData();
183: }
184: return data.length();
185: }
186:
187: /**
188: * Concatenate additional characters onto the end of the data
189: * stored in this node. Note that this, and insert(), are the paths
190: * by which a DOM could wind up accumulating more data than the
191: * language's strings can easily handle. (See above discussion.)
192: *
193: * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if node is readonly.
194: */
195: public void appendData(String data) {
196:
197: if (isReadOnly()) {
198: String msg = DOMMessageFormatter.formatMessage(
199: DOMMessageFormatter.DOM_DOMAIN,
200: "NO_MODIFICATION_ALLOWED_ERR", null);
201: throw new DOMException(
202: DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
203: }
204: if (data == null) {
205: return;
206: }
207: if (needsSyncData()) {
208: synchronizeData();
209: }
210:
211: setNodeValue(this .data + data);
212:
213: } // appendData(String)
214:
215: /**
216: * Remove a range of characters from the node's value. Throws a
217: * DOMException if the offset is beyond the end of the
218: * string. However, a deletion _count_ that exceeds the available
219: * data is accepted as a delete-to-end request.
220: *
221: * @throws DOMException(INDEX_SIZE_ERR) if offset is negative or
222: * greater than length, or if count is negative.
223: *
224: * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if node is
225: * readonly.
226: */
227: public void deleteData(int offset, int count) throws DOMException {
228:
229: internalDeleteData(offset, count, false);
230: } // deleteData(int,int)
231:
232: /** NON-DOM INTERNAL: Within DOM actions, we sometimes need to be able
233: * to control which mutation events are spawned. This version of the
234: * deleteData operation allows us to do so. It is not intended
235: * for use by application programs.
236: */
237: void internalDeleteData(int offset, int count, boolean replace)
238: throws DOMException {
239:
240: CoreDocumentImpl ownerDocument = ownerDocument();
241: if (ownerDocument.errorChecking) {
242: if (isReadOnly()) {
243: String msg = DOMMessageFormatter.formatMessage(
244: DOMMessageFormatter.DOM_DOMAIN,
245: "NO_MODIFICATION_ALLOWED_ERR", null);
246: throw new DOMException(
247: DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
248: }
249:
250: if (count < 0) {
251: String msg = DOMMessageFormatter.formatMessage(
252: DOMMessageFormatter.DOM_DOMAIN,
253: "INDEX_SIZE_ERR", null);
254: throw new DOMException(DOMException.INDEX_SIZE_ERR, msg);
255: }
256: }
257:
258: if (needsSyncData()) {
259: synchronizeData();
260: }
261: int tailLength = Math.max(data.length() - count - offset, 0);
262: try {
263: String value = data.substring(0, offset)
264: + (tailLength > 0 ? data.substring(offset + count,
265: offset + count + tailLength) : "");
266:
267: setNodeValueInternal(value, replace);
268:
269: // notify document
270: ownerDocument.deletedText(this , offset, count);
271: } catch (StringIndexOutOfBoundsException e) {
272: String msg = DOMMessageFormatter.formatMessage(
273: DOMMessageFormatter.DOM_DOMAIN, "INDEX_SIZE_ERR",
274: null);
275: throw new DOMException(DOMException.INDEX_SIZE_ERR, msg);
276: }
277:
278: } // internalDeleteData(int,int,boolean)
279:
280: /**
281: * Insert additional characters into the data stored in this node,
282: * at the offset specified.
283: *
284: * @throws DOMException(INDEX_SIZE_ERR) if offset is negative or
285: * greater than length.
286: *
287: * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if node is readonly.
288: */
289: public void insertData(int offset, String data) throws DOMException {
290:
291: internalInsertData(offset, data, false);
292:
293: } // insertData(int,int)
294:
295: /** NON-DOM INTERNAL: Within DOM actions, we sometimes need to be able
296: * to control which mutation events are spawned. This version of the
297: * insertData operation allows us to do so. It is not intended
298: * for use by application programs.
299: */
300: void internalInsertData(int offset, String data, boolean replace)
301: throws DOMException {
302:
303: CoreDocumentImpl ownerDocument = ownerDocument();
304:
305: if (ownerDocument.errorChecking && isReadOnly()) {
306: String msg = DOMMessageFormatter.formatMessage(
307: DOMMessageFormatter.DOM_DOMAIN,
308: "NO_MODIFICATION_ALLOWED_ERR", null);
309: throw new DOMException(
310: DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
311: }
312:
313: if (needsSyncData()) {
314: synchronizeData();
315: }
316: try {
317: String value = new StringBuffer(this .data).insert(offset,
318: data).toString();
319:
320: setNodeValueInternal(value, replace);
321:
322: // notify document
323: ownerDocument.insertedText(this , offset, data.length());
324: } catch (StringIndexOutOfBoundsException e) {
325: String msg = DOMMessageFormatter.formatMessage(
326: DOMMessageFormatter.DOM_DOMAIN, "INDEX_SIZE_ERR",
327: null);
328: throw new DOMException(DOMException.INDEX_SIZE_ERR, msg);
329: }
330:
331: } // internalInsertData(int,String,boolean)
332:
333: /**
334: * Replace a series of characters at the specified (zero-based)
335: * offset with a new string, NOT necessarily of the same
336: * length. Convenience method, equivalent to a delete followed by an
337: * insert. Throws a DOMException if the specified offset is beyond
338: * the end of the existing data.
339: *
340: * @param offset The offset at which to begin replacing.
341: *
342: * @param count The number of characters to remove,
343: * interpreted as in the delete() method.
344: *
345: * @param data The new string to be inserted at offset in place of
346: * the removed data. Note that the entire string will
347: * be inserted -- the count parameter does not affect
348: * insertion, and the new data may be longer or shorter
349: * than the substring it replaces.
350: *
351: * @throws DOMException(INDEX_SIZE_ERR) if offset is negative or
352: * greater than length, or if count is negative.
353: *
354: * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if node is
355: * readonly.
356: */
357: public void replaceData(int offset, int count, String data)
358: throws DOMException {
359:
360: CoreDocumentImpl ownerDocument = ownerDocument();
361:
362: // The read-only check is done by deleteData()
363: // ***** This could be more efficient w/r/t Mutation Events,
364: // specifically by aggregating DOMAttrModified and
365: // DOMSubtreeModified. But mutation events are
366: // underspecified; I don't feel compelled
367: // to deal with it right now.
368: if (ownerDocument.errorChecking && isReadOnly()) {
369: String msg = DOMMessageFormatter.formatMessage(
370: DOMMessageFormatter.DOM_DOMAIN,
371: "NO_MODIFICATION_ALLOWED_ERR", null);
372: throw new DOMException(
373: DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
374: }
375:
376: if (needsSyncData()) {
377: synchronizeData();
378: }
379:
380: //notify document
381: ownerDocument.replacingData(this );
382:
383: // keep old value for document notification
384: String oldvalue = this .data;
385:
386: internalDeleteData(offset, count, true);
387: internalInsertData(offset, data, true);
388:
389: ownerDocument.replacedCharacterData(this , oldvalue, this .data);
390:
391: } // replaceData(int,int,String)
392:
393: /**
394: * Store character data into this node.
395: *
396: * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if node is readonly.
397: */
398: public void setData(String value) throws DOMException {
399: setNodeValue(value);
400: }
401:
402: /**
403: * Substring is more than a convenience function. In some
404: * implementations of the DOM, where the stored data may exceed the
405: * length that can be returned in a single string, the only way to
406: * read it all is to extract it in chunks via this method.
407: *
408: * @param offset Zero-based offset of first character to retrieve.
409: * @param count Number of characters to retrieve.
410: *
411: * If the sum of offset and count exceeds the length, all characters
412: * to end of data are returned.
413: *
414: * @throws DOMException(INDEX_SIZE_ERR) if offset is negative or
415: * greater than length, or if count is negative.
416: *
417: * @throws DOMException(WSTRING_SIZE_ERR) In some implementations,
418: * count may exceed the permitted length of strings. If so,
419: * substring() will throw this DOMException advising the user to
420: * instead retrieve the data in smaller chunks.
421: */
422: public String substringData(int offset, int count)
423: throws DOMException {
424:
425: if (needsSyncData()) {
426: synchronizeData();
427: }
428:
429: int length = data.length();
430: if (count < 0 || offset < 0 || offset > length - 1) {
431: String msg = DOMMessageFormatter.formatMessage(
432: DOMMessageFormatter.DOM_DOMAIN, "INDEX_SIZE_ERR",
433: null);
434: throw new DOMException(DOMException.INDEX_SIZE_ERR, msg);
435: }
436:
437: int tailIndex = Math.min(offset + count, length);
438:
439: return data.substring(offset, tailIndex);
440:
441: } // substringData(int,int):String
442:
443: } // class CharacterDataImpl
|