1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (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 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is mozilla.org code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Mozilla Foundation.
19 : * Portions created by the Initial Developer are Copyright (C) 2011
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Alexander Surkov <surkov.alexander@gmail.com> (original author)
24 : *
25 : * Alternatively, the contents of this file may be used under the terms of
26 : * either of the GNU General Public License Version 2 or later (the "GPL"),
27 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 : * in which case the provisions of the GPL or the LGPL are applicable instead
29 : * of those above. If you wish to allow use of your version of this file only
30 : * under the terms of either the GPL or the LGPL, and not to allow others to
31 : * use your version of this file under the terms of the MPL, indicate your
32 : * decision by deleting the provisions above and replace them with the notice
33 : * and other provisions required by the GPL or the LGPL. If you do not delete
34 : * the provisions above, a recipient may use your version of this file under
35 : * the terms of any one of the MPL, the GPL or the LGPL.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 : #include "TextUpdater.h"
40 :
41 : #include "nsDocAccessible.h"
42 : #include "nsTextAccessible.h"
43 :
44 : void
45 0 : TextUpdater::Run(nsDocAccessible* aDocument, nsTextAccessible* aTextLeaf,
46 : const nsAString& aNewText)
47 : {
48 0 : NS_ASSERTION(aTextLeaf, "No text leaf accessible?");
49 :
50 0 : const nsString& oldText = aTextLeaf->Text();
51 0 : PRUint32 oldLen = oldText.Length(), newLen = aNewText.Length();
52 0 : PRUint32 minLen = NS_MIN(oldLen, newLen);
53 :
54 : // Skip coinciding begin substrings.
55 0 : PRUint32 skipStart = 0;
56 0 : for (; skipStart < minLen; skipStart++) {
57 0 : if (aNewText[skipStart] != oldText[skipStart])
58 0 : break;
59 : }
60 :
61 : // The text was changed. Do update.
62 0 : if (skipStart != minLen || oldLen != newLen) {
63 0 : TextUpdater updater(aDocument, aTextLeaf);
64 0 : updater.DoUpdate(aNewText, oldText, skipStart);
65 : }
66 0 : }
67 :
68 : void
69 0 : TextUpdater::DoUpdate(const nsAString& aNewText, const nsAString& aOldText,
70 : PRUint32 aSkipStart)
71 : {
72 0 : nsAccessible* parent = mTextLeaf->Parent();
73 0 : if (!parent)
74 0 : return;
75 :
76 0 : mHyperText = parent->AsHyperText();
77 0 : if (!mHyperText) {
78 0 : NS_ERROR("Text leaf parent is not hypertext!");
79 0 : return;
80 : }
81 :
82 : // Get the text leaf accessible offset and invalidate cached offsets after it.
83 0 : mTextOffset = mHyperText->GetChildOffset(mTextLeaf, true);
84 0 : NS_ASSERTION(mTextOffset != -1,
85 : "Text leaf hasn't offset within hyper text!");
86 :
87 0 : PRUint32 oldLen = aOldText.Length(), newLen = aNewText.Length();
88 0 : PRUint32 minLen = NS_MIN(oldLen, newLen);
89 :
90 : // Trim coinciding substrings from the end.
91 0 : PRUint32 skipEnd = 0;
92 0 : while (minLen - skipEnd > aSkipStart &&
93 0 : aNewText[newLen - skipEnd - 1] == aOldText[oldLen - skipEnd - 1]) {
94 0 : skipEnd++;
95 : }
96 :
97 0 : PRUint32 strLen1 = oldLen - aSkipStart - skipEnd;
98 0 : PRUint32 strLen2 = newLen - aSkipStart - skipEnd;
99 :
100 0 : const nsAString& str1 = Substring(aOldText, aSkipStart, strLen1);
101 0 : const nsAString& str2 = Substring(aNewText, aSkipStart, strLen2);
102 :
103 : // Increase offset of the text leaf on skipped characters amount.
104 0 : mTextOffset += aSkipStart;
105 :
106 : // It could be single insertion or removal or the case of long strings. Do not
107 : // calculate the difference between long strings and prefer to fire pair of
108 : // insert/remove events as the old string was replaced on the new one.
109 0 : if (strLen1 == 0 || strLen2 == 0 ||
110 : strLen1 > kMaxStrLen || strLen2 > kMaxStrLen) {
111 0 : if (strLen1 > 0) {
112 : // Fire text change event for removal.
113 : nsRefPtr<AccEvent> textRemoveEvent =
114 0 : new AccTextChangeEvent(mHyperText, mTextOffset, str1, false);
115 0 : mDocument->FireDelayedAccessibleEvent(textRemoveEvent);
116 : }
117 :
118 0 : if (strLen2 > 0) {
119 : // Fire text change event for insertion.
120 : nsRefPtr<AccEvent> textInsertEvent =
121 0 : new AccTextChangeEvent(mHyperText, mTextOffset, str2, true);
122 0 : mDocument->FireDelayedAccessibleEvent(textInsertEvent);
123 : }
124 :
125 0 : mDocument->MaybeNotifyOfValueChange(mHyperText);
126 :
127 : // Update the text.
128 0 : mTextLeaf->SetText(aNewText);
129 : return;
130 : }
131 :
132 : // Otherwise find the difference between strings and fire events.
133 : // Note: we can skip initial and final coinciding characters since they don't
134 : // affect the Levenshtein distance.
135 :
136 : // Compute the flat structured matrix need to compute the difference.
137 0 : PRUint32 len1 = strLen1 + 1, len2 = strLen2 + 1;
138 0 : PRUint32* entries = new PRUint32[len1 * len2];
139 :
140 0 : for (PRUint32 colIdx = 0; colIdx < len1; colIdx++)
141 0 : entries[colIdx] = colIdx;
142 :
143 0 : PRUint32* row = entries;
144 0 : for (PRUint32 rowIdx = 1; rowIdx < len2; rowIdx++) {
145 0 : PRUint32* prevRow = row;
146 0 : row += len1;
147 0 : row[0] = rowIdx;
148 0 : for (PRUint32 colIdx = 1; colIdx < len1; colIdx++) {
149 0 : if (str1[colIdx - 1] != str2[rowIdx - 1]) {
150 0 : PRUint32 left = row[colIdx - 1];
151 0 : PRUint32 up = prevRow[colIdx];
152 0 : PRUint32 upleft = prevRow[colIdx - 1];
153 0 : row[colIdx] = NS_MIN(upleft, NS_MIN(left, up)) + 1;
154 : } else {
155 0 : row[colIdx] = prevRow[colIdx - 1];
156 : }
157 : }
158 : }
159 :
160 : // Compute events based on the difference.
161 0 : nsTArray<nsRefPtr<AccEvent> > events;
162 0 : ComputeTextChangeEvents(str1, str2, entries, events);
163 :
164 0 : delete [] entries;
165 :
166 : // Fire events.
167 0 : for (PRInt32 idx = events.Length() - 1; idx >= 0; idx--)
168 0 : mDocument->FireDelayedAccessibleEvent(events[idx]);
169 :
170 0 : mDocument->MaybeNotifyOfValueChange(mHyperText);
171 :
172 : // Update the text.
173 0 : mTextLeaf->SetText(aNewText);
174 : }
175 :
176 : void
177 0 : TextUpdater::ComputeTextChangeEvents(const nsAString& aStr1,
178 : const nsAString& aStr2,
179 : PRUint32* aEntries,
180 : nsTArray<nsRefPtr<AccEvent> >& aEvents)
181 : {
182 0 : PRInt32 colIdx = aStr1.Length(), rowIdx = aStr2.Length();
183 :
184 : // Point at which strings last matched.
185 0 : PRInt32 colEnd = colIdx;
186 0 : PRInt32 rowEnd = rowIdx;
187 :
188 0 : PRInt32 colLen = colEnd + 1;
189 0 : PRUint32* row = aEntries + rowIdx * colLen;
190 0 : PRUint32 dist = row[colIdx]; // current Levenshtein distance
191 0 : while (rowIdx && colIdx) { // stop when we can't move diagonally
192 0 : if (aStr1[colIdx - 1] == aStr2[rowIdx - 1]) { // match
193 0 : if (rowIdx < rowEnd) { // deal with any pending insertion
194 0 : FireInsertEvent(Substring(aStr2, rowIdx, rowEnd - rowIdx),
195 0 : rowIdx, aEvents);
196 : }
197 0 : if (colIdx < colEnd) { // deal with any pending deletion
198 0 : FireDeleteEvent(Substring(aStr1, colIdx, colEnd - colIdx),
199 0 : rowIdx, aEvents);
200 : }
201 :
202 0 : colEnd = --colIdx; // reset the match point
203 0 : rowEnd = --rowIdx;
204 0 : row -= colLen;
205 0 : continue;
206 : }
207 0 : --dist;
208 0 : if (dist == row[colIdx - 1 - colLen]) { // substitution
209 0 : --colIdx;
210 0 : --rowIdx;
211 0 : row -= colLen;
212 0 : continue;
213 : }
214 0 : if (dist == row[colIdx - colLen]) { // insertion
215 0 : --rowIdx;
216 0 : row -= colLen;
217 0 : continue;
218 : }
219 0 : if (dist == row[colIdx - 1]) { // deletion
220 0 : --colIdx;
221 0 : continue;
222 : }
223 0 : NS_NOTREACHED("huh?");
224 0 : return;
225 : }
226 :
227 0 : if (rowEnd)
228 0 : FireInsertEvent(Substring(aStr2, 0, rowEnd), 0, aEvents);
229 0 : if (colEnd)
230 0 : FireDeleteEvent(Substring(aStr1, 0, colEnd), 0, aEvents);
231 : }
|