001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.visualweb.designer.jsf.text;
042:
043: import java.util.Arrays;
044: import org.netbeans.modules.visualweb.api.designer.DomProvider.DomPosition;
045: import org.netbeans.modules.visualweb.api.designer.markup.MarkupService;
046: import org.netbeans.modules.visualweb.insync.faces.Entities;
047: import org.openide.ErrorManager;
048: import org.w3c.dom.DocumentFragment;
049: import org.w3c.dom.Element;
050: import org.w3c.dom.Node;
051: import org.w3c.dom.NodeList;
052: import org.w3c.dom.Text;
053:
054: /**
055: * XXX Moved from designer/../Position.
056: *
057: * Represents a location within a DOM document. The position is defined
058: * the same as the w3c Range description of a boundary point:
059: * http://www.w3.org/TR/DOM-Level-2-Traversal-Range/
060: *
061: * @todo Rename to DomPosition? There are many other Position classes.
062: *
063: * @author Tor Norbye
064: */
065: /*public*/class DomPositionImpl implements DomPosition {
066: /**
067: * Special position which represents an uninitialized, unknown or
068: * nonexistent position in any document.
069: */
070: // public static final Position NONE = new Position(null, -1, Bias.FORWARD);
071: private final DomDocumentImpl domDocumentImpl;
072: private final Node node;
073: private final int offset;
074: private final Bias bias;
075:
076: /** Create a new position at the given node and offset. Note that the node is
077: * the PARENT of the node you're pointing to - read the DOM traversal document
078: * referenced at the top of this class' javadoc. Text nodes are special handled;
079: * in this case the node points to the text node and the offset to a character
080: * within the text node. */
081: static DomPosition create(DomDocumentImpl domDocumentImpl,
082: Node node, int offset, Bias bias) {
083: if (domDocumentImpl == null || node == null) {
084: return NONE;
085: }
086: return new DomPositionImpl(domDocumentImpl, node, offset, bias);
087: }
088:
089: private/*public*/DomPositionImpl(DomDocumentImpl domDocumentImpl,
090: Node node, int offset, Bias bias) {
091: if (domDocumentImpl == null || node == null) {
092: throw new NullPointerException(
093: "Parameters domDocumentImpl or node may not be null"
094: + ", domDocumentImpl=" + domDocumentImpl
095: + ", node=" + node); // NOI18N
096: }
097: this .domDocumentImpl = domDocumentImpl;
098: this .node = node;
099: this .offset = offset;
100: this .bias = bias;
101: }
102:
103: // // XXX is this a mistake? Should I make positions immutable?? They are hardly ever
104: // // mutated... See setLocation too
105: // public void setOffset(int offset) {
106: // this.offset = offset;
107: // }
108:
109: /**
110: * Return the bias of the position. Positions are always between nodes or
111: * characters; this defines which side we have an affinity to.
112: */
113: public Bias getBias() {
114: return bias;
115: }
116:
117: /** Create a position for the given node. This will be the Node's PARENT
118: * node plus its index in that parent's node list.
119: * If after is true, the position should point to the position AFTER
120: * this element.
121: */
122: // public static Position create(Node node, boolean after) {
123: static DomPosition createNext(DomDocumentImpl domDocumentImpl,
124: Node node, boolean after) {
125: if (node == null) {
126: return NONE;
127: }
128:
129: if (node.getNodeType() == Node.TEXT_NODE) {
130: return new DomPositionImpl(domDocumentImpl, node,
131: after ? node.getNodeValue().length() : 0,
132: after ? Bias.BACKWARD : Bias.FORWARD);
133: } else {
134: Node parent = node.getParentNode();
135:
136: if (parent == null) {
137: return NONE;
138: }
139:
140: int index = -1;
141:
142: while (node != null) {
143: node = node.getPreviousSibling();
144: index++;
145: }
146:
147: if (after) {
148: index++;
149: }
150:
151: return new DomPositionImpl(domDocumentImpl, parent, index,
152: after ? Bias.BACKWARD : Bias.FORWARD);
153: }
154: }
155:
156: // public static Position create(Node node, Bias bias) {
157: // Node parent = node.getParentNode();
158: // int index = -1;
159: //
160: // while (node != null) {
161: // node = node.getPreviousSibling();
162: // index++;
163: // }
164: //
165: // if (bias == Bias.BACKWARD) {
166: // index--;
167: //
168: // if (index < 0) {
169: // index = 0;
170: // bias = Bias.FORWARD;
171: // }
172: // }
173: //
174: // return new Position(parent, index, bias);
175: // }
176:
177: // // XXX is this a mistake? Should I make positions immutable?? They are hardly ever
178: // // mutated... See setOffset too
179: // void setLocation(Node node, int offset, Bias bias) {
180: // this.node = node;
181: // this.offset = offset;
182: // this.bias = bias;
183: // }
184:
185: /**
186: * Fetches the current offset
187: *
188: * @return the offset >= 0
189: */
190: public int getOffset() {
191: return offset;
192: }
193:
194: /**
195: * Fetches the element/node that contains the position
196: *
197: * @return the element/node
198: */
199: public Node getNode() {
200: return node;
201: }
202:
203: /** Return true if this position points to some position inside the
204: * given element. Pointing to either side of the element is not considered
205: * inside.
206: * @todo XXX THIS NEEDS A UNIT TEST!
207: */
208: public boolean isInside(Element element) {
209: if (this == DomPositionImpl.NONE) {
210: return false;
211: }
212:
213: // Compute positions before and after the element, then see
214: // if "this" is after/equals to the before and before/equal to the after.
215: Node curr = element;
216: Node parent = element.getParentNode();
217:
218: if (parent == null) {
219: // #116200 Possible NPE.
220: return false;
221: }
222:
223: int index = -1;
224: while (curr != null) {
225: curr = curr.getPreviousSibling();
226: index++;
227: }
228:
229: if ((compareBoundaryPoints(parent, index, node, offset) > 0)
230: && (compareBoundaryPoints(parent, index + 1, node,
231: offset) < 0)) {
232: return true;
233: } else {
234: return false;
235: }
236: }
237:
238: /**
239: * Return true iff this position is earlier than (or at the same position
240: * as) the given position in the document
241: * @param pos The position to compare to
242: * @return True iff this position is earlier than or equal to the
243: * given position
244: */
245: // public boolean isEarlierThan(Position pos) {
246: public boolean isEarlierThan(DomPosition pos) {
247: return compareBoundaryPoints(pos, this ) <= 0;
248: }
249:
250: /**
251: * Return true iff this position is later than (or at the same position
252: * as) the given position in the document
253: * @param pos The position to compare to
254: * @return True iff this position is later than or equal to the
255: * given position
256: */
257: // public boolean isLaterThan(Position pos) {
258: public boolean isLaterThan(DomPosition pos) {
259: return compareBoundaryPoints(pos, this ) >= 0;
260: }
261:
262: /**
263: * Return true iff this position is earlier than (but NOT at the same position
264: * as) the given position in the document
265: * @param pos The position to compare to
266: * @return True iff this position is strictly earlier than (e.g. not
267: * equal to) the given position
268: */
269: // public boolean isStrictlyEarlierThan(Position pos) {
270: public boolean isStrictlyEarlierThan(DomPosition pos) {
271: return compareBoundaryPoints(pos, this ) < 0;
272: }
273:
274: /**
275: * Return true iff this position is later than (but NOT at the same position
276: * as) the given position in the document
277: * @param pos The position to compare to
278: * @return True iff this position is strictly later than (e.g. not
279: * equal to) the given position
280: */
281: // public boolean isStrictlyLaterThan(Position pos) {
282: public boolean isStrictlyLaterThan(DomPosition pos) {
283: return compareBoundaryPoints(pos, this ) > 0;
284: }
285:
286: public boolean equals(Object object) {
287: if (object instanceof DomPositionImpl) {
288: // return ((getNode() == ((Position)pos).getNode())
289: // && (getOffset() == ((Position)pos).getOffset()));
290: DomPositionImpl pos = (DomPositionImpl) object;
291: // XXX Should be equals used as well? And bias too?
292: return getNode() == pos.getNode()
293: && getOffset() == pos.getOffset();
294: // && getBias() == pos.getBias();
295: }
296: return false;
297: }
298:
299: public int hashCode() { // XXX is this the right signature?
300: // return getOffset() * getNode().hashCode();
301: return Arrays
302: .hashCode(new Object[] { getNode(), getOffset() /*, getBias()*/});
303: }
304:
305: // public static short compareBoundaryPoints(Position a, Position b) {
306: public static short compareBoundaryPoints(DomPosition a,
307: DomPosition b) {
308: assert (a != NONE) && (b != NONE);
309:
310: // Node endPointA = a.node;
311: // int offsetA = a.offset;
312: // Node endPointB = b.node;
313: // int offsetB = b.offset;
314: Node endPointA = a.getNode();
315: int offsetA = a.getOffset();
316: Node endPointB = b.getNode();
317: int offsetB = b.getOffset();
318:
319: return compareBoundaryPoints(endPointA, offsetA, endPointB,
320: offsetB);
321: }
322:
323: // This method from Xerces' RangeImpl.java
324: public static short compareBoundaryPoints(Node endPointA,
325: int offsetA, Node endPointB, int offsetB) {
326: // XXX do special handling of Position.NONE
327: //Log.err.log("compareBoundaryPoints(" + a +", " + b + ")");
328: // The DOM Spec outlines four cases that need to be tested
329: // to compare two range boundary points:
330: // case 1: same container
331: // case 2: Child C of container A is ancestor of B
332: // case 3: Child C of container B is ancestor of A
333: // case 4: preorder traversal of context tree.
334: // case 1: same container
335: if (endPointA == endPointB) {
336: if (offsetA < offsetB) {
337: return 1;
338: }
339:
340: if (offsetA == offsetB) {
341: return 0;
342: }
343:
344: return -1;
345: }
346:
347: // case 2: Child C of container A is ancestor of B
348: // This can be quickly tested by walking the parent chain of B
349: for (Node c = endPointB, p = c.getParentNode(); p != null; c = p, p = p
350: .getParentNode()) {
351: if (p == endPointA) {
352: int index = indexOf(c, endPointA);
353:
354: if (offsetA <= index) {
355: return 1;
356: }
357:
358: return -1;
359: }
360: }
361:
362: // case 3: Child C of container B is ancestor of A
363: // This can be quickly tested by walking the parent chain of A
364: for (Node c = endPointA, p = c.getParentNode(); p != null; c = p, p = p
365: .getParentNode()) {
366: if (p == endPointB) {
367: int index = indexOf(c, endPointB);
368:
369: if (index < offsetB) {
370: return 1;
371: }
372:
373: return -1;
374: }
375: }
376:
377: // case 4: preorder traversal of context tree.
378: // Instead of literally walking the context tree in pre-order,
379: // we use relative node depth walking which is usually faster
380: int depthDiff = 0;
381:
382: for (Node n = endPointA; n != null; n = n.getParentNode())
383: depthDiff++;
384:
385: for (Node n = endPointB; n != null; n = n.getParentNode())
386: depthDiff--;
387:
388: while (depthDiff > 0) {
389: endPointA = endPointA.getParentNode();
390: depthDiff--;
391: }
392:
393: while (depthDiff < 0) {
394: endPointB = endPointB.getParentNode();
395: depthDiff++;
396: }
397:
398: for (Node pA = endPointA.getParentNode(), pB = endPointB
399: .getParentNode(); pA != pB; pA = pA.getParentNode(), pB = pB
400: .getParentNode()) {
401: endPointA = pA;
402: endPointB = pB;
403: }
404:
405: for (Node n = endPointA.getNextSibling(); n != null; n = n
406: .getNextSibling()) {
407: if (n == endPointB) {
408: return 1;
409: }
410: }
411:
412: return -1;
413: }
414:
415: // Also from Xerces:
416:
417: /** what is the index of the child in the parent */
418: private static int indexOf(Node child, Node parent) {
419: if (child.getParentNode() != parent) {
420: return -1;
421: }
422:
423: int i = 0;
424:
425: for (Node node = parent.getFirstChild(); node != child; node = node
426: .getNextSibling()) {
427: i++;
428: }
429:
430: return i;
431: }
432:
433: /**
434: * Return the position which is earliest in the document: a or b.
435: * @todo Document behavior where one or both of the positions are Position.NONE
436: * @param a The first position to compare
437: * @param b The second position to compare
438: * @return The position which is earliest in the document: a or b
439: */
440: // public static Position first(Position a, Position b) {
441: public static DomPosition first(DomPosition a, DomPosition b) {
442: if (a == NONE) {
443: return b;
444: } else if (b == NONE) {
445: return a;
446: }
447:
448: if (a.isEarlierThan(b)) {
449: return a;
450: } else {
451: return b;
452: }
453: }
454:
455: /**
456: * Return the position which is latest in the document: a or b.
457: * @todo Document behavior where one or both of the positions are Position.NONE
458: * @param a The first position to compare
459: * @param b The second position to compare
460: * @return The position which is latest in the document: a or b
461: */
462: // public static Position last(Position a, Position b) {
463: public static DomPosition last(DomPosition a, DomPosition b) {
464: if (a == NONE) {
465: return b;
466: } else if (b == NONE) {
467: return a;
468: }
469:
470: if (a.isLaterThan(b)) {
471: return a;
472: } else {
473: return b;
474: }
475: }
476:
477: public String toString() {
478: if (this == NONE) {
479: return "Position.NONE";
480: }
481:
482: Node curr = node;
483:
484: while (curr.getParentNode() != null) {
485: curr = curr.getParentNode();
486: }
487:
488: String type;
489:
490: if (curr instanceof DocumentFragment) {
491: type = "FRAG";
492: } else {
493: type = "DOC";
494: }
495:
496: if (node instanceof Text) {
497: String str = node.getNodeValue();
498: String before = str.substring(0, offset);
499: String after = str.substring(offset);
500:
501: // return "Position-" + type + "(rendered=" + MarkupService.isRenderedNode(node) + ",[#text:" + offset +
502: return "Position-" + type + "(rendered="
503: + domDocumentImpl.isRenderedNode(node) + ",[#text:"
504: + offset + ": Bias=" + bias + "; " + before + "^"
505: + after + "])";
506: } else {
507: NodeList nl = node.getChildNodes();
508: String description = "";
509:
510: if (offset < nl.getLength()) {
511: description = " (before " + nl.item(offset) + ")";
512:
513: if (offset > 0) {
514: description = description + " (after "
515: + nl.item(offset - 1) + ")";
516: }
517: } else if ((offset == nl.getLength()) && (offset > 0)) {
518: description = " (after " + nl.item(offset - 1) + ")";
519: }
520:
521: // return "Position-" + type + "(rendered=" + MarkupService.isRenderedNode(node) + "," + getNode() + "," +
522: return "Position-" + type + "(rendered="
523: + domDocumentImpl.isRenderedNode(node) + ","
524: + getNode() + "," + getOffset() + "):" + bias
525: + "; " + description;
526: }
527: }
528:
529: // Moved to designer/markup
530: // /**
531: // * Return true iff the node corresponding to this position is rendered. This is not defined
532: // * for the Position.NONE object. By "is rendered" I mean that the position points to a node
533: // * in a renderer-hierarchy DOM (such as HTML rendered from JSF components).
534: // */
535: // public boolean isRendered() {
536: // return isRenderedNode(node);
537: // }
538: //
539: // public static boolean isRenderedNode(Node n) {
540: // if (n == null) { // includes Position.NONE
541: //
542: // return false;
543: // }
544: //
545: // if (n instanceof RaveElement) {
546: // return ((RaveElement)n).isRendered();
547: // }
548: //
549: // if (n instanceof RaveTextElement) {
550: // return ((RaveTextElement)n).isRendered();
551: // }
552: //
553: // return false;
554: // }
555:
556: // /** Return true iff the given node is a node in the "source" tree, e.g.
557: // * the JSP document. Returns false otherwise - e.g. the element is
558: // * in a DocumentFragment. Does not check to see if the Element is in
559: // * the "right" document.
560: // * @param curr The node to be checked
561: // * @param dom The JSPX document DOM
562: // */
563: // public static boolean isSourceNode(Node curr, org.w3c.dom.Document dom) {
564: // return !isRenderedNode(curr);
565: // return !MarkupService.isRenderedNode(curr);
566: // }
567:
568: /** Gets whether is it rendered position. Note: <code>NONE</code> is not considered rendered position.
569: * XXX There should be only rendered position here (in the designer). */
570: public boolean isRenderedPosition() {
571: if (this == DomPositionImpl.NONE) {
572: return false;
573: }
574:
575: // return MarkupService.isRenderedNode(node);
576: return domDocumentImpl.isRenderedNode(node);
577: }
578:
579: /** Gets whether it is source position. Note: <code>NONE</code> is not considered source position.
580: * XXX There should be only rendered position here (in the designer). */
581: public boolean isSourcePosition() {
582: if (this == DomPositionImpl.NONE) {
583: return false;
584: }
585:
586: return node == MarkupService.getSourceNodeForNode(node);
587: }
588:
589: /**
590: * Return a position in the rendered DOM that corresponds to this position (which needs to
591: * be a position in a source DOM).
592: * @throws UnsupportedOperationException if this is Position.NONE instance
593: */
594: // public Position getRenderedPosition() {
595: public DomPosition getRenderedPosition() {
596: // TODO -- cache the rendered position for the caret!!! Perhaps it should be a caret method instead!
597: // assert this != Position.NONE;
598: // if (this == Position.NONE) {
599: // if (this == DomPosition.NONE) {
600: // throw new UnsupportedOperationException("Method getRenderedPosition() can't be called on Position.NONE instance!"); // NOI18N
601: // }
602:
603: // assert !isRendered() : this;
604: // if (MarkupService.isRenderedNode(node)) {
605: if (domDocumentImpl.isRenderedNode(node)) {
606: ErrorManager.getDefault().notify(
607: ErrorManager.INFORMATIONAL,
608: new IllegalStateException(
609: "Node is expected not rendered, node="
610: + node)); // NOI18N
611: }
612:
613: // TODO - rewrite this. Moved from elsewhere and clashing a bit. Use different
614: // temp variables than node and offset.
615: Node node = this .node;
616: NodeList children = node.getChildNodes();
617: int offset = this .offset;
618:
619: if ((node.getNodeType() == Node.ELEMENT_NODE)
620: || (node.getNodeType() == Node.DOCUMENT_FRAGMENT_NODE)) {
621: if (children.getLength() == 0) {
622: // The position points to a -potential- insert position (where there are
623: // no nodes yet)
624: // Node n = ((RaveRenderNode)node).getRenderedNode();
625: Node n = MarkupService.getRenderedNodeForNode(node);
626:
627: if (n != null) {
628: return domDocumentImpl.createDomPosition(n, 0,
629: Bias.FORWARD);
630: } else {
631: // return NONE;
632: return DomPosition.NONE;
633: }
634: } else if ((bias == Bias.BACKWARD) && (offset > 0)
635: && ((offset - 1) < children.getLength())) {
636: node = node.getChildNodes().item(offset - 1);
637:
638: // return Position.create(((RaveRenderNode)node).getRenderedNode(), true);
639: return domDocumentImpl.createNextDomPosition(
640: MarkupService.getRenderedNodeForNode(node),
641: true);
642: } else if (offset < children.getLength()) {
643: node = node.getChildNodes().item(offset);
644:
645: // return Position.create(((RaveRenderNode)node).getRenderedNode(), false);
646: return domDocumentImpl.createNextDomPosition(
647: MarkupService.getRenderedNodeForNode(node),
648: false);
649: } else {
650: // The offset is larger than or equal to the number of children.
651: // Use the last child.
652: node = node.getChildNodes().item(
653: children.getLength() - 1);
654:
655: // return Position.create(((RaveRenderNode)node).getRenderedNode(), true);
656: return domDocumentImpl.createNextDomPosition(
657: MarkupService.getRenderedNodeForNode(node),
658: true);
659: }
660:
661: // } else if (node.getNodeType() == Node.TEXT_NODE) {
662: // } else if (node instanceof RaveText) { // text, cdata section etc. ?
663: } else if (node instanceof Text) { // text, cdata section etc. ?
664:
665: Text xs = (Text) node;
666: // assert !xs.isRendered();
667: // if (MarkupService.isRenderedNode(xs)) {
668: if (domDocumentImpl.isRenderedNode(xs)) {
669: ErrorManager.getDefault().notify(
670: ErrorManager.INFORMATIONAL,
671: new IllegalStateException(
672: "Node is expected to be not rendered, node="
673: + xs)); // NOI18N
674: }
675:
676: // RaveText xr = xs.getRendered();
677: Text xr = MarkupService.getRenderedTextForText(xs);
678:
679: if (xr == null) {
680: // This node has not yet been rendered
681: return NONE;
682: }
683:
684: // Compute the offset position corresponding to "node,offset"
685: // in xt.
686: // assert offset <= node.getNodeValue().length();
687: if (offset > node.getNodeValue().length()) {
688: ErrorManager
689: .getDefault()
690: .notify(
691: ErrorManager.INFORMATIONAL,
692: new IllegalStateException(
693: "Offset is greater than expected, offset="
694: + offset // NOI18N
695: + ", node.getNodeValue().lenght()="
696: + node.getNodeValue()
697: .length()
698: + ", for node=" + node)); // NOI18N
699: // return NONE;
700: return DomPosition.NONE;
701: }
702:
703: int renderOffset;
704:
705: // if (xs.isJspx()) {
706: if (MarkupService.isJspxNode(xs)) {
707: String jspx = xs.getNodeValue();
708: String xhtml = xr.getNodeValue();
709: if (jspx == xhtml || jspx.indexOf('&') == -1) {
710: renderOffset = offset;
711: } else {
712: // <markup_separation>
713: // renderOffset = MarkupServiceProvider.getDefault().
714: // getExpandedOffset(jspx, offset);
715: // ====
716: // renderOffset = InSyncService.getProvider().getExpandedOffset(jspx, offset);
717: // renderOffset = WebForm.getDomProviderService().getExpandedOffset(jspx, offset);
718: renderOffset = Entities.getExpandedOffset(jspx,
719: offset);
720: // </markup_separation>
721: }
722: } else {
723: // Html to Html rendering: no offset change
724: renderOffset = offset;
725: }
726:
727: // Ensure that the offset is valid in the rendered node since the
728: // source may be ahead (as in right when you add a new character in a word;
729: // we haven't yet updated the UI (that's done in a delayed fashion
730: // via the DomSynchronizer)
731: int max = xr.getNodeValue().length();
732:
733: if (renderOffset > max) {
734: renderOffset = max;
735: }
736:
737: return new DomPositionImpl(domDocumentImpl, xr,
738: renderOffset, bias);
739: } else {
740: // Not sure how to handle this one
741: ErrorManager.getDefault().log(
742: "Unexpected node type in getRendered: " + node);
743:
744: return DomPositionImpl.NONE;
745: }
746: }
747:
748: /** If the current position is in a rendered/DocumentFragment subtree of the
749: * document, try to locate the equivalent position in the source document
750: * and return that. If it cannot find an equivalent source
751: * position it will return Position.NONE.
752: */
753: // public Position getSourcePosition() {
754: public DomPosition getSourcePosition() {
755: // assert this != Position.NONE;
756:
757: // assert isRendered() : this;
758: // if (!MarkupService.isRenderedNode(node)) {
759: if (!domDocumentImpl.isRenderedNode(node)) {
760: ErrorManager.getDefault().notify(
761: ErrorManager.INFORMATIONAL,
762: new IllegalStateException(
763: "Node is expected to be rendered, node="
764: + node)); // NOI18N
765: }
766:
767: // TODO - rewrite this. Moved from elsewhere and clashing a bit. Use different
768: // temp variables than node and offset.
769: Node node = this .node;
770: NodeList children = node.getChildNodes();
771: int offset = this .offset;
772:
773: if ((node.getNodeType() == Node.ELEMENT_NODE)
774: || (node.getNodeType() == Node.DOCUMENT_FRAGMENT_NODE)) {
775: if (children.getLength() == 0) {
776: // The position points to a -potential- insert position (where there are
777: // no nodes yet)
778: // Node n = ((RaveRenderNode)node).getSourceNode();
779: Node n = MarkupService.getSourceNodeForNode(node);
780:
781: if (n != null) {
782: return new DomPositionImpl(domDocumentImpl, n, 0,
783: Bias.FORWARD);
784: } else {
785: // return NONE;
786: return DomPosition.NONE;
787: }
788: } else if ((bias == Bias.BACKWARD) && (offset > 0)
789: && ((offset - 1) < children.getLength())) {
790: node = node.getChildNodes().item(offset - 1);
791:
792: Node curr = node;
793:
794: // while (curr instanceof RaveRenderNode) {
795: // Node source = ((RaveRenderNode)curr).getSourceNode();
796: while (curr != null) {
797: Node source = MarkupService
798: .getSourceNodeForNode(curr);
799:
800: if (source != null) {
801: return domDocumentImpl.createNextDomPosition(
802: source, true);
803: }
804:
805: curr = curr.getParentNode();
806: }
807:
808: // return Position.NONE;
809: return DomPosition.NONE;
810: } else if (offset < children.getLength()) {
811: node = node.getChildNodes().item(offset);
812:
813: Node curr = node;
814:
815: // while (curr instanceof RaveRenderNode) {
816: // Node source = ((RaveRenderNode)curr).getSourceNode();
817: while (curr != null) {
818: Node source = MarkupService
819: .getSourceNodeForNode(curr);
820:
821: if (source != null) {
822: return domDocumentImpl.createNextDomPosition(
823: source, false);
824: }
825:
826: curr = curr.getParentNode();
827: }
828:
829: return DomPositionImpl.NONE;
830: } else {
831: // The offset is larger than or equal to the number of children.
832: // Use the last child.
833: node = node.getChildNodes().item(
834: children.getLength() - 1);
835:
836: Node curr = node;
837:
838: // while (curr instanceof RaveRenderNode) {
839: // Node source = ((RaveRenderNode)curr).getSourceNode();
840: while (curr != null) {
841: Node source = MarkupService
842: .getSourceNodeForNode(curr);
843:
844: if (source != null) {
845: return domDocumentImpl.createNextDomPosition(
846: source, true);
847: }
848:
849: curr = curr.getParentNode();
850: }
851:
852: return DomPositionImpl.NONE;
853: }
854:
855: // } else if (node.getNodeType() == Node.TEXT_NODE) {
856: // } else if (node instanceof RaveText) { // text, cdata section etc. ?
857: } else if (node instanceof Text) { // text, cdata section etc. ?
858:
859: Text xr = (Text) node;
860: // assert xr.isRendered();
861: // if (!MarkupService.isRenderedNode(xr)) {
862: if (!domDocumentImpl.isRenderedNode(xr)) {
863: ErrorManager.getDefault().notify(
864: ErrorManager.INFORMATIONAL,
865: new IllegalStateException(
866: "Node is expected to be rendered, node="
867: + node)); // NOI18N
868: }
869:
870: // RaveText xs = xr.getSource();
871: Text xs = MarkupService.getSourceTextForText(xr);
872:
873: if (xs == null) {
874: // This node has not yet been rendered
875: return NONE;
876: }
877:
878: // Compute the offset position corresponding to "node,offset"
879: // in xt.
880: assert offset <= xr.getNodeValue().length();
881:
882: int sourceOffset;
883:
884: // if (xs.isJspx()) {
885: if (MarkupService.isJspxNode(xs)) {
886: String jspx = xs.getNodeValue();
887: String xhtml = xr.getNodeValue();
888: if (jspx == xhtml || jspx.indexOf('&') == -1) {
889: sourceOffset = offset;
890: } else {
891: // <markup_separation>
892: // sourceOffset = MarkupServiceProvider.getDefault().
893: // getUnexpandedOffset(jspx, offset);
894: // ====
895: // sourceOffset = InSyncService.getProvider().getUnexpandedOffset(jspx, offset);
896: // sourceOffset = WebForm.getDomProviderService().getUnexpandedOffset(jspx, offset);
897: sourceOffset = Entities.getUnexpandedOffset(jspx,
898: offset);
899: // </markup_separation>
900: }
901: } else {
902: // Html to Html rendering: no offset change
903: sourceOffset = offset;
904: }
905:
906: // Ensure that the offset is valid in the rendered node since the
907: // source may be ahead (as in right when you add a new character in a word;
908: // we haven't yet updated the UI (that's done in a delayed fashion
909: // via the DomSynchronizer)
910: int max = xs.getNodeValue().length();
911:
912: if (sourceOffset > max) {
913: sourceOffset = max;
914: }
915:
916: return new DomPositionImpl(domDocumentImpl, xs,
917: sourceOffset, bias);
918: } else {
919: // Not sure how to handle this one
920: ErrorManager.getDefault().log(
921: "Unexpected node type in getSource: " + node);
922:
923: return DomPositionImpl.NONE;
924: }
925: }
926:
927: /** If the position is pointing to a specific element (e.g. right before or right after),
928: * return it. Otherwise, return null.
929: * @return The element pointed to by this position, or null
930: */
931: public Element getTargetElement() {
932: if ((this == NONE) || node instanceof Text) {
933: return null;
934: }
935:
936: NodeList nl = node.getChildNodes();
937:
938: Node target = null;
939:
940: if (bias == Bias.FORWARD) {
941: if (offset < nl.getLength()) {
942: target = nl.item(offset);
943: } else if ((offset == nl.getLength()) && (offset > 0)) {
944: target = nl.item(offset - 1);
945: }
946: } else if (offset > 0) { // bias is backwards
947: target = nl.item(offset - 1);
948: }
949:
950: // if (target instanceof RaveElement) {
951: // return (RaveElement)target;
952: // }
953: if (target instanceof Element) {
954: return (Element) target;
955: }
956:
957: return null;
958: }
959: }
|