halostatue / diff-lcs

Generate difference sets between Ruby sequences.
http://halostatue.github.com/diff-lcs
Other
290 stars 57 forks source link

Would like to embed diff-lcs such that it yields a html diff as a string #41

Open bwl21 opened 7 years ago

bwl21 commented 7 years ago

I currently use

rawDiff     = Diffy::Diff.new(other.trace_orig, self.trace_orig)
diff_as_html=rawDiff.to_s(:html)

But diffy calls another executable. In our case we use ldiff as provided by liff-lcs.

I wanted to achieve something like

diff_as_html=diffs = Diff::LCS.htmldiff('foo', 'bfoobar')

which should yield

"<div class=\"diff\">\n  <ul>\n    <li class=\"del\"><del>foo</del></li>\n    <li class=\"ins\"><ins>foo<strong>bar</strong></ins></li>\n  </ul>\n</div>\n"

How can I achieve this?

halostatue commented 7 years ago

So…I’m not 100% sure, to be honest. I don't see adding an htmldiff method the way you’ve written its suggested output (I don’t agree with the <ul>/<li> output), but there is a class, Diff::LCS::HTMLDiff which may lead you in the direction you are after creating. You’d end up creating the appropriate callbacks to put the sort of output you want.

bwl21 commented 7 years ago

Just to explain the context. I have a bunch of text snippets ( in particular a bunch of requirement spec items), and I want to generate an HTML file which shows the difference between the two versions as a table with one row per spec item. The styling of the difference is done by css.

I don't care if it uses li or div or whatever as long as I can style it properly. As of now, I use, what Diffy provides.

I managed to somehow use Diff::LCS::HTMLDiff but it seems to be not close to what I want:

  1. I have to provide an array of characters instead of strings - well thats possible
  2. I only get the diff on character level, not on lines as well

so I think I will try to add another method to Diffy (Diffy.diff_noshell) and replace the code that calls the external diff command by some code from ldiff.rb.

There is a related issue there https://github.com/samg/diffy/issues/63.

So we can close this if you do not want to provide a htmldiff method.

halostatue commented 7 years ago

I’m not against the idea of an htmldiff method as such, but I’d rather work on something that makes the whole toolkit easier to use—because the styling and choices that people would make for how they show a diff in HTML will be different for every person and use case. What I’m suggesting is to look at the implementations of both ldiff and htmldiff (which are in Diff::LCS::Ldiff and Diff::LCS::HTMLDiff) respectively to implement it as you have suggested. I’m more than happy to work through your implementation and from that we may be able to figure out a way to have an htmldiff method that provides an HTML-structured diff output that allows for deep customization (and, in the process, improve the existing htmldiff program which is pretty crappy, IMO).

bwl21 commented 7 years ago

I went in to the thing again and found a solution which works for me. As it is not generic I cannot prepare a pull request. But it might be of interest. I copy it from my rspec

describe "arhtmldiff" do
  it "calls sdiff" do
    require 'diff/lcs'

    class Callbacks
      attr_accessor :output

      def initialize(output, options = {})
        @output = output
        @state  = :init
        options ||= {}

        @styles={
            ins: "text-decoration: underline;color:blue;",
            del: "text-decoration: line-through;color:red;",
            eq: ""
        }
      end

      def to_html(element)
        element #.gsub(/./, {'<' => '&lt;', '>' => '&gt;', '&' => '&amp;'})
      end

      def handle_entry(element, state)
        #binding.pry
        unless @state == state
          @output.push "</span>" unless @state == :init
          @state = state
          @output.push %Q{<span style="#{@styles[state]}">}
        end

        @output.push(to_html(element))
      end

      private :handle_entry

      # This will be called with both lines are the same
      def match(event)
        handle_entry(event.old_element, :eq)
      end

      # This will be called when there is a line in A that isn't in B
      def discard_a(event)
        handle_entry(event.old_element, :del)
      end

      # This will be called when there is a line in B that isn't in A
      def discard_b(event)
        handle_entry(event.new_element, :ins)
      end
    end

    data_old = %q{
foo
bar}
    data_new = %q{
boo
far
and more
}

    output = []

    output.push %Q{<pre>}

    callback_obj = Callbacks.new(output)
    xx           = Diff::LCS.traverse_sequences(data_old, data_new, callback_obj)

    output.push %Q{</pre>}
    puts output.join

    expect(output.join).to eq %q{<pre><span style="">
</span><span style="text-decoration: line-through;color:red;">f</span><span style="text-decoration: underline;color:blue;">b</span><span style="">oo
</span><span style="text-decoration: line-through;color:red;">b</span><span style="text-decoration: underline;color:blue;">f</span><span style="">ar</span><span style="text-decoration: underline;color:blue;">
and more
</pre>}

  end
end
halostatue commented 7 years ago

Thanks. I’ll look this over to see if I can extract something common.

mtomov commented 2 years ago

Thanks @bwl21 - that was very useful!