001: /*
002: * Copyright 2007 Outerthought bvba and Schaubroeck nv
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.outerj.daisy.diff.html;
017:
018: import java.util.ArrayList;
019: import java.util.Iterator;
020: import java.util.List;
021: import java.util.Locale;
022:
023: import org.eclipse.compare.rangedifferencer.IRangeComparator;
024: import org.outerj.daisy.diff.html.ancestor.AncestorComparator;
025: import org.outerj.daisy.diff.html.ancestor.AncestorComparatorResult;
026: import org.outerj.daisy.diff.html.dom.BodyNode;
027: import org.outerj.daisy.diff.html.dom.DomTree;
028: import org.outerj.daisy.diff.html.dom.Node;
029: import org.outerj.daisy.diff.html.dom.TextNode;
030: import org.outerj.daisy.diff.html.dom.helper.LastCommonParentResult;
031: import org.outerj.daisy.diff.html.modification.Modification;
032: import org.outerj.daisy.diff.html.modification.ModificationType;
033:
034: /**
035: * A comparator that generates a DOM tree of sorts from handling SAX events.
036: * Then it can be used to compute the difference between DOM trees and mark
037: * elements accordingly.
038: */
039: public class TextNodeComparator implements IRangeComparator,
040: Iterable<TextNode> {
041:
042: private List<TextNode> textNodes = new ArrayList<TextNode>(50);
043: private List<Modification> lastModified = new ArrayList<Modification>();
044: private BodyNode bodyNode = new BodyNode();
045: private Locale locale;
046:
047: public TextNodeComparator(DomTree tree, Locale locale) {
048: super ();
049: this .locale = locale;
050: textNodes = tree.getTextNodes();
051: bodyNode = tree.getBodyNode();
052: }
053:
054: public BodyNode getBodyNode() {
055: return bodyNode;
056: }
057:
058: public int getRangeCount() {
059: return textNodes.size();
060: }
061:
062: public TextNode getTextNode(int i) {
063: return textNodes.get(i);
064: }
065:
066: private long newID = 0;
067:
068: public void markAsNew(int start, int end) {
069: if (end <= start)
070: return;
071:
072: if (whiteAfterLastChangedPart)
073: getTextNode(start).setWhiteBefore(false);
074:
075: List<Modification> nextLastModified = new ArrayList<Modification>();
076:
077: for (int i = start; i < end; i++) {
078: Modification mod = new Modification(ModificationType.ADDED);
079: mod.setID(newID);
080: if (lastModified.size() > 0) {
081: mod.setPrevious(lastModified.get(0));
082: if (lastModified.get(0).getNext() == null) {
083: for (Modification lastMod : lastModified) {
084: lastMod.setNext(mod);
085: }
086: }
087: }
088: nextLastModified.add(mod);
089: getTextNode(i).setModification(mod);
090: }
091: if (start < end) {
092: getTextNode(start).getModification().setFirstOfID(true);
093: }
094: newID++;
095: lastModified = nextLastModified;
096: }
097:
098: public boolean rangesEqual(int i1, IRangeComparator rangeComp,
099: int i2) {
100: TextNodeComparator comp;
101: try {
102: comp = (TextNodeComparator) rangeComp;
103: } catch (RuntimeException e) {
104: return false;
105: }
106:
107: return getTextNode(i1).isSameText(comp.getTextNode(i2));
108: }
109:
110: public boolean skipRangeComparison(int arg0, int arg1,
111: IRangeComparator arg2) {
112: return false;
113: }
114:
115: private long changedID = 0;
116: private boolean changedIDUsed = false;
117:
118: public void handlePossibleChangedPart(int leftstart, int leftend,
119: int rightstart, int rightend,
120: TextNodeComparator leftComparator) {
121: int i = rightstart;
122: int j = leftstart;
123:
124: if (changedIDUsed) {
125: changedID++;
126: changedIDUsed = false;
127: }
128:
129: List<Modification> nextLastModified = new ArrayList<Modification>();
130:
131: String changes = null;
132: while (i < rightend) {
133: AncestorComparator acthis = new AncestorComparator(
134: getTextNode(i).getParentTree());
135: AncestorComparator acother = new AncestorComparator(
136: leftComparator.getTextNode(j).getParentTree());
137:
138: AncestorComparatorResult result = acthis .getResult(acother,
139: locale);
140:
141: if (result.isChanged()) {
142:
143: Modification mod = new Modification(
144: ModificationType.CHANGED);
145:
146: if (!changedIDUsed) {
147: mod.setFirstOfID(true);
148: if (nextLastModified.size() > 0) {
149: lastModified = nextLastModified;
150: nextLastModified = new ArrayList<Modification>();
151: }
152: } else if (result.getChanges() != null
153: && !result.getChanges().equals(changes)) {
154: changedID++;
155: mod.setFirstOfID(true);
156: if (nextLastModified.size() > 0) {
157: lastModified = nextLastModified;
158: nextLastModified = new ArrayList<Modification>();
159: }
160: }
161:
162: if (lastModified.size() > 0) {
163: mod.setPrevious(lastModified.get(0));
164: if (lastModified.get(0).getNext() == null) {
165: for (Modification lastMod : lastModified) {
166: lastMod.setNext(mod);
167: }
168: }
169: }
170: nextLastModified.add(mod);
171:
172: mod.setChanges(result.getChanges());
173: mod.setID(changedID);
174:
175: getTextNode(i).setModification(mod);
176: changes = result.getChanges();
177: changedIDUsed = true;
178: } else if (changedIDUsed) {
179: changedID++;
180: changedIDUsed = false;
181: }
182:
183: i++;
184: j++;
185: }
186:
187: if (nextLastModified.size() > 0)
188: lastModified = nextLastModified;
189:
190: }
191:
192: //used to remove the whitespace between a red and green block
193: private boolean whiteAfterLastChangedPart = false;
194: private long deletedID = 0;
195:
196: public void markAsDeleted(int start, int end,
197: TextNodeComparator oldComp, int before) {
198:
199: if (end <= start)
200: return;
201:
202: if (before > 0 && getTextNode(before - 1).isWhiteAfter()) {
203: whiteAfterLastChangedPart = true;
204: } else {
205: whiteAfterLastChangedPart = false;
206: }
207:
208: List<Modification> nextLastModified = new ArrayList<Modification>();
209:
210: for (int i = start; i < end; i++) {
211: Modification mod = new Modification(
212: ModificationType.REMOVED);
213: mod.setID(deletedID);
214: if (lastModified.size() > 0) {
215: mod.setPrevious(lastModified.get(0));
216: if (lastModified.get(0).getNext() == null) {
217: for (Modification lastMod : lastModified) {
218: lastMod.setNext(mod);
219: }
220: }
221: }
222: nextLastModified.add(mod);
223:
224: //oldComp is used here because we're going to move its deleted elements
225: // to this tree!
226: oldComp.getTextNode(i).setModification(mod);
227: }
228: oldComp.getTextNode(start).getModification().setFirstOfID(true);
229:
230: List<Node> deletedNodes = oldComp.getBodyNode()
231: .getMinimalDeletedSet(deletedID);
232:
233: // Set prevLeaf to the leaf after which the old HTML needs to be
234: // inserted
235: Node prevLeaf = null;
236: if (before > 0)
237: prevLeaf = getTextNode(before - 1);
238:
239: // Set nextLeaf to the leaf before which the old HTML needs to be
240: // inserted
241: Node nextLeaf = null;
242: if (before < getRangeCount())
243: nextLeaf = getTextNode(before);
244:
245: while (deletedNodes.size() > 0) {
246: LastCommonParentResult prevResult, nextResult;
247: if (prevLeaf != null) {
248: prevResult = prevLeaf.getLastCommonParent(deletedNodes
249: .get(0));
250: } else {
251: prevResult = new LastCommonParentResult();
252: prevResult.setLastCommonParent(getBodyNode());
253: prevResult.setIndexInLastCommonParent(0);
254: }
255: if (nextLeaf != null) {
256: nextResult = nextLeaf.getLastCommonParent(deletedNodes
257: .get(deletedNodes.size() - 1));
258: } else {
259: nextResult = new LastCommonParentResult();
260: nextResult.setLastCommonParent(getBodyNode());
261: nextResult.setIndexInLastCommonParent(getBodyNode()
262: .getNbChildren());
263: }
264:
265: if (prevResult.getLastCommonParentDepth() == nextResult
266: .getLastCommonParentDepth()) {
267: //We need some metric to choose which way to add...
268: if (deletedNodes.get(0).getParent() == deletedNodes
269: .get(deletedNodes.size() - 1).getParent()
270: && prevResult.getLastCommonParent() == nextResult
271: .getLastCommonParent()) {
272: //The difference is not in the parent
273: prevResult.setLastCommonParentDepth(prevResult
274: .getLastCommonParentDepth() + 1);
275:
276: } else {
277: //The difference is in the parent, so compare them
278: // now THIS is tricky
279: double distancePrev = deletedNodes.get(0)
280: .getParent().getMatchRatio(
281: prevResult.getLastCommonParent());
282: double distanceNext = deletedNodes.get(
283: deletedNodes.size() - 1).getParent()
284: .getMatchRatio(
285: nextResult.getLastCommonParent());
286:
287: if (distancePrev <= distanceNext) {
288: prevResult.setLastCommonParentDepth(prevResult
289: .getLastCommonParentDepth() + 1);
290: } else {
291: nextResult.setLastCommonParentDepth(nextResult
292: .getLastCommonParentDepth() + 1);
293: }
294: }
295:
296: }
297:
298: if (prevResult.getLastCommonParentDepth() > nextResult
299: .getLastCommonParentDepth()) {
300:
301: // Inserting at the front
302: if (prevResult.isSplittingNeeded()) {
303: prevLeaf.getParent().splitUntill(
304: prevResult.getLastCommonParent(), prevLeaf,
305: true);
306: }
307: prevLeaf = deletedNodes.remove(0).copyTree();
308: prevLeaf.setParent(prevResult.getLastCommonParent());
309: prevResult.getLastCommonParent().addChild(
310: prevResult.getIndexInLastCommonParent() + 1,
311: prevLeaf);
312:
313: } else if (prevResult.getLastCommonParentDepth() < nextResult
314: .getLastCommonParentDepth()) {
315: // Inserting at the back
316: if (nextResult.isSplittingNeeded()) {
317: boolean splitOccured = nextLeaf.getParent()
318: .splitUntill(
319: nextResult.getLastCommonParent(),
320: nextLeaf, false);
321:
322: if (splitOccured) {
323: // The place where to insert is shifted one place to the
324: // right
325: nextResult
326: .setIndexInLastCommonParent(nextResult
327: .getIndexInLastCommonParent() + 1);
328: }
329: }
330: nextLeaf = deletedNodes.remove(deletedNodes.size() - 1)
331: .copyTree();
332: nextLeaf.setParent(nextResult.getLastCommonParent());
333: nextResult.getLastCommonParent().addChild(
334: nextResult.getIndexInLastCommonParent(),
335: nextLeaf);
336: } else
337: throw new IllegalStateException();
338:
339: }
340: lastModified = nextLastModified;
341: deletedID++;
342: }
343:
344: public void expandWhiteSpace() {
345: getBodyNode().expandWhiteSpace();
346: }
347:
348: public Iterator<TextNode> iterator() {
349: return textNodes.iterator();
350: }
351: }
|