How to use Diff method of diff Package

Best Got code snippet using diff.Diff

diff.go

Source:diff.go Github

copy

Full Screen

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", "&para;<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}...

Full Screen

Full Screen

diff_test.go

Source:diff_test.go Github

copy

Full Screen

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&para;<br></span><del style=\"background:#ffe6e6;\">&lt;B&gt;b&lt;/B&gt;</del><ins style=\"background:#e6ffe6;\">c&amp;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}...

Full Screen

Full Screen

string.php

Source:string.php Github

copy

Full Screen

...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}...

Full Screen

Full Screen

Diff

Using AI Code Generation

copy

Full Screen

1import (2func main() {3    dmp := diffmatchpatch.New()4    diff := dmp.DiffMain("Hello World", "Hello Go", false)5    fmt.Println(diff)6}7[{1 Hello } {0  } {1 Go}]

Full Screen

Full Screen

Diff

Using AI Code Generation

copy

Full Screen

1import (2func main() {3    dmp := diffmatchpatch.New()4    d := dmp.DiffMain("Hello World", "Hello Go", false)5    fmt.Println(dmp.DiffPrettyText(d))6}7import (8func main() {9    dmp := diffmatchpatch.New()10    d := dmp.DiffLines("Hello World", "Hello Go")11    fmt.Println(dmp.DiffPrettyText(d))12}

Full Screen

Full Screen

Diff

Using AI Code Generation

copy

Full Screen

1import (2func main() {3  dmp := diffmatchpatch.New()4  d := dmp.DiffMain(a, b, false)5  fmt.Println(dmp.DiffPrettyText(d))6}7import (8func main() {9  dmp := diffmatchpatch.New()10  d := dmp.DiffMain(a, b, false)11  fmt.Println(dmp.DiffPrettyHtml(d))12}13import (14func main() {15  dmp := diffmatchpatch.New()16  d := dmp.DiffMain(a, b, false)17  fmt.Println(dmp.DiffPrettyHtml(d))18}19import (20func main() {21  dmp := diffmatchpatch.New()22  d := dmp.DiffMain(a, b, false)23  fmt.Println(dmp.DiffText1(d))24}25import (

Full Screen

Full Screen

Diff

Using AI Code Generation

copy

Full Screen

1import (2func main() {3    dmp := diffmatchpatch.New()4    d := dmp.DiffMain(a, b, false)5    fmt.Println(dmp.DiffPrettyText(d))6}

Full Screen

Full Screen

Diff

Using AI Code Generation

copy

Full Screen

1import (2func main() {3	dmp := diffmatchpatch.New()4	diffs := dmp.DiffMain(a, b, false)5	fmt.Println(diffs)6}7[{0 Hello, world!}]

Full Screen

Full Screen

Diff

Using AI Code Generation

copy

Full Screen

1import (2func main() {3	dmp := diffmatchpatch.New()4	diffs := dmp.DiffMain(a, b, false)5	fmt.Println(diffs)6}7[{0 hello world!} {1 !}]8import (9func main() {10	dmp := diffmatchpatch.New()11	diffs := dmp.DiffMain(a, b, false)12	fmt.Println(dmp.DiffToText(diffs))13}14import (15func main() {16	dmp := diffmatchpatch.New()17	diffs := dmp.DiffMain(a, b, false)18	patches := dmp.PatchMake(diffs)19	fmt.Println(patches)20}21[{[] [{0 hello world!} {1 !}]}]22import (23func main() {24	dmp := diffmatchpatch.New()25	diffs := dmp.DiffMain(a, b, false)26	patches := dmp.PatchMake(diffs)27	var res []interface{}28	res = dmp.PatchApply(patches, a)29	fmt.Println(res)30}31import (32func main() {33	dmp := diffmatchpatch.New()

Full Screen

Full Screen

Diff

Using AI Code Generation

copy

Full Screen

1import (2func main() {3	dmp := diffmatchpatch.New()4	diffs := dmp.DiffMain(a, b, false)5	fmt.Println(diffs)6}7[{1 Hello} {0 ,} {1 } {0 world!}]8import (9func main() {10	dmp := diffmatchpatch.New()11	diffs := dmp.DiffMain(a, b, false)12	fmt.Println(dmp.DiffPrettyText(diffs))13}14The DiffText1() method returns a string which contains the text1

Full Screen

Full Screen

Diff

Using AI Code Generation

copy

Full Screen

1import (2func main() {3	dmp := diffmatchpatch.New()4	diffs := dmp.DiffMain(a, b, false)5	fmt.Println(diffs)6}

Full Screen

Full Screen

Diff

Using AI Code Generation

copy

Full Screen

1import (2func main() {3	dmp := diffmatchpatch.New()4	diffs := dmp.DiffMain(a, b, false)5	fmt.Println(diffs)6}7[{0 Hello} {1  World}]8import (9func main() {10	dmp := diffmatchpatch.New()11	diffs := dmp.DiffMain(a, b, false)12	fmt.Println(dmp.DiffPrettyText(diffs))13}

Full Screen

Full Screen

Diff

Using AI Code Generation

copy

Full Screen

1import (2func main() {3	file1, err := ioutil.ReadFile("file1.txt")4	if err != nil {5		log.Fatal(err)6	}7	file2, err := ioutil.ReadFile("file2.txt")8	if err != nil {9		log.Fatal(err)10	}11	file1String := string(file1)12	file2String := string(file2)13	dmp := diffmatchpatch.New()14	patch := dmp.PatchMake(file1String, file2String)15	patchString := dmp.PatchToText(patch)16	fmt.Println(patchString)17}

Full Screen

Full Screen

Automation Testing Tutorials

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.

LambdaTest Learning Hubs:

YouTube

You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful