yatwiki/diff/diff.go

110 lines
2.2 KiB
Go

// Paul's Simple Diff Algorithm v 0.1
// (C) Paul Butler 2007 <http://www.paulbutler.org/>
// May be used and distributed under the zlib/libpng license.
// https://github.com/paulgb/simplediff/
package diff
import (
"html"
"io"
"strings"
)
var Separator string = "\n"
type Operation int8
const (
OP_INSERTED Operation = 1
OP_UNCHANGED = 2
OP_REMOVED = 3
)
type Instruction struct {
Op Operation
Content []string
}
func (this *Instruction) RenderHTML() string {
sc := strings.Join(this.Content, Separator)
if len(sc) == 0 {
return ""
}
switch this.Op {
case OP_INSERTED:
return `<ins>` + sc + `</ins>`
case OP_REMOVED:
return `<del>` + sc + `</del>`
default: //OP_UNCHANGED:
return sc
}
}
type twoints struct {
O, N int
}
func Diff(o, n []string) []Instruction {
if len(o) == 0 && len(n) == 0 {
return []Instruction{}
}
if len(o) == 0 {
return []Instruction{{OP_INSERTED, n}}
}
if len(n) == 0 {
return []Instruction{{OP_REMOVED, o}}
}
maxl := 0
omax := 0
nmax := 0
matrix := make(map[twoints]int, len(o)+1)
for oindex, ovalue := range o {
// Find keys in new, where value matchines ovalue
nkeys := make([]int, 0)
for nindex, nvalue := range n {
if nvalue == ovalue {
nkeys = append(nkeys, nindex)
}
}
// Build diff matrix
for _, nindex := range nkeys {
matrix[twoints{oindex, nindex}] = matrix[twoints{oindex - 1, nindex - 1}] + 1
if matrix[twoints{oindex, nindex}] > maxl {
maxl = matrix[twoints{oindex, nindex}]
omax = oindex + 1 - maxl
nmax = nindex + 1 - maxl
}
}
}
if maxl == 0 {
return []Instruction{
{OP_REMOVED, o},
{OP_INSERTED, n},
}
}
ret := Diff(o[:omax], n[:nmax])
if maxl != nmax {
ret = append(ret, Instruction{OP_UNCHANGED, n[nmax : maxl-nmax]})
}
ret = append(ret, Diff(o[omax+maxl:], n[nmax+maxl:])...)
return ret
}
// HtmlDiff splits o and n by newlines, and writes an HTML-safe version to the dest writer.
func HtmlDiff(o, n string, dest io.Writer) {
ops := Diff(strings.Split(html.EscapeString(o), Separator), strings.Split(html.EscapeString(n), Separator))
for _, op := range ops {
dest.Write([]byte(op.RenderHTML()))
}
}