/xerces-2_11_0/src/org/apache/xerces/dom/RangeImpl.java

# · Java · 2099 lines · 1288 code · 178 blank · 633 comment · 458 complexity · 8fa017bf99806c9ceba1e62fc3c90960 MD5 · raw file

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