You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. module RedmineDiff
  2. class Diff
  3. VERSION = 0.3
  4. def Diff.lcs(a, b)
  5. astart = 0
  6. bstart = 0
  7. afinish = a.length-1
  8. bfinish = b.length-1
  9. mvector = []
  10. # First we prune off any common elements at the beginning
  11. while (astart <= afinish && bstart <= afinish && a[astart] == b[bstart])
  12. mvector[astart] = bstart
  13. astart += 1
  14. bstart += 1
  15. end
  16. # now the end
  17. while (astart <= afinish && bstart <= bfinish && a[afinish] == b[bfinish])
  18. mvector[afinish] = bfinish
  19. afinish -= 1
  20. bfinish -= 1
  21. end
  22. bmatches = b.reverse_hash(bstart..bfinish)
  23. thresh = []
  24. links = []
  25. (astart..afinish).each { |aindex|
  26. aelem = a[aindex]
  27. next unless bmatches.has_key? aelem
  28. k = nil
  29. bmatches[aelem].reverse.each { |bindex|
  30. if k && (thresh[k] > bindex) && (thresh[k-1] < bindex)
  31. thresh[k] = bindex
  32. else
  33. k = thresh.replacenextlarger(bindex, k)
  34. end
  35. links[k] = [ (k==0) ? nil : links[k-1], aindex, bindex ] if k
  36. }
  37. }
  38. if !thresh.empty?
  39. link = links[thresh.length-1]
  40. while link
  41. mvector[link[1]] = link[2]
  42. link = link[0]
  43. end
  44. end
  45. return mvector
  46. end
  47. def makediff(a, b)
  48. mvector = Diff.lcs(a, b)
  49. ai = bi = 0
  50. while ai < mvector.length
  51. bline = mvector[ai]
  52. if bline
  53. while bi < bline
  54. discardb(bi, b[bi])
  55. bi += 1
  56. end
  57. match(ai, bi)
  58. bi += 1
  59. else
  60. discarda(ai, a[ai])
  61. end
  62. ai += 1
  63. end
  64. while ai < a.length
  65. discarda(ai, a[ai])
  66. ai += 1
  67. end
  68. while bi < b.length
  69. discardb(bi, b[bi])
  70. bi += 1
  71. end
  72. match(ai, bi)
  73. 1
  74. end
  75. def compactdiffs
  76. diffs = []
  77. @diffs.each { |df|
  78. i = 0
  79. curdiff = []
  80. while i < df.length
  81. whot = df[i][0]
  82. s = @isstring ? df[i][2].chr : [df[i][2]]
  83. p = df[i][1]
  84. last = df[i][1]
  85. i += 1
  86. while df[i] && df[i][0] == whot && df[i][1] == last+1
  87. s << df[i][2]
  88. last = df[i][1]
  89. i += 1
  90. end
  91. curdiff.push [whot, p, s]
  92. end
  93. diffs.push curdiff
  94. }
  95. return diffs
  96. end
  97. attr_reader :diffs, :difftype
  98. def initialize(diffs_or_a, b = nil, isstring = nil)
  99. if b.nil?
  100. @diffs = diffs_or_a
  101. @isstring = isstring
  102. else
  103. @diffs = []
  104. @curdiffs = []
  105. makediff(diffs_or_a, b)
  106. @difftype = diffs_or_a.class
  107. end
  108. end
  109. def match(ai, bi)
  110. @diffs.push @curdiffs unless @curdiffs.empty?
  111. @curdiffs = []
  112. end
  113. def discarda(i, elem)
  114. @curdiffs.push ['-', i, elem]
  115. end
  116. def discardb(i, elem)
  117. @curdiffs.push ['+', i, elem]
  118. end
  119. def compact
  120. return Diff.new(compactdiffs)
  121. end
  122. def compact!
  123. @diffs = compactdiffs
  124. end
  125. def inspect
  126. @diffs.inspect
  127. end
  128. end
  129. end
  130. module Diffable
  131. def diff(b)
  132. RedmineDiff::Diff.new(self, b)
  133. end
  134. # Create a hash that maps elements of the array to arrays of indices
  135. # where the elements are found.
  136. def reverse_hash(range = (0...self.length))
  137. revmap = {}
  138. range.each { |i|
  139. elem = self[i]
  140. if revmap.has_key? elem
  141. revmap[elem].push i
  142. else
  143. revmap[elem] = [i]
  144. end
  145. }
  146. return revmap
  147. end
  148. def replacenextlarger(value, high = nil)
  149. high ||= self.length
  150. if self.empty? || value > self[-1]
  151. push value
  152. return high
  153. end
  154. # binary search for replacement point
  155. low = 0
  156. while low < high
  157. index = (high+low)/2
  158. found = self[index]
  159. return nil if value == found
  160. if value > found
  161. low = index + 1
  162. else
  163. high = index
  164. end
  165. end
  166. self[low] = value
  167. # $stderr << "replace #{value} : 0/#{low}/#{init_high} (#{steps} steps) (#{init_high-low} off )\n"
  168. # $stderr.puts self.inspect
  169. #gets
  170. #p length - low
  171. return low
  172. end
  173. def patch(diff)
  174. newary = nil
  175. if diff.difftype == String
  176. newary = diff.difftype.new('')
  177. else
  178. newary = diff.difftype.new
  179. end
  180. ai = 0
  181. bi = 0
  182. diff.diffs.each { |d|
  183. d.each { |mod|
  184. case mod[0]
  185. when '-'
  186. while ai < mod[1]
  187. newary << self[ai]
  188. ai += 1
  189. bi += 1
  190. end
  191. ai += 1
  192. when '+'
  193. while bi < mod[1]
  194. newary << self[ai]
  195. ai += 1
  196. bi += 1
  197. end
  198. newary << mod[2]
  199. bi += 1
  200. else
  201. raise "Unknown diff action"
  202. end
  203. }
  204. }
  205. while ai < self.length
  206. newary << self[ai]
  207. ai += 1
  208. bi += 1
  209. end
  210. return newary
  211. end
  212. end
  213. class Array
  214. include Diffable
  215. end
  216. class String
  217. include Diffable
  218. end
  219. =begin
  220. = Diff
  221. (({diff.rb})) - computes the differences between two arrays or
  222. strings. Copyright (C) 2001 Lars Christensen
  223. == Synopsis
  224. diff = Diff.new(a, b)
  225. b = a.patch(diff)
  226. == Class Diff
  227. === Class Methods
  228. --- Diff.new(a, b)
  229. --- a.diff(b)
  230. Creates a Diff object which represent the differences between
  231. ((|a|)) and ((|b|)). ((|a|)) and ((|b|)) can be either be arrays
  232. of any objects, strings, or object of any class that include
  233. module ((|Diffable|))
  234. == Module Diffable
  235. The module ((|Diffable|)) is intended to be included in any class for
  236. which differences are to be computed. Diffable is included into String
  237. and Array when (({diff.rb})) is (({require}))'d.
  238. Classes including Diffable should implement (({[]})) to get element at
  239. integer indices, (({<<})) to append elements to the object and
  240. (({ClassName#new})) should accept 0 arguments to create a new empty
  241. object.
  242. === Instance Methods
  243. --- Diffable#patch(diff)
  244. Applies the differences from ((|diff|)) to the object ((|obj|))
  245. and return the result. ((|obj|)) is not changed. ((|obj|)) and
  246. can be either an array or a string, but must match the object
  247. from which the ((|diff|)) was created.
  248. =end