|
- /*
- ==============================================================================
-
- This file is part of the JUCE library.
- Copyright (c) 2022 - Raw Material Software Limited
-
- JUCE is an open source library subject to commercial or open-source
- licensing.
-
- The code included in this file is provided under the terms of the ISC license
- http://www.isc.org/downloads/software-support-policy/isc-license. Permission
- To use, copy, modify, and/or distribute this software for any purpose with or
- without fee is hereby granted provided that the above copyright notice and
- this permission notice appear in all copies.
-
- JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
- EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
- DISCLAIMED.
-
- ==============================================================================
- */
-
- namespace juce
- {
-
- struct TextDiffHelpers
- {
- enum { minLengthToMatch = 3,
- maxComplexity = 16 * 1024 * 1024 };
-
- struct StringRegion
- {
- StringRegion (const String& s) noexcept
- : text (s.getCharPointer()), start (0), length (s.length()) {}
-
- StringRegion (String::CharPointerType t, int s, int len) noexcept
- : text (t), start (s), length (len) {}
-
- void incrementStart() noexcept { ++text; ++start; --length; }
-
- String::CharPointerType text;
- int start, length;
- };
-
- static void addInsertion (TextDiff& td, String::CharPointerType text, int index, int length)
- {
- TextDiff::Change c;
- c.insertedText = String (text, (size_t) length);
- c.start = index;
- c.length = 0;
- td.changes.add (c);
- }
-
- static void addDeletion (TextDiff& td, int index, int length)
- {
- TextDiff::Change c;
- c.start = index;
- c.length = length;
- td.changes.add (c);
- }
-
- static void diffSkippingCommonStart (TextDiff& td, StringRegion a, StringRegion b)
- {
- for (;;)
- {
- auto ca = *a.text;
- auto cb = *b.text;
-
- if (ca != cb || ca == 0)
- break;
-
- a.incrementStart();
- b.incrementStart();
- }
-
- diffRecursively (td, a, b);
- }
-
- static void diffRecursively (TextDiff& td, StringRegion a, StringRegion b)
- {
- int indexA = 0, indexB = 0;
- auto len = findLongestCommonSubstring (a.text, a.length, indexA,
- b.text, b.length, indexB);
-
- if (len >= minLengthToMatch)
- {
- if (indexA > 0 && indexB > 0)
- diffSkippingCommonStart (td, StringRegion (a.text, a.start, indexA),
- StringRegion (b.text, b.start, indexB));
- else if (indexA > 0)
- addDeletion (td, b.start, indexA);
- else if (indexB > 0)
- addInsertion (td, b.text, b.start, indexB);
-
- diffRecursively (td, StringRegion (a.text + (indexA + len), a.start + indexA + len, a.length - indexA - len),
- StringRegion (b.text + (indexB + len), b.start + indexB + len, b.length - indexB - len));
- }
- else
- {
- if (a.length > 0) addDeletion (td, b.start, a.length);
- if (b.length > 0) addInsertion (td, b.text, b.start, b.length);
- }
- }
-
- static int findLongestCommonSubstring (String::CharPointerType a, const int lenA, int& indexInA,
- String::CharPointerType b, const int lenB, int& indexInB) noexcept
- {
- if (lenA == 0 || lenB == 0)
- return 0;
-
- if (lenA * lenB > maxComplexity)
- return findCommonSuffix (a, lenA, indexInA,
- b, lenB, indexInB);
-
- auto scratchSpace = sizeof (int) * (2 + 2 * (size_t) lenB);
-
- if (scratchSpace < 4096)
- {
- JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6255)
- auto* scratch = (int*) alloca (scratchSpace);
- JUCE_END_IGNORE_WARNINGS_MSVC
-
- return findLongestCommonSubstring (a, lenA, indexInA, b, lenB, indexInB, scratchSpace, scratch);
- }
-
- HeapBlock<int> scratch (scratchSpace);
- return findLongestCommonSubstring (a, lenA, indexInA, b, lenB, indexInB, scratchSpace, scratch);
- }
-
- static int findLongestCommonSubstring (String::CharPointerType a, const int lenA, int& indexInA,
- String::CharPointerType b, const int lenB, int& indexInB,
- const size_t scratchSpace, int* const lines) noexcept
- {
- zeromem (lines, scratchSpace);
-
- auto* l0 = lines;
- auto* l1 = l0 + lenB + 1;
-
- int loopsWithoutImprovement = 0;
- int bestLength = 0;
-
- for (int i = 0; i < lenA; ++i)
- {
- auto ca = a.getAndAdvance();
- auto b2 = b;
-
- for (int j = 0; j < lenB; ++j)
- {
- if (ca != b2.getAndAdvance())
- {
- l1[j + 1] = 0;
- }
- else
- {
- auto len = l0[j] + 1;
- l1[j + 1] = len;
-
- if (len > bestLength)
- {
- loopsWithoutImprovement = 0;
- bestLength = len;
- indexInA = i;
- indexInB = j;
- }
- }
- }
-
- if (++loopsWithoutImprovement > 100)
- break;
-
- std::swap (l0, l1);
- }
-
- indexInA -= bestLength - 1;
- indexInB -= bestLength - 1;
- return bestLength;
- }
-
- static int findCommonSuffix (String::CharPointerType a, int lenA, int& indexInA,
- String::CharPointerType b, int lenB, int& indexInB) noexcept
- {
- int length = 0;
- a += lenA - 1;
- b += lenB - 1;
-
- while (length < lenA && length < lenB && *a == *b)
- {
- --a;
- --b;
- ++length;
- }
-
- indexInA = lenA - length;
- indexInB = lenB - length;
- return length;
- }
- };
-
- TextDiff::TextDiff (const String& original, const String& target)
- {
- TextDiffHelpers::diffSkippingCommonStart (*this, original, target);
- }
-
- String TextDiff::appliedTo (String text) const
- {
- for (auto& c : changes)
- text = c.appliedTo (text);
-
- return text;
- }
-
- bool TextDiff::Change::isDeletion() const noexcept
- {
- return insertedText.isEmpty();
- }
-
- String TextDiff::Change::appliedTo (const String& text) const noexcept
- {
- return text.replaceSection (start, length, insertedText);
- }
-
-
- //==============================================================================
- //==============================================================================
- #if JUCE_UNIT_TESTS
-
- class DiffTests final : public UnitTest
- {
- public:
- DiffTests()
- : UnitTest ("TextDiff class", UnitTestCategories::text)
- {}
-
- static String createString (Random& r)
- {
- juce_wchar buffer[500] = { 0 };
-
- for (int i = r.nextInt (numElementsInArray (buffer) - 1); --i >= 0;)
- {
- if (r.nextInt (10) == 0)
- {
- do
- {
- buffer[i] = (juce_wchar) (1 + r.nextInt (0x10ffff - 1));
- }
- while (! CharPointer_UTF16::canRepresent (buffer[i]));
- }
- else
- buffer[i] = (juce_wchar) ('a' + r.nextInt (3));
- }
-
- return CharPointer_UTF32 (buffer);
- }
-
- void testDiff (const String& a, const String& b)
- {
- TextDiff diff (a, b);
- auto result = diff.appliedTo (a);
- expectEquals (result, b);
- }
-
- void runTest() override
- {
- beginTest ("TextDiff");
-
- auto r = getRandom();
-
- testDiff (String(), String());
- testDiff ("x", String());
- testDiff (String(), "x");
- testDiff ("x", "x");
- testDiff ("x", "y");
- testDiff ("xxx", "x");
- testDiff ("x", "xxx");
-
- for (int i = 1000; --i >= 0;)
- {
- auto s = createString (r);
- testDiff (s, createString (r));
- testDiff (s + createString (r), s + createString (r));
- }
- }
- };
-
- static DiffTests diffTests;
-
- #endif
-
- } // namespace juce
|