Best Atoum code snippet using diff.diff
diff.go
Source:diff.go
1// Copyright (c) 2012-2016 The go-diff authors. All rights reserved.2// https://github.com/sergi/go-diff3// See the included LICENSE file for license details.4//5// go-diff is a Go implementation of Google's Diff, Match, and Patch library6// Original library is Copyright (c) 2006 Google Inc.7// http://code.google.com/p/google-diff-match-patch/8package diffmatchpatch9import (10 "bytes"11 "errors"12 "fmt"13 "html"14 "math"15 "net/url"16 "regexp"17 "strconv"18 "strings"19 "time"20 "unicode/utf8"21)22// Operation defines the operation of a diff item.23type Operation int824//go:generate stringer -type=Operation -trimprefix=Diff25const (26 // DiffDelete item represents a delete diff.27 DiffDelete Operation = -128 // DiffInsert item represents an insert diff.29 DiffInsert Operation = 130 // DiffEqual item represents an equal diff.31 DiffEqual Operation = 032)33// Diff represents one diff operation34type Diff struct {35 Type Operation36 Text string37}38// splice removes amount elements from slice at index index, replacing them with elements.39func splice(slice []Diff, index int, amount int, elements ...Diff) []Diff {40 if len(elements) == amount {41 // Easy case: overwrite the relevant items.42 copy(slice[index:], elements)43 return slice44 }45 if len(elements) < amount {46 // Fewer new items than old.47 // Copy in the new items.48 copy(slice[index:], elements)49 // Shift the remaining items left.50 copy(slice[index+len(elements):], slice[index+amount:])51 // Calculate the new end of the slice.52 end := len(slice) - amount + len(elements)53 // Zero stranded elements at end so that they can be garbage collected.54 tail := slice[end:]55 for i := range tail {56 tail[i] = Diff{}57 }58 return slice[:end]59 }60 // More new items than old.61 // Make room in slice for new elements.62 // There's probably an even more efficient way to do this,63 // but this is simple and clear.64 need := len(slice) - amount + len(elements)65 for len(slice) < need {66 slice = append(slice, Diff{})67 }68 // Shift slice elements right to make room for new elements.69 copy(slice[index+len(elements):], slice[index+amount:])70 // Copy in new elements.71 copy(slice[index:], elements)72 return slice73}74// DiffMain finds the differences between two texts.75// If an invalid UTF-8 sequence is encountered, it will be replaced by the Unicode replacement character.76func (dmp *DiffMatchPatch) DiffMain(text1, text2 string, checklines bool) []Diff {77 return dmp.DiffMainRunes([]rune(text1), []rune(text2), checklines)78}79// DiffMainRunes finds the differences between two rune sequences.80// If an invalid UTF-8 sequence is encountered, it will be replaced by the Unicode replacement character.81func (dmp *DiffMatchPatch) DiffMainRunes(text1, text2 []rune, checklines bool) []Diff {82 var deadline time.Time83 if dmp.DiffTimeout > 0 {84 deadline = time.Now().Add(dmp.DiffTimeout)85 }86 return dmp.diffMainRunes(text1, text2, checklines, deadline)87}88func (dmp *DiffMatchPatch) diffMainRunes(text1, text2 []rune, checklines bool, deadline time.Time) []Diff {89 if runesEqual(text1, text2) {90 var diffs []Diff91 if len(text1) > 0 {92 diffs = append(diffs, Diff{DiffEqual, string(text1)})93 }94 return diffs95 }96 // Trim off common prefix (speedup).97 commonlength := commonPrefixLength(text1, text2)98 commonprefix := text1[:commonlength]99 text1 = text1[commonlength:]100 text2 = text2[commonlength:]101 // Trim off common suffix (speedup).102 commonlength = commonSuffixLength(text1, text2)103 commonsuffix := text1[len(text1)-commonlength:]104 text1 = text1[:len(text1)-commonlength]105 text2 = text2[:len(text2)-commonlength]106 // Compute the diff on the middle block.107 diffs := dmp.diffCompute(text1, text2, checklines, deadline)108 // Restore the prefix and suffix.109 if len(commonprefix) != 0 {110 diffs = append([]Diff{Diff{DiffEqual, string(commonprefix)}}, diffs...)111 }112 if len(commonsuffix) != 0 {113 diffs = append(diffs, Diff{DiffEqual, string(commonsuffix)})114 }115 return dmp.DiffCleanupMerge(diffs)116}117// diffCompute finds the differences between two rune slices. Assumes that the texts do not have any common prefix or suffix.118func (dmp *DiffMatchPatch) diffCompute(text1, text2 []rune, checklines bool, deadline time.Time) []Diff {119 diffs := []Diff{}120 if len(text1) == 0 {121 // Just add some text (speedup).122 return append(diffs, Diff{DiffInsert, string(text2)})123 } else if len(text2) == 0 {124 // Just delete some text (speedup).125 return append(diffs, Diff{DiffDelete, string(text1)})126 }127 var longtext, shorttext []rune128 if len(text1) > len(text2) {129 longtext = text1130 shorttext = text2131 } else {132 longtext = text2133 shorttext = text1134 }135 if i := runesIndex(longtext, shorttext); i != -1 {136 op := DiffInsert137 // Swap insertions for deletions if diff is reversed.138 if len(text1) > len(text2) {139 op = DiffDelete140 }141 // Shorter text is inside the longer text (speedup).142 return []Diff{143 Diff{op, string(longtext[:i])},144 Diff{DiffEqual, string(shorttext)},145 Diff{op, string(longtext[i+len(shorttext):])},146 }147 } else if len(shorttext) == 1 {148 // Single character string.149 // After the previous speedup, the character can't be an equality.150 return []Diff{151 Diff{DiffDelete, string(text1)},152 Diff{DiffInsert, string(text2)},153 }154 // Check to see if the problem can be split in two.155 } else if hm := dmp.diffHalfMatch(text1, text2); hm != nil {156 // A half-match was found, sort out the return data.157 text1A := hm[0]158 text1B := hm[1]159 text2A := hm[2]160 text2B := hm[3]161 midCommon := hm[4]162 // Send both pairs off for separate processing.163 diffsA := dmp.diffMainRunes(text1A, text2A, checklines, deadline)164 diffsB := dmp.diffMainRunes(text1B, text2B, checklines, deadline)165 // Merge the results.166 diffs := diffsA167 diffs = append(diffs, Diff{DiffEqual, string(midCommon)})168 diffs = append(diffs, diffsB...)169 return diffs170 } else if checklines && len(text1) > 100 && len(text2) > 100 {171 return dmp.diffLineMode(text1, text2, deadline)172 }173 return dmp.diffBisect(text1, text2, deadline)174}175// diffLineMode does a quick line-level diff on both []runes, then rediff the parts for greater accuracy. This speedup can produce non-minimal diffs.176func (dmp *DiffMatchPatch) diffLineMode(text1, text2 []rune, deadline time.Time) []Diff {177 // Scan the text on a line-by-line basis first.178 text1, text2, linearray := dmp.diffLinesToRunes(text1, text2)179 diffs := dmp.diffMainRunes(text1, text2, false, deadline)180 // Convert the diff back to original text.181 diffs = dmp.DiffCharsToLines(diffs, linearray)182 // Eliminate freak matches (e.g. blank lines)183 diffs = dmp.DiffCleanupSemantic(diffs)184 // Rediff any replacement blocks, this time character-by-character.185 // Add a dummy entry at the end.186 diffs = append(diffs, Diff{DiffEqual, ""})187 pointer := 0188 countDelete := 0189 countInsert := 0190 // NOTE: Rune slices are slower than using strings in this case.191 textDelete := ""192 textInsert := ""193 for pointer < len(diffs) {194 switch diffs[pointer].Type {195 case DiffInsert:196 countInsert++197 textInsert += diffs[pointer].Text198 case DiffDelete:199 countDelete++200 textDelete += diffs[pointer].Text201 case DiffEqual:202 // Upon reaching an equality, check for prior redundancies.203 if countDelete >= 1 && countInsert >= 1 {204 // Delete the offending records and add the merged ones.205 diffs = splice(diffs, pointer-countDelete-countInsert,206 countDelete+countInsert)207 pointer = pointer - countDelete - countInsert208 a := dmp.diffMainRunes([]rune(textDelete), []rune(textInsert), false, deadline)209 for j := len(a) - 1; j >= 0; j-- {210 diffs = splice(diffs, pointer, 0, a[j])211 }212 pointer = pointer + len(a)213 }214 countInsert = 0215 countDelete = 0216 textDelete = ""217 textInsert = ""218 }219 pointer++220 }221 return diffs[:len(diffs)-1] // Remove the dummy entry at the end.222}223// DiffBisect finds the 'middle snake' of a diff, split the problem in two and return the recursively constructed diff.224// If an invalid UTF-8 sequence is encountered, it will be replaced by the Unicode replacement character.225// See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.226func (dmp *DiffMatchPatch) DiffBisect(text1, text2 string, deadline time.Time) []Diff {227 // Unused in this code, but retained for interface compatibility.228 return dmp.diffBisect([]rune(text1), []rune(text2), deadline)229}230// diffBisect finds the 'middle snake' of a diff, splits the problem in two and returns the recursively constructed diff.231// See Myers's 1986 paper: An O(ND) Difference Algorithm and Its Variations.232func (dmp *DiffMatchPatch) diffBisect(runes1, runes2 []rune, deadline time.Time) []Diff {233 // Cache the text lengths to prevent multiple calls.234 runes1Len, runes2Len := len(runes1), len(runes2)235 maxD := (runes1Len + runes2Len + 1) / 2236 vOffset := maxD237 vLength := 2 * maxD238 v1 := make([]int, vLength)239 v2 := make([]int, vLength)240 for i := range v1 {241 v1[i] = -1242 v2[i] = -1243 }244 v1[vOffset+1] = 0245 v2[vOffset+1] = 0246 delta := runes1Len - runes2Len247 // If the total number of characters is odd, then the front path will collide with the reverse path.248 front := (delta%2 != 0)249 // Offsets for start and end of k loop. Prevents mapping of space beyond the grid.250 k1start := 0251 k1end := 0252 k2start := 0253 k2end := 0254 for d := 0; d < maxD; d++ {255 // Bail out if deadline is reached.256 if !deadline.IsZero() && d%16 == 0 && time.Now().After(deadline) {257 break258 }259 // Walk the front path one step.260 for k1 := -d + k1start; k1 <= d-k1end; k1 += 2 {261 k1Offset := vOffset + k1262 var x1 int263 if k1 == -d || (k1 != d && v1[k1Offset-1] < v1[k1Offset+1]) {264 x1 = v1[k1Offset+1]265 } else {266 x1 = v1[k1Offset-1] + 1267 }268 y1 := x1 - k1269 for x1 < runes1Len && y1 < runes2Len {270 if runes1[x1] != runes2[y1] {271 break272 }273 x1++274 y1++275 }276 v1[k1Offset] = x1277 if x1 > runes1Len {278 // Ran off the right of the graph.279 k1end += 2280 } else if y1 > runes2Len {281 // Ran off the bottom of the graph.282 k1start += 2283 } else if front {284 k2Offset := vOffset + delta - k1285 if k2Offset >= 0 && k2Offset < vLength && v2[k2Offset] != -1 {286 // Mirror x2 onto top-left coordinate system.287 x2 := runes1Len - v2[k2Offset]288 if x1 >= x2 {289 // Overlap detected.290 return dmp.diffBisectSplit(runes1, runes2, x1, y1, deadline)291 }292 }293 }294 }295 // Walk the reverse path one step.296 for k2 := -d + k2start; k2 <= d-k2end; k2 += 2 {297 k2Offset := vOffset + k2298 var x2 int299 if k2 == -d || (k2 != d && v2[k2Offset-1] < v2[k2Offset+1]) {300 x2 = v2[k2Offset+1]301 } else {302 x2 = v2[k2Offset-1] + 1303 }304 var y2 = x2 - k2305 for x2 < runes1Len && y2 < runes2Len {306 if runes1[runes1Len-x2-1] != runes2[runes2Len-y2-1] {307 break308 }309 x2++310 y2++311 }312 v2[k2Offset] = x2313 if x2 > runes1Len {314 // Ran off the left of the graph.315 k2end += 2316 } else if y2 > runes2Len {317 // Ran off the top of the graph.318 k2start += 2319 } else if !front {320 k1Offset := vOffset + delta - k2321 if k1Offset >= 0 && k1Offset < vLength && v1[k1Offset] != -1 {322 x1 := v1[k1Offset]323 y1 := vOffset + x1 - k1Offset324 // Mirror x2 onto top-left coordinate system.325 x2 = runes1Len - x2326 if x1 >= x2 {327 // Overlap detected.328 return dmp.diffBisectSplit(runes1, runes2, x1, y1, deadline)329 }330 }331 }332 }333 }334 // Diff took too long and hit the deadline or number of diffs equals number of characters, no commonality at all.335 return []Diff{336 Diff{DiffDelete, string(runes1)},337 Diff{DiffInsert, string(runes2)},338 }339}340func (dmp *DiffMatchPatch) diffBisectSplit(runes1, runes2 []rune, x, y int,341 deadline time.Time) []Diff {342 runes1a := runes1[:x]343 runes2a := runes2[:y]344 runes1b := runes1[x:]345 runes2b := runes2[y:]346 // Compute both diffs serially.347 diffs := dmp.diffMainRunes(runes1a, runes2a, false, deadline)348 diffsb := dmp.diffMainRunes(runes1b, runes2b, false, deadline)349 return append(diffs, diffsb...)350}351// DiffLinesToChars splits two texts into a list of strings, and educes the texts to a string of hashes where each Unicode character represents one line.352// It's slightly faster to call DiffLinesToRunes first, followed by DiffMainRunes.353func (dmp *DiffMatchPatch) DiffLinesToChars(text1, text2 string) (string, string, []string) {354 chars1, chars2, lineArray := dmp.DiffLinesToRunes(text1, text2)355 return string(chars1), string(chars2), lineArray356}357// DiffLinesToRunes splits two texts into a list of runes. Each rune represents one line.358func (dmp *DiffMatchPatch) DiffLinesToRunes(text1, text2 string) ([]rune, []rune, []string) {359 // '\x00' is a valid character, but various debuggers don't like it. So we'll insert a junk entry to avoid generating a null character.360 lineArray := []string{""} // e.g. lineArray[4] == 'Hello\n'361 lineHash := map[string]int{} // e.g. lineHash['Hello\n'] == 4362 chars1 := dmp.diffLinesToRunesMunge(text1, &lineArray, lineHash)363 chars2 := dmp.diffLinesToRunesMunge(text2, &lineArray, lineHash)364 return chars1, chars2, lineArray365}366func (dmp *DiffMatchPatch) diffLinesToRunes(text1, text2 []rune) ([]rune, []rune, []string) {367 return dmp.DiffLinesToRunes(string(text1), string(text2))368}369// diffLinesToRunesMunge splits a text into an array of strings, and reduces the texts to a []rune where each Unicode character represents one line.370// We use strings instead of []runes as input mainly because you can't use []rune as a map key.371func (dmp *DiffMatchPatch) diffLinesToRunesMunge(text string, lineArray *[]string, lineHash map[string]int) []rune {372 // Walk the text, pulling out a substring for each line. text.split('\n') would would temporarily double our memory footprint. Modifying text would create many large strings to garbage collect.373 lineStart := 0374 lineEnd := -1375 runes := []rune{}376 for lineEnd < len(text)-1 {377 lineEnd = indexOf(text, "\n", lineStart)378 if lineEnd == -1 {379 lineEnd = len(text) - 1380 }381 line := text[lineStart : lineEnd+1]382 lineStart = lineEnd + 1383 lineValue, ok := lineHash[line]384 if ok {385 runes = append(runes, rune(lineValue))386 } else {387 *lineArray = append(*lineArray, line)388 lineHash[line] = len(*lineArray) - 1389 runes = append(runes, rune(len(*lineArray)-1))390 }391 }392 return runes393}394// DiffCharsToLines rehydrates the text in a diff from a string of line hashes to real lines of text.395func (dmp *DiffMatchPatch) DiffCharsToLines(diffs []Diff, lineArray []string) []Diff {396 hydrated := make([]Diff, 0, len(diffs))397 for _, aDiff := range diffs {398 chars := aDiff.Text399 text := make([]string, len(chars))400 for i, r := range chars {401 text[i] = lineArray[r]402 }403 aDiff.Text = strings.Join(text, "")404 hydrated = append(hydrated, aDiff)405 }406 return hydrated407}408// DiffCommonPrefix determines the common prefix length of two strings.409func (dmp *DiffMatchPatch) DiffCommonPrefix(text1, text2 string) int {410 // Unused in this code, but retained for interface compatibility.411 return commonPrefixLength([]rune(text1), []rune(text2))412}413// DiffCommonSuffix determines the common suffix length of two strings.414func (dmp *DiffMatchPatch) DiffCommonSuffix(text1, text2 string) int {415 // Unused in this code, but retained for interface compatibility.416 return commonSuffixLength([]rune(text1), []rune(text2))417}418// commonPrefixLength returns the length of the common prefix of two rune slices.419func commonPrefixLength(text1, text2 []rune) int {420 // Linear search. See comment in commonSuffixLength.421 n := 0422 for ; n < len(text1) && n < len(text2); n++ {423 if text1[n] != text2[n] {424 return n425 }426 }427 return n428}429// commonSuffixLength returns the length of the common suffix of two rune slices.430func commonSuffixLength(text1, text2 []rune) int {431 // Use linear search rather than the binary search discussed at https://neil.fraser.name/news/2007/10/09/.432 // See discussion at https://github.com/sergi/go-diff/issues/54.433 i1 := len(text1)434 i2 := len(text2)435 for n := 0; ; n++ {436 i1--437 i2--438 if i1 < 0 || i2 < 0 || text1[i1] != text2[i2] {439 return n440 }441 }442}443// DiffCommonOverlap determines if the suffix of one string is the prefix of another.444func (dmp *DiffMatchPatch) DiffCommonOverlap(text1 string, text2 string) int {445 // Cache the text lengths to prevent multiple calls.446 text1Length := len(text1)447 text2Length := len(text2)448 // Eliminate the null case.449 if text1Length == 0 || text2Length == 0 {450 return 0451 }452 // Truncate the longer string.453 if text1Length > text2Length {454 text1 = text1[text1Length-text2Length:]455 } else if text1Length < text2Length {456 text2 = text2[0:text1Length]457 }458 textLength := int(math.Min(float64(text1Length), float64(text2Length)))459 // Quick check for the worst case.460 if text1 == text2 {461 return textLength462 }463 // Start by looking for a single character match and increase length until no match is found. Performance analysis: http://neil.fraser.name/news/2010/11/04/464 best := 0465 length := 1466 for {467 pattern := text1[textLength-length:]468 found := strings.Index(text2, pattern)469 if found == -1 {470 break471 }472 length += found473 if found == 0 || text1[textLength-length:] == text2[0:length] {474 best = length475 length++476 }477 }478 return best479}480// DiffHalfMatch checks whether the two texts share a substring which is at least half the length of the longer text. This speedup can produce non-minimal diffs.481func (dmp *DiffMatchPatch) DiffHalfMatch(text1, text2 string) []string {482 // Unused in this code, but retained for interface compatibility.483 runeSlices := dmp.diffHalfMatch([]rune(text1), []rune(text2))484 if runeSlices == nil {485 return nil486 }487 result := make([]string, len(runeSlices))488 for i, r := range runeSlices {489 result[i] = string(r)490 }491 return result492}493func (dmp *DiffMatchPatch) diffHalfMatch(text1, text2 []rune) [][]rune {494 if dmp.DiffTimeout <= 0 {495 // Don't risk returning a non-optimal diff if we have unlimited time.496 return nil497 }498 var longtext, shorttext []rune499 if len(text1) > len(text2) {500 longtext = text1501 shorttext = text2502 } else {503 longtext = text2504 shorttext = text1505 }506 if len(longtext) < 4 || len(shorttext)*2 < len(longtext) {507 return nil // Pointless.508 }509 // First check if the second quarter is the seed for a half-match.510 hm1 := dmp.diffHalfMatchI(longtext, shorttext, int(float64(len(longtext)+3)/4))511 // Check again based on the third quarter.512 hm2 := dmp.diffHalfMatchI(longtext, shorttext, int(float64(len(longtext)+1)/2))513 hm := [][]rune{}514 if hm1 == nil && hm2 == nil {515 return nil516 } else if hm2 == nil {517 hm = hm1518 } else if hm1 == nil {519 hm = hm2520 } else {521 // Both matched. Select the longest.522 if len(hm1[4]) > len(hm2[4]) {523 hm = hm1524 } else {525 hm = hm2526 }527 }528 // A half-match was found, sort out the return data.529 if len(text1) > len(text2) {530 return hm531 }532 return [][]rune{hm[2], hm[3], hm[0], hm[1], hm[4]}533}534// diffHalfMatchI checks if a substring of shorttext exist within longtext such that the substring is at least half the length of longtext?535// Returns a slice containing the prefix of longtext, the suffix of longtext, the prefix of shorttext, the suffix of shorttext and the common middle, or null if there was no match.536func (dmp *DiffMatchPatch) diffHalfMatchI(l, s []rune, i int) [][]rune {537 var bestCommonA []rune538 var bestCommonB []rune539 var bestCommonLen int540 var bestLongtextA []rune541 var bestLongtextB []rune542 var bestShorttextA []rune543 var bestShorttextB []rune544 // Start with a 1/4 length substring at position i as a seed.545 seed := l[i : i+len(l)/4]546 for j := runesIndexOf(s, seed, 0); j != -1; j = runesIndexOf(s, seed, j+1) {547 prefixLength := commonPrefixLength(l[i:], s[j:])548 suffixLength := commonSuffixLength(l[:i], s[:j])549 if bestCommonLen < suffixLength+prefixLength {550 bestCommonA = s[j-suffixLength : j]551 bestCommonB = s[j : j+prefixLength]552 bestCommonLen = len(bestCommonA) + len(bestCommonB)553 bestLongtextA = l[:i-suffixLength]554 bestLongtextB = l[i+prefixLength:]555 bestShorttextA = s[:j-suffixLength]556 bestShorttextB = s[j+prefixLength:]557 }558 }559 if bestCommonLen*2 < len(l) {560 return nil561 }562 return [][]rune{563 bestLongtextA,564 bestLongtextB,565 bestShorttextA,566 bestShorttextB,567 append(bestCommonA, bestCommonB...),568 }569}570// DiffCleanupSemantic reduces the number of edits by eliminating semantically trivial equalities.571func (dmp *DiffMatchPatch) DiffCleanupSemantic(diffs []Diff) []Diff {572 changes := false573 // Stack of indices where equalities are found.574 equalities := make([]int, 0, len(diffs))575 var lastequality string576 // Always equal to diffs[equalities[equalitiesLength - 1]][1]577 var pointer int // Index of current position.578 // Number of characters that changed prior to the equality.579 var lengthInsertions1, lengthDeletions1 int580 // Number of characters that changed after the equality.581 var lengthInsertions2, lengthDeletions2 int582 for pointer < len(diffs) {583 if diffs[pointer].Type == DiffEqual {584 // Equality found.585 equalities = append(equalities, pointer)586 lengthInsertions1 = lengthInsertions2587 lengthDeletions1 = lengthDeletions2588 lengthInsertions2 = 0589 lengthDeletions2 = 0590 lastequality = diffs[pointer].Text591 } else {592 // An insertion or deletion.593 if diffs[pointer].Type == DiffInsert {594 lengthInsertions2 += len(diffs[pointer].Text)595 } else {596 lengthDeletions2 += len(diffs[pointer].Text)597 }598 // Eliminate an equality that is smaller or equal to the edits on both sides of it.599 difference1 := int(math.Max(float64(lengthInsertions1), float64(lengthDeletions1)))600 difference2 := int(math.Max(float64(lengthInsertions2), float64(lengthDeletions2)))601 if len(lastequality) > 0 &&602 (len(lastequality) <= difference1) &&603 (len(lastequality) <= difference2) {604 // Duplicate record.605 insPoint := equalities[len(equalities)-1]606 diffs = splice(diffs, insPoint, 0, Diff{DiffDelete, lastequality})607 // Change second copy to insert.608 diffs[insPoint+1].Type = DiffInsert609 // Throw away the equality we just deleted.610 equalities = equalities[:len(equalities)-1]611 if len(equalities) > 0 {612 equalities = equalities[:len(equalities)-1]613 }614 pointer = -1615 if len(equalities) > 0 {616 pointer = equalities[len(equalities)-1]617 }618 lengthInsertions1 = 0 // Reset the counters.619 lengthDeletions1 = 0620 lengthInsertions2 = 0621 lengthDeletions2 = 0622 lastequality = ""623 changes = true624 }625 }626 pointer++627 }628 // Normalize the diff.629 if changes {630 diffs = dmp.DiffCleanupMerge(diffs)631 }632 diffs = dmp.DiffCleanupSemanticLossless(diffs)633 // Find any overlaps between deletions and insertions.634 // e.g: <del>abcxxx</del><ins>xxxdef</ins>635 // -> <del>abc</del>xxx<ins>def</ins>636 // e.g: <del>xxxabc</del><ins>defxxx</ins>637 // -> <ins>def</ins>xxx<del>abc</del>638 // Only extract an overlap if it is as big as the edit ahead or behind it.639 pointer = 1640 for pointer < len(diffs) {641 if diffs[pointer-1].Type == DiffDelete &&642 diffs[pointer].Type == DiffInsert {643 deletion := diffs[pointer-1].Text644 insertion := diffs[pointer].Text645 overlapLength1 := dmp.DiffCommonOverlap(deletion, insertion)646 overlapLength2 := dmp.DiffCommonOverlap(insertion, deletion)647 if overlapLength1 >= overlapLength2 {648 if float64(overlapLength1) >= float64(len(deletion))/2 ||649 float64(overlapLength1) >= float64(len(insertion))/2 {650 // Overlap found. Insert an equality and trim the surrounding edits.651 diffs = splice(diffs, pointer, 0, Diff{DiffEqual, insertion[:overlapLength1]})652 diffs[pointer-1].Text =653 deletion[0 : len(deletion)-overlapLength1]654 diffs[pointer+1].Text = insertion[overlapLength1:]655 pointer++656 }657 } else {658 if float64(overlapLength2) >= float64(len(deletion))/2 ||659 float64(overlapLength2) >= float64(len(insertion))/2 {660 // Reverse overlap found. Insert an equality and swap and trim the surrounding edits.661 overlap := Diff{DiffEqual, deletion[:overlapLength2]}662 diffs = splice(diffs, pointer, 0, overlap)663 diffs[pointer-1].Type = DiffInsert664 diffs[pointer-1].Text = insertion[0 : len(insertion)-overlapLength2]665 diffs[pointer+1].Type = DiffDelete666 diffs[pointer+1].Text = deletion[overlapLength2:]667 pointer++668 }669 }670 pointer++671 }672 pointer++673 }674 return diffs675}676// Define some regex patterns for matching boundaries.677var (678 nonAlphaNumericRegex = regexp.MustCompile(`[^a-zA-Z0-9]`)679 whitespaceRegex = regexp.MustCompile(`\s`)680 linebreakRegex = regexp.MustCompile(`[\r\n]`)681 blanklineEndRegex = regexp.MustCompile(`\n\r?\n$`)682 blanklineStartRegex = regexp.MustCompile(`^\r?\n\r?\n`)683)684// diffCleanupSemanticScore computes a score representing whether the internal boundary falls on logical boundaries.685// Scores range from 6 (best) to 0 (worst). Closure, but does not reference any external variables.686func diffCleanupSemanticScore(one, two string) int {687 if len(one) == 0 || len(two) == 0 {688 // Edges are the best.689 return 6690 }691 // Each port of this function behaves slightly differently due to subtle differences in each language's definition of things like 'whitespace'. Since this function's purpose is largely cosmetic, the choice has been made to use each language's native features rather than force total conformity.692 rune1, _ := utf8.DecodeLastRuneInString(one)693 rune2, _ := utf8.DecodeRuneInString(two)694 char1 := string(rune1)695 char2 := string(rune2)696 nonAlphaNumeric1 := nonAlphaNumericRegex.MatchString(char1)697 nonAlphaNumeric2 := nonAlphaNumericRegex.MatchString(char2)698 whitespace1 := nonAlphaNumeric1 && whitespaceRegex.MatchString(char1)699 whitespace2 := nonAlphaNumeric2 && whitespaceRegex.MatchString(char2)700 lineBreak1 := whitespace1 && linebreakRegex.MatchString(char1)701 lineBreak2 := whitespace2 && linebreakRegex.MatchString(char2)702 blankLine1 := lineBreak1 && blanklineEndRegex.MatchString(one)703 blankLine2 := lineBreak2 && blanklineEndRegex.MatchString(two)704 if blankLine1 || blankLine2 {705 // Five points for blank lines.706 return 5707 } else if lineBreak1 || lineBreak2 {708 // Four points for line breaks.709 return 4710 } else if nonAlphaNumeric1 && !whitespace1 && whitespace2 {711 // Three points for end of sentences.712 return 3713 } else if whitespace1 || whitespace2 {714 // Two points for whitespace.715 return 2716 } else if nonAlphaNumeric1 || nonAlphaNumeric2 {717 // One point for non-alphanumeric.718 return 1719 }720 return 0721}722// DiffCleanupSemanticLossless looks for single edits surrounded on both sides by equalities which can be shifted sideways to align the edit to a word boundary.723// E.g: The c<ins>at c</ins>ame. -> The <ins>cat </ins>came.724func (dmp *DiffMatchPatch) DiffCleanupSemanticLossless(diffs []Diff) []Diff {725 pointer := 1726 // Intentionally ignore the first and last element (don't need checking).727 for pointer < len(diffs)-1 {728 if diffs[pointer-1].Type == DiffEqual &&729 diffs[pointer+1].Type == DiffEqual {730 // This is a single edit surrounded by equalities.731 equality1 := diffs[pointer-1].Text732 edit := diffs[pointer].Text733 equality2 := diffs[pointer+1].Text734 // First, shift the edit as far left as possible.735 commonOffset := dmp.DiffCommonSuffix(equality1, edit)736 if commonOffset > 0 {737 commonString := edit[len(edit)-commonOffset:]738 equality1 = equality1[0 : len(equality1)-commonOffset]739 edit = commonString + edit[:len(edit)-commonOffset]740 equality2 = commonString + equality2741 }742 // Second, step character by character right, looking for the best fit.743 bestEquality1 := equality1744 bestEdit := edit745 bestEquality2 := equality2746 bestScore := diffCleanupSemanticScore(equality1, edit) +747 diffCleanupSemanticScore(edit, equality2)748 for len(edit) != 0 && len(equality2) != 0 {749 _, sz := utf8.DecodeRuneInString(edit)750 if len(equality2) < sz || edit[:sz] != equality2[:sz] {751 break752 }753 equality1 += edit[:sz]754 edit = edit[sz:] + equality2[:sz]755 equality2 = equality2[sz:]756 score := diffCleanupSemanticScore(equality1, edit) +757 diffCleanupSemanticScore(edit, equality2)758 // The >= encourages trailing rather than leading whitespace on edits.759 if score >= bestScore {760 bestScore = score761 bestEquality1 = equality1762 bestEdit = edit763 bestEquality2 = equality2764 }765 }766 if diffs[pointer-1].Text != bestEquality1 {767 // We have an improvement, save it back to the diff.768 if len(bestEquality1) != 0 {769 diffs[pointer-1].Text = bestEquality1770 } else {771 diffs = splice(diffs, pointer-1, 1)772 pointer--773 }774 diffs[pointer].Text = bestEdit775 if len(bestEquality2) != 0 {776 diffs[pointer+1].Text = bestEquality2777 } else {778 diffs = append(diffs[:pointer+1], diffs[pointer+2:]...)779 pointer--780 }781 }782 }783 pointer++784 }785 return diffs786}787// DiffCleanupEfficiency reduces the number of edits by eliminating operationally trivial equalities.788func (dmp *DiffMatchPatch) DiffCleanupEfficiency(diffs []Diff) []Diff {789 changes := false790 // Stack of indices where equalities are found.791 type equality struct {792 data int793 next *equality794 }795 var equalities *equality796 // Always equal to equalities[equalitiesLength-1][1]797 lastequality := ""798 pointer := 0 // Index of current position.799 // Is there an insertion operation before the last equality.800 preIns := false801 // Is there a deletion operation before the last equality.802 preDel := false803 // Is there an insertion operation after the last equality.804 postIns := false805 // Is there a deletion operation after the last equality.806 postDel := false807 for pointer < len(diffs) {808 if diffs[pointer].Type == DiffEqual { // Equality found.809 if len(diffs[pointer].Text) < dmp.DiffEditCost &&810 (postIns || postDel) {811 // Candidate found.812 equalities = &equality{813 data: pointer,814 next: equalities,815 }816 preIns = postIns817 preDel = postDel818 lastequality = diffs[pointer].Text819 } else {820 // Not a candidate, and can never become one.821 equalities = nil822 lastequality = ""823 }824 postIns = false825 postDel = false826 } else { // An insertion or deletion.827 if diffs[pointer].Type == DiffDelete {828 postDel = true829 } else {830 postIns = true831 }832 // Five types to be split:833 // <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>834 // <ins>A</ins>X<ins>C</ins><del>D</del>835 // <ins>A</ins><del>B</del>X<ins>C</ins>836 // <ins>A</del>X<ins>C</ins><del>D</del>837 // <ins>A</ins><del>B</del>X<del>C</del>838 var sumPres int839 if preIns {840 sumPres++841 }842 if preDel {843 sumPres++844 }845 if postIns {846 sumPres++847 }848 if postDel {849 sumPres++850 }851 if len(lastequality) > 0 &&852 ((preIns && preDel && postIns && postDel) ||853 ((len(lastequality) < dmp.DiffEditCost/2) && sumPres == 3)) {854 insPoint := equalities.data855 // Duplicate record.856 diffs = splice(diffs, insPoint, 0, Diff{DiffDelete, lastequality})857 // Change second copy to insert.858 diffs[insPoint+1].Type = DiffInsert859 // Throw away the equality we just deleted.860 equalities = equalities.next861 lastequality = ""862 if preIns && preDel {863 // No changes made which could affect previous entry, keep going.864 postIns = true865 postDel = true866 equalities = nil867 } else {868 if equalities != nil {869 equalities = equalities.next870 }871 if equalities != nil {872 pointer = equalities.data873 } else {874 pointer = -1875 }876 postIns = false877 postDel = false878 }879 changes = true880 }881 }882 pointer++883 }884 if changes {885 diffs = dmp.DiffCleanupMerge(diffs)886 }887 return diffs888}889// DiffCleanupMerge reorders and merges like edit sections. Merge equalities.890// Any edit section can move as long as it doesn't cross an equality.891func (dmp *DiffMatchPatch) DiffCleanupMerge(diffs []Diff) []Diff {892 // Add a dummy entry at the end.893 diffs = append(diffs, Diff{DiffEqual, ""})894 pointer := 0895 countDelete := 0896 countInsert := 0897 commonlength := 0898 textDelete := []rune(nil)899 textInsert := []rune(nil)900 for pointer < len(diffs) {901 switch diffs[pointer].Type {902 case DiffInsert:903 countInsert++904 textInsert = append(textInsert, []rune(diffs[pointer].Text)...)905 pointer++906 break907 case DiffDelete:908 countDelete++909 textDelete = append(textDelete, []rune(diffs[pointer].Text)...)910 pointer++911 break912 case DiffEqual:913 // Upon reaching an equality, check for prior redundancies.914 if countDelete+countInsert > 1 {915 if countDelete != 0 && countInsert != 0 {916 // Factor out any common prefixies.917 commonlength = commonPrefixLength(textInsert, textDelete)918 if commonlength != 0 {919 x := pointer - countDelete - countInsert920 if x > 0 && diffs[x-1].Type == DiffEqual {921 diffs[x-1].Text += string(textInsert[:commonlength])922 } else {923 diffs = append([]Diff{Diff{DiffEqual, string(textInsert[:commonlength])}}, diffs...)924 pointer++925 }926 textInsert = textInsert[commonlength:]927 textDelete = textDelete[commonlength:]928 }929 // Factor out any common suffixies.930 commonlength = commonSuffixLength(textInsert, textDelete)931 if commonlength != 0 {932 insertIndex := len(textInsert) - commonlength933 deleteIndex := len(textDelete) - commonlength934 diffs[pointer].Text = string(textInsert[insertIndex:]) + diffs[pointer].Text935 textInsert = textInsert[:insertIndex]936 textDelete = textDelete[:deleteIndex]937 }938 }939 // Delete the offending records and add the merged ones.940 if countDelete == 0 {941 diffs = splice(diffs, pointer-countInsert,942 countDelete+countInsert,943 Diff{DiffInsert, string(textInsert)})944 } else if countInsert == 0 {945 diffs = splice(diffs, pointer-countDelete,946 countDelete+countInsert,947 Diff{DiffDelete, string(textDelete)})948 } else {949 diffs = splice(diffs, pointer-countDelete-countInsert,950 countDelete+countInsert,951 Diff{DiffDelete, string(textDelete)},952 Diff{DiffInsert, string(textInsert)})953 }954 pointer = pointer - countDelete - countInsert + 1955 if countDelete != 0 {956 pointer++957 }958 if countInsert != 0 {959 pointer++960 }961 } else if pointer != 0 && diffs[pointer-1].Type == DiffEqual {962 // Merge this equality with the previous one.963 diffs[pointer-1].Text += diffs[pointer].Text964 diffs = append(diffs[:pointer], diffs[pointer+1:]...)965 } else {966 pointer++967 }968 countInsert = 0969 countDelete = 0970 textDelete = nil971 textInsert = nil972 break973 }974 }975 if len(diffs[len(diffs)-1].Text) == 0 {976 diffs = diffs[0 : len(diffs)-1] // Remove the dummy entry at the end.977 }978 // Second pass: look for single edits surrounded on both sides by equalities which can be shifted sideways to eliminate an equality. E.g: A<ins>BA</ins>C -> <ins>AB</ins>AC979 changes := false980 pointer = 1981 // Intentionally ignore the first and last element (don't need checking).982 for pointer < (len(diffs) - 1) {983 if diffs[pointer-1].Type == DiffEqual &&984 diffs[pointer+1].Type == DiffEqual {985 // This is a single edit surrounded by equalities.986 if strings.HasSuffix(diffs[pointer].Text, diffs[pointer-1].Text) {987 // Shift the edit over the previous equality.988 diffs[pointer].Text = diffs[pointer-1].Text +989 diffs[pointer].Text[:len(diffs[pointer].Text)-len(diffs[pointer-1].Text)]990 diffs[pointer+1].Text = diffs[pointer-1].Text + diffs[pointer+1].Text991 diffs = splice(diffs, pointer-1, 1)992 changes = true993 } else if strings.HasPrefix(diffs[pointer].Text, diffs[pointer+1].Text) {994 // Shift the edit over the next equality.995 diffs[pointer-1].Text += diffs[pointer+1].Text996 diffs[pointer].Text =997 diffs[pointer].Text[len(diffs[pointer+1].Text):] + diffs[pointer+1].Text998 diffs = splice(diffs, pointer+1, 1)999 changes = true1000 }1001 }1002 pointer++1003 }1004 // If shifts were made, the diff needs reordering and another shift sweep.1005 if changes {1006 diffs = dmp.DiffCleanupMerge(diffs)1007 }1008 return diffs1009}1010// DiffXIndex returns the equivalent location in s2.1011func (dmp *DiffMatchPatch) DiffXIndex(diffs []Diff, loc int) int {1012 chars1 := 01013 chars2 := 01014 lastChars1 := 01015 lastChars2 := 01016 lastDiff := Diff{}1017 for i := 0; i < len(diffs); i++ {1018 aDiff := diffs[i]1019 if aDiff.Type != DiffInsert {1020 // Equality or deletion.1021 chars1 += len(aDiff.Text)1022 }1023 if aDiff.Type != DiffDelete {1024 // Equality or insertion.1025 chars2 += len(aDiff.Text)1026 }1027 if chars1 > loc {1028 // Overshot the location.1029 lastDiff = aDiff1030 break1031 }1032 lastChars1 = chars11033 lastChars2 = chars21034 }1035 if lastDiff.Type == DiffDelete {1036 // The location was deleted.1037 return lastChars21038 }1039 // Add the remaining character length.1040 return lastChars2 + (loc - lastChars1)1041}1042// DiffPrettyHtml converts a []Diff into a pretty HTML report.1043// It is intended as an example from which to write one's own display functions.1044func (dmp *DiffMatchPatch) DiffPrettyHtml(diffs []Diff) string {1045 var buff bytes.Buffer1046 for _, diff := range diffs {1047 text := strings.Replace(html.EscapeString(diff.Text), "\n", "¶<br>", -1)1048 switch diff.Type {1049 case DiffInsert:1050 _, _ = buff.WriteString("<ins style=\"background:#e6ffe6;\">")1051 _, _ = buff.WriteString(text)1052 _, _ = buff.WriteString("</ins>")1053 case DiffDelete:1054 _, _ = buff.WriteString("<del style=\"background:#ffe6e6;\">")1055 _, _ = buff.WriteString(text)1056 _, _ = buff.WriteString("</del>")1057 case DiffEqual:1058 _, _ = buff.WriteString("<span>")1059 _, _ = buff.WriteString(text)1060 _, _ = buff.WriteString("</span>")1061 }1062 }1063 return buff.String()1064}1065// DiffPrettyText converts a []Diff into a colored text report.1066func (dmp *DiffMatchPatch) DiffPrettyText(diffs []Diff) string {1067 var buff bytes.Buffer1068 for _, diff := range diffs {1069 text := diff.Text1070 switch diff.Type {1071 case DiffInsert:1072 _, _ = buff.WriteString("\x1b[32m")1073 _, _ = buff.WriteString(text)1074 _, _ = buff.WriteString("\x1b[0m")1075 case DiffDelete:1076 _, _ = buff.WriteString("\x1b[31m")1077 _, _ = buff.WriteString(text)1078 _, _ = buff.WriteString("\x1b[0m")1079 case DiffEqual:1080 _, _ = buff.WriteString(text)1081 }1082 }1083 return buff.String()1084}1085// DiffText1 computes and returns the source text (all equalities and deletions).1086func (dmp *DiffMatchPatch) DiffText1(diffs []Diff) string {1087 //StringBuilder text = new StringBuilder()1088 var text bytes.Buffer1089 for _, aDiff := range diffs {1090 if aDiff.Type != DiffInsert {1091 _, _ = text.WriteString(aDiff.Text)1092 }1093 }1094 return text.String()1095}1096// DiffText2 computes and returns the destination text (all equalities and insertions).1097func (dmp *DiffMatchPatch) DiffText2(diffs []Diff) string {1098 var text bytes.Buffer1099 for _, aDiff := range diffs {1100 if aDiff.Type != DiffDelete {1101 _, _ = text.WriteString(aDiff.Text)1102 }1103 }1104 return text.String()1105}1106// DiffLevenshtein computes the Levenshtein distance that is the number of inserted, deleted or substituted characters.1107func (dmp *DiffMatchPatch) DiffLevenshtein(diffs []Diff) int {1108 levenshtein := 01109 insertions := 01110 deletions := 01111 for _, aDiff := range diffs {1112 switch aDiff.Type {1113 case DiffInsert:1114 insertions += utf8.RuneCountInString(aDiff.Text)1115 case DiffDelete:1116 deletions += utf8.RuneCountInString(aDiff.Text)1117 case DiffEqual:1118 // A deletion and an insertion is one substitution.1119 levenshtein += max(insertions, deletions)1120 insertions = 01121 deletions = 01122 }1123 }1124 levenshtein += max(insertions, deletions)1125 return levenshtein1126}1127// DiffToDelta crushes the diff into an encoded string which describes the operations required to transform text1 into text2.1128// E.g. =3\t-2\t+ing -> Keep 3 chars, delete 2 chars, insert 'ing'. Operations are tab-separated. Inserted text is escaped using %xx notation.1129func (dmp *DiffMatchPatch) DiffToDelta(diffs []Diff) string {1130 var text bytes.Buffer1131 for _, aDiff := range diffs {1132 switch aDiff.Type {1133 case DiffInsert:1134 _, _ = text.WriteString("+")1135 _, _ = text.WriteString(strings.Replace(url.QueryEscape(aDiff.Text), "+", " ", -1))1136 _, _ = text.WriteString("\t")1137 break1138 case DiffDelete:1139 _, _ = text.WriteString("-")1140 _, _ = text.WriteString(strconv.Itoa(utf8.RuneCountInString(aDiff.Text)))1141 _, _ = text.WriteString("\t")1142 break1143 case DiffEqual:1144 _, _ = text.WriteString("=")1145 _, _ = text.WriteString(strconv.Itoa(utf8.RuneCountInString(aDiff.Text)))1146 _, _ = text.WriteString("\t")1147 break1148 }1149 }1150 delta := text.String()1151 if len(delta) != 0 {1152 // Strip off trailing tab character.1153 delta = delta[0 : utf8.RuneCountInString(delta)-1]1154 delta = unescaper.Replace(delta)1155 }1156 return delta1157}1158// DiffFromDelta given the original text1, and an encoded string which describes the operations required to transform text1 into text2, comAdde the full diff.1159func (dmp *DiffMatchPatch) DiffFromDelta(text1 string, delta string) (diffs []Diff, err error) {1160 i := 01161 runes := []rune(text1)1162 for _, token := range strings.Split(delta, "\t") {1163 if len(token) == 0 {1164 // Blank tokens are ok (from a trailing \t).1165 continue1166 }1167 // Each token begins with a one character parameter which specifies the operation of this token (delete, insert, equality).1168 param := token[1:]1169 switch op := token[0]; op {1170 case '+':1171 // Decode would Diff all "+" to " "1172 param = strings.Replace(param, "+", "%2b", -1)1173 param, err = url.QueryUnescape(param)1174 if err != nil {1175 return nil, err1176 }1177 if !utf8.ValidString(param) {1178 return nil, fmt.Errorf("invalid UTF-8 token: %q", param)1179 }1180 diffs = append(diffs, Diff{DiffInsert, param})1181 case '=', '-':1182 n, err := strconv.ParseInt(param, 10, 0)1183 if err != nil {1184 return nil, err1185 } else if n < 0 {1186 return nil, errors.New("Negative number in DiffFromDelta: " + param)1187 }1188 i += int(n)1189 // Break out if we are out of bounds, go1.6 can't handle this very well1190 if i > len(runes) {1191 break1192 }1193 // Remember that string slicing is by byte - we want by rune here.1194 text := string(runes[i-int(n) : i])1195 if op == '=' {1196 diffs = append(diffs, Diff{DiffEqual, text})1197 } else {1198 diffs = append(diffs, Diff{DiffDelete, text})1199 }1200 default:1201 // Anything else is an error.1202 return nil, errors.New("Invalid diff operation in DiffFromDelta: " + string(token[0]))1203 }1204 }1205 if i != len(runes) {1206 return nil, fmt.Errorf("Delta length (%v) is different from source text length (%v)", i, len(text1))1207 }1208 return diffs, nil1209}...
diff_test.go
Source:diff_test.go
1// Copyright (c) 2012-2016 The go-diff authors. All rights reserved.2// https://github.com/sergi/go-diff3// See the included LICENSE file for license details.4//5// go-diff is a Go implementation of Google's Diff, Match, and Patch library6// Original library is Copyright (c) 2006 Google Inc.7// http://code.google.com/p/google-diff-match-patch/8package diffmatchpatch9import (10 "bytes"11 "fmt"12 "strconv"13 "strings"14 "testing"15 "time"16 "unicode/utf8"17 "github.com/stretchr/testify/assert"18)19func pretty(diffs []Diff) string {20 var w bytes.Buffer21 for i, diff := range diffs {22 _, _ = w.WriteString(fmt.Sprintf("%v. ", i))23 switch diff.Type {24 case DiffInsert:25 _, _ = w.WriteString("DiffIns")26 case DiffDelete:27 _, _ = w.WriteString("DiffDel")28 case DiffEqual:29 _, _ = w.WriteString("DiffEql")30 default:31 _, _ = w.WriteString("Unknown")32 }33 _, _ = w.WriteString(fmt.Sprintf(": %v\n", diff.Text))34 }35 return w.String()36}37func diffRebuildTexts(diffs []Diff) []string {38 texts := []string{"", ""}39 for _, d := range diffs {40 if d.Type != DiffInsert {41 texts[0] += d.Text42 }43 if d.Type != DiffDelete {44 texts[1] += d.Text45 }46 }47 return texts48}49func TestDiffCommonPrefix(t *testing.T) {50 type TestCase struct {51 Name string52 Text1 string53 Text2 string54 Expected int55 }56 dmp := New()57 for i, tc := range []TestCase{58 {"Null", "abc", "xyz", 0},59 {"Non-null", "1234abcdef", "1234xyz", 4},60 {"Whole", "1234", "1234xyz", 4},61 } {62 actual := dmp.DiffCommonPrefix(tc.Text1, tc.Text2)63 assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name))64 }65}66func BenchmarkDiffCommonPrefix(b *testing.B) {67 s := "ABCDEFGHIJKLMNOPQRSTUVWXYZÃ
ÃÃ"68 dmp := New()69 for i := 0; i < b.N; i++ {70 dmp.DiffCommonPrefix(s, s)71 }72}73func TestCommonPrefixLength(t *testing.T) {74 type TestCase struct {75 Text1 string76 Text2 string77 Expected int78 }79 for i, tc := range []TestCase{80 {"abc", "xyz", 0},81 {"1234abcdef", "1234xyz", 4},82 {"1234", "1234xyz", 4},83 } {84 actual := commonPrefixLength([]rune(tc.Text1), []rune(tc.Text2))85 assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %#v", i, tc))86 }87}88func TestDiffCommonSuffix(t *testing.T) {89 type TestCase struct {90 Name string91 Text1 string92 Text2 string93 Expected int94 }95 dmp := New()96 for i, tc := range []TestCase{97 {"Null", "abc", "xyz", 0},98 {"Non-null", "abcdef1234", "xyz1234", 4},99 {"Whole", "1234", "xyz1234", 4},100 } {101 actual := dmp.DiffCommonSuffix(tc.Text1, tc.Text2)102 assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name))103 }104}105var SinkInt int // exported sink var to avoid compiler optimizations in benchmarks106func BenchmarkDiffCommonSuffix(b *testing.B) {107 s := "ABCDEFGHIJKLMNOPQRSTUVWXYZÃ
ÃÃ"108 dmp := New()109 b.ResetTimer()110 for i := 0; i < b.N; i++ {111 SinkInt = dmp.DiffCommonSuffix(s, s)112 }113}114func BenchmarkCommonLength(b *testing.B) {115 data := []struct {116 name string117 x, y []rune118 }{119 {name: "empty", x: nil, y: []rune{}},120 {name: "short", x: []rune("AABCC"), y: []rune("AA-CC")},121 {name: "long",122 x: []rune(strings.Repeat("A", 1000) + "B" + strings.Repeat("C", 1000)),123 y: []rune(strings.Repeat("A", 1000) + "-" + strings.Repeat("C", 1000)),124 },125 }126 b.Run("prefix", func(b *testing.B) {127 for _, d := range data {128 b.Run(d.name, func(b *testing.B) {129 for i := 0; i < b.N; i++ {130 SinkInt = commonPrefixLength(d.x, d.y)131 }132 })133 }134 })135 b.Run("suffix", func(b *testing.B) {136 for _, d := range data {137 b.Run(d.name, func(b *testing.B) {138 for i := 0; i < b.N; i++ {139 SinkInt = commonSuffixLength(d.x, d.y)140 }141 })142 }143 })144}145func TestCommonSuffixLength(t *testing.T) {146 type TestCase struct {147 Text1 string148 Text2 string149 Expected int150 }151 for i, tc := range []TestCase{152 {"abc", "xyz", 0},153 {"abcdef1234", "xyz1234", 4},154 {"1234", "xyz1234", 4},155 {"123", "a3", 1},156 } {157 actual := commonSuffixLength([]rune(tc.Text1), []rune(tc.Text2))158 assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %#v", i, tc))159 }160}161func TestDiffCommonOverlap(t *testing.T) {162 type TestCase struct {163 Name string164 Text1 string165 Text2 string166 Expected int167 }168 dmp := New()169 for i, tc := range []TestCase{170 {"Null", "", "abcd", 0},171 {"Whole", "abc", "abcd", 3},172 {"Null", "123456", "abcd", 0},173 {"Null", "123456xxx", "xxxabcd", 3},174 // Some overly clever languages (C#) may treat ligatures as equal to their component letters, e.g. U+FB01 == 'fi'175 {"Unicode", "fi", "\ufb01i", 0},176 } {177 actual := dmp.DiffCommonOverlap(tc.Text1, tc.Text2)178 assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name))179 }180}181func TestDiffHalfMatch(t *testing.T) {182 type TestCase struct {183 Text1 string184 Text2 string185 Expected []string186 }187 dmp := New()188 dmp.DiffTimeout = 1189 for i, tc := range []TestCase{190 // No match191 {"1234567890", "abcdef", nil},192 {"12345", "23", nil},193 // Single Match194 {"1234567890", "a345678z", []string{"12", "90", "a", "z", "345678"}},195 {"a345678z", "1234567890", []string{"a", "z", "12", "90", "345678"}},196 {"abc56789z", "1234567890", []string{"abc", "z", "1234", "0", "56789"}},197 {"a23456xyz", "1234567890", []string{"a", "xyz", "1", "7890", "23456"}},198 // Multiple Matches199 {"121231234123451234123121", "a1234123451234z", []string{"12123", "123121", "a", "z", "1234123451234"}},200 {"x-=-=-=-=-=-=-=-=-=-=-=-=", "xx-=-=-=-=-=-=-=", []string{"", "-=-=-=-=-=", "x", "", "x-=-=-=-=-=-=-="}},201 {"-=-=-=-=-=-=-=-=-=-=-=-=y", "-=-=-=-=-=-=-=yy", []string{"-=-=-=-=-=", "", "", "y", "-=-=-=-=-=-=-=y"}},202 // Non-optimal halfmatch, ptimal diff would be -q+x=H-i+e=lloHe+Hu=llo-Hew+y not -qHillo+x=HelloHe-w+Hulloy203 {"qHilloHelloHew", "xHelloHeHulloy", []string{"qHillo", "w", "x", "Hulloy", "HelloHe"}},204 } {205 actual := dmp.DiffHalfMatch(tc.Text1, tc.Text2)206 assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %#v", i, tc))207 }208 dmp.DiffTimeout = 0209 for i, tc := range []TestCase{210 // Optimal no halfmatch211 {"qHilloHelloHew", "xHelloHeHulloy", nil},212 } {213 actual := dmp.DiffHalfMatch(tc.Text1, tc.Text2)214 assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %#v", i, tc))215 }216}217func BenchmarkDiffHalfMatch(b *testing.B) {218 s1, s2 := speedtestTexts()219 dmp := New()220 b.ResetTimer()221 for i := 0; i < b.N; i++ {222 dmp.DiffHalfMatch(s1, s2)223 }224}225func TestDiffBisectSplit(t *testing.T) {226 type TestCase struct {227 Text1 string228 Text2 string229 }230 dmp := New()231 for _, tc := range []TestCase{232 {"STUV\x05WX\x05YZ\x05[", "WĺĻļ\x05YZ\x05ĽľĿÅZ"},233 } {234 diffs := dmp.diffBisectSplit([]rune(tc.Text1),235 []rune(tc.Text2), 7, 6, time.Now().Add(time.Hour))236 for _, d := range diffs {237 assert.True(t, utf8.ValidString(d.Text))238 }239 // TODO define the expected outcome240 }241}242func TestDiffLinesToChars(t *testing.T) {243 type TestCase struct {244 Text1 string245 Text2 string246 ExpectedChars1 string247 ExpectedChars2 string248 ExpectedLines []string249 }250 dmp := New()251 for i, tc := range []TestCase{252 {"", "alpha\r\nbeta\r\n\r\n\r\n", "", "\u0001\u0002\u0003\u0003", []string{"", "alpha\r\n", "beta\r\n", "\r\n"}},253 {"a", "b", "\u0001", "\u0002", []string{"", "a", "b"}},254 // Omit final newline.255 {"alpha\nbeta\nalpha", "", "\u0001\u0002\u0003", "", []string{"", "alpha\n", "beta\n", "alpha"}},256 } {257 actualChars1, actualChars2, actualLines := dmp.DiffLinesToChars(tc.Text1, tc.Text2)258 assert.Equal(t, tc.ExpectedChars1, actualChars1, fmt.Sprintf("Test case #%d, %#v", i, tc))259 assert.Equal(t, tc.ExpectedChars2, actualChars2, fmt.Sprintf("Test case #%d, %#v", i, tc))260 assert.Equal(t, tc.ExpectedLines, actualLines, fmt.Sprintf("Test case #%d, %#v", i, tc))261 }262 // More than 256 to reveal any 8-bit limitations.263 n := 300264 lineList := []string{265 "", // Account for the initial empty element of the lines array.266 }267 var charList []rune268 for x := 1; x < n+1; x++ {269 lineList = append(lineList, strconv.Itoa(x)+"\n")270 charList = append(charList, rune(x))271 }272 lines := strings.Join(lineList, "")273 chars := string(charList)274 assert.Equal(t, n, utf8.RuneCountInString(chars))275 actualChars1, actualChars2, actualLines := dmp.DiffLinesToChars(lines, "")276 assert.Equal(t, chars, actualChars1)277 assert.Equal(t, "", actualChars2)278 assert.Equal(t, lineList, actualLines)279}280func TestDiffCharsToLines(t *testing.T) {281 type TestCase struct {282 Diffs []Diff283 Lines []string284 Expected []Diff285 }286 dmp := New()287 for i, tc := range []TestCase{288 {289 Diffs: []Diff{290 {DiffEqual, "\u0001\u0002\u0001"},291 {DiffInsert, "\u0002\u0001\u0002"},292 },293 Lines: []string{"", "alpha\n", "beta\n"},294 Expected: []Diff{295 {DiffEqual, "alpha\nbeta\nalpha\n"},296 {DiffInsert, "beta\nalpha\nbeta\n"},297 },298 },299 } {300 actual := dmp.DiffCharsToLines(tc.Diffs, tc.Lines)301 assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %#v", i, tc))302 }303 // More than 256 to reveal any 8-bit limitations.304 n := 300305 lineList := []string{306 "", // Account for the initial empty element of the lines array.307 }308 charList := []rune{}309 for x := 1; x <= n; x++ {310 lineList = append(lineList, strconv.Itoa(x)+"\n")311 charList = append(charList, rune(x))312 }313 assert.Equal(t, n, len(charList))314 actual := dmp.DiffCharsToLines([]Diff{Diff{DiffDelete, string(charList)}}, lineList)315 assert.Equal(t, []Diff{Diff{DiffDelete, strings.Join(lineList, "")}}, actual)316}317func TestDiffCleanupMerge(t *testing.T) {318 type TestCase struct {319 Name string320 Diffs []Diff321 Expected []Diff322 }323 dmp := New()324 for i, tc := range []TestCase{325 {326 "Null case",327 []Diff{},328 []Diff{},329 },330 {331 "No Diff case",332 []Diff{Diff{DiffEqual, "a"}, Diff{DiffDelete, "b"}, Diff{DiffInsert, "c"}},333 []Diff{Diff{DiffEqual, "a"}, Diff{DiffDelete, "b"}, Diff{DiffInsert, "c"}},334 },335 {336 "Merge equalities",337 []Diff{Diff{DiffEqual, "a"}, Diff{DiffEqual, "b"}, Diff{DiffEqual, "c"}},338 []Diff{Diff{DiffEqual, "abc"}},339 },340 {341 "Merge deletions",342 []Diff{Diff{DiffDelete, "a"}, Diff{DiffDelete, "b"}, Diff{DiffDelete, "c"}},343 []Diff{Diff{DiffDelete, "abc"}},344 },345 {346 "Merge insertions",347 []Diff{Diff{DiffInsert, "a"}, Diff{DiffInsert, "b"}, Diff{DiffInsert, "c"}},348 []Diff{Diff{DiffInsert, "abc"}},349 },350 {351 "Merge interweave",352 []Diff{Diff{DiffDelete, "a"}, Diff{DiffInsert, "b"}, Diff{DiffDelete, "c"}, Diff{DiffInsert, "d"}, Diff{DiffEqual, "e"}, Diff{DiffEqual, "f"}},353 []Diff{Diff{DiffDelete, "ac"}, Diff{DiffInsert, "bd"}, Diff{DiffEqual, "ef"}},354 },355 {356 "Prefix and suffix detection",357 []Diff{Diff{DiffDelete, "a"}, Diff{DiffInsert, "abc"}, Diff{DiffDelete, "dc"}},358 []Diff{Diff{DiffEqual, "a"}, Diff{DiffDelete, "d"}, Diff{DiffInsert, "b"}, Diff{DiffEqual, "c"}},359 },360 {361 "Prefix and suffix detection with equalities",362 []Diff{Diff{DiffEqual, "x"}, Diff{DiffDelete, "a"}, Diff{DiffInsert, "abc"}, Diff{DiffDelete, "dc"}, Diff{DiffEqual, "y"}},363 []Diff{Diff{DiffEqual, "xa"}, Diff{DiffDelete, "d"}, Diff{DiffInsert, "b"}, Diff{DiffEqual, "cy"}},364 },365 {366 "Same test as above but with unicode (\u0101 will appear in diffs with at least 257 unique lines)",367 []Diff{Diff{DiffEqual, "x"}, Diff{DiffDelete, "\u0101"}, Diff{DiffInsert, "\u0101bc"}, Diff{DiffDelete, "dc"}, Diff{DiffEqual, "y"}},368 []Diff{Diff{DiffEqual, "x\u0101"}, Diff{DiffDelete, "d"}, Diff{DiffInsert, "b"}, Diff{DiffEqual, "cy"}},369 },370 {371 "Slide edit left",372 []Diff{Diff{DiffEqual, "a"}, Diff{DiffInsert, "ba"}, Diff{DiffEqual, "c"}},373 []Diff{Diff{DiffInsert, "ab"}, Diff{DiffEqual, "ac"}},374 },375 {376 "Slide edit right",377 []Diff{Diff{DiffEqual, "c"}, Diff{DiffInsert, "ab"}, Diff{DiffEqual, "a"}},378 []Diff{Diff{DiffEqual, "ca"}, Diff{DiffInsert, "ba"}},379 },380 {381 "Slide edit left recursive",382 []Diff{Diff{DiffEqual, "a"}, Diff{DiffDelete, "b"}, Diff{DiffEqual, "c"}, Diff{DiffDelete, "ac"}, Diff{DiffEqual, "x"}},383 []Diff{Diff{DiffDelete, "abc"}, Diff{DiffEqual, "acx"}},384 },385 {386 "Slide edit right recursive",387 []Diff{Diff{DiffEqual, "x"}, Diff{DiffDelete, "ca"}, Diff{DiffEqual, "c"}, Diff{DiffDelete, "b"}, Diff{DiffEqual, "a"}},388 []Diff{Diff{DiffEqual, "xca"}, Diff{DiffDelete, "cba"}},389 },390 } {391 actual := dmp.DiffCleanupMerge(tc.Diffs)392 assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name))393 }394}395func TestDiffCleanupSemanticLossless(t *testing.T) {396 type TestCase struct {397 Name string398 Diffs []Diff399 Expected []Diff400 }401 dmp := New()402 for i, tc := range []TestCase{403 {404 "Null case",405 []Diff{},406 []Diff{},407 },408 {409 "Blank lines",410 []Diff{411 Diff{DiffEqual, "AAA\r\n\r\nBBB"},412 Diff{DiffInsert, "\r\nDDD\r\n\r\nBBB"},413 Diff{DiffEqual, "\r\nEEE"},414 },415 []Diff{416 Diff{DiffEqual, "AAA\r\n\r\n"},417 Diff{DiffInsert, "BBB\r\nDDD\r\n\r\n"},418 Diff{DiffEqual, "BBB\r\nEEE"},419 },420 },421 {422 "Line boundaries",423 []Diff{424 Diff{DiffEqual, "AAA\r\nBBB"},425 Diff{DiffInsert, " DDD\r\nBBB"},426 Diff{DiffEqual, " EEE"},427 },428 []Diff{429 Diff{DiffEqual, "AAA\r\n"},430 Diff{DiffInsert, "BBB DDD\r\n"},431 Diff{DiffEqual, "BBB EEE"},432 },433 },434 {435 "Word boundaries",436 []Diff{437 Diff{DiffEqual, "The c"},438 Diff{DiffInsert, "ow and the c"},439 Diff{DiffEqual, "at."},440 },441 []Diff{442 Diff{DiffEqual, "The "},443 Diff{DiffInsert, "cow and the "},444 Diff{DiffEqual, "cat."},445 },446 },447 {448 "Alphanumeric boundaries",449 []Diff{450 Diff{DiffEqual, "The-c"},451 Diff{DiffInsert, "ow-and-the-c"},452 Diff{DiffEqual, "at."},453 },454 []Diff{455 Diff{DiffEqual, "The-"},456 Diff{DiffInsert, "cow-and-the-"},457 Diff{DiffEqual, "cat."},458 },459 },460 {461 "Hitting the start",462 []Diff{463 Diff{DiffEqual, "a"},464 Diff{DiffDelete, "a"},465 Diff{DiffEqual, "ax"},466 },467 []Diff{468 Diff{DiffDelete, "a"},469 Diff{DiffEqual, "aax"},470 },471 },472 {473 "Hitting the end",474 []Diff{475 Diff{DiffEqual, "xa"},476 Diff{DiffDelete, "a"},477 Diff{DiffEqual, "a"},478 },479 []Diff{480 Diff{DiffEqual, "xaa"},481 Diff{DiffDelete, "a"},482 },483 },484 {485 "Sentence boundaries",486 []Diff{487 Diff{DiffEqual, "The xxx. The "},488 Diff{DiffInsert, "zzz. The "},489 Diff{DiffEqual, "yyy."},490 },491 []Diff{492 Diff{DiffEqual, "The xxx."},493 Diff{DiffInsert, " The zzz."},494 Diff{DiffEqual, " The yyy."},495 },496 },497 {498 "UTF-8 strings",499 []Diff{500 Diff{DiffEqual, "The â. The "},501 Diff{DiffInsert, "â. The "},502 Diff{DiffEqual, "â."},503 },504 []Diff{505 Diff{DiffEqual, "The â."},506 Diff{DiffInsert, " The â."},507 Diff{DiffEqual, " The â."},508 },509 },510 {511 "Rune boundaries",512 []Diff{513 Diff{DiffEqual, "ââ"},514 Diff{DiffInsert, "ââ"},515 Diff{DiffEqual, "ââ"},516 },517 []Diff{518 Diff{DiffEqual, "ââ"},519 Diff{DiffInsert, "ââ"},520 Diff{DiffEqual, "ââ"},521 },522 },523 } {524 actual := dmp.DiffCleanupSemanticLossless(tc.Diffs)525 assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name))526 }527}528func TestDiffCleanupSemantic(t *testing.T) {529 type TestCase struct {530 Name string531 Diffs []Diff532 Expected []Diff533 }534 dmp := New()535 for i, tc := range []TestCase{536 {537 "Null case",538 []Diff{},539 []Diff{},540 },541 {542 "No elimination #1",543 []Diff{544 {DiffDelete, "ab"},545 {DiffInsert, "cd"},546 {DiffEqual, "12"},547 {DiffDelete, "e"},548 },549 []Diff{550 {DiffDelete, "ab"},551 {DiffInsert, "cd"},552 {DiffEqual, "12"},553 {DiffDelete, "e"},554 },555 },556 {557 "No elimination #2",558 []Diff{559 {DiffDelete, "abc"},560 {DiffInsert, "ABC"},561 {DiffEqual, "1234"},562 {DiffDelete, "wxyz"},563 },564 []Diff{565 {DiffDelete, "abc"},566 {DiffInsert, "ABC"},567 {DiffEqual, "1234"},568 {DiffDelete, "wxyz"},569 },570 },571 {572 "No elimination #3",573 []Diff{574 {DiffEqual, "2016-09-01T03:07:1"},575 {DiffInsert, "5.15"},576 {DiffEqual, "4"},577 {DiffDelete, "."},578 {DiffEqual, "80"},579 {DiffInsert, "0"},580 {DiffEqual, "78"},581 {DiffDelete, "3074"},582 {DiffEqual, "1Z"},583 },584 []Diff{585 {DiffEqual, "2016-09-01T03:07:1"},586 {DiffInsert, "5.15"},587 {DiffEqual, "4"},588 {DiffDelete, "."},589 {DiffEqual, "80"},590 {DiffInsert, "0"},591 {DiffEqual, "78"},592 {DiffDelete, "3074"},593 {DiffEqual, "1Z"},594 },595 },596 {597 "Simple elimination",598 []Diff{599 {DiffDelete, "a"},600 {DiffEqual, "b"},601 {DiffDelete, "c"},602 },603 []Diff{604 {DiffDelete, "abc"},605 {DiffInsert, "b"},606 },607 },608 {609 "Backpass elimination",610 []Diff{611 {DiffDelete, "ab"},612 {DiffEqual, "cd"},613 {DiffDelete, "e"},614 {DiffEqual, "f"},615 {DiffInsert, "g"},616 },617 []Diff{618 {DiffDelete, "abcdef"},619 {DiffInsert, "cdfg"},620 },621 },622 {623 "Multiple eliminations",624 []Diff{625 {DiffInsert, "1"},626 {DiffEqual, "A"},627 {DiffDelete, "B"},628 {DiffInsert, "2"},629 {DiffEqual, "_"},630 {DiffInsert, "1"},631 {DiffEqual, "A"},632 {DiffDelete, "B"},633 {DiffInsert, "2"},634 },635 []Diff{636 {DiffDelete, "AB_AB"},637 {DiffInsert, "1A2_1A2"},638 },639 },640 {641 "Word boundaries",642 []Diff{643 {DiffEqual, "The c"},644 {DiffDelete, "ow and the c"},645 {DiffEqual, "at."},646 },647 []Diff{648 {DiffEqual, "The "},649 {DiffDelete, "cow and the "},650 {DiffEqual, "cat."},651 },652 },653 {654 "No overlap elimination",655 []Diff{656 {DiffDelete, "abcxx"},657 {DiffInsert, "xxdef"},658 },659 []Diff{660 {DiffDelete, "abcxx"},661 {DiffInsert, "xxdef"},662 },663 },664 {665 "Overlap elimination",666 []Diff{667 {DiffDelete, "abcxxx"},668 {DiffInsert, "xxxdef"},669 },670 []Diff{671 {DiffDelete, "abc"},672 {DiffEqual, "xxx"},673 {DiffInsert, "def"},674 },675 },676 {677 "Reverse overlap elimination",678 []Diff{679 {DiffDelete, "xxxabc"},680 {DiffInsert, "defxxx"},681 },682 []Diff{683 {DiffInsert, "def"},684 {DiffEqual, "xxx"},685 {DiffDelete, "abc"},686 },687 },688 {689 "Two overlap eliminations",690 []Diff{691 {DiffDelete, "abcd1212"},692 {DiffInsert, "1212efghi"},693 {DiffEqual, "----"},694 {DiffDelete, "A3"},695 {DiffInsert, "3BC"},696 },697 []Diff{698 {DiffDelete, "abcd"},699 {DiffEqual, "1212"},700 {DiffInsert, "efghi"},701 {DiffEqual, "----"},702 {DiffDelete, "A"},703 {DiffEqual, "3"},704 {DiffInsert, "BC"},705 },706 },707 {708 "Test case for adapting DiffCleanupSemantic to be equal to the Python version #19",709 []Diff{710 {DiffEqual, "James McCarthy "},711 {DiffDelete, "close to "},712 {DiffEqual, "sign"},713 {DiffDelete, "ing"},714 {DiffInsert, "s"},715 {DiffEqual, " new "},716 {DiffDelete, "E"},717 {DiffInsert, "fi"},718 {DiffEqual, "ve"},719 {DiffInsert, "-yea"},720 {DiffEqual, "r"},721 {DiffDelete, "ton"},722 {DiffEqual, " deal"},723 {DiffInsert, " at Everton"},724 },725 []Diff{726 {DiffEqual, "James McCarthy "},727 {DiffDelete, "close to "},728 {DiffEqual, "sign"},729 {DiffDelete, "ing"},730 {DiffInsert, "s"},731 {DiffEqual, " new "},732 {DiffInsert, "five-year deal at "},733 {DiffEqual, "Everton"},734 {DiffDelete, " deal"},735 },736 },737 } {738 actual := dmp.DiffCleanupSemantic(tc.Diffs)739 assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name))740 }741}742func BenchmarkDiffCleanupSemantic(b *testing.B) {743 s1, s2 := speedtestTexts()744 dmp := New()745 diffs := dmp.DiffMain(s1, s2, false)746 b.ResetTimer()747 for i := 0; i < b.N; i++ {748 dmp.DiffCleanupSemantic(diffs)749 }750}751func TestDiffCleanupEfficiency(t *testing.T) {752 type TestCase struct {753 Name string754 Diffs []Diff755 Expected []Diff756 }757 dmp := New()758 dmp.DiffEditCost = 4759 for i, tc := range []TestCase{760 {761 "Null case",762 []Diff{},763 []Diff{},764 },765 {766 "No elimination",767 []Diff{768 Diff{DiffDelete, "ab"},769 Diff{DiffInsert, "12"},770 Diff{DiffEqual, "wxyz"},771 Diff{DiffDelete, "cd"},772 Diff{DiffInsert, "34"},773 },774 []Diff{775 Diff{DiffDelete, "ab"},776 Diff{DiffInsert, "12"},777 Diff{DiffEqual, "wxyz"},778 Diff{DiffDelete, "cd"},779 Diff{DiffInsert, "34"},780 },781 },782 {783 "Four-edit elimination",784 []Diff{785 Diff{DiffDelete, "ab"},786 Diff{DiffInsert, "12"},787 Diff{DiffEqual, "xyz"},788 Diff{DiffDelete, "cd"},789 Diff{DiffInsert, "34"},790 },791 []Diff{792 Diff{DiffDelete, "abxyzcd"},793 Diff{DiffInsert, "12xyz34"},794 },795 },796 {797 "Three-edit elimination",798 []Diff{799 Diff{DiffInsert, "12"},800 Diff{DiffEqual, "x"},801 Diff{DiffDelete, "cd"},802 Diff{DiffInsert, "34"},803 },804 []Diff{805 Diff{DiffDelete, "xcd"},806 Diff{DiffInsert, "12x34"},807 },808 },809 {810 "Backpass elimination",811 []Diff{812 Diff{DiffDelete, "ab"},813 Diff{DiffInsert, "12"},814 Diff{DiffEqual, "xy"},815 Diff{DiffInsert, "34"},816 Diff{DiffEqual, "z"},817 Diff{DiffDelete, "cd"},818 Diff{DiffInsert, "56"},819 },820 []Diff{821 Diff{DiffDelete, "abxyzcd"},822 Diff{DiffInsert, "12xy34z56"},823 },824 },825 } {826 actual := dmp.DiffCleanupEfficiency(tc.Diffs)827 assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name))828 }829 dmp.DiffEditCost = 5830 for i, tc := range []TestCase{831 {832 "High cost elimination",833 []Diff{834 Diff{DiffDelete, "ab"},835 Diff{DiffInsert, "12"},836 Diff{DiffEqual, "wxyz"},837 Diff{DiffDelete, "cd"},838 Diff{DiffInsert, "34"},839 },840 []Diff{841 Diff{DiffDelete, "abwxyzcd"},842 Diff{DiffInsert, "12wxyz34"},843 },844 },845 } {846 actual := dmp.DiffCleanupEfficiency(tc.Diffs)847 assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name))848 }849}850func TestDiffPrettyHtml(t *testing.T) {851 type TestCase struct {852 Diffs []Diff853 Expected string854 }855 dmp := New()856 for i, tc := range []TestCase{857 {858 Diffs: []Diff{859 {DiffEqual, "a\n"},860 {DiffDelete, "<B>b</B>"},861 {DiffInsert, "c&d"},862 },863 Expected: "<span>a¶<br></span><del style=\"background:#ffe6e6;\"><B>b</B></del><ins style=\"background:#e6ffe6;\">c&d</ins>",864 },865 } {866 actual := dmp.DiffPrettyHtml(tc.Diffs)867 assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %#v", i, tc))868 }869}870func TestDiffPrettyText(t *testing.T) {871 type TestCase struct {872 Diffs []Diff873 Expected string874 }875 dmp := New()876 for i, tc := range []TestCase{877 {878 Diffs: []Diff{879 {DiffEqual, "a\n"},880 {DiffDelete, "<B>b</B>"},881 {DiffInsert, "c&d"},882 },883 Expected: "a\n\x1b[31m<B>b</B>\x1b[0m\x1b[32mc&d\x1b[0m",884 },885 } {886 actual := dmp.DiffPrettyText(tc.Diffs)887 assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %#v", i, tc))888 }889}890func TestDiffText(t *testing.T) {891 type TestCase struct {892 Diffs []Diff893 ExpectedText1 string894 ExpectedText2 string895 }896 dmp := New()897 for i, tc := range []TestCase{898 {899 Diffs: []Diff{900 {DiffEqual, "jump"},901 {DiffDelete, "s"},902 {DiffInsert, "ed"},903 {DiffEqual, " over "},904 {DiffDelete, "the"},905 {DiffInsert, "a"},906 {DiffEqual, " lazy"},907 },908 ExpectedText1: "jumps over the lazy",909 ExpectedText2: "jumped over a lazy",910 },911 } {912 actualText1 := dmp.DiffText1(tc.Diffs)913 assert.Equal(t, tc.ExpectedText1, actualText1, fmt.Sprintf("Test case #%d, %#v", i, tc))914 actualText2 := dmp.DiffText2(tc.Diffs)915 assert.Equal(t, tc.ExpectedText2, actualText2, fmt.Sprintf("Test case #%d, %#v", i, tc))916 }917}918func TestDiffDelta(t *testing.T) {919 type TestCase struct {920 Name string921 Text string922 Delta string923 ErrorMessagePrefix string924 }925 dmp := New()926 for i, tc := range []TestCase{927 {"Delta shorter than text", "jumps over the lazyx", "=4\t-1\t+ed\t=6\t-3\t+a\t=5\t+old dog", "Delta length (19) is different from source text length (20)"},928 {"Delta longer than text", "umps over the lazy", "=4\t-1\t+ed\t=6\t-3\t+a\t=5\t+old dog", "Delta length (19) is different from source text length (18)"},929 {"Invalid URL escaping", "", "+%c3%xy", "invalid URL escape \"%xy\""},930 {"Invalid UTF-8 sequence", "", "+%c3xy", "invalid UTF-8 token: \"\\xc3xy\""},931 {"Invalid diff operation", "", "a", "Invalid diff operation in DiffFromDelta: a"},932 {"Invalid diff syntax", "", "-", "strconv.ParseInt: parsing \"\": invalid syntax"},933 {"Negative number in delta", "", "--1", "Negative number in DiffFromDelta: -1"},934 {"Empty case", "", "", ""},935 } {936 diffs, err := dmp.DiffFromDelta(tc.Text, tc.Delta)937 msg := fmt.Sprintf("Test case #%d, %s", i, tc.Name)938 if tc.ErrorMessagePrefix == "" {939 assert.Nil(t, err, msg)940 assert.Nil(t, diffs, msg)941 } else {942 e := err.Error()943 if strings.HasPrefix(e, tc.ErrorMessagePrefix) {944 e = tc.ErrorMessagePrefix945 }946 assert.Nil(t, diffs, msg)947 assert.Equal(t, tc.ErrorMessagePrefix, e, msg)948 }949 }950 // Convert a diff into delta string.951 diffs := []Diff{952 Diff{DiffEqual, "jump"},953 Diff{DiffDelete, "s"},954 Diff{DiffInsert, "ed"},955 Diff{DiffEqual, " over "},956 Diff{DiffDelete, "the"},957 Diff{DiffInsert, "a"},958 Diff{DiffEqual, " lazy"},959 Diff{DiffInsert, "old dog"},960 }961 text1 := dmp.DiffText1(diffs)962 assert.Equal(t, "jumps over the lazy", text1)963 delta := dmp.DiffToDelta(diffs)964 assert.Equal(t, "=4\t-1\t+ed\t=6\t-3\t+a\t=5\t+old dog", delta)965 // Convert delta string into a diff.966 deltaDiffs, err := dmp.DiffFromDelta(text1, delta)967 assert.Equal(t, diffs, deltaDiffs)968 // Test deltas with special characters.969 diffs = []Diff{970 Diff{DiffEqual, "\u0680 \x00 \t %"},971 Diff{DiffDelete, "\u0681 \x01 \n ^"},972 Diff{DiffInsert, "\u0682 \x02 \\ |"},973 }974 text1 = dmp.DiffText1(diffs)975 assert.Equal(t, "\u0680 \x00 \t %\u0681 \x01 \n ^", text1)976 // Lowercase, due to UrlEncode uses lower.977 delta = dmp.DiffToDelta(diffs)978 assert.Equal(t, "=7\t-7\t+%DA%82 %02 %5C %7C", delta)979 deltaDiffs, err = dmp.DiffFromDelta(text1, delta)980 assert.Equal(t, diffs, deltaDiffs)981 assert.Nil(t, err)982 // Verify pool of unchanged characters.983 diffs = []Diff{984 Diff{DiffInsert, "A-Z a-z 0-9 - _ . ! ~ * ' ( ) ; / ? : @ & = + $ , # "},985 }986 delta = dmp.DiffToDelta(diffs)987 assert.Equal(t, "+A-Z a-z 0-9 - _ . ! ~ * ' ( ) ; / ? : @ & = + $ , # ", delta, "Unchanged characters.")988 // Convert delta string into a diff.989 deltaDiffs, err = dmp.DiffFromDelta("", delta)990 assert.Equal(t, diffs, deltaDiffs)991 assert.Nil(t, err)992}993func TestDiffXIndex(t *testing.T) {994 type TestCase struct {995 Name string996 Diffs []Diff997 Location int998 Expected int999 }1000 dmp := New()1001 for i, tc := range []TestCase{1002 {"Translation on equality", []Diff{{DiffDelete, "a"}, {DiffInsert, "1234"}, {DiffEqual, "xyz"}}, 2, 5},1003 {"Translation on deletion", []Diff{{DiffEqual, "a"}, {DiffDelete, "1234"}, {DiffEqual, "xyz"}}, 3, 1},1004 } {1005 actual := dmp.DiffXIndex(tc.Diffs, tc.Location)1006 assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name))1007 }1008}1009func TestDiffLevenshtein(t *testing.T) {1010 type TestCase struct {1011 Name string1012 Diffs []Diff1013 Expected int1014 }1015 dmp := New()1016 for i, tc := range []TestCase{1017 {"Levenshtein with trailing equality", []Diff{{DiffDelete, "абв"}, {DiffInsert, "1234"}, {DiffEqual, "ÑÑÑ"}}, 4},1018 {"Levenshtein with leading equality", []Diff{{DiffEqual, "ÑÑÑ"}, {DiffDelete, "абв"}, {DiffInsert, "1234"}}, 4},1019 {"Levenshtein with middle equality", []Diff{{DiffDelete, "абв"}, {DiffEqual, "ÑÑÑ"}, {DiffInsert, "1234"}}, 7},1020 } {1021 actual := dmp.DiffLevenshtein(tc.Diffs)1022 assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name))1023 }1024}1025func TestDiffBisect(t *testing.T) {1026 type TestCase struct {1027 Name string1028 Time time.Time1029 Expected []Diff1030 }1031 dmp := New()1032 for i, tc := range []TestCase{1033 {1034 Name: "normal",1035 Time: time.Date(9999, time.December, 31, 23, 59, 59, 59, time.UTC),1036 Expected: []Diff{1037 {DiffDelete, "c"},1038 {DiffInsert, "m"},1039 {DiffEqual, "a"},1040 {DiffDelete, "t"},1041 {DiffInsert, "p"},1042 },1043 },1044 {1045 Name: "Negative deadlines count as having infinite time",1046 Time: time.Date(0001, time.January, 01, 00, 00, 00, 00, time.UTC),1047 Expected: []Diff{1048 {DiffDelete, "c"},1049 {DiffInsert, "m"},1050 {DiffEqual, "a"},1051 {DiffDelete, "t"},1052 {DiffInsert, "p"},1053 },1054 },1055 {1056 Name: "Timeout",1057 Time: time.Now().Add(time.Nanosecond),1058 Expected: []Diff{1059 {DiffDelete, "cat"},1060 {DiffInsert, "map"},1061 },1062 },1063 } {1064 actual := dmp.DiffBisect("cat", "map", tc.Time)1065 assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name))1066 }1067 // Test for invalid UTF-8 sequences1068 assert.Equal(t, []Diff{1069 Diff{DiffEqual, "��"},1070 }, dmp.DiffBisect("\xe0\xe5", "\xe0\xe5", time.Now().Add(time.Minute)))1071}1072func TestDiffMain(t *testing.T) {1073 type TestCase struct {1074 Text1 string1075 Text2 string1076 Expected []Diff1077 }1078 dmp := New()1079 // Perform a trivial diff.1080 for i, tc := range []TestCase{1081 {1082 "",1083 "",1084 nil,1085 },1086 {1087 "abc",1088 "abc",1089 []Diff{Diff{DiffEqual, "abc"}},1090 },1091 {1092 "abc",1093 "ab123c",1094 []Diff{Diff{DiffEqual, "ab"}, Diff{DiffInsert, "123"}, Diff{DiffEqual, "c"}},1095 },1096 {1097 "a123bc",1098 "abc",1099 []Diff{Diff{DiffEqual, "a"}, Diff{DiffDelete, "123"}, Diff{DiffEqual, "bc"}},1100 },1101 {1102 "abc",1103 "a123b456c",1104 []Diff{Diff{DiffEqual, "a"}, Diff{DiffInsert, "123"}, Diff{DiffEqual, "b"}, Diff{DiffInsert, "456"}, Diff{DiffEqual, "c"}},1105 },1106 {1107 "a123b456c",1108 "abc",1109 []Diff{Diff{DiffEqual, "a"}, Diff{DiffDelete, "123"}, Diff{DiffEqual, "b"}, Diff{DiffDelete, "456"}, Diff{DiffEqual, "c"}},1110 },1111 } {1112 actual := dmp.DiffMain(tc.Text1, tc.Text2, false)1113 assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %#v", i, tc))1114 }1115 // Perform a real diff and switch off the timeout.1116 dmp.DiffTimeout = 01117 for i, tc := range []TestCase{1118 {1119 "a",1120 "b",1121 []Diff{Diff{DiffDelete, "a"}, Diff{DiffInsert, "b"}},1122 },1123 {1124 "Apples are a fruit.",1125 "Bananas are also fruit.",1126 []Diff{1127 Diff{DiffDelete, "Apple"},1128 Diff{DiffInsert, "Banana"},1129 Diff{DiffEqual, "s are a"},1130 Diff{DiffInsert, "lso"},1131 Diff{DiffEqual, " fruit."},1132 },1133 },1134 {1135 "ax\t",1136 "\u0680x\u0000",1137 []Diff{1138 Diff{DiffDelete, "a"},1139 Diff{DiffInsert, "\u0680"},1140 Diff{DiffEqual, "x"},1141 Diff{DiffDelete, "\t"},1142 Diff{DiffInsert, "\u0000"},1143 },1144 },1145 {1146 "1ayb2",1147 "abxab",1148 []Diff{1149 Diff{DiffDelete, "1"},1150 Diff{DiffEqual, "a"},1151 Diff{DiffDelete, "y"},1152 Diff{DiffEqual, "b"},1153 Diff{DiffDelete, "2"},1154 Diff{DiffInsert, "xab"},1155 },1156 },1157 {1158 "abcy",1159 "xaxcxabc",1160 []Diff{1161 Diff{DiffInsert, "xaxcx"},1162 Diff{DiffEqual, "abc"}, Diff{DiffDelete, "y"},1163 },1164 },1165 {1166 "ABCDa=bcd=efghijklmnopqrsEFGHIJKLMNOefg",1167 "a-bcd-efghijklmnopqrs",1168 []Diff{1169 Diff{DiffDelete, "ABCD"},1170 Diff{DiffEqual, "a"},1171 Diff{DiffDelete, "="},1172 Diff{DiffInsert, "-"},1173 Diff{DiffEqual, "bcd"},1174 Diff{DiffDelete, "="},1175 Diff{DiffInsert, "-"},1176 Diff{DiffEqual, "efghijklmnopqrs"},1177 Diff{DiffDelete, "EFGHIJKLMNOefg"},1178 },1179 },1180 {1181 "a [[Pennsylvania]] and [[New",1182 " and [[Pennsylvania]]",1183 []Diff{1184 Diff{DiffInsert, " "},1185 Diff{DiffEqual, "a"},1186 Diff{DiffInsert, "nd"},1187 Diff{DiffEqual, " [[Pennsylvania]]"},1188 Diff{DiffDelete, " and [[New"},1189 },1190 },1191 } {1192 actual := dmp.DiffMain(tc.Text1, tc.Text2, false)1193 assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %#v", i, tc))1194 }1195 // Test for invalid UTF-8 sequences1196 assert.Equal(t, []Diff{1197 Diff{DiffDelete, "��"},1198 }, dmp.DiffMain("\xe0\xe5", "", false))1199}1200func TestDiffMainWithTimeout(t *testing.T) {1201 dmp := New()1202 dmp.DiffTimeout = 200 * time.Millisecond1203 a := "`Twas brillig, and the slithy toves\nDid gyre and gimble in the wabe:\nAll mimsy were the borogoves,\nAnd the mome raths outgrabe.\n"1204 b := "I am the very model of a modern major general,\nI've information vegetable, animal, and mineral,\nI know the kings of England, and I quote the fights historical,\nFrom Marathon to Waterloo, in order categorical.\n"1205 // Increase the text lengths by 1024 times to ensure a timeout.1206 for x := 0; x < 13; x++ {1207 a = a + a1208 b = b + b1209 }1210 startTime := time.Now()1211 dmp.DiffMain(a, b, true)1212 endTime := time.Now()1213 delta := endTime.Sub(startTime)1214 // Test that we took at least the timeout period.1215 assert.True(t, delta >= dmp.DiffTimeout, fmt.Sprintf("%v !>= %v", delta, dmp.DiffTimeout))1216 // Test that we didn't take forever (be very forgiving). Theoretically this test could fail very occasionally if the OS task swaps or locks up for a second at the wrong moment.1217 assert.True(t, delta < (dmp.DiffTimeout*100), fmt.Sprintf("%v !< %v", delta, dmp.DiffTimeout*100))1218}1219func TestDiffMainWithCheckLines(t *testing.T) {1220 type TestCase struct {1221 Text1 string1222 Text2 string1223 }1224 dmp := New()1225 dmp.DiffTimeout = 01226 // Test cases must be at least 100 chars long to pass the cutoff.1227 for i, tc := range []TestCase{1228 {1229 "1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n",1230 "abcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\n",1231 },1232 {1233 "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890",1234 "abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij",1235 },1236 {1237 "1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n",1238 "abcdefghij\n1234567890\n1234567890\n1234567890\nabcdefghij\n1234567890\n1234567890\n1234567890\nabcdefghij\n1234567890\n1234567890\n1234567890\nabcdefghij\n",1239 },1240 } {1241 resultWithoutCheckLines := dmp.DiffMain(tc.Text1, tc.Text2, false)1242 resultWithCheckLines := dmp.DiffMain(tc.Text1, tc.Text2, true)1243 // TODO this fails for the third test case, why?1244 if i != 2 {1245 assert.Equal(t, resultWithoutCheckLines, resultWithCheckLines, fmt.Sprintf("Test case #%d, %#v", i, tc))1246 }1247 assert.Equal(t, diffRebuildTexts(resultWithoutCheckLines), diffRebuildTexts(resultWithCheckLines), fmt.Sprintf("Test case #%d, %#v", i, tc))1248 }1249}1250func BenchmarkDiffMain(bench *testing.B) {1251 s1 := "`Twas brillig, and the slithy toves\nDid gyre and gimble in the wabe:\nAll mimsy were the borogoves,\nAnd the mome raths outgrabe.\n"1252 s2 := "I am the very model of a modern major general,\nI've information vegetable, animal, and mineral,\nI know the kings of England, and I quote the fights historical,\nFrom Marathon to Waterloo, in order categorical.\n"1253 // Increase the text lengths by 1024 times to ensure a timeout.1254 for x := 0; x < 10; x++ {1255 s1 = s1 + s11256 s2 = s2 + s21257 }1258 dmp := New()1259 dmp.DiffTimeout = time.Second1260 bench.ResetTimer()1261 for i := 0; i < bench.N; i++ {1262 dmp.DiffMain(s1, s2, true)1263 }1264}1265func BenchmarkDiffMainLarge(b *testing.B) {1266 s1, s2 := speedtestTexts()1267 dmp := New()1268 b.ResetTimer()1269 for i := 0; i < b.N; i++ {1270 dmp.DiffMain(s1, s2, true)1271 }1272}1273func BenchmarkDiffMainRunesLargeLines(b *testing.B) {1274 s1, s2 := speedtestTexts()1275 dmp := New()1276 b.ResetTimer()1277 for i := 0; i < b.N; i++ {1278 text1, text2, linearray := dmp.DiffLinesToRunes(s1, s2)1279 diffs := dmp.DiffMainRunes(text1, text2, false)1280 diffs = dmp.DiffCharsToLines(diffs, linearray)1281 }1282}...
string.php
Source:string.php
1<?php2/**3 * Parses unified or context diffs output from eg. the diff utility.4 *5 * Example:6 * <code>7 * $patch = file_get_contents('example.patch');8 * $diff = new Text_Diff('string', array($patch));9 * $renderer = new Text_Diff_Renderer_inline();10 * echo $renderer->render($diff);11 * </code>12 *13 * Copyright 2005 Ãrjan Persson <o@42mm.org>14 * Copyright 2005-2010 The Horde Project (http://www.horde.org/)15 *16 * See the enclosed file COPYING for license information (LGPL). If you did17 * not receive this file, see http://opensource.org/licenses/lgpl-license.php.18 *19 * @author Ãrjan Persson <o@42mm.org>20 * @package Text_Diff21 * @since 0.2.022 */23class Text_Diff_Engine_string {24 /**25 * Parses a unified or context diff.26 *27 * First param contains the whole diff and the second can be used to force28 * a specific diff type. If the second parameter is 'autodetect', the29 * diff will be examined to find out which type of diff this is.30 *31 * @param string $diff The diff content.32 * @param string $mode The diff mode of the content in $diff. One of33 * 'context', 'unified', or 'autodetect'.34 *35 * @return array List of all diff operations.36 */37 function diff($diff, $mode = 'autodetect')38 {39 // Detect line breaks.40 $lnbr = "\n";41 if (strpos($diff, "\r\n") !== false) {42 $lnbr = "\r\n";43 } elseif (strpos($diff, "\r") !== false) {44 $lnbr = "\r";45 }46 // Make sure we have a line break at the EOF.47 if (substr($diff, -strlen($lnbr)) != $lnbr) {48 $diff .= $lnbr;49 }50 if ($mode != 'autodetect' && $mode != 'context' && $mode != 'unified') {51 return PEAR::raiseError('Type of diff is unsupported');52 }53 if ($mode == 'autodetect') {54 $context = strpos($diff, '***');55 $unified = strpos($diff, '---');56 if ($context === $unified) {57 return PEAR::raiseError('Type of diff could not be detected');58 } elseif ($context === false || $unified === false) {59 $mode = $context !== false ? 'context' : 'unified';60 } else {61 $mode = $context < $unified ? 'context' : 'unified';62 }63 }64 // Split by new line and remove the diff header, if there is one.65 $diff = explode($lnbr, $diff);66 if (($mode == 'context' && strpos($diff[0], '***') === 0) ||67 ($mode == 'unified' && strpos($diff[0], '---') === 0)) {68 array_shift($diff);69 array_shift($diff);70 }71 if ($mode == 'context') {72 return $this->parseContextDiff($diff);73 } else {74 return $this->parseUnifiedDiff($diff);75 }76 }77 /**78 * Parses an array containing the unified diff.79 *80 * @param array $diff Array of lines.81 *82 * @return array List of all diff operations.83 */84 function parseUnifiedDiff($diff)85 {86 $edits = array();87 $end = count($diff) - 1;88 for ($i = 0; $i < $end;) {89 $diff1 = array();90 switch (substr($diff[$i], 0, 1)) {91 case ' ':92 do {93 $diff1[] = substr($diff[$i], 1);94 } while (++$i < $end && substr($diff[$i], 0, 1) == ' ');95 $edits[] = new Text_Diff_Op_copy($diff1);96 break;97 case '+':98 // get all new lines99 do {100 $diff1[] = substr($diff[$i], 1);101 } while (++$i < $end && substr($diff[$i], 0, 1) == '+');102 $edits[] = new Text_Diff_Op_add($diff1);103 break;104 case '-':105 // get changed or removed lines106 $diff2 = array();107 do {108 $diff1[] = substr($diff[$i], 1);109 } while (++$i < $end && substr($diff[$i], 0, 1) == '-');110 while ($i < $end && substr($diff[$i], 0, 1) == '+') {111 $diff2[] = substr($diff[$i++], 1);112 }113 if (count($diff2) == 0) {114 $edits[] = new Text_Diff_Op_delete($diff1);115 } else {116 $edits[] = new Text_Diff_Op_change($diff1, $diff2);117 }118 break;119 default:120 $i++;121 break;122 }123 }124 return $edits;125 }126 /**127 * Parses an array containing the context diff.128 *129 * @param array $diff Array of lines.130 *131 * @return array List of all diff operations.132 */133 function parseContextDiff(&$diff)134 {135 $edits = array();136 $i = $max_i = $j = $max_j = 0;137 $end = count($diff) - 1;138 while ($i < $end && $j < $end) {139 while ($i >= $max_i && $j >= $max_j) {140 // Find the boundaries of the diff output of the two files141 for ($i = $j;142 $i < $end && substr($diff[$i], 0, 3) == '***';143 $i++);144 for ($max_i = $i;145 $max_i < $end && substr($diff[$max_i], 0, 3) != '---';146 $max_i++);147 for ($j = $max_i;148 $j < $end && substr($diff[$j], 0, 3) == '---';149 $j++);150 for ($max_j = $j;151 $max_j < $end && substr($diff[$max_j], 0, 3) != '***';152 $max_j++);153 }154 // find what hasn't been changed155 $array = array();156 while ($i < $max_i &&157 $j < $max_j &&158 strcmp($diff[$i], $diff[$j]) == 0) {159 $array[] = substr($diff[$i], 2);160 $i++;161 $j++;162 }163 while ($i < $max_i && ($max_j-$j) <= 1) {164 if ($diff[$i] != '' && substr($diff[$i], 0, 1) != ' ') {165 break;166 }167 $array[] = substr($diff[$i++], 2);168 }169 while ($j < $max_j && ($max_i-$i) <= 1) {170 if ($diff[$j] != '' && substr($diff[$j], 0, 1) != ' ') {171 break;172 }173 $array[] = substr($diff[$j++], 2);174 }175 if (count($array) > 0) {176 $edits[] = new Text_Diff_Op_copy($array);177 }178 if ($i < $max_i) {179 $diff1 = array();180 switch (substr($diff[$i], 0, 1)) {181 case '!':182 $diff2 = array();183 do {184 $diff1[] = substr($diff[$i], 2);185 if ($j < $max_j && substr($diff[$j], 0, 1) == '!') {186 $diff2[] = substr($diff[$j++], 2);187 }188 } while (++$i < $max_i && substr($diff[$i], 0, 1) == '!');189 $edits[] = new Text_Diff_Op_change($diff1, $diff2);190 break;191 case '+':192 do {193 $diff1[] = substr($diff[$i], 2);194 } while (++$i < $max_i && substr($diff[$i], 0, 1) == '+');195 $edits[] = new Text_Diff_Op_add($diff1);196 break;197 case '-':198 do {199 $diff1[] = substr($diff[$i], 2);200 } while (++$i < $max_i && substr($diff[$i], 0, 1) == '-');201 $edits[] = new Text_Diff_Op_delete($diff1);202 break;203 }204 }205 if ($j < $max_j) {206 $diff2 = array();207 switch (substr($diff[$j], 0, 1)) {208 case '+':209 do {210 $diff2[] = substr($diff[$j++], 2);211 } while ($j < $max_j && substr($diff[$j], 0, 1) == '+');212 $edits[] = new Text_Diff_Op_add($diff2);213 break;214 case '-':215 do {216 $diff2[] = substr($diff[$j++], 2);217 } while ($j < $max_j && substr($diff[$j], 0, 1) == '-');218 $edits[] = new Text_Diff_Op_delete($diff2);219 break;220 }221 }222 }223 return $edits;224 }225}...
diff
Using AI Code Generation
1$diff = new Diff($oldlines, $newlines);2echo $renderer->render($diff);3$diff = new Diff($oldlines, $newlines);4echo $renderer->render($diff);5$diff = new Diff($oldlines, $newlines);6echo $renderer->render($diff);7$diff = new Diff($oldlines, $newlines);8echo $renderer->render($diff);9$diff = new Diff($oldlines, $newlines);10echo $renderer->render($diff);11$diff = new Diff($oldlines, $newlines);12echo $renderer->render($diff);13$diff = new Diff($oldlines, $newlines);14echo $renderer->render($diff);15$diff = new Diff($oldlines, $newlines);16echo $renderer->render($diff);17$diff = new Diff($oldlines, $newlines);18echo $renderer->render($diff);19$diff = new Diff($oldlines, $newlines);20echo $renderer->render($diff);21$diff = new Diff($oldlines, $newlines);22echo $renderer->render($diff);23$diff = new Diff($oldlines, $newlines);24echo $renderer->render($diff);25$diff = new Diff($oldlines, $newlines);
diff
Using AI Code Generation
1$diff = new diff();2$diff->diff('test1.txt', 'test2.txt');3$diff->getDiff();4$diff = new diff();5$diff->diff('test2.txt', 'test1.txt');6$diff->getDiff();
diff
Using AI Code Generation
1require_once('Diff.php');2require_once('Diff/Renderer/inline.php');3$diff = new Text_Diff('auto',array(explode(' ', 'This is a test'),explode(' ', 'This is a test')));4$renderer = new Text_Diff_Renderer_inline();5echo $renderer->render($diff);6require_once('Diff.php');7require_once('Diff/Renderer/inline.php');8$diff = new Text_Diff('auto',array(explode(' ', 'This is a test'),explode(' ', 'This is a test')));9$renderer = new Text_Diff_Renderer_inline();10echo $renderer->render($diff);11require_once('Diff.php');12require_once('Diff/Renderer/inline.php');13$diff = new Text_Diff('auto',array(explode(' ', 'This is a test'),explode(' ', 'This is a test')));14$renderer = new Text_Diff_Renderer_inline();15echo $renderer->render($diff);16require_once('Diff.php');17require_once('Diff/Renderer/inline.php');18$diff = new Text_Diff('auto',array(explode(' ', 'This is a test'),explode(' ', 'This is a test')));19$renderer = new Text_Diff_Renderer_inline();20echo $renderer->render($diff);21require_once('Diff.php');22require_once('Diff/Renderer/inline.php');23$diff = new Text_Diff('auto',array(explode(' ', 'This is a test'),explode(' ', 'This is a test')));24$renderer = new Text_Diff_Renderer_inline();25echo $renderer->render($diff);26require_once('Diff.php');27require_once('Diff/Renderer/inline.php');28$diff = new Text_Diff('auto',array(explode(' ', 'This is a test'),explode(' ', 'This is a test')));29$renderer = new Text_Diff_Renderer_inline();30echo $renderer->render($diff);31require_once('Diff.php');32require_once('Diff/Renderer
diff
Using AI Code Generation
1require_once("diff.php");2$diff = new diff();3$diff->setOldFile('old.txt');4$diff->setNewFile('new.txt');5$diff->diff();6$diff->printDiff();7 (8 (9 (10 (11 (12 (13 (
diff
Using AI Code Generation
1include "diff.php";2$diff = new diff();3$diff->setOldText("This is old text");4$diff->setNewText("This is new text");5$diffResult = $diff->getDiff();6echo $diffResult;7include "diff.php";8$diff = new diff();9$diff->setOldText("This is old text");10$diff->setNewText("This is new text");11$diffResult = $diff->getDiff();12echo $diffResult;13include "diff.php";14$diff = new diff();15$diff->setOldText("This is old text");16$diff->setNewText("This is new text");17$diffResult = $diff->getDiff();18echo $diffResult;19class diff{20 private $oldText = "";21 private $newText = "";22 private $diffResult = "";23 public function setOldText($oldText){24 $this->oldText = $oldText;25 }26 public function setNewText($newText){27 $this->newText = $newText;28 }29 public function getDiff(){30 $oldTextArray = explode(" ", $this->oldText);31 $newTextArray = explode(" ", $this->newText);32 $oldTextArraySize = count($oldTextArray);33 $newTextArraySize = count($newTextArray);
diff
Using AI Code Generation
1$diff = new Diff();2$diff->diff('file1.txt', 'file2.txt');3echo $diff->getOutput();4$diff = new Diff();5$diff->diff('This is a test', 'This is a test');6echo $diff->getOutput();7$diff = new Diff();8$diff->diff(array('a', 'b', 'c', 'd'), array('a', 'c', 'd', 'e'));9echo $diff->getOutput();10$diff = new Diff();11$diff->diff('file1.txt', 'file2.txt');12echo $diff->getOutput('html');13$diff = new Diff();14$diff->diff('This is a test', 'This is a test');15echo $diff->getOutput('html');16$diff = new Diff();17$diff->diff(array('a', 'b', 'c', 'd'), array('a', 'c', 'd', 'e'));18echo $diff->getOutput('html');19$diff = new Diff();20$diff->diff('file1.txt', 'file2.txt');21echo $diff->getOutput('text');22$diff = new Diff();23$diff->diff('
diff
Using AI Code Generation
1require_once("diff.php");2$diff = new diff();3$diff->diffMethod($oldfile,$newfile,$diffFile);4require_once("diff.php");5$diff = new diff();6$diff->diffMethod($oldfile,$newfile,$diffFile);7require_once("diff.php");8$diff = new diff();9$diff->diffMethod($oldfile,$newfile,$diffFile);10require_once("diff.php");11$diff = new diff();12$diff->diffMethod($oldfile,$newfile,$diffFile);13require_once("diff.php");14$diff = new diff();15$diff->diffMethod($oldfile,$newfile,$diffFile);16require_once("diff.php");17$diff = new diff();18$diff->diffMethod($oldfile,$newfile,$diffFile);19require_once("diff.php");20$diff = new diff();21$diff->diffMethod($oldfile,$newfile,$diffFile);22require_once("diff.php");23$diff = new diff();24$diff->diffMethod($oldfile,$newfile,$diffFile);25require_once("diff.php");26$diff = new diff();27$diff->diffMethod($oldfile,$newfile,$diffFile);28require_once("diff.php");29$diff = new diff();30$diff->diffMethod($oldfile,$newfile,$diffFile);31require_once("diff.php");32$diff = new diff();33$diff->diffMethod($
Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Execute automation tests with diff on a cloud-based Grid of 3000+ real browsers and operating systems for both web and mobile applications.
Test now for FreeGet 100 minutes of automation test minutes FREE!!