TextUpdater.cpp 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. #include "TextUpdater.h"
  6. #include "Accessible-inl.h"
  7. #include "DocAccessible-inl.h"
  8. #include "TextLeafAccessible.h"
  9. #include <algorithm>
  10. using namespace mozilla::a11y;
  11. void
  12. TextUpdater::Run(DocAccessible* aDocument, TextLeafAccessible* aTextLeaf,
  13. const nsAString& aNewText)
  14. {
  15. NS_ASSERTION(aTextLeaf, "No text leaf accessible?");
  16. const nsString& oldText = aTextLeaf->Text();
  17. uint32_t oldLen = oldText.Length(), newLen = aNewText.Length();
  18. uint32_t minLen = std::min(oldLen, newLen);
  19. // Skip coinciding begin substrings.
  20. uint32_t skipStart = 0;
  21. for (; skipStart < minLen; skipStart++) {
  22. if (aNewText[skipStart] != oldText[skipStart])
  23. break;
  24. }
  25. // The text was changed. Do update.
  26. if (skipStart != minLen || oldLen != newLen) {
  27. TextUpdater updater(aDocument, aTextLeaf);
  28. updater.DoUpdate(aNewText, oldText, skipStart);
  29. }
  30. }
  31. void
  32. TextUpdater::DoUpdate(const nsAString& aNewText, const nsAString& aOldText,
  33. uint32_t aSkipStart)
  34. {
  35. Accessible* parent = mTextLeaf->Parent();
  36. if (!parent)
  37. return;
  38. mHyperText = parent->AsHyperText();
  39. if (!mHyperText) {
  40. NS_ERROR("Text leaf parent is not hypertext!");
  41. return;
  42. }
  43. // Get the text leaf accessible offset and invalidate cached offsets after it.
  44. mTextOffset = mHyperText->GetChildOffset(mTextLeaf, true);
  45. NS_ASSERTION(mTextOffset != -1,
  46. "Text leaf hasn't offset within hyper text!");
  47. uint32_t oldLen = aOldText.Length(), newLen = aNewText.Length();
  48. uint32_t minLen = std::min(oldLen, newLen);
  49. // Trim coinciding substrings from the end.
  50. uint32_t skipEnd = 0;
  51. while (minLen - skipEnd > aSkipStart &&
  52. aNewText[newLen - skipEnd - 1] == aOldText[oldLen - skipEnd - 1]) {
  53. skipEnd++;
  54. }
  55. uint32_t strLen1 = oldLen - aSkipStart - skipEnd;
  56. uint32_t strLen2 = newLen - aSkipStart - skipEnd;
  57. const nsAString& str1 = Substring(aOldText, aSkipStart, strLen1);
  58. const nsAString& str2 = Substring(aNewText, aSkipStart, strLen2);
  59. // Increase offset of the text leaf on skipped characters amount.
  60. mTextOffset += aSkipStart;
  61. // It could be single insertion or removal or the case of long strings. Do not
  62. // calculate the difference between long strings and prefer to fire pair of
  63. // insert/remove events as the old string was replaced on the new one.
  64. if (strLen1 == 0 || strLen2 == 0 ||
  65. strLen1 > kMaxStrLen || strLen2 > kMaxStrLen) {
  66. if (strLen1 > 0) {
  67. // Fire text change event for removal.
  68. RefPtr<AccEvent> textRemoveEvent =
  69. new AccTextChangeEvent(mHyperText, mTextOffset, str1, false);
  70. mDocument->FireDelayedEvent(textRemoveEvent);
  71. }
  72. if (strLen2 > 0) {
  73. // Fire text change event for insertion.
  74. RefPtr<AccEvent> textInsertEvent =
  75. new AccTextChangeEvent(mHyperText, mTextOffset, str2, true);
  76. mDocument->FireDelayedEvent(textInsertEvent);
  77. }
  78. mDocument->MaybeNotifyOfValueChange(mHyperText);
  79. // Update the text.
  80. mTextLeaf->SetText(aNewText);
  81. return;
  82. }
  83. // Otherwise find the difference between strings and fire events.
  84. // Note: we can skip initial and final coinciding characters since they don't
  85. // affect the Levenshtein distance.
  86. // Compute the flat structured matrix need to compute the difference.
  87. uint32_t len1 = strLen1 + 1, len2 = strLen2 + 1;
  88. uint32_t* entries = new uint32_t[len1 * len2];
  89. for (uint32_t colIdx = 0; colIdx < len1; colIdx++)
  90. entries[colIdx] = colIdx;
  91. uint32_t* row = entries;
  92. for (uint32_t rowIdx = 1; rowIdx < len2; rowIdx++) {
  93. uint32_t* prevRow = row;
  94. row += len1;
  95. row[0] = rowIdx;
  96. for (uint32_t colIdx = 1; colIdx < len1; colIdx++) {
  97. if (str1[colIdx - 1] != str2[rowIdx - 1]) {
  98. uint32_t left = row[colIdx - 1];
  99. uint32_t up = prevRow[colIdx];
  100. uint32_t upleft = prevRow[colIdx - 1];
  101. row[colIdx] = std::min(upleft, std::min(left, up)) + 1;
  102. } else {
  103. row[colIdx] = prevRow[colIdx - 1];
  104. }
  105. }
  106. }
  107. // Compute events based on the difference.
  108. nsTArray<RefPtr<AccEvent> > events;
  109. ComputeTextChangeEvents(str1, str2, entries, events);
  110. delete [] entries;
  111. // Fire events.
  112. for (int32_t idx = events.Length() - 1; idx >= 0; idx--)
  113. mDocument->FireDelayedEvent(events[idx]);
  114. mDocument->MaybeNotifyOfValueChange(mHyperText);
  115. // Update the text.
  116. mTextLeaf->SetText(aNewText);
  117. }
  118. void
  119. TextUpdater::ComputeTextChangeEvents(const nsAString& aStr1,
  120. const nsAString& aStr2,
  121. uint32_t* aEntries,
  122. nsTArray<RefPtr<AccEvent> >& aEvents)
  123. {
  124. int32_t colIdx = aStr1.Length(), rowIdx = aStr2.Length();
  125. // Point at which strings last matched.
  126. int32_t colEnd = colIdx;
  127. int32_t rowEnd = rowIdx;
  128. int32_t colLen = colEnd + 1;
  129. uint32_t* row = aEntries + rowIdx * colLen;
  130. uint32_t dist = row[colIdx]; // current Levenshtein distance
  131. while (rowIdx && colIdx) { // stop when we can't move diagonally
  132. if (aStr1[colIdx - 1] == aStr2[rowIdx - 1]) { // match
  133. if (rowIdx < rowEnd) { // deal with any pending insertion
  134. FireInsertEvent(Substring(aStr2, rowIdx, rowEnd - rowIdx),
  135. rowIdx, aEvents);
  136. }
  137. if (colIdx < colEnd) { // deal with any pending deletion
  138. FireDeleteEvent(Substring(aStr1, colIdx, colEnd - colIdx),
  139. rowIdx, aEvents);
  140. }
  141. colEnd = --colIdx; // reset the match point
  142. rowEnd = --rowIdx;
  143. row -= colLen;
  144. continue;
  145. }
  146. --dist;
  147. if (dist == row[colIdx - 1 - colLen]) { // substitution
  148. --colIdx;
  149. --rowIdx;
  150. row -= colLen;
  151. continue;
  152. }
  153. if (dist == row[colIdx - colLen]) { // insertion
  154. --rowIdx;
  155. row -= colLen;
  156. continue;
  157. }
  158. if (dist == row[colIdx - 1]) { // deletion
  159. --colIdx;
  160. continue;
  161. }
  162. NS_NOTREACHED("huh?");
  163. return;
  164. }
  165. if (rowEnd)
  166. FireInsertEvent(Substring(aStr2, 0, rowEnd), 0, aEvents);
  167. if (colEnd)
  168. FireDeleteEvent(Substring(aStr1, 0, colEnd), 0, aEvents);
  169. }