1. /*
  2. * The Apache Software License, Version 1.1
  3. *
  4. *
  5. * Copyright (c) 1999-2003 The Apache Software Foundation. All rights
  6. * reserved.
  7. *
  8. * Redistribution and use in source and binary forms, with or without
  9. * modification, are permitted provided that the following conditions
  10. * are met:
  11. *
  12. * 1. Redistributions of source code must retain the above copyright
  13. * notice, this list of conditions and the following disclaimer.
  14. *
  15. * 2. Redistributions in binary form must reproduce the above copyright
  16. * notice, this list of conditions and the following disclaimer in
  17. * the documentation and/or other materials provided with the
  18. * distribution.
  19. *
  20. * 3. The end-user documentation included with the redistribution,
  21. * if any, must include the following acknowledgment:
  22. * "This product includes software developed by the
  23. * Apache Software Foundation (http://www.apache.org/)."
  24. * Alternately, this acknowledgment may appear in the software itself,
  25. * if and wherever such third-party acknowledgments normally appear.
  26. *
  27. * 4. The names "Xerces" and "Apache Software Foundation" must
  28. * not be used to endorse or promote products derived from this
  29. * software without prior written permission. For written
  30. * permission, please contact apache@apache.org.
  31. *
  32. * 5. Products derived from this software may not be called "Apache",
  33. * nor may "Apache" appear in their name, without prior written
  34. * permission of the Apache Software Foundation.
  35. *
  36. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  37. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  38. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  39. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  40. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  41. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  42. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  43. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  44. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  45. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  46. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  47. * SUCH DAMAGE.
  48. * ====================================================================
  49. *
  50. * This software consists of voluntary contributions made by many
  51. * individuals on behalf of the Apache Software Foundation and was
  52. * originally based on software copyright (c) 1999, International
  53. * Business Machines, Inc., http://www.apache.org. For more
  54. * information on the Apache Software Foundation, please see
  55. * <http://www.apache.org/>.
  56. */
  57. package com.sun.org.apache.xerces.internal.dom;
  58. import java.util.Vector;
  59. import org.w3c.dom.CharacterData;
  60. import org.w3c.dom.DOMException;
  61. import org.w3c.dom.DocumentFragment;
  62. import org.w3c.dom.Node;
  63. import org.w3c.dom.ranges.Range;
  64. import org.w3c.dom.ranges.RangeException;
  65. /** The RangeImpl class implements the org.w3c.dom.range.Range interface.
  66. * <p> Please see the API documentation for the interface classes
  67. * and use the interfaces in your client programs.
  68. *
  69. * @version $Id: RangeImpl.java,v 1.28 2003/11/02 15:07:58 mrglavas Exp $
  70. */
  71. public class RangeImpl implements Range {
  72. //
  73. // Constants
  74. //
  75. //
  76. // Data
  77. //
  78. DocumentImpl fDocument;
  79. Node fStartContainer;
  80. Node fEndContainer;
  81. int fStartOffset;
  82. int fEndOffset;
  83. boolean fIsCollapsed;
  84. boolean fDetach = false;
  85. Node fInsertNode = null;
  86. Node fDeleteNode = null;
  87. Node fSplitNode = null;
  88. /** The constructor. Clients must use DocumentRange.createRange(),
  89. * because it registers the Range with the document, so it can
  90. * be fixed-up.
  91. */
  92. public RangeImpl(DocumentImpl document) {
  93. fDocument = document;
  94. fStartContainer = document;
  95. fEndContainer = document;
  96. fStartOffset = 0;
  97. fEndOffset = 0;
  98. fDetach = false;
  99. }
  100. public Node getStartContainer() {
  101. if ( fDetach ) {
  102. throw new DOMException(
  103. DOMException.INVALID_STATE_ERR,
  104. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
  105. }
  106. return fStartContainer;
  107. }
  108. public int getStartOffset() {
  109. if ( fDetach ) {
  110. throw new DOMException(
  111. DOMException.INVALID_STATE_ERR,
  112. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
  113. }
  114. return fStartOffset;
  115. }
  116. public Node getEndContainer() {
  117. if ( fDetach ) {
  118. throw new DOMException(
  119. DOMException.INVALID_STATE_ERR,
  120. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
  121. }
  122. return fEndContainer;
  123. }
  124. public int getEndOffset() {
  125. if ( fDetach ) {
  126. throw new DOMException(
  127. DOMException.INVALID_STATE_ERR,
  128. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
  129. }
  130. return fEndOffset;
  131. }
  132. public boolean getCollapsed() {
  133. if ( fDetach ) {
  134. throw new DOMException(
  135. DOMException.INVALID_STATE_ERR,
  136. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
  137. }
  138. return (fStartContainer == fEndContainer
  139. && fStartOffset == fEndOffset);
  140. }
  141. public Node getCommonAncestorContainer() {
  142. if ( fDetach ) {
  143. throw new DOMException(
  144. DOMException.INVALID_STATE_ERR,
  145. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
  146. }
  147. Vector startV = new Vector();
  148. Node node;
  149. for (node=fStartContainer; node != null;
  150. node=node.getParentNode())
  151. {
  152. startV.addElement(node);
  153. }
  154. Vector endV = new Vector();
  155. for (node=fEndContainer; node != null;
  156. node=node.getParentNode())
  157. {
  158. endV.addElement(node);
  159. }
  160. int s = startV.size()-1;
  161. int e = endV.size()-1;
  162. Object result = null;
  163. while (s>=0 && e>=0) {
  164. if (startV.elementAt(s) == endV.elementAt(e)) {
  165. result = startV.elementAt(s);
  166. } else {
  167. break;
  168. }
  169. --s;
  170. --e;
  171. }
  172. return (Node)result;
  173. }
  174. public void setStart(Node refNode, int offset)
  175. throws RangeException, DOMException
  176. {
  177. if( fDetach) {
  178. throw new DOMException(
  179. DOMException.INVALID_STATE_ERR,
  180. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
  181. }
  182. if ( !isLegalContainer(refNode)) {
  183. throw new RangeExceptionImpl(
  184. RangeException.INVALID_NODE_TYPE_ERR,
  185. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
  186. }
  187. if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) {
  188. throw new DOMException(
  189. DOMException.WRONG_DOCUMENT_ERR,
  190. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
  191. }
  192. checkIndex(refNode, offset);
  193. fStartContainer = refNode;
  194. fStartOffset = offset;
  195. }
  196. public void setEnd(Node refNode, int offset)
  197. throws RangeException, DOMException
  198. {
  199. if( fDetach) {
  200. throw new DOMException(
  201. DOMException.INVALID_STATE_ERR,
  202. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
  203. }
  204. if ( !isLegalContainer(refNode)) {
  205. throw new RangeExceptionImpl(
  206. RangeException.INVALID_NODE_TYPE_ERR,
  207. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
  208. }
  209. if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) {
  210. throw new DOMException(
  211. DOMException.WRONG_DOCUMENT_ERR,
  212. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
  213. }
  214. checkIndex(refNode, offset);
  215. fEndContainer = refNode;
  216. fEndOffset = offset;
  217. }
  218. public void setStartBefore(Node refNode)
  219. throws RangeException
  220. {
  221. if( fDetach) {
  222. throw new DOMException(
  223. DOMException.INVALID_STATE_ERR,
  224. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
  225. }
  226. if ( !hasLegalRootContainer(refNode) ||
  227. !isLegalContainedNode(refNode) )
  228. {
  229. throw new RangeExceptionImpl(
  230. RangeException.INVALID_NODE_TYPE_ERR,
  231. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
  232. }
  233. if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) {
  234. throw new DOMException(
  235. DOMException.WRONG_DOCUMENT_ERR,
  236. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
  237. }
  238. fStartContainer = refNode.getParentNode();
  239. int i = 0;
  240. for (Node n = refNode; n!=null; n = n.getPreviousSibling()) {
  241. i++;
  242. }
  243. fStartOffset = i-1;
  244. }
  245. public void setStartAfter(Node refNode)
  246. throws RangeException
  247. {
  248. if( fDetach) {
  249. throw new DOMException(
  250. DOMException.INVALID_STATE_ERR,
  251. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
  252. }
  253. if ( !hasLegalRootContainer(refNode) ||
  254. !isLegalContainedNode(refNode)) {
  255. throw new RangeExceptionImpl(
  256. RangeException.INVALID_NODE_TYPE_ERR,
  257. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
  258. }
  259. if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) {
  260. throw new DOMException(
  261. DOMException.WRONG_DOCUMENT_ERR,
  262. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
  263. }
  264. fStartContainer = refNode.getParentNode();
  265. int i = 0;
  266. for (Node n = refNode; n!=null; n = n.getPreviousSibling()) {
  267. i++;
  268. }
  269. fStartOffset = i;
  270. }
  271. public void setEndBefore(Node refNode)
  272. throws RangeException
  273. {
  274. if( fDetach) {
  275. throw new DOMException(
  276. DOMException.INVALID_STATE_ERR,
  277. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
  278. }
  279. if ( !hasLegalRootContainer(refNode) ||
  280. !isLegalContainedNode(refNode)) {
  281. throw new RangeExceptionImpl(
  282. RangeException.INVALID_NODE_TYPE_ERR,
  283. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
  284. }
  285. if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) {
  286. throw new DOMException(
  287. DOMException.WRONG_DOCUMENT_ERR,
  288. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
  289. }
  290. fEndContainer = refNode.getParentNode();
  291. int i = 0;
  292. for (Node n = refNode; n!=null; n = n.getPreviousSibling()) {
  293. i++;
  294. }
  295. fEndOffset = i-1;
  296. }
  297. public void setEndAfter(Node refNode)
  298. throws RangeException
  299. {
  300. if( fDetach) {
  301. throw new DOMException(
  302. DOMException.INVALID_STATE_ERR,
  303. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
  304. }
  305. if ( !hasLegalRootContainer(refNode) ||
  306. !isLegalContainedNode(refNode)) {
  307. throw new RangeExceptionImpl(
  308. RangeException.INVALID_NODE_TYPE_ERR,
  309. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
  310. }
  311. if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) {
  312. throw new DOMException(
  313. DOMException.WRONG_DOCUMENT_ERR,
  314. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
  315. }
  316. fEndContainer = refNode.getParentNode();
  317. int i = 0;
  318. for (Node n = refNode; n!=null; n = n.getPreviousSibling()) {
  319. i++;
  320. }
  321. fEndOffset = i;
  322. }
  323. public void collapse(boolean toStart) {
  324. if( fDetach) {
  325. throw new DOMException(
  326. DOMException.INVALID_STATE_ERR,
  327. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
  328. }
  329. if (toStart) {
  330. fEndContainer = fStartContainer;
  331. fEndOffset = fStartOffset;
  332. } else {
  333. fStartContainer = fEndContainer;
  334. fStartOffset = fEndOffset;
  335. }
  336. }
  337. public void selectNode(Node refNode)
  338. throws RangeException
  339. {
  340. if( fDetach) {
  341. throw new DOMException(
  342. DOMException.INVALID_STATE_ERR,
  343. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
  344. }
  345. if ( !isLegalContainer( refNode.getParentNode() ) ||
  346. !isLegalContainedNode( refNode ) ) {
  347. throw new RangeExceptionImpl(
  348. RangeException.INVALID_NODE_TYPE_ERR,
  349. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
  350. }
  351. if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) {
  352. throw new DOMException(
  353. DOMException.WRONG_DOCUMENT_ERR,
  354. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
  355. }
  356. Node parent = refNode.getParentNode();
  357. if (parent != null ) // REVIST: what to do if it IS null?
  358. {
  359. fStartContainer = parent;
  360. fEndContainer = parent;
  361. int i = 0;
  362. for (Node n = refNode; n!=null; n = n.getPreviousSibling()) {
  363. i++;
  364. }
  365. fStartOffset = i-1;
  366. fEndOffset = fStartOffset+1;
  367. }
  368. }
  369. public void selectNodeContents(Node refNode)
  370. throws RangeException
  371. {
  372. if( fDetach) {
  373. throw new DOMException(
  374. DOMException.INVALID_STATE_ERR,
  375. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
  376. }
  377. if ( !isLegalContainer(refNode)) {
  378. throw new RangeExceptionImpl(
  379. RangeException.INVALID_NODE_TYPE_ERR,
  380. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
  381. }
  382. if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) {
  383. throw new DOMException(
  384. DOMException.WRONG_DOCUMENT_ERR,
  385. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
  386. }
  387. fStartContainer = refNode;
  388. fEndContainer = refNode;
  389. Node first = refNode.getFirstChild();
  390. fStartOffset = 0;
  391. if (first == null) {
  392. fEndOffset = 0;
  393. } else {
  394. int i = 0;
  395. for (Node n = first; n!=null; n = n.getNextSibling()) {
  396. i++;
  397. }
  398. fEndOffset = i;
  399. }
  400. }
  401. public short compareBoundaryPoints(short how, Range sourceRange)
  402. throws DOMException
  403. {
  404. if( fDetach) {
  405. throw new DOMException(
  406. DOMException.INVALID_STATE_ERR,
  407. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
  408. }
  409. Node endPointA;
  410. Node endPointB;
  411. int offsetA;
  412. int offsetB;
  413. if (how == START_TO_START) {
  414. endPointA = sourceRange.getStartContainer();
  415. endPointB = fStartContainer;
  416. offsetA = sourceRange.getStartOffset();
  417. offsetB = fStartOffset;
  418. } else
  419. if (how == START_TO_END) {
  420. endPointA = sourceRange.getStartContainer();
  421. endPointB = fEndContainer;
  422. offsetA = sourceRange.getStartOffset();
  423. offsetB = fEndOffset;
  424. } else
  425. if (how == END_TO_START) {
  426. endPointA = sourceRange.getEndContainer();
  427. endPointB = fStartContainer;
  428. offsetA = sourceRange.getEndOffset();
  429. offsetB = fStartOffset;
  430. } else {
  431. endPointA = sourceRange.getEndContainer();
  432. endPointB = fEndContainer;
  433. offsetA = sourceRange.getEndOffset();
  434. offsetB = fEndOffset;
  435. }
  436. // The DOM Spec outlines four cases that need to be tested
  437. // to compare two range boundary points:
  438. // case 1: same container
  439. // case 2: Child C of container A is ancestor of B
  440. // case 3: Child C of container B is ancestor of A
  441. // case 4: preorder traversal of context tree.
  442. // case 1: same container
  443. if (endPointA == endPointB) {
  444. if (offsetA < offsetB) return 1;
  445. if (offsetA == offsetB) return 0;
  446. return -1;
  447. }
  448. // case 2: Child C of container A is ancestor of B
  449. // This can be quickly tested by walking the parent chain of B
  450. for ( Node c = endPointB, p = c.getParentNode();
  451. p != null;
  452. c = p, p = p.getParentNode())
  453. {
  454. if (p == endPointA) {
  455. int index = indexOf(c, endPointA);
  456. if (offsetA <= index) return 1;
  457. return -1;
  458. }
  459. }
  460. // case 3: Child C of container B is ancestor of A
  461. // This can be quickly tested by walking the parent chain of A
  462. for ( Node c = endPointA, p = c.getParentNode();
  463. p != null;
  464. c = p, p = p.getParentNode())
  465. {
  466. if (p == endPointB) {
  467. int index = indexOf(c, endPointB);
  468. if (index < offsetB) return 1;
  469. return -1;
  470. }
  471. }
  472. // case 4: preorder traversal of context tree.
  473. // Instead of literally walking the context tree in pre-order,
  474. // we use relative node depth walking which is usually faster
  475. int depthDiff = 0;
  476. for ( Node n = endPointA; n != null; n = n.getParentNode() )
  477. depthDiff++;
  478. for ( Node n = endPointB; n != null; n = n.getParentNode() )
  479. depthDiff--;
  480. while (depthDiff > 0) {
  481. endPointA = endPointA.getParentNode();
  482. depthDiff--;
  483. }
  484. while (depthDiff < 0) {
  485. endPointB = endPointB.getParentNode();
  486. depthDiff++;
  487. }
  488. for (Node pA = endPointA.getParentNode(),
  489. pB = endPointB.getParentNode();
  490. pA != pB;
  491. pA = pA.getParentNode(), pB = pB.getParentNode() )
  492. {
  493. endPointA = pA;
  494. endPointB = pB;
  495. }
  496. for ( Node n = endPointA.getNextSibling();
  497. n != null;
  498. n = n.getNextSibling() )
  499. {
  500. if (n == endPointB) {
  501. return 1;
  502. }
  503. }
  504. return -1;
  505. }
  506. public void deleteContents()
  507. throws DOMException
  508. {
  509. traverseContents(DELETE_CONTENTS);
  510. }
  511. public DocumentFragment extractContents()
  512. throws DOMException
  513. {
  514. return traverseContents(EXTRACT_CONTENTS);
  515. }
  516. public DocumentFragment cloneContents()
  517. throws DOMException
  518. {
  519. return traverseContents(CLONE_CONTENTS);
  520. }
  521. public void insertNode(Node newNode)
  522. throws DOMException, RangeException
  523. {
  524. if ( newNode == null ) return; //throw exception?
  525. if( fDetach) {
  526. throw new DOMException(
  527. DOMException.INVALID_STATE_ERR,
  528. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
  529. }
  530. if ( fDocument != newNode.getOwnerDocument() ) {
  531. throw new DOMException(DOMException.WRONG_DOCUMENT_ERR,
  532. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
  533. }
  534. int type = newNode.getNodeType();
  535. if (type == Node.ATTRIBUTE_NODE
  536. || type == Node.ENTITY_NODE
  537. || type == Node.NOTATION_NODE
  538. || type == Node.DOCUMENT_NODE)
  539. {
  540. throw new RangeExceptionImpl(
  541. RangeException.INVALID_NODE_TYPE_ERR,
  542. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
  543. }
  544. Node cloneCurrent;
  545. Node current;
  546. int currentChildren = 0;
  547. //boolean MULTIPLE_MODE = false;
  548. if (fStartContainer.getNodeType() == Node.TEXT_NODE) {
  549. Node parent = fStartContainer.getParentNode();
  550. currentChildren = parent.getChildNodes().getLength(); //holds number of kids before insertion
  551. // split text node: results is 3 nodes..
  552. cloneCurrent = fStartContainer.cloneNode(false);
  553. ((TextImpl)cloneCurrent).setNodeValueInternal(
  554. (cloneCurrent.getNodeValue()).substring(fStartOffset));
  555. ((TextImpl)fStartContainer).setNodeValueInternal(
  556. (fStartContainer.getNodeValue()).substring(0,fStartOffset));
  557. Node next = fStartContainer.getNextSibling();
  558. if (next != null) {
  559. if (parent != null) {
  560. parent.insertBefore(newNode, next);
  561. parent.insertBefore(cloneCurrent, next);
  562. }
  563. } else {
  564. if (parent != null) {
  565. parent.appendChild(newNode);
  566. parent.appendChild(cloneCurrent);
  567. }
  568. }
  569. //update ranges after the insertion
  570. if ( fEndContainer == fStartContainer) {
  571. fEndContainer = cloneCurrent; //endContainer is the new Node created
  572. fEndOffset -= fStartOffset;
  573. }
  574. else if ( fEndContainer == parent ) { //endContainer was not a text Node.
  575. //endOffset + = number_of_children_added
  576. fEndOffset += (parent.getChildNodes().getLength() - currentChildren);
  577. }
  578. // signal other Ranges to update their start/end containers/offsets
  579. signalSplitData(fStartContainer, cloneCurrent, fStartOffset);
  580. } else { // ! TEXT_NODE
  581. if ( fEndContainer == fStartContainer ) //need to remember number of kids
  582. currentChildren= fEndContainer.getChildNodes().getLength();
  583. current = fStartContainer.getFirstChild();
  584. int i = 0;
  585. for(i = 0; i < fStartOffset && current != null; i++) {
  586. current=current.getNextSibling();
  587. }
  588. if (current != null) {
  589. fStartContainer.insertBefore(newNode, current);
  590. } else {
  591. fStartContainer.appendChild(newNode);
  592. }
  593. //update fEndOffset. ex:<body><p/></body>. Range(start;end): body,0; body,1
  594. // insert <h1>: <body></h1><p/></body>. Range(start;end): body,0; body,2
  595. if ( fEndContainer == fStartContainer ) { //update fEndOffset
  596. fEndOffset += (fEndContainer.getChildNodes().getLength() - currentChildren);
  597. }
  598. }
  599. }
  600. public void surroundContents(Node newParent)
  601. throws DOMException, RangeException
  602. {
  603. if (newParent==null) return;
  604. if( fDetach) {
  605. throw new DOMException(
  606. DOMException.INVALID_STATE_ERR,
  607. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
  608. }
  609. int type = newParent.getNodeType();
  610. if (type == Node.ATTRIBUTE_NODE
  611. || type == Node.ENTITY_NODE
  612. || type == Node.NOTATION_NODE
  613. || type == Node.DOCUMENT_TYPE_NODE
  614. || type == Node.DOCUMENT_NODE
  615. || type == Node.DOCUMENT_FRAGMENT_NODE)
  616. {
  617. throw new RangeExceptionImpl(
  618. RangeException.INVALID_NODE_TYPE_ERR,
  619. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
  620. }
  621. Node root = getCommonAncestorContainer();
  622. Node realStart = fStartContainer;
  623. Node realEnd = fEndContainer;
  624. if (fStartContainer.getNodeType() == Node.TEXT_NODE) {
  625. realStart = fStartContainer.getParentNode();
  626. }
  627. if (fEndContainer.getNodeType() == Node.TEXT_NODE) {
  628. realEnd = fEndContainer.getParentNode();
  629. }
  630. if (realStart != realEnd) {
  631. throw new RangeExceptionImpl(
  632. RangeException.BAD_BOUNDARYPOINTS_ERR,
  633. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "BAD_BOUNDARYPOINTS_ERR", null));
  634. }
  635. DocumentFragment frag = extractContents();
  636. insertNode(newParent);
  637. newParent.appendChild(frag);
  638. selectNode(newParent);
  639. }
  640. public Range cloneRange(){
  641. if( fDetach) {
  642. throw new DOMException(
  643. DOMException.INVALID_STATE_ERR,
  644. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
  645. }
  646. Range range = fDocument.createRange();
  647. range.setStart(fStartContainer, fStartOffset);
  648. range.setEnd(fEndContainer, fEndOffset);
  649. return range;
  650. }
  651. public String toString(){
  652. if( fDetach) {
  653. throw new DOMException(
  654. DOMException.INVALID_STATE_ERR,
  655. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
  656. }
  657. Node node = fStartContainer;
  658. Node stopNode = fEndContainer;
  659. StringBuffer sb = new StringBuffer();
  660. if (fStartContainer.getNodeType() == Node.TEXT_NODE
  661. || fStartContainer.getNodeType() == Node.CDATA_SECTION_NODE
  662. ) {
  663. if (fStartContainer == fEndContainer) {
  664. sb.append(fStartContainer.getNodeValue().substring(fStartOffset, fEndOffset));
  665. return sb.toString();
  666. }
  667. sb.append(fStartContainer.getNodeValue().substring(fStartOffset));
  668. node=nextNode (node,true); //fEndContainer!=fStartContainer
  669. }
  670. else { //fStartContainer is not a TextNode
  671. node=node.getFirstChild();
  672. if (fStartOffset>0) { //find a first node within a range, specified by fStartOffset
  673. int counter=0;
  674. while (counter<fStartOffset && node!=null) {
  675. node=node.getNextSibling();
  676. counter++;
  677. }
  678. }
  679. if (node == null) {
  680. node = nextNode(fStartContainer,false);
  681. }
  682. }
  683. if ( fEndContainer.getNodeType()!= Node.TEXT_NODE &&
  684. fEndContainer.getNodeType()!= Node.CDATA_SECTION_NODE ){
  685. int i=fEndOffset;
  686. stopNode = fEndContainer.getFirstChild();
  687. while( i>0 && stopNode!=null ){
  688. --i;
  689. stopNode = stopNode.getNextSibling();
  690. }
  691. if ( stopNode == null )
  692. stopNode = nextNode( fEndContainer, false );
  693. }
  694. while (node != stopNode) { //look into all kids of the Range
  695. if (node == null) break;
  696. if (node.getNodeType() == Node.TEXT_NODE
  697. || node.getNodeType() == Node.CDATA_SECTION_NODE) {
  698. sb.append(node.getNodeValue());
  699. }
  700. node = nextNode(node, true);
  701. }
  702. if (fEndContainer.getNodeType() == Node.TEXT_NODE
  703. || fEndContainer.getNodeType() == Node.CDATA_SECTION_NODE) {
  704. sb.append(fEndContainer.getNodeValue().substring(0,fEndOffset));
  705. }
  706. return sb.toString();
  707. }
  708. public void detach() {
  709. fDetach = true;
  710. fDocument.removeRange(this);
  711. }
  712. //
  713. // Mutation functions
  714. //
  715. /** Signal other Ranges to update their start/end
  716. * containers/offsets. The data has already been split
  717. * into the two Nodes.
  718. */
  719. void signalSplitData(Node node, Node newNode, int offset) {
  720. fSplitNode = node;
  721. // notify document
  722. fDocument.splitData(node, newNode, offset);
  723. fSplitNode = null;
  724. }
  725. /** Fix up this Range if another Range has split a Text Node
  726. * into 2 Nodes.
  727. */
  728. void receiveSplitData(Node node, Node newNode, int offset) {
  729. if (node == null || newNode == null) return;
  730. if (fSplitNode == node) return;
  731. if (node == fStartContainer
  732. && fStartContainer.getNodeType() == Node.TEXT_NODE) {
  733. if (fStartOffset > offset) {
  734. fStartOffset = fStartOffset - offset;
  735. fStartContainer = newNode;
  736. }
  737. }
  738. if (node == fEndContainer
  739. && fEndContainer.getNodeType() == Node.TEXT_NODE) {
  740. if (fEndOffset > offset) {
  741. fEndOffset = fEndOffset-offset;
  742. fEndContainer = newNode;
  743. }
  744. }
  745. }
  746. /** This function inserts text into a Node and invokes
  747. * a method to fix-up all other Ranges.
  748. */
  749. void deleteData(CharacterData node, int offset, int count) {
  750. fDeleteNode = node;
  751. node.deleteData( offset, count);
  752. fDeleteNode = null;
  753. }
  754. /** This function is called from DOM.
  755. * The text has already beeen inserted.
  756. * Fix-up any offsets.
  757. */
  758. void receiveDeletedText(Node node, int offset, int count) {
  759. if (node == null) return;
  760. if (fDeleteNode == node) return;
  761. if (node == fStartContainer
  762. && fStartContainer.getNodeType() == Node.TEXT_NODE) {
  763. if (fStartOffset > offset+count) {
  764. fStartOffset = offset+(fStartOffset-(offset+count));
  765. } else
  766. if (fStartOffset > offset) {
  767. fStartOffset = offset;
  768. }
  769. }
  770. if (node == fEndContainer
  771. && fEndContainer.getNodeType() == Node.TEXT_NODE) {
  772. if (fEndOffset > offset+count) {
  773. fEndOffset = offset+(fEndOffset-(offset+count));
  774. } else
  775. if (fEndOffset > offset) {
  776. fEndOffset = offset;
  777. }
  778. }
  779. }
  780. /** This function inserts text into a Node and invokes
  781. * a method to fix-up all other Ranges.
  782. */
  783. void insertData(CharacterData node, int index, String insert) {
  784. fInsertNode = node;
  785. node.insertData( index, insert);
  786. fInsertNode = null;
  787. }
  788. /** This function is called from DOM.
  789. * The text has already beeen inserted.
  790. * Fix-up any offsets.
  791. */
  792. void receiveInsertedText(Node node, int index, int len) {
  793. if (node == null) return;
  794. if (fInsertNode == node) return;
  795. if (node == fStartContainer
  796. && fStartContainer.getNodeType() == Node.TEXT_NODE) {
  797. if (index < fStartOffset) {
  798. fStartOffset = fStartOffset+len;
  799. }
  800. }
  801. if (node == fEndContainer
  802. && fEndContainer.getNodeType() == Node.TEXT_NODE) {
  803. if (index < fEndOffset) {
  804. fEndOffset = fEndOffset+len;
  805. }
  806. }
  807. }
  808. /** This function is called from DOM.
  809. * The text has already beeen replaced.
  810. * Fix-up any offsets.
  811. */
  812. void receiveReplacedText(Node node) {
  813. if (node == null) return;
  814. if (node == fStartContainer
  815. && fStartContainer.getNodeType() == Node.TEXT_NODE) {
  816. fStartOffset = 0;
  817. }
  818. if (node == fEndContainer
  819. && fEndContainer.getNodeType() == Node.TEXT_NODE) {
  820. fEndOffset = 0;
  821. }
  822. }
  823. /** This function is called from the DOM.
  824. * This node has already been inserted into the DOM.
  825. * Fix-up any offsets.
  826. */
  827. public void insertedNodeFromDOM(Node node) {
  828. if (node == null) return;
  829. if (fInsertNode == node) return;
  830. Node parent = node.getParentNode();
  831. if (parent == fStartContainer) {
  832. int index = indexOf(node, fStartContainer);
  833. if (index < fStartOffset) {
  834. fStartOffset++;
  835. }
  836. }
  837. if (parent == fEndContainer) {
  838. int index = indexOf(node, fEndContainer);
  839. if (index < fEndOffset) {
  840. fEndOffset++;
  841. }
  842. }
  843. }
  844. /** This function is called within Range
  845. * instead of Node.removeChild,
  846. * so that the range can remember that it is actively
  847. * removing this child.
  848. */
  849. Node fRemoveChild = null;
  850. Node removeChild(Node parent, Node child) {
  851. fRemoveChild = child;
  852. Node n = parent.removeChild(child);
  853. fRemoveChild = null;
  854. return n;
  855. }
  856. /** This function must be called by the DOM _BEFORE_
  857. * a node is deleted, because at that time it is
  858. * connected in the DOM tree, which we depend on.
  859. */
  860. void removeNode(Node node) {
  861. if (node == null) return;
  862. if (fRemoveChild == node) return;
  863. Node parent = node.getParentNode();
  864. if (parent == fStartContainer) {
  865. int index = indexOf(node, fStartContainer);
  866. if (index < fStartOffset) {
  867. fStartOffset--;
  868. }
  869. }
  870. if (parent == fEndContainer) {
  871. int index = indexOf(node, fEndContainer);
  872. if (index < fEndOffset) {
  873. fEndOffset--;
  874. }
  875. }
  876. //startContainer or endContainer or both is/are the ancestor(s) of the Node to be deleted
  877. if (parent != fStartContainer
  878. || parent != fEndContainer) {
  879. if (isAncestorOf(node, fStartContainer)) {
  880. fStartContainer = parent;
  881. fStartOffset = indexOf( node, parent);
  882. }
  883. if (isAncestorOf(node, fEndContainer)) {
  884. fEndContainer = parent;
  885. fEndOffset = indexOf( node, parent);
  886. }
  887. }
  888. }
  889. //
  890. // Utility functions.
  891. //
  892. // parameters for traverseContents(int)
  893. //REVIST: use boolean, since there are only 2 now...
  894. static final int EXTRACT_CONTENTS = 1;
  895. static final int CLONE_CONTENTS = 2;
  896. static final int DELETE_CONTENTS = 3;
  897. /**
  898. * This is the master routine invoked to visit the nodes
  899. * selected by this range. For each such node, different
  900. * actions are taken depending on the value of the
  901. * <code>how</code> argument.
  902. *
  903. * @param how Specifies what type of traversal is being
  904. * requested (extract, clone, or delete).
  905. * Legal values for this argument are:
  906. *
  907. * <ol>
  908. * <li><code>EXTRACT_CONTENTS</code> - will produce
  909. * a document fragment containing the range's content.
  910. * Partially selected nodes are copied, but fully
  911. * selected nodes are moved.
  912. *
  913. * <li><code>CLONE_CONTENTS</code> - will leave the
  914. * context tree of the range undisturbed, but sill
  915. * produced cloned content in a document fragment
  916. *
  917. * <li><code>DELETE_CONTENTS</code> - will delete from
  918. * the context tree of the range, all fully selected
  919. * nodes.
  920. * </ol>
  921. *
  922. * @return Returns a document fragment containing any
  923. * copied or extracted nodes. If the <code>how</code>
  924. * parameter was <code>DELETE_CONTENTS</code>, the
  925. * return value is null.
  926. */
  927. private DocumentFragment traverseContents( int how )
  928. throws DOMException
  929. {
  930. if (fStartContainer == null || fEndContainer == null) {
  931. return null; // REVIST: Throw exception?
  932. }
  933. //Check for a detached range.
  934. if( fDetach) {
  935. throw new DOMException(
  936. DOMException.INVALID_STATE_ERR,
  937. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
  938. }
  939. /*
  940. Traversal is accomplished by first determining the
  941. relationship between the endpoints of the range.
  942. For each of four significant relationships, we will
  943. delegate the traversal call to a method that
  944. can make appropriate assumptions.
  945. */
  946. // case 1: same container
  947. if ( fStartContainer == fEndContainer )
  948. return traverseSameContainer( how );
  949. // case 2: Child C of start container is ancestor of end container
  950. // This can be quickly tested by walking the parent chain of
  951. // end container
  952. int endContainerDepth = 0;
  953. for ( Node c = fEndContainer, p = c.getParentNode();
  954. p != null;
  955. c = p, p = p.getParentNode())
  956. {
  957. if (p == fStartContainer)
  958. return traverseCommonStartContainer( c, how );
  959. ++endContainerDepth;
  960. }
  961. // case 3: Child C of container B is ancestor of A
  962. // This can be quickly tested by walking the parent chain of A
  963. int startContainerDepth = 0;
  964. for ( Node c = fStartContainer, p = c.getParentNode();
  965. p != null;
  966. c = p, p = p.getParentNode())
  967. {
  968. if (p == fEndContainer)
  969. return traverseCommonEndContainer( c, how );
  970. ++startContainerDepth;
  971. }
  972. // case 4: There is a common ancestor container. Find the
  973. // ancestor siblings that are children of that container.
  974. int depthDiff = startContainerDepth - endContainerDepth;
  975. Node startNode = fStartContainer;
  976. while (depthDiff > 0) {
  977. startNode = startNode.getParentNode();
  978. depthDiff--;
  979. }
  980. Node endNode = fEndContainer;
  981. while (depthDiff < 0) {
  982. endNode = endNode.getParentNode();
  983. depthDiff++;
  984. }
  985. // ascend the ancestor hierarchy until we have a common parent.
  986. for( Node sp = startNode.getParentNode(), ep = endNode.getParentNode();
  987. sp!=ep;
  988. sp = sp.getParentNode(), ep = ep.getParentNode() )
  989. {
  990. startNode = sp;
  991. endNode = ep;
  992. }
  993. return traverseCommonAncestors( startNode, endNode, how );
  994. }
  995. /**
  996. * Visits the nodes selected by this range when we know
  997. * a-priori that the start and end containers are the same.
  998. * This method is invoked by the generic <code>traverse</code>
  999. * method.
  1000. *
  1001. * @param how Specifies what type of traversal is being
  1002. * requested (extract, clone, or delete).
  1003. * Legal values for this argument are:
  1004. *
  1005. * <ol>
  1006. * <li><code>EXTRACT_CONTENTS</code> - will produce
  1007. * a document fragment containing the range's content.
  1008. * Partially selected nodes are copied, but fully
  1009. * selected nodes are moved.
  1010. *
  1011. * <li><code>CLONE_CONTENTS</code> - will leave the
  1012. * context tree of the range undisturbed, but sill
  1013. * produced cloned content in a document fragment
  1014. *
  1015. * <li><code>DELETE_CONTENTS</code> - will delete from
  1016. * the context tree of the range, all fully selected
  1017. * nodes.
  1018. * </ol>
  1019. *
  1020. * @return Returns a document fragment containing any
  1021. * copied or extracted nodes. If the <code>how</code>
  1022. * parameter was <code>DELETE_CONTENTS</code>, the
  1023. * return value is null.
  1024. */
  1025. private DocumentFragment traverseSameContainer( int how )
  1026. {
  1027. DocumentFragment frag = null;
  1028. if ( how!=DELETE_CONTENTS)
  1029. frag = fDocument.createDocumentFragment();
  1030. // If selection is empty, just return the fragment
  1031. if ( fStartOffset==fEndOffset )
  1032. return frag;
  1033. // Text node needs special case handling
  1034. if ( fStartContainer.getNodeType()==Node.TEXT_NODE )
  1035. {
  1036. // get the substring
  1037. String s = fStartContainer.getNodeValue();
  1038. String sub = s.substring( fStartOffset, fEndOffset );
  1039. // set the original text node to its new value
  1040. if ( how != CLONE_CONTENTS )
  1041. {
  1042. ((TextImpl)fStartContainer).deleteData(fStartOffset,
  1043. fEndOffset-fStartOffset) ;
  1044. // Nothing is partially selected, so collapse to start point
  1045. collapse( true );
  1046. }
  1047. if ( how==DELETE_CONTENTS)
  1048. return null;
  1049. frag.appendChild( fDocument.createTextNode(sub) );
  1050. return frag;
  1051. }
  1052. // Copy nodes between the start/end offsets.
  1053. Node n = getSelectedNode( fStartContainer, fStartOffset );
  1054. int cnt = fEndOffset - fStartOffset;
  1055. while( cnt > 0 )
  1056. {
  1057. Node sibling = n.getNextSibling();
  1058. Node xferNode = traverseFullySelected( n, how );
  1059. if ( frag!=null )
  1060. frag.appendChild( xferNode );
  1061. --cnt;
  1062. n = sibling;
  1063. }
  1064. // Nothing is partially selected, so collapse to start point
  1065. if ( how != CLONE_CONTENTS )
  1066. collapse( true );
  1067. return frag;
  1068. }
  1069. /**
  1070. * Visits the nodes selected by this range when we know
  1071. * a-priori that the start and end containers are not the
  1072. * same, but the start container is an ancestor of the
  1073. * end container. This method is invoked by the generic
  1074. * <code>traverse</code> method.
  1075. *
  1076. * @param endAncestor
  1077. * The ancestor of the end container that is a direct child
  1078. * of the start container.
  1079. *
  1080. * @param how Specifies what type of traversal is being
  1081. * requested (extract, clone, or delete).
  1082. * Legal values for this argument are:
  1083. *
  1084. * <ol>
  1085. * <li><code>EXTRACT_CONTENTS</code> - will produce
  1086. * a document fragment containing the range's content.
  1087. * Partially selected nodes are copied, but fully
  1088. * selected nodes are moved.
  1089. *
  1090. * <li><code>CLONE_CONTENTS</code> - will leave the
  1091. * context tree of the range undisturbed, but sill
  1092. * produced cloned content in a document fragment
  1093. *
  1094. * <li><code>DELETE_CONTENTS</code> - will delete from
  1095. * the context tree of the range, all fully selected
  1096. * nodes.
  1097. * </ol>
  1098. *
  1099. * @return Returns a document fragment containing any
  1100. * copied or extracted nodes. If the <code>how</code>
  1101. * parameter was <code>DELETE_CONTENTS</code>, the
  1102. * return value is null.
  1103. */
  1104. private DocumentFragment
  1105. traverseCommonStartContainer( Node endAncestor, int how )
  1106. {
  1107. DocumentFragment frag = null;
  1108. if ( how!=DELETE_CONTENTS)
  1109. frag = fDocument.createDocumentFragment();
  1110. Node n = traverseRightBoundary( endAncestor, how );
  1111. if ( frag!=null )
  1112. frag.appendChild( n );
  1113. int endIdx = indexOf( endAncestor, fStartContainer );
  1114. int cnt = endIdx - fStartOffset;
  1115. if ( cnt <=0 )
  1116. {
  1117. // Collapse to just before the endAncestor, which
  1118. // is partially selected.
  1119. if ( how != CLONE_CONTENTS )
  1120. {
  1121. setEndBefore( endAncestor );
  1122. collapse( false );
  1123. }
  1124. return frag;
  1125. }
  1126. n = endAncestor.getPreviousSibling();
  1127. while( cnt > 0 )
  1128. {
  1129. Node sibling = n.getPreviousSibling();
  1130. Node xferNode = traverseFullySelected( n, how );
  1131. if ( frag!=null )
  1132. frag.insertBefore( xferNode, frag.getFirstChild() );
  1133. --cnt;
  1134. n = sibling;
  1135. }
  1136. // Collapse to just before the endAncestor, which
  1137. // is partially selected.
  1138. if ( how != CLONE_CONTENTS )
  1139. {
  1140. setEndBefore( endAncestor );
  1141. collapse( false );
  1142. }
  1143. return frag;
  1144. }
  1145. /**
  1146. * Visits the nodes selected by this range when we know
  1147. * a-priori that the start and end containers are not the
  1148. * same, but the end container is an ancestor of the
  1149. * start container. This method is invoked by the generic
  1150. * <code>traverse</code> method.
  1151. *
  1152. * @param startAncestor
  1153. * The ancestor of the start container that is a direct
  1154. * child of the end container.
  1155. *
  1156. * @param how Specifies what type of traversal is being
  1157. * requested (extract, clone, or delete).
  1158. * Legal values for this argument are:
  1159. *
  1160. * <ol>
  1161. * <li><code>EXTRACT_CONTENTS</code> - will produce
  1162. * a document fragment containing the range's content.
  1163. * Partially selected nodes are copied, but fully
  1164. * selected nodes are moved.
  1165. *
  1166. * <li><code>CLONE_CONTENTS</code> - will leave the
  1167. * context tree of the range undisturbed, but sill
  1168. * produced cloned content in a document fragment
  1169. *
  1170. * <li><code>DELETE_CONTENTS</code> - will delete from
  1171. * the context tree of the range, all fully selected
  1172. * nodes.
  1173. * </ol>
  1174. *
  1175. * @return Returns a document fragment containing any
  1176. * copied or extracted nodes. If the <code>how</code>
  1177. * parameter was <code>DELETE_CONTENTS</code>, the
  1178. * return value is null.
  1179. */
  1180. private DocumentFragment
  1181. traverseCommonEndContainer( Node startAncestor, int how )
  1182. {
  1183. DocumentFragment frag = null;
  1184. if ( how!=DELETE_CONTENTS)
  1185. frag = fDocument.createDocumentFragment();
  1186. Node n = traverseLeftBoundary( startAncestor, how );
  1187. if ( frag!=null )
  1188. frag.appendChild( n );
  1189. int startIdx = indexOf( startAncestor, fEndContainer );
  1190. ++startIdx; // Because we already traversed it....
  1191. int cnt = fEndOffset - startIdx;
  1192. n = startAncestor.getNextSibling();
  1193. while( cnt > 0 )
  1194. {
  1195. Node sibling = n.getNextSibling();
  1196. Node xferNode = traverseFullySelected( n, how );
  1197. if ( frag!=null )
  1198. frag.appendChild( xferNode );
  1199. --cnt;
  1200. n = sibling;
  1201. }
  1202. if ( how != CLONE_CONTENTS )
  1203. {
  1204. setStartAfter( startAncestor );
  1205. collapse( true );
  1206. }
  1207. return frag;
  1208. }
  1209. /**
  1210. * Visits the nodes selected by this range when we know
  1211. * a-priori that the start and end containers are not
  1212. * the same, and we also know that neither the start
  1213. * nor end container is an ancestor of the other.
  1214. * This method is invoked by
  1215. * the generic <code>traverse</code> method.
  1216. *
  1217. * @param startAncestor
  1218. * Given a common ancestor of the start and end containers,
  1219. * this parameter is the ancestor (or self) of the start
  1220. * container that is a direct child of the common ancestor.
  1221. *
  1222. * @param endAncestor
  1223. * Given a common ancestor of the start and end containers,
  1224. * this parameter is the ancestor (or self) of the end
  1225. * container that is a direct child of the common ancestor.
  1226. *
  1227. * @param how Specifies what type of traversal is being
  1228. * requested (extract, clone, or delete).
  1229. * Legal values for this argument are:
  1230. *
  1231. * <ol>
  1232. * <li><code>EXTRACT_CONTENTS</code> - will produce
  1233. * a document fragment containing the range's content.
  1234. * Partially selected nodes are copied, but fully
  1235. * selected nodes are moved.
  1236. *
  1237. * <li><code>CLONE_CONTENTS</code> - will leave the
  1238. * context tree of the range undisturbed, but sill
  1239. * produced cloned content in a document fragment
  1240. *
  1241. * <li><code>DELETE_CONTENTS</code> - will delete from
  1242. * the context tree of the range, all fully selected
  1243. * nodes.
  1244. * </ol>
  1245. *
  1246. * @return Returns a document fragment containing any
  1247. * copied or extracted nodes. If the <code>how</code>
  1248. * parameter was <code>DELETE_CONTENTS</code>, the
  1249. * return value is null.
  1250. */
  1251. private DocumentFragment
  1252. traverseCommonAncestors( Node startAncestor, Node endAncestor, int how )
  1253. {
  1254. DocumentFragment frag = null;
  1255. if ( how!=DELETE_CONTENTS)
  1256. frag = fDocument.createDocumentFragment();
  1257. Node n = traverseLeftBoundary( startAncestor, how );
  1258. if ( frag!=null )
  1259. frag.appendChild( n );
  1260. Node commonParent = startAncestor.getParentNode();
  1261. int startOffset = indexOf( startAncestor, commonParent );
  1262. int endOffset = indexOf( endAncestor, commonParent );
  1263. ++startOffset;
  1264. int cnt = endOffset - startOffset;
  1265. Node sibling = startAncestor.getNextSibling();
  1266. while( cnt > 0 )
  1267. {
  1268. Node nextSibling = sibling.getNextSibling();
  1269. n = traverseFullySelected( sibling, how );
  1270. if ( frag!=null )
  1271. frag.appendChild( n );
  1272. sibling = nextSibling;
  1273. --cnt;
  1274. }
  1275. n = traverseRightBoundary( endAncestor, how );
  1276. if ( frag!=null )
  1277. frag.appendChild( n );
  1278. if ( how != CLONE_CONTENTS )
  1279. {
  1280. setStartAfter( startAncestor );
  1281. collapse( true );
  1282. }
  1283. return frag;
  1284. }
  1285. /**
  1286. * Traverses the "right boundary" of this range and
  1287. * operates on each "boundary node" according to the
  1288. * <code>how</code> parameter. It is a-priori assumed
  1289. * by this method that the right boundary does
  1290. * not contain the range's start container.
  1291. * <p>
  1292. * A "right boundary" is best visualized by thinking
  1293. * of a sample tree:<pre>
  1294. * A
  1295. * /|\
  1296. * / | \
  1297. * / | \
  1298. * B C D
  1299. * /|\ /|\
  1300. * E F G H I J
  1301. * </pre>
  1302. * Imagine first a range that begins between the
  1303. * "E" and "F" nodes and ends between the
  1304. * "I" and "J" nodes. The start container is
  1305. * "B" and the end container is "D". Given this setup,
  1306. * the following applies:
  1307. * <p>
  1308. * Partially Selected Nodes: B, D<br>
  1309. * Fully Selected Nodes: F, G, C, H, I
  1310. * <p>
  1311. * The "right boundary" is the highest subtree node
  1312. * that contains the ending container. The root of
  1313. * this subtree is always partially selected.
  1314. * <p>
  1315. * In this example, the nodes that are traversed
  1316. * as "right boundary" nodes are: H, I, and D.
  1317. *
  1318. * @param root The node that is the root of the "right boundary" subtree.
  1319. *
  1320. * @param how Specifies what type of traversal is being
  1321. * requested (extract, clone, or delete).
  1322. * Legal values for this argument are:
  1323. *
  1324. * <ol>
  1325. * <li><code>EXTRACT_CONTENTS</code> - will produce
  1326. * a node containing the boundaries content.
  1327. * Partially selected nodes are copied, but fully
  1328. * selected nodes are moved.
  1329. *
  1330. * <li><code>CLONE_CONTENTS</code> - will leave the
  1331. * context tree of the range undisturbed, but will
  1332. * produced cloned content.
  1333. *
  1334. * <li><code>DELETE_CONTENTS</code> - will delete from
  1335. * the context tree of the range, all fully selected
  1336. * nodes within the boundary.
  1337. * </ol>
  1338. *
  1339. * @return Returns a node that is the result of visiting nodes.
  1340. * If the traversal operation is
  1341. * <code>DELETE_CONTENTS</code> the return value is null.
  1342. */
  1343. private Node traverseRightBoundary( Node root, int how )
  1344. {
  1345. Node next = getSelectedNode( fEndContainer, fEndOffset-1 );
  1346. boolean isFullySelected = ( next!=fEndContainer );
  1347. if ( next==root )
  1348. return traverseNode( next, isFullySelected, false, how );
  1349. Node parent = next.getParentNode();
  1350. Node clonedParent = traverseNode( parent, false, false, how );
  1351. while( parent!=null )
  1352. {
  1353. while( next!=null )
  1354. {
  1355. Node prevSibling = next.getPreviousSibling();
  1356. Node clonedChild =
  1357. traverseNode( next, isFullySelected, false, how );
  1358. if ( how!=DELETE_CONTENTS )
  1359. {
  1360. clonedParent.insertBefore(
  1361. clonedChild,
  1362. clonedParent.getFirstChild()
  1363. );
  1364. }
  1365. isFullySelected = true;
  1366. next = prevSibling;
  1367. }
  1368. if ( parent==root )
  1369. return clonedParent;
  1370. next = parent.getPreviousSibling();
  1371. parent = parent.getParentNode();
  1372. Node clonedGrandParent = traverseNode( parent, false, false, how );
  1373. if ( how!=DELETE_CONTENTS )
  1374. clonedGrandParent.appendChild( clonedParent );
  1375. clonedParent = clonedGrandParent;
  1376. }
  1377. // should never occur
  1378. return null;
  1379. }
  1380. /**
  1381. * Traverses the "left boundary" of this range and
  1382. * operates on each "boundary node" according to the
  1383. * <code>how</code> parameter. It is a-priori assumed
  1384. * by this method that the left boundary does
  1385. * not contain the range's end container.
  1386. * <p>
  1387. * A "left boundary" is best visualized by thinking
  1388. * of a sample tree:<pre>
  1389. *
  1390. * A
  1391. * /|\
  1392. * / | \
  1393. * / | \
  1394. * B C D
  1395. * /|\ /|\
  1396. * E F G H I J
  1397. * </pre>
  1398. * Imagine first a range that begins between the
  1399. * "E" and "F" nodes and ends between the
  1400. * "I" and "J" nodes. The start container is
  1401. * "B" and the end container is "D". Given this setup,
  1402. * the following applies:
  1403. * <p>
  1404. * Partially Selected Nodes: B, D<br>
  1405. * Fully Selected Nodes: F, G, C, H, I
  1406. * <p>
  1407. * The "left boundary" is the highest subtree node
  1408. * that contains the starting container. The root of
  1409. * this subtree is always partially selected.
  1410. * <p>
  1411. * In this example, the nodes that are traversed
  1412. * as "left boundary" nodes are: F, G, and B.
  1413. *
  1414. * @param root The node that is the root of the "left boundary" subtree.
  1415. *
  1416. * @param how Specifies what type of traversal is being
  1417. * requested (extract, clone, or delete).
  1418. * Legal values for this argument are:
  1419. *
  1420. * <ol>
  1421. * <li><code>EXTRACT_CONTENTS</code> - will produce
  1422. * a node containing the boundaries content.
  1423. * Partially selected nodes are copied, but fully
  1424. * selected nodes are moved.
  1425. *
  1426. * <li><code>CLONE_CONTENTS</code> - will leave the
  1427. * context tree of the range undisturbed, but will
  1428. * produced cloned content.
  1429. *
  1430. * <li><code>DELETE_CONTENTS</code> - will delete from
  1431. * the context tree of the range, all fully selected
  1432. * nodes within the boundary.
  1433. * </ol>
  1434. *
  1435. * @return Returns a node that is the result of visiting nodes.
  1436. * If the traversal operation is
  1437. * <code>DELETE_CONTENTS</code> the return value is null.
  1438. */
  1439. private Node traverseLeftBoundary( Node root, int how )
  1440. {
  1441. Node next = getSelectedNode( getStartContainer(), getStartOffset() );
  1442. boolean isFullySelected = ( next!=getStartContainer() );
  1443. if ( next==root )
  1444. return traverseNode( next, isFullySelected, true, how );
  1445. Node parent = next.getParentNode();
  1446. Node clonedParent = traverseNode( parent, false, true, how );
  1447. while( parent!=null )
  1448. {
  1449. while( next!=null )
  1450. {
  1451. Node nextSibling = next.getNextSibling();
  1452. Node clonedChild =
  1453. traverseNode( next, isFullySelected, true, how );
  1454. if ( how!=DELETE_CONTENTS )
  1455. clonedParent.appendChild(clonedChild);
  1456. isFullySelected = true;
  1457. next = nextSibling;
  1458. }
  1459. if ( parent==root )
  1460. return clonedParent;
  1461. next = parent.getNextSibling();
  1462. parent = parent.getParentNode();
  1463. Node clonedGrandParent = traverseNode( parent, false, true, how );
  1464. if ( how!=DELETE_CONTENTS )
  1465. clonedGrandParent.appendChild( clonedParent );
  1466. clonedParent = clonedGrandParent;
  1467. }
  1468. // should never occur
  1469. return null;
  1470. }
  1471. /**
  1472. * Utility method for traversing a single node.
  1473. * Does not properly handle a text node containing both the
  1474. * start and end offsets. Such nodes should
  1475. * have been previously detected and been routed to traverseTextNode.
  1476. *
  1477. * @param n The node to be traversed.
  1478. *
  1479. * @param isFullySelected
  1480. * Set to true if the node is fully selected. Should be
  1481. * false otherwise.
  1482. * Note that although the DOM 2 specification says that a
  1483. * text node that is boththe start and end container is not
  1484. * selected, we treat it here as if it were partially
  1485. * selected.
  1486. *
  1487. * @param isLeft Is true if we are traversing the node as part of navigating
  1488. * the "left boundary" of the range. If this value is false,
  1489. * it implies we are navigating the "right boundary" of the
  1490. * range.
  1491. *
  1492. * @param how Specifies what type of traversal is being
  1493. * requested (extract, clone, or delete).
  1494. * Legal values for this argument are:
  1495. *
  1496. * <ol>
  1497. * <li><code>EXTRACT_CONTENTS</code> - will simply
  1498. * return the original node.
  1499. *
  1500. * <li><code>CLONE_CONTENTS</code> - will leave the
  1501. * context tree of the range undisturbed, but will
  1502. * return a cloned node.
  1503. *
  1504. * <li><code>DELETE_CONTENTS</code> - will delete the
  1505. * node from it's parent, but will return null.
  1506. * </ol>
  1507. *
  1508. * @return Returns a node that is the result of visiting the node.
  1509. * If the traversal operation is
  1510. * <code>DELETE_CONTENTS</code> the return value is null.
  1511. */
  1512. private Node traverseNode( Node n, boolean isFullySelected, boolean isLeft, int how )
  1513. {
  1514. if ( isFullySelected )
  1515. return traverseFullySelected( n, how );
  1516. if ( n.getNodeType()==Node.TEXT_NODE )
  1517. return traverseTextNode( n, isLeft, how );
  1518. return traversePartiallySelected( n, how );
  1519. }
  1520. /**
  1521. * Utility method for traversing a single node when
  1522. * we know a-priori that the node if fully
  1523. * selected.
  1524. *
  1525. * @param n The node to be traversed.
  1526. *
  1527. * @param how Specifies what type of traversal is being
  1528. * requested (extract, clone, or delete).
  1529. * Legal values for this argument are:
  1530. *
  1531. * <ol>
  1532. * <li><code>EXTRACT_CONTENTS</code> - will simply
  1533. * return the original node.
  1534. *
  1535. * <li><code>CLONE_CONTENTS</code> - will leave the
  1536. * context tree of the range undisturbed, but will
  1537. * return a cloned node.
  1538. *
  1539. * <li><code>DELETE_CONTENTS</code> - will delete the
  1540. * node from it's parent, but will return null.
  1541. * </ol>
  1542. *
  1543. * @return Returns a node that is the result of visiting the node.
  1544. * If the traversal operation is
  1545. * <code>DELETE_CONTENTS</code> the return value is null.
  1546. */
  1547. private Node traverseFullySelected( Node n, int how )
  1548. {
  1549. switch( how )
  1550. {
  1551. case CLONE_CONTENTS:
  1552. return n.cloneNode( true );
  1553. case EXTRACT_CONTENTS:
  1554. if ( n.getNodeType()==Node.DOCUMENT_TYPE_NODE )
  1555. {
  1556. // TBD: This should be a HIERARCHY_REQUEST_ERR
  1557. throw new RangeExceptionImpl(
  1558. RangeException.INVALID_NODE_TYPE_ERR,
  1559. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
  1560. }
  1561. return n;
  1562. case DELETE_CONTENTS:
  1563. n.getParentNode().removeChild(n);
  1564. return null;
  1565. }
  1566. return null;
  1567. }
  1568. /**
  1569. * Utility method for traversing a single node when
  1570. * we know a-priori that the node if partially
  1571. * selected and is not a text node.
  1572. *
  1573. * @param n The node to be traversed.
  1574. *
  1575. * @param how Specifies what type of traversal is being
  1576. * requested (extract, clone, or delete).
  1577. * Legal values for this argument are:
  1578. *
  1579. * <ol>
  1580. * <li><code>EXTRACT_CONTENTS</code> - will simply
  1581. * return the original node.
  1582. *
  1583. * <li><code>CLONE_CONTENTS</code> - will leave the
  1584. * context tree of the range undisturbed, but will
  1585. * return a cloned node.
  1586. *
  1587. * <li><code>DELETE_CONTENTS</code> - will delete the
  1588. * node from it's parent, but will return null.
  1589. * </ol>
  1590. *
  1591. * @return Returns a node that is the result of visiting the node.
  1592. * If the traversal operation is
  1593. * <code>DELETE_CONTENTS</code> the return value is null.
  1594. */
  1595. private Node traversePartiallySelected( Node n, int how )
  1596. {
  1597. switch( how )
  1598. {
  1599. case DELETE_CONTENTS:
  1600. return null;
  1601. case CLONE_CONTENTS:
  1602. case EXTRACT_CONTENTS:
  1603. return n.cloneNode( false );
  1604. }
  1605. return null;
  1606. }
  1607. /**
  1608. * Utility method for traversing a text node that we know
  1609. * a-priori to be on a left or right boundary of the range.
  1610. * This method does not properly handle text nodes that contain
  1611. * both the start and end points of the range.
  1612. *
  1613. * @param n The node to be traversed.
  1614. *
  1615. * @param isLeft Is true if we are traversing the node as part of navigating
  1616. * the "left boundary" of the range. If this value is false,
  1617. * it implies we are navigating the "right boundary" of the
  1618. * range.
  1619. *
  1620. * @param how Specifies what type of traversal is being
  1621. * requested (extract, clone, or delete).
  1622. * Legal values for this argument are:
  1623. *
  1624. * <ol>
  1625. * <li><code>EXTRACT_CONTENTS</code> - will simply
  1626. * return the original node.
  1627. *
  1628. * <li><code>CLONE_CONTENTS</code> - will leave the
  1629. * context tree of the range undisturbed, but will
  1630. * return a cloned node.
  1631. *
  1632. * <li><code>DELETE_CONTENTS</code> - will delete the
  1633. * node from it's parent, but will return null.
  1634. * </ol>
  1635. *
  1636. * @return Returns a node that is the result of visiting the node.
  1637. * If the traversal operation is
  1638. * <code>DELETE_CONTENTS</code> the return value is null.
  1639. */
  1640. private Node traverseTextNode( Node n, boolean isLeft, int how )
  1641. {
  1642. String txtValue = n.getNodeValue();
  1643. String newNodeValue;
  1644. String oldNodeValue;
  1645. if ( isLeft )
  1646. {
  1647. int offset = getStartOffset();
  1648. newNodeValue = txtValue.substring( offset );
  1649. oldNodeValue = txtValue.substring( 0, offset );
  1650. }
  1651. else
  1652. {
  1653. int offset = getEndOffset();
  1654. newNodeValue = txtValue.substring( 0, offset );
  1655. oldNodeValue = txtValue.substring( offset );
  1656. }
  1657. if ( how != CLONE_CONTENTS )
  1658. n.setNodeValue( oldNodeValue );
  1659. if ( how==DELETE_CONTENTS )
  1660. return null;
  1661. Node newNode = n.cloneNode( false );
  1662. newNode.setNodeValue( newNodeValue );
  1663. return newNode;
  1664. }
  1665. void checkIndex(Node refNode, int offset) throws DOMException
  1666. {
  1667. if (offset < 0) {
  1668. throw new DOMException(
  1669. DOMException.INDEX_SIZE_ERR,
  1670. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INDEX_SIZE_ERR", null));
  1671. }
  1672. int type = refNode.getNodeType();
  1673. // If the node contains text, ensure that the
  1674. // offset of the range is <= to the length of the text
  1675. if (type == Node.TEXT_NODE
  1676. || type == Node.CDATA_SECTION_NODE
  1677. || type == Node.COMMENT_NODE
  1678. || type == Node.PROCESSING_INSTRUCTION_NODE) {
  1679. if (offset > refNode.getNodeValue().length()) {
  1680. throw new DOMException(DOMException.INDEX_SIZE_ERR,
  1681. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INDEX_SIZE_ERR", null));
  1682. }
  1683. }
  1684. else {
  1685. // Since the node is not text, ensure that the offset
  1686. // is valid with respect to the number of child nodes
  1687. if (offset > refNode.getChildNodes().getLength()) {
  1688. throw new DOMException(DOMException.INDEX_SIZE_ERR,
  1689. DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INDEX_SIZE_ERR", null));
  1690. }
  1691. }
  1692. }
  1693. /**
  1694. * Given a node, calculate what the Range's root container
  1695. * for that node would be.
  1696. */
  1697. private Node getRootContainer( Node node )
  1698. {
  1699. if ( node==null )
  1700. return null;
  1701. while( node.getParentNode()!=null )
  1702. node = node.getParentNode();
  1703. return node;
  1704. }
  1705. /**
  1706. * Returns true IFF the given node can serve as a container
  1707. * for a range's boundary points.
  1708. */
  1709. private boolean isLegalContainer( Node node )
  1710. {
  1711. if ( node==null )
  1712. return false;
  1713. while( node!=null )
  1714. {
  1715. switch( node.getNodeType() )
  1716. {
  1717. case Node.ENTITY_NODE:
  1718. case Node.NOTATION_NODE:
  1719. case Node.DOCUMENT_TYPE_NODE:
  1720. return false;
  1721. }
  1722. node = node.getParentNode();
  1723. }
  1724. return true;
  1725. }
  1726. /**
  1727. * Finds the root container for the given node and determines
  1728. * if that root container is legal with respect to the
  1729. * DOM 2 specification. At present, that means the root
  1730. * container must be either an attribute, a document,
  1731. * or a document fragment.
  1732. */
  1733. private boolean hasLegalRootContainer( Node node )
  1734. {
  1735. if ( node==null )
  1736. return false;
  1737. Node rootContainer = getRootContainer( node );
  1738. switch( rootContainer.getNodeType() )
  1739. {
  1740. case Node.ATTRIBUTE_NODE:
  1741. case Node.DOCUMENT_NODE:
  1742. case Node.DOCUMENT_FRAGMENT_NODE:
  1743. return true;
  1744. }
  1745. return false;
  1746. }
  1747. /**
  1748. * Returns true IFF the given node can be contained by
  1749. * a range.
  1750. */
  1751. private boolean isLegalContainedNode( Node node )
  1752. {
  1753. if ( node==null )
  1754. return false;
  1755. switch( node.getNodeType() )
  1756. {
  1757. case Node.DOCUMENT_NODE:
  1758. case Node.DOCUMENT_FRAGMENT_NODE:
  1759. case Node.ATTRIBUTE_NODE:
  1760. case Node.ENTITY_NODE:
  1761. case Node.NOTATION_NODE:
  1762. return false;
  1763. }
  1764. return true;
  1765. }
  1766. Node nextNode(Node node, boolean visitChildren) {
  1767. if (node == null) return null;
  1768. Node result;
  1769. if (visitChildren) {
  1770. result = node.getFirstChild();
  1771. if (result != null) {
  1772. return result;
  1773. }
  1774. }
  1775. // if hasSibling, return sibling
  1776. result = node.getNextSibling();
  1777. if (result != null) {
  1778. return result;
  1779. }
  1780. // return parent's 1st sibling.
  1781. Node parent = node.getParentNode();
  1782. while (parent != null
  1783. && parent != fDocument
  1784. ) {
  1785. result = parent.getNextSibling();
  1786. if (result != null) {
  1787. return result;
  1788. } else {
  1789. parent = parent.getParentNode();
  1790. }
  1791. } // while (parent != null && parent != fRoot) {
  1792. // end of list, return null
  1793. return null;
  1794. }
  1795. /** is a an ancestor of b ? */
  1796. boolean isAncestorOf(Node a, Node b) {
  1797. for (Node node=b; node != null; node=node.getParentNode()) {
  1798. if (node == a) return true;
  1799. }
  1800. return false;
  1801. }
  1802. /** what is the index of the child in the parent */
  1803. int indexOf(Node child, Node parent) {
  1804. if (child.getParentNode() != parent) return -1;
  1805. int i = 0;
  1806. for(Node node = parent.getFirstChild(); node!= child; node=node.getNextSibling()) {
  1807. i++;
  1808. }
  1809. return i;
  1810. }
  1811. /**
  1812. * Utility method to retrieve a child node by index. This method
  1813. * assumes the caller is trying to find out which node is
  1814. * selected by the given index. Note that if the index is
  1815. * greater than the number of children, this implies that the
  1816. * first node selected is the parent node itself.
  1817. *
  1818. * @param container A container node
  1819. *
  1820. * @param offset An offset within the container for which a selected node should
  1821. * be computed. If the offset is less than zero, or if the offset
  1822. * is greater than the number of children, the container is returned.
  1823. *
  1824. * @return Returns either a child node of the container or the
  1825. * container itself.
  1826. */
  1827. private Node getSelectedNode( Node container, int offset )
  1828. {
  1829. if ( container.getNodeType() == Node.TEXT_NODE )
  1830. return container;
  1831. // This case is an important convenience for
  1832. // traverseRightBoundary()
  1833. if ( offset<0 )
  1834. return container;
  1835. Node child = container.getFirstChild();
  1836. while( child!=null && offset > 0 )
  1837. {
  1838. --offset;
  1839. child = child.getNextSibling();
  1840. }
  1841. if ( child!=null )
  1842. return child;
  1843. return container;
  1844. }
  1845. }