PageRenderTime 65ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/java-1.7.0-openjdk/openjdk/jaxp/src/com/sun/org/apache/xerces/internal/dom/RangeImpl.java

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