package diffmatchpatch
import (
"bytes"
"errors"
"math"
"net/url"
"regexp"
"strconv"
"strings"
)
type Patch struct {
diffs []Diff
Start1 int
Start2 int
Length1 int
Length2 int
}
func (p *Patch ) String () string {
var coords1 , coords2 string
if p .Length1 == 0 {
coords1 = strconv .Itoa (p .Start1 ) + ",0"
} else if p .Length1 == 1 {
coords1 = strconv .Itoa (p .Start1 + 1 )
} else {
coords1 = strconv .Itoa (p .Start1 +1 ) + "," + strconv .Itoa (p .Length1 )
}
if p .Length2 == 0 {
coords2 = strconv .Itoa (p .Start2 ) + ",0"
} else if p .Length2 == 1 {
coords2 = strconv .Itoa (p .Start2 + 1 )
} else {
coords2 = strconv .Itoa (p .Start2 +1 ) + "," + strconv .Itoa (p .Length2 )
}
var text bytes .Buffer
_, _ = text .WriteString ("@@ -" + coords1 + " +" + coords2 + " @@\n" )
for _ , aDiff := range p .diffs {
switch aDiff .Type {
case DiffInsert :
_, _ = text .WriteString ("+" )
case DiffDelete :
_, _ = text .WriteString ("-" )
case DiffEqual :
_, _ = text .WriteString (" " )
}
_, _ = text .WriteString (strings .Replace (url .QueryEscape (aDiff .Text ), "+" , " " , -1 ))
_, _ = text .WriteString ("\n" )
}
return unescaper .Replace (text .String ())
}
func (dmp *DiffMatchPatch ) PatchAddContext (patch Patch , text string ) Patch {
if len (text ) == 0 {
return patch
}
pattern := text [patch .Start2 : patch .Start2 +patch .Length1 ]
padding := 0
for strings .Index (text , pattern ) != strings .LastIndex (text , pattern ) &&
len (pattern ) < dmp .MatchMaxBits -2 *dmp .PatchMargin {
padding += dmp .PatchMargin
maxStart := max (0 , patch .Start2 -padding )
minEnd := min (len (text ), patch .Start2 +patch .Length1 +padding )
pattern = text [maxStart :minEnd ]
}
padding += dmp .PatchMargin
prefix := text [max (0 , patch .Start2 -padding ):patch .Start2 ]
if len (prefix ) != 0 {
patch .diffs = append ([]Diff {Diff {DiffEqual , prefix }}, patch .diffs ...)
}
suffix := text [patch .Start2 +patch .Length1 : min (len (text ), patch .Start2 +patch .Length1 +padding )]
if len (suffix ) != 0 {
patch .diffs = append (patch .diffs , Diff {DiffEqual , suffix })
}
patch .Start1 -= len (prefix )
patch .Start2 -= len (prefix )
patch .Length1 += len (prefix ) + len (suffix )
patch .Length2 += len (prefix ) + len (suffix )
return patch
}
func (dmp *DiffMatchPatch ) PatchMake (opt ...interface {}) []Patch {
if len (opt ) == 1 {
diffs , _ := opt [0 ].([]Diff )
text1 := dmp .DiffText1 (diffs )
return dmp .PatchMake (text1 , diffs )
} else if len (opt ) == 2 {
text1 := opt [0 ].(string )
switch t := opt [1 ].(type ) {
case string :
diffs := dmp .DiffMain (text1 , t , true )
if len (diffs ) > 2 {
diffs = dmp .DiffCleanupSemantic (diffs )
diffs = dmp .DiffCleanupEfficiency (diffs )
}
return dmp .PatchMake (text1 , diffs )
case []Diff :
return dmp .patchMake2 (text1 , t )
}
} else if len (opt ) == 3 {
return dmp .PatchMake (opt [0 ], opt [2 ])
}
return []Patch {}
}
func (dmp *DiffMatchPatch ) patchMake2 (text1 string , diffs []Diff ) []Patch {
patches := []Patch {}
if len (diffs ) == 0 {
return patches
}
patch := Patch {}
charCount1 := 0
charCount2 := 0
prepatchText := text1
postpatchText := text1
for i , aDiff := range diffs {
if len (patch .diffs ) == 0 && aDiff .Type != DiffEqual {
patch .Start1 = charCount1
patch .Start2 = charCount2
}
switch aDiff .Type {
case DiffInsert :
patch .diffs = append (patch .diffs , aDiff )
patch .Length2 += len (aDiff .Text )
postpatchText = postpatchText [:charCount2 ] +
aDiff .Text + postpatchText [charCount2 :]
case DiffDelete :
patch .Length1 += len (aDiff .Text )
patch .diffs = append (patch .diffs , aDiff )
postpatchText = postpatchText [:charCount2 ] + postpatchText [charCount2 +len (aDiff .Text ):]
case DiffEqual :
if len (aDiff .Text ) <= 2 *dmp .PatchMargin &&
len (patch .diffs ) != 0 && i != len (diffs )-1 {
patch .diffs = append (patch .diffs , aDiff )
patch .Length1 += len (aDiff .Text )
patch .Length2 += len (aDiff .Text )
}
if len (aDiff .Text ) >= 2 *dmp .PatchMargin {
if len (patch .diffs ) != 0 {
patch = dmp .PatchAddContext (patch , prepatchText )
patches = append (patches , patch )
patch = Patch {}
prepatchText = postpatchText
charCount1 = charCount2
}
}
}
if aDiff .Type != DiffInsert {
charCount1 += len (aDiff .Text )
}
if aDiff .Type != DiffDelete {
charCount2 += len (aDiff .Text )
}
}
if len (patch .diffs ) != 0 {
patch = dmp .PatchAddContext (patch , prepatchText )
patches = append (patches , patch )
}
return patches
}
func (dmp *DiffMatchPatch ) PatchDeepCopy (patches []Patch ) []Patch {
patchesCopy := []Patch {}
for _ , aPatch := range patches {
patchCopy := Patch {}
for _ , aDiff := range aPatch .diffs {
patchCopy .diffs = append (patchCopy .diffs , Diff {
aDiff .Type ,
aDiff .Text ,
})
}
patchCopy .Start1 = aPatch .Start1
patchCopy .Start2 = aPatch .Start2
patchCopy .Length1 = aPatch .Length1
patchCopy .Length2 = aPatch .Length2
patchesCopy = append (patchesCopy , patchCopy )
}
return patchesCopy
}
func (dmp *DiffMatchPatch ) PatchApply (patches []Patch , text string ) (string , []bool ) {
if len (patches ) == 0 {
return text , []bool {}
}
patches = dmp .PatchDeepCopy (patches )
nullPadding := dmp .PatchAddPadding (patches )
text = nullPadding + text + nullPadding
patches = dmp .PatchSplitMax (patches )
x := 0
delta := 0
results := make ([]bool , len (patches ))
for _ , aPatch := range patches {
expectedLoc := aPatch .Start2 + delta
text1 := dmp .DiffText1 (aPatch .diffs )
var startLoc int
endLoc := -1
if len (text1 ) > dmp .MatchMaxBits {
startLoc = dmp .MatchMain (text , text1 [:dmp .MatchMaxBits ], expectedLoc )
if startLoc != -1 {
endLoc = dmp .MatchMain (text ,
text1 [len (text1 )-dmp .MatchMaxBits :], expectedLoc +len (text1 )-dmp .MatchMaxBits )
if endLoc == -1 || startLoc >= endLoc {
startLoc = -1
}
}
} else {
startLoc = dmp .MatchMain (text , text1 , expectedLoc )
}
if startLoc == -1 {
results [x ] = false
delta -= aPatch .Length2 - aPatch .Length1
} else {
results [x ] = true
delta = startLoc - expectedLoc
var text2 string
if endLoc == -1 {
text2 = text [startLoc :int (math .Min (float64 (startLoc +len (text1 )), float64 (len (text ))))]
} else {
text2 = text [startLoc :int (math .Min (float64 (endLoc +dmp .MatchMaxBits ), float64 (len (text ))))]
}
if text1 == text2 {
text = text [:startLoc ] + dmp .DiffText2 (aPatch .diffs ) + text [startLoc +len (text1 ):]
} else {
diffs := dmp .DiffMain (text1 , text2 , false )
if len (text1 ) > dmp .MatchMaxBits && float64 (dmp .DiffLevenshtein (diffs ))/float64 (len (text1 )) > dmp .PatchDeleteThreshold {
results [x ] = false
} else {
diffs = dmp .DiffCleanupSemanticLossless (diffs )
index1 := 0
for _ , aDiff := range aPatch .diffs {
if aDiff .Type != DiffEqual {
index2 := dmp .DiffXIndex (diffs , index1 )
if aDiff .Type == DiffInsert {
text = text [:startLoc +index2 ] + aDiff .Text + text [startLoc +index2 :]
} else if aDiff .Type == DiffDelete {
startIndex := startLoc + index2
text = text [:startIndex ] +
text [startIndex +dmp .DiffXIndex (diffs , index1 +len (aDiff .Text ))-index2 :]
}
}
if aDiff .Type != DiffDelete {
index1 += len (aDiff .Text )
}
}
}
}
}
x ++
}
text = text [len (nullPadding ) : len (nullPadding )+(len (text )-2 *len (nullPadding ))]
return text , results
}
func (dmp *DiffMatchPatch ) PatchAddPadding (patches []Patch ) string {
paddingLength := dmp .PatchMargin
nullPadding := ""
for x := 1 ; x <= paddingLength ; x ++ {
nullPadding += string (rune (x ))
}
for i := range patches {
patches [i ].Start1 += paddingLength
patches [i ].Start2 += paddingLength
}
if len (patches [0 ].diffs ) == 0 || patches [0 ].diffs [0 ].Type != DiffEqual {
patches [0 ].diffs = append ([]Diff {Diff {DiffEqual , nullPadding }}, patches [0 ].diffs ...)
patches [0 ].Start1 -= paddingLength
patches [0 ].Start2 -= paddingLength
patches [0 ].Length1 += paddingLength
patches [0 ].Length2 += paddingLength
} else if paddingLength > len (patches [0 ].diffs [0 ].Text ) {
extraLength := paddingLength - len (patches [0 ].diffs [0 ].Text )
patches [0 ].diffs [0 ].Text = nullPadding [len (patches [0 ].diffs [0 ].Text ):] + patches [0 ].diffs [0 ].Text
patches [0 ].Start1 -= extraLength
patches [0 ].Start2 -= extraLength
patches [0 ].Length1 += extraLength
patches [0 ].Length2 += extraLength
}
last := len (patches ) - 1
if len (patches [last ].diffs ) == 0 || patches [last ].diffs [len (patches [last ].diffs )-1 ].Type != DiffEqual {
patches [last ].diffs = append (patches [last ].diffs , Diff {DiffEqual , nullPadding })
patches [last ].Length1 += paddingLength
patches [last ].Length2 += paddingLength
} else if paddingLength > len (patches [last ].diffs [len (patches [last ].diffs )-1 ].Text ) {
lastDiff := patches [last ].diffs [len (patches [last ].diffs )-1 ]
extraLength := paddingLength - len (lastDiff .Text )
patches [last ].diffs [len (patches [last ].diffs )-1 ].Text += nullPadding [:extraLength ]
patches [last ].Length1 += extraLength
patches [last ].Length2 += extraLength
}
return nullPadding
}
func (dmp *DiffMatchPatch ) PatchSplitMax (patches []Patch ) []Patch {
patchSize := dmp .MatchMaxBits
for x := 0 ; x < len (patches ); x ++ {
if patches [x ].Length1 <= patchSize {
continue
}
bigpatch := patches [x ]
patches = append (patches [:x ], patches [x +1 :]...)
x --
Start1 := bigpatch .Start1
Start2 := bigpatch .Start2
precontext := ""
for len (bigpatch .diffs ) != 0 {
patch := Patch {}
empty := true
patch .Start1 = Start1 - len (precontext )
patch .Start2 = Start2 - len (precontext )
if len (precontext ) != 0 {
patch .Length1 = len (precontext )
patch .Length2 = len (precontext )
patch .diffs = append (patch .diffs , Diff {DiffEqual , precontext })
}
for len (bigpatch .diffs ) != 0 && patch .Length1 < patchSize -dmp .PatchMargin {
diffType := bigpatch .diffs [0 ].Type
diffText := bigpatch .diffs [0 ].Text
if diffType == DiffInsert {
patch .Length2 += len (diffText )
Start2 += len (diffText )
patch .diffs = append (patch .diffs , bigpatch .diffs [0 ])
bigpatch .diffs = bigpatch .diffs [1 :]
empty = false
} else if diffType == DiffDelete && len (patch .diffs ) == 1 && patch .diffs [0 ].Type == DiffEqual && len (diffText ) > 2 *patchSize {
patch .Length1 += len (diffText )
Start1 += len (diffText )
empty = false
patch .diffs = append (patch .diffs , Diff {diffType , diffText })
bigpatch .diffs = bigpatch .diffs [1 :]
} else {
diffText = diffText [:min (len (diffText ), patchSize -patch .Length1 -dmp .PatchMargin )]
patch .Length1 += len (diffText )
Start1 += len (diffText )
if diffType == DiffEqual {
patch .Length2 += len (diffText )
Start2 += len (diffText )
} else {
empty = false
}
patch .diffs = append (patch .diffs , Diff {diffType , diffText })
if diffText == bigpatch .diffs [0 ].Text {
bigpatch .diffs = bigpatch .diffs [1 :]
} else {
bigpatch .diffs [0 ].Text =
bigpatch .diffs [0 ].Text [len (diffText ):]
}
}
}
precontext = dmp .DiffText2 (patch .diffs )
precontext = precontext [max (0 , len (precontext )-dmp .PatchMargin ):]
postcontext := ""
if len (dmp .DiffText1 (bigpatch .diffs )) > dmp .PatchMargin {
postcontext = dmp .DiffText1 (bigpatch .diffs )[:dmp .PatchMargin ]
} else {
postcontext = dmp .DiffText1 (bigpatch .diffs )
}
if len (postcontext ) != 0 {
patch .Length1 += len (postcontext )
patch .Length2 += len (postcontext )
if len (patch .diffs ) != 0 && patch .diffs [len (patch .diffs )-1 ].Type == DiffEqual {
patch .diffs [len (patch .diffs )-1 ].Text += postcontext
} else {
patch .diffs = append (patch .diffs , Diff {DiffEqual , postcontext })
}
}
if !empty {
x ++
patches = append (patches [:x ], append ([]Patch {patch }, patches [x :]...)...)
}
}
}
return patches
}
func (dmp *DiffMatchPatch ) PatchToText (patches []Patch ) string {
var text bytes .Buffer
for _ , aPatch := range patches {
_, _ = text .WriteString (aPatch .String ())
}
return text .String ()
}
func (dmp *DiffMatchPatch ) PatchFromText (textline string ) ([]Patch , error ) {
patches := []Patch {}
if len (textline ) == 0 {
return patches , nil
}
text := strings .Split (textline , "\n" )
textPointer := 0
patchHeader := regexp .MustCompile ("^@@ -(\\d+),?(\\d*) \\+(\\d+),?(\\d*) @@$" )
var patch Patch
var sign uint8
var line string
for textPointer < len (text ) {
if !patchHeader .MatchString (text [textPointer ]) {
return patches , errors .New ("Invalid patch string: " + text [textPointer ])
}
patch = Patch {}
m := patchHeader .FindStringSubmatch (text [textPointer ])
patch .Start1 , _ = strconv .Atoi (m [1 ])
if len (m [2 ]) == 0 {
patch .Start1 --
patch .Length1 = 1
} else if m [2 ] == "0" {
patch .Length1 = 0
} else {
patch .Start1 --
patch .Length1 , _ = strconv .Atoi (m [2 ])
}
patch .Start2 , _ = strconv .Atoi (m [3 ])
if len (m [4 ]) == 0 {
patch .Start2 --
patch .Length2 = 1
} else if m [4 ] == "0" {
patch .Length2 = 0
} else {
patch .Start2 --
patch .Length2 , _ = strconv .Atoi (m [4 ])
}
textPointer ++
for textPointer < len (text ) {
if len (text [textPointer ]) > 0 {
sign = text [textPointer ][0 ]
} else {
textPointer ++
continue
}
line = text [textPointer ][1 :]
line = strings .Replace (line , "+" , "%2b" , -1 )
line , _ = url .QueryUnescape (line )
if sign == '-' {
patch .diffs = append (patch .diffs , Diff {DiffDelete , line })
} else if sign == '+' {
patch .diffs = append (patch .diffs , Diff {DiffInsert , line })
} else if sign == ' ' {
patch .diffs = append (patch .diffs , Diff {DiffEqual , line })
} else if sign == '@' {
break
} else {
return patches , errors .New ("Invalid patch mode '" + string (sign ) + "' in: " + string (line ))
}
textPointer ++
}
patches = append (patches , patch )
}
return patches , nil
}
The pages are generated with Golds v0.8.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .