HTMLDiff

repository·master·Indexed 42 days ago

https://github.com/myobie/htmldiff

A Ruby gem for generating HTML-formatted diffs between two text strings. It utilizes a tweaked LCS algorithm to produce customizable diff sequences via configurable tokenizers and formatters. Key features include the `HTMLDiff.diff` method for generating markup with `<ins>` and `<del>` tags, a `merge_threshold` option to control diff chunkiness, and a flexible `html_format` system to override default tags and CSS classes.

Tokens
3.6K
Snippets
13
Records
17
Agent score
54%

What's inside HTMLDiff

  1. Control diff chunkiness with `merge_threshold`

    master

    The :merge_threshold option controls how much unchanged text is grouped with changes.

    • Higher value: Produces "chunkier" diffs (more words grouped into a single <ins> or <del> block).
    • Lower value (e.g., 0): Produces "choppier" diffs (more granular changes).
    • false or -1: Disables merging across whitespace (will still merge whitespace itself).
    # Granular/choppy diff
    diff = HTMLDiff.diff(old_text, new_text, merge_threshold: 0)
  2. How HTMLDiff works

    master

    HTMLDiff operates in three distinct stages:

    1. Tokenization: The input strings are split into an array of tokens (via HTMLDiff::Tokenizer).
    2. Diff Generation: The HTMLDiff::Differ module uses the LCS (Longest Common Subsequence) algorithm to identify differences between token arrays.
    3. Formatting: The differences are converted into the final output format by a formatter.
  3. Upgrade from HTMLDiff 0.0.1 to 1.0.0

    master

    Version 1.0.0 maintains the basic HTMLDiff.diff(a, b) API syntax but introduces changes to default HTML output, class structures, and the underlying diff algorithm.

    Key changes include:

    • Simplified HTML Tags: Default output now uses plain <ins> and <del> tags without CSS classes.
    • Class Refactoring: HTMLDiff::DiffBuilder is deprecated. Use the top-level HTMLDiff.diff method instead.
    • Algorithm Update: The engine now uses the diff-lcs gem, which may produce slightly different diff results compared to older versions.
  4. Perform a basic text diff

    master

    Use HTMLDiff.diff(old_text, new_text) to generate an HTML-formatted diff between two strings. By default, it uses <del> and <ins> tags to highlight deletions and insertions.

    require 'htmldiff'
    
    old_text = "The quick red fox jumped over the dog."
    new_text = "The red fox hopped over the lazy dog."
    
    diff = HTMLDiff.diff(old_text, new_text)
  5. Restore legacy HTML class behavior in HTMLDiff 1.0.0

    master

    In version 1.0.0, the default output no longer includes the diffins, diffdel, and diffmod classes. To restore the exact HTML output format used in version 0.0.1, pass a configuration hash to the html_format: option in the HTMLDiff.diff method.

    html_format = { class_insert: 'diffins', class_delete: 'diffdel', class_replace: 'diffmod' }
    HTMLDiff.diff(old_text, new_text, html_format: html_format)
  6. Use a custom output formatter

    master

    If you need a non-HTML format (like JSON or XML) or custom logic, provide an object that responds to #format. The #format method receives an array of changes, where each change is an array: [action, old_string, new_string].

    Actions include:

    • = : Equal (unchanged)
    • - : Remove
    • + : Add
    • ! : Replace
    module MyCustomFormatter
      def self.format(changes)
        changes.each_with_object(+'') do |(action, old_string, new_string), content|
          case action
          when '='
            content << new_string if new_string
          when '-'
            content << %(<removed>#{old_string}</removed>) if old_string
          when '+'
            content << %(<added>#{new_string}</added>) if new_string
          when '!'
            content << %(<removed>#{old_string}</removed>) if old_string
            content << %(<added>#{new_string}</added>) if new_string
          end
        end
      end
    end
    
    diff = HTMLDiff.diff(old_text, new_text, formatter: MyCustomFormatter)
  7. Customize HTML output with `html_format`

    master

    You can control the HTML tags and CSS classes used for diff elements by passing an html_format hash to HTMLDiff.diff. This allows you to override default tags (like del and ins) and apply specific CSS classes for deletions, insertions, and replacements.

    diff = HTMLDiff.diff(old_text, new_text, html_format: {
      tag: 'span',
      class_delete: 'highlight removed',
      class_insert: 'highlight added'
    })
  8. Replace deprecated HTMLDiff::DiffBuilder with HTMLDiff.diff

    master

    The HTMLDiff::DiffBuilder class is deprecated in version 1.0.0 and will raise a warning when used. You should replace direct builder instantiation with the simplified HTMLDiff.diff method.

    # Old (Deprecated)
    HTMLDiff::DiffBuilder.new(a, b).build
    
    # Replace with
    HTMLDiff.diff(a, b)
  9. Use a custom tokenizer

    master

    You can control the granularity of the diff by providing a tokenizer object that responds to #tokenize. The method should return an Array of strings.

    Note: Your tokenizer must include whitespace tokens so that the resulting tokens can be joined back into the original string exactly.

    module MyCustomTokenizer
      def self.tokenize(string)
        string.split(/(\b|\s)/).reject(&:empty?)
      end
    end
    
    diff = HTMLDiff.diff(old_text, new_text, tokenizer: MyCustomTokenizer)
  10. Reference: `html_format` options

    master

    The html_format hash supports the following keys to customize the output structure:

    OptionDescription
    :tagBase HTML tag for all change nodes (default: none)
    :tag_deleteTag for deleted content (overrides :tag, default: "del")
    :tag_insertTag for inserted content (overrides :tag, default: "ins")
    :tag_replaceTag for replaced content (overrides :tag_delete, :tag)
    :tag_replace_deleteTag for deleted content in replacements (overrides :tag_replace, :tag_delete, :tag)
    :tag_replace_insertTag for inserted content in replacements (overrides :tag_replace, :tag_insert, :tag)
    :tag_unchangedTag for unchanged content (optional)
    :classBase CSS class(es) for all change nodes
    :class_deleteCSS class(es) for deleted content (overrides :class)
    :class_insertCSS class(es) for inserted content (overrides :class)
    :class_replaceCSS class(es) for replaced content (overrides :class_delete, :class_insert, :class)
    :class_replace_deleteCSS class(es) for deleted content in replacements (overrides :class_replace, :class_delete, :class)
    :class_replace_insertCSS class(es) for inserted content in replacements (overrides :class_replace, :class_insert, :class)
    :class_unchangedCSS class(es) for unchanged content (optional)
  11. Use a custom formatter with HTMLDiff

    master

    To change how the diff output is rendered (e.g., to use a different markup language or CSS classes), provide an object that implements a format method to the formatter option.

    class MyCustomFormatter
      def format(changes)
        # Implement custom rendering logic based on changes
        "Custom Diff: #{changes.inspect}"
      end
    end
    
    HTMLDiff.diff(old_str, new_str, formatter: MyCustomFormatter.new)