Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2009-2017 java-diff-utils.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.gitlive.difflib.text
import dev.gitlive.difflib.BiPredicate
import dev.gitlive.difflib.DiffUtils
import dev.gitlive.difflib.Function
import dev.gitlive.difflib.algorithm.DiffException
import dev.gitlive.difflib.patch.AbstractDelta
import dev.gitlive.difflib.patch.ChangeDelta
import dev.gitlive.difflib.patch.Chunk
import dev.gitlive.difflib.patch.DeleteDelta
import dev.gitlive.difflib.patch.InsertDelta
import dev.gitlive.difflib.patch.Patch
import dev.gitlive.difflib.text.DiffRow.Tag
import kotlin.math.max
/**
* This class for generating DiffRows for side-by-sidy view. You can customize the way of generating. For example, show
* inline diffs on not, ignoring white spaces or/and blank lines and so on. All parameters for generating are optional.
* If you do not specify them, the class will use the default values.
*
* These values are: showInlineDiffs = false; ignoreWhiteSpaces = true; ignoreBlankLines = true; ...
*
* For instantiating the DiffRowGenerator you should use the its builder. Like in example `
* DiffRowGenerator generator = new DiffRowGenerator.Builder().showInlineDiffs(true).
* ignoreWhiteSpaces(true).columnWidth(100).build();
` *
*/
class DiffRowGenerator private constructor(builder: Builder) {
private val columnWidth: Int
private val equalizer: BiPredicate
private val ignoreWhiteSpaces: Boolean
private val inlineDiffSplitter: Function>
private val mergeOriginalRevised: Boolean
private val newTag: Function
private val oldTag: Function
private val reportLinesUnchanged: Boolean
private val lineNormalizer: Function
private val showInlineDiffs: Boolean
init {
showInlineDiffs = builder.showInlineDiffs
ignoreWhiteSpaces = builder.ignoreWhiteSpaces
oldTag = builder.oldTag
newTag = builder.newTag
columnWidth = builder.columnWidth
mergeOriginalRevised = builder.mergeOriginalRevised
inlineDiffSplitter = builder.inlineDiffSplitter
equalizer = if (ignoreWhiteSpaces) IGNORE_WHITESPACE_EQUALIZER else DEFAULT_EQUALIZER
reportLinesUnchanged = builder.reportLinesUnchanged
lineNormalizer = builder.lineNormalizer
}
/**
* Get the DiffRows describing the difference between original and revised texts using the given patch. Useful for
* displaying side-by-side diff.
*
* @param original the original text
* @param revised the revised text
* @return the DiffRows between original and revised texts
*/
// @Throws(DiffException::class)
fun generateDiffRows(original: List, revised: List): List {
return generateDiffRows(original, DiffUtils.diff(original, revised, equalizer))
}
/**
* Generates the DiffRows describing the difference between original and revised texts using the given patch. Useful
* for displaying side-by-side diff.
*
* @param original the original text
* @param patch the given patch
* @return the DiffRows between original and revised texts
*/
// @Throws(DiffException::class)
fun generateDiffRows(original: List, patch: Patch): List {
val diffRows = ArrayList()
var endPos = 0
val deltaList = patch.getDeltas()
for (delta in deltaList) {
val orig = delta.source
val rev = delta.target
for (line in original.subList(endPos, orig.position)) {
diffRows.add(buildDiffRow(Tag.EQUAL, line, line))
}
// Inserted DiffRow
if (delta is InsertDelta<*>) {
endPos = orig.last() + 1
for (line in rev.lines!!) {
diffRows.add(buildDiffRow(Tag.INSERT, "", line))
}
continue
}
// Deleted DiffRow
if (delta is DeleteDelta<*>) {
endPos = orig.last() + 1
for (line in orig.lines!!) {
diffRows.add(buildDiffRow(Tag.DELETE, line, ""))
}
continue
}
if (showInlineDiffs) {
diffRows.addAll(generateInlineDiffs(delta))
} else {
for (j in 0 until max(orig.size(), rev.size())) {
diffRows.add(buildDiffRow(Tag.CHANGE,
if (orig.lines!!.size > j) orig.lines!![j] else "",
if (rev.lines!!.size > j) rev.lines!![j] else ""))
}
}
endPos = orig.last() + 1
}
// Copy the final matching chunk if any.
for (line in original.subList(endPos, original.size)) {
diffRows.add(buildDiffRow(Tag.EQUAL, line, line))
}
return diffRows
}
private fun buildDiffRow(type: Tag, orgline: String, newline: String): DiffRow {
if (reportLinesUnchanged) {
return DiffRow(type, orgline, newline)
} else {
var wrapOrg = preprocessLine(orgline)
if (Tag.DELETE == type) {
if (mergeOriginalRevised || showInlineDiffs) {
wrapOrg = oldTag(true) + wrapOrg + oldTag(false)
}
}
var wrapNew = preprocessLine(newline)
if (Tag.INSERT == type) {
if (mergeOriginalRevised) {
wrapOrg = newTag(true) + wrapNew + newTag(false)
} else if (showInlineDiffs) {
wrapNew = newTag(true) + wrapNew + newTag(false)
}
}
return DiffRow(type, wrapOrg, wrapNew)
}
}
private fun buildDiffRowWithoutNormalizing(type: Tag, orgline: String, newline: String): DiffRow {
return DiffRow(type,
StringUtils.wrapText(orgline, columnWidth),
StringUtils.wrapText(newline, columnWidth))
}
fun normalizeLines(list: List): List {
return list.map { lineNormalizer(it) }.toList()
}
/**
* Add the inline diffs for given delta
*
* @param delta the given delta
*/
// @Throws(DiffException::class)
private fun generateInlineDiffs(delta: AbstractDelta): List {
val orig = normalizeLines(delta.source.lines!!)
val rev = normalizeLines(delta.target.lines!!)
val origList: MutableList
val revList: MutableList
val joinedOrig = orig.joinToString("\n")
val joinedRev = rev.joinToString("\n")
origList = inlineDiffSplitter(joinedOrig)
revList = inlineDiffSplitter(joinedRev)
val inlineDeltas = DiffUtils.diff(origList, revList).getDeltas()
inlineDeltas.reverse()
for (inlineDelta in inlineDeltas) {
val inlineOrig = inlineDelta.source
val inlineRev = inlineDelta.target
if (inlineDelta is DeleteDelta<*>) {
wrapInTag(origList, inlineOrig.position, inlineOrig
.position + inlineOrig.size(), oldTag)
} else if (inlineDelta is InsertDelta<*>) {
if (mergeOriginalRevised) {
origList.addAll(inlineOrig.position,
revList.subList(inlineRev.position, inlineRev.position + inlineRev.size()))
wrapInTag(origList, inlineOrig.position, inlineOrig.position + inlineRev.size(), newTag)
} else {
wrapInTag(revList, inlineRev.position, inlineRev.position + inlineRev.size(), newTag)
}
} else if (inlineDelta is ChangeDelta<*>) {
if (mergeOriginalRevised) {
origList.addAll(inlineOrig.position + inlineOrig.size(),
revList.subList(inlineRev.position, inlineRev.position + inlineRev.size()))
wrapInTag(origList, inlineOrig.position + inlineOrig.size(), inlineOrig.position + inlineOrig.size()
+ inlineRev.size(), newTag)
} else {
wrapInTag(revList, inlineRev.position, inlineRev.position + inlineRev.size(), newTag)
}
wrapInTag(origList, inlineOrig.position, inlineOrig
.position + inlineOrig.size(), oldTag)
}
}
val origResult = StringBuilder()
val revResult = StringBuilder()
for (character in origList) {
origResult.append(character)
}
for (character in revList) {
revResult.append(character)
}
val original = origResult.toString().lines()
val revised = revResult.toString().lines()
val diffRows = ArrayList()
for (j in 0 until max(original.size, revised.size)) {
diffRows.add(buildDiffRowWithoutNormalizing(Tag.CHANGE,
if (original.size > j) original[j] else "",
if (revised.size > j) revised[j] else ""))
}
return diffRows
}
private fun preprocessLine(line: String): String {
return if (columnWidth == 0) {
lineNormalizer(line)
} else {
StringUtils.wrapText(lineNormalizer(line), columnWidth)
}
}
/**
* This class used for building the DiffRowGenerator.
*
* @author dmitry
*/
class Builder internal constructor() {
internal var showInlineDiffs = false
internal var ignoreWhiteSpaces = false
internal var oldTag = { f: Boolean -> if (f) "" else "" }
internal var newTag = { f: Boolean -> if (f) "" else "" }
internal var columnWidth = 0
internal var mergeOriginalRevised = false
internal var reportLinesUnchanged = false
internal var inlineDiffSplitter = SPLITTER_BY_CHARACTER
internal var lineNormalizer = LINE_NORMALIZER_FOR_HTML
/**
* Show inline diffs in generating diff rows or not.
*
* @param val the value to set. Default: false.
* @return builder with configured showInlineDiff parameter
*/
fun showInlineDiffs(`val`: Boolean): Builder {
showInlineDiffs = `val`
return this
}
/**
* Ignore white spaces in generating diff rows or not.
*
* @param val the value to set. Default: true.
* @return builder with configured ignoreWhiteSpaces parameter
*/
fun ignoreWhiteSpaces(`val`: Boolean): Builder {
ignoreWhiteSpaces = `val`
return this
}
/**
* Give the originial old and new text lines to Diffrow without any additional processing and without any tags to
* highlight the change.
*
* @param val the value to set. Default: false.
* @return builder with configured reportLinesUnWrapped parameter
*/
fun reportLinesUnchanged(`val`: Boolean): Builder {
reportLinesUnchanged = `val`
return this
}
/**
* Generator for Old-Text-Tags.
*
* @param generator the tag generator
* @return builder with configured ignoreBlankLines parameter
*/
fun oldTag(generator: Function): Builder {
this.oldTag = generator
return this
}
/**
* Generator for New-Text-Tags.
*
* @param generator
* @return
*/
fun newTag(generator: Function): Builder {
this.newTag = generator
return this
}
/**
* Set the column width of generated lines of original and revised texts.
*
* @param width the width to set. Making it < 0 doesn't have any sense. Default 80. @return builder with config
* ured ignoreBlankLines parameter
*/
fun columnWidth(width: Int): Builder {
if (width >= 0) {
columnWidth = width
}
return this
}
/**
* Build the DiffRowGenerator. If some parameters is not set, the default values are used.
*
* @return the customized DiffRowGenerator
*/
fun build(): DiffRowGenerator {
return DiffRowGenerator(this)
}
/**
* Merge the complete result within the original text. This makes sense for one line display.
*
* @param mergeOriginalRevised
* @return
*/
fun mergeOriginalRevised(mergeOriginalRevised: Boolean): Builder {
this.mergeOriginalRevised = mergeOriginalRevised
return this
}
/**
* Per default each character is separatly processed. This variant introduces processing by word, which does not
* deliver in word changes. Therefore the whole word will be tagged as changed:
*
*
* false: (aBa : aba) -- changed: a(B)a : a(b)a
* true: (aBa : aba) -- changed: (aBa) : (aba)
*
*/
fun inlineDiffByWord(inlineDiffByWord: Boolean): Builder {
inlineDiffSplitter = if (inlineDiffByWord) SPLITTER_BY_WORD else SPLITTER_BY_CHARACTER
return this
}
/**
* To provide some customized splitting a splitter can be provided. Here someone could think about sentence splitter,
* comma splitter or stuff like that.
*
* @param inlineDiffSplitter
* @return
*/
fun inlineDiffBySplitter(inlineDiffSplitter: Function>): Builder {
this.inlineDiffSplitter = inlineDiffSplitter
return this
}
/**
* By default DiffRowGenerator preprocesses lines for HTML output. Tabs and special HTML characters like "<"
* are replaced with its encoded value. To change this you can provide a customized line normalizer here.
*
* @param lineNormalizer
* @return
*/
fun lineNormalizer(lineNormalizer: Function): Builder {
this.lineNormalizer = lineNormalizer
return this
}
}
companion object {
val DEFAULT_EQUALIZER: BiPredicate = { obj1, obj2 -> obj1 == obj2 }
val IGNORE_WHITESPACE_EQUALIZER = { original: String, revised: String -> adjustWhitespace(original) == adjustWhitespace(revised) }
val LINE_NORMALIZER_FOR_HTML: Function = { StringUtils.normalize(it) }
/**
* Splitting lines by character to achieve char by char diff checking.
*/
val SPLITTER_BY_CHARACTER: Function> = { line: String ->
val list = ArrayList(line.length)
for (character in line) {
list.add(character.toString())
}
list
}
val SPLIT_BY_WORD_PATTERN = Regex("\\s+|[,.\\[\\](){}/\\\\*+\\-#]")
/**
* Splitting lines by word to achieve word by word diff checking.
*/
val SPLITTER_BY_WORD = { line: String -> splitStringPreserveDelimiter(line, SPLIT_BY_WORD_PATTERN) }
val WHITESPACE_PATTERN = Regex("\\s+")
fun create(): Builder {
return Builder()
}
private fun adjustWhitespace(raw: String): String {
return WHITESPACE_PATTERN.replace(raw.trim { it <= ' ' }, " ")
}
fun splitStringPreserveDelimiter(str: String?, SPLIT_PATTERN: Regex): MutableList {
val list = ArrayList()
if (str != null) {
val results = SPLIT_PATTERN.findAll(str)
var pos = 0
for (result in results) {
if (pos < result.range.first) {
list.add(str.substring(pos, result.range.first))
}
list.add(result.value)
pos = result.range.last + 1
}
if (pos < str.length) {
list.add(str.substring(pos))
}
}
return list
}
/**
* Wrap the elements in the sequence with the given tag
*
* @param startPosition the position from which tag should start. The counting start from a zero.
* @param endPosition the position before which tag should should be closed.
* @param tagGenerator the tag generator
*/
internal fun wrapInTag(sequence: MutableList, startPosition: Int,
endPosition: Int, tagGenerator: Function) {
var endPos = endPosition
while (endPos >= startPosition) {
//search position for end tag
while (endPos > startPosition) {
if ("\n" != sequence[endPos - 1]) {
break
}
endPos--
}
if (endPos == startPosition) {
break
}
sequence.add(endPos, tagGenerator(false))
endPos--
//search position for end tag
while (endPos > startPosition) {
if ("\n" == sequence[endPos - 1]) {
break
}
endPos--
}
sequence.add(endPos, tagGenerator(true))
endPos--
}
// sequence.add(endPosition, tagGenerator.apply(false));
// sequence.add(startPosition, tagGenerator.apply(true));
}
}
}