{"action":"created","comment":{"url":"https://api.github.com/repos/zendesk/abbreviato/pulls/comments/90588379","pull_request_review_id":11107701,"id":90588379,"diff_hunk":"@@ -0,0 +1,194 @@\n+require 'nokogiri'\n+require 'htmlentities'\n+\n+class TruncatedSaxDocument < Nokogiri::XML::SAX::Document\n+  IGNORABLE_TAGS = %w[html head body].freeze\n+\n+  # These don't have to be closed (which also impacts ongoing length calculations)\n+  # http://www.456bereastreet.com/archive/201005/void_empty_elements_and_self-closing_start_tags_in_html/\n+  VOID_TAGS = %w[area base br col command hr img input keygen link meta param source wbr].freeze\n+\n+  attr_reader :truncated_string,\n+    :max_length,\n+    :tail,\n+    :ignored_levels,\n+    :truncated\n+\n+  def initialize(options)\n+    @html_coder = HTMLEntities.new\n+\n+    @max_length = options[:max_length]\n+    @tail = options[:tail] || ''\n+\n+    @truncated_string = \"\"\n+    @closing_tags = []\n+    @estimated_length = 0\n+    @ignored_levels = 0\n+    @truncated = false\n+  end\n+\n+  # This method is called when the parser encounters an open tag\n+  def start_element(name, attributes)\n+    return if max_length_reached || ignorable_tag?(name)\n+\n+    # If already in ignore mode, go in deeper\n+    if ignore_mode?\n+      enter_ignored_level unless single_tag_element?(name)\n+      return\n+    end\n+\n+    string_to_add = opening_tag(name, attributes)\n+\n+    # Abort if there is not enough space to add the combined opening tag and (potentially) the closing tag\n+    length_of_tags = overridden_tag_length(name, string_to_add)\n+    if length_of_tags > remaining_length\n+      enter_ignored_level unless single_tag_element?(name)\n+      return\n+    end\n+\n+    # Save the tag so we can push it on at the end\n+    @closing_tags.push name unless single_tag_element?(name)\n+\n+    append_to_truncated_string(string_to_add, length_of_tags)\n+  end\n+\n+  # This method is called when the parser encounters characters between tags\n+  def characters(decoded_string)\n+    return if max_length_reached || ignore_mode?\n+\n+    # Use encoded length, so &gt; counts as 4 bytes, not 1 (which is what '>' would give)\n+    encoded_string = @html_coder.encode(decoded_string, :named)\n+    string_to_append = if encoded_string.bytesize > remaining_length\n+      # This is the line which prevents HTML entities getting truncated - treat them as a single char\n+      str = @html_coder.encode(truncate_string(decoded_string), :named)\n+      str << tail if remaining_length - str.bytesize >= tail.bytesize\n+      str\n+    else\n+      encoded_string\n+    end\n+    append_to_truncated_string(string_to_append)\n+  end\n+\n+  # This method is called when the parser encounters a comment\n+  def comment(string)\n+    comment = comment_tag(string)\n+    if comment.bytesize <= remaining_length\n+      append_to_truncated_string(comment)\n+    else\n+      @truncated = true\n+    end\n+  end\n+\n+  # This method is called when the parser encounters cdata. In practice, this also\n+  # gets called for this style of comment inside an element:\n+  #\n+  #   <style><!--\n+  #     /* Font Definitions */\n+  #     @font-face\n+  #       {font-family:Wingdings;\n+  #       panose-1:5 0 0 0 0 0 0 0 0 0;}\n+  #   --></style>\n+  #\n+  def cdata_block(string)\n+    if string.bytesize <= remaining_length\n+      append_to_truncated_string(string)\n+    else\n+      @truncated = true\n+    end\n+  end\n+\n+  # This method is called when the parser encounters a closing tag\n+  def end_element(name)\n+    if ignore_mode?\n+      exit_ignored_level unless single_tag_element?(name)\n+      return\n+    end\n+    return if max_length_reached || ignorable_tag?(name)","path":"lib/abbreviato/truncated_sax_document.rb","position":110,"original_position":106,"commit_id":"433eb815a770c1d9eecf2f3797769e488cb1aece","original_commit_id":"076ef724dff8a5d13d5a3204dd1dcc7ca4ae3ab0","user":{"login":"rmartz00","id":10818230,"avatar_url":"https://avatars.githubusercontent.com/u/10818230?v=3","gravatar_id":"","url":"https://api.github.com/users/rmartz00","html_url":"https://github.com/rmartz00","followers_url":"https://api.github.com/users/rmartz00/followers","following_url":"https://api.github.com/users/rmartz00/following{/other_user}","gists_url":"https://api.github.com/users/rmartz00/gists{/gist_id}","starred_url":"https://api.github.com/users/rmartz00/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rmartz00/subscriptions","organizations_url":"https://api.github.com/users/rmartz00/orgs","repos_url":"https://api.github.com/users/rmartz00/repos","events_url":"https://api.github.com/users/rmartz00/events{/privacy}","received_events_url":"https://api.github.com/users/rmartz00/received_events","type":"User","site_admin":false},"body":"Couldn't there be content added to the truncated string in `start_element` though that could push it to then be at `max_length_reached` by the time `characters` is called? Otherwise why does the `max_length_reached` check need to be in `characters` then? Seems like there needs to be an additional `@truncated = true` in `characters` if `max_length_reached` (or DRY it up in `max_length_reached` instead).","created_at":"2016-12-02T05:19:27Z","updated_at":"2016-12-02T05:19:27Z","html_url":"https://github.com/zendesk/abbreviato/pull/1#discussion_r90588379","pull_request_url":"https://api.github.com/repos/zendesk/abbreviato/pulls/1","_links":{"self":{"href":"https://api.github.com/repos/zendesk/abbreviato/pulls/comments/90588379"},"html":{"href":"https://github.com/zendesk/abbreviato/pull/1#discussion_r90588379"},"pull_request":{"href":"https://api.github.com/repos/zendesk/abbreviato/pulls/1"}}},"pull_request":{"url":"https://api.github.com/repos/zendesk/abbreviato/pulls/1","id":91139587,"html_url":"https://github.com/zendesk/abbreviato/pull/1","diff_url":"https://github.com/zendesk/abbreviato/pull/1.diff","patch_url":"https://github.com/zendesk/abbreviato/pull/1.patch","issue_url":"https://api.github.com/repos/zendesk/abbreviato/issues/1","number":1,"state":"open","locked":false,"title":"[EM-599] Add support to truncate down to bytesize","user":{"login":"jwsf","id":2756561,"avatar_url":"https://avatars.githubusercontent.com/u/2756561?v=3","gravatar_id":"","url":"https://api.github.com/users/jwsf","html_url":"https://github.com/jwsf","followers_url":"https://api.github.com/users/jwsf/followers","following_url":"https://api.github.com/users/jwsf/following{/other_user}","gists_url":"https://api.github.com/users/jwsf/gists{/gist_id}","starred_url":"https://api.github.com/users/jwsf/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jwsf/subscriptions","organizations_url":"https://api.github.com/users/jwsf/orgs","repos_url":"https://api.github.com/users/jwsf/repos","events_url":"https://api.github.com/users/jwsf/events{/privacy}","received_events_url":"https://api.github.com/users/jwsf/received_events","type":"User","site_admin":false},"body":"**Description**\r\n\r\nThe main purpose of this PR is to add support for truncating an HTML document by bytesize instead of by character. It also adds some commonly-used code-cleanliness tools.\r\n- [x] Link to TravisCI.org\r\n- [x] Add Rubocop\r\n- [x] Add Flay\r\n- [x] Add bundle:audit\r\n- [x] Add Brakeman\r\n- [x] Check performance is proportional to truncation size (not input size)\r\n- [x] Check memory usage is proportional to truncation size (not input size)\r\n- [x] Strip out features we don't need - stripping elements, stripping attributes, altering where the tail goes, not including elements and/or attributes in bytesize count, counting by character, always include comments (they contain styles)\r\n- [x] Output is HTML-encoded, HTML entities don't get truncated part-way thorugh\r\n- [x] Support cdata (for css inside comments inside <style> elements\r\n- [x] Comments and CData are not truncated - they are included in their entirety or omitted\r\n- [x] Allow HTML entities to be used in tail\r\n\r\nHere is an example of the truncation (from [here](https://github.com/zendesk/truncato/blob/08a9408d0318b4a65158aad779a28637e8864ee8/spec/fixtures/real_world_example.html) & [here](https://github.com/zendesk/truncato/blob/08a9408d0318b4a65158aad779a28637e8864ee8/spec/fixtures/real_world_example_truncated.html))\r\n\r\n![html truncation 2](https://cloud.githubusercontent.com/assets/2756561/19955817/5c7c6046-a142-11e6-85aa-fb99bb780bbf.png)\r\n\r\n","created_at":"2016-10-27T06:22:34Z","updated_at":"2016-12-02T05:19:27Z","closed_at":null,"merged_at":null,"merge_commit_sha":"441d9da6a4936462352d7de5032e29135456ee2b","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/zendesk/abbreviato/pulls/1/commits","review_comments_url":"https://api.github.com/repos/zendesk/abbreviato/pulls/1/comments","review_comment_url":"https://api.github.com/repos/zendesk/abbreviato/pulls/comments{/number}","comments_url":"https://api.github.com/repos/zendesk/abbreviato/issues/1/comments","statuses_url":"https://api.github.com/repos/zendesk/abbreviato/statuses/433eb815a770c1d9eecf2f3797769e488cb1aece","head":{"label":"zendesk:jwilson/EM-599-bytesize","ref":"jwilson/EM-599-bytesize","sha":"433eb815a770c1d9eecf2f3797769e488cb1aece","user":{"login":"zendesk","id":11525,"avatar_url":"https://avatars.githubusercontent.com/u/11525?v=3","gravatar_id":"","url":"https://api.github.com/users/zendesk","html_url":"https://github.com/zendesk","followers_url":"https://api.github.com/users/zendesk/followers","following_url":"https://api.github.com/users/zendesk/following{/other_user}","gists_url":"https://api.github.com/users/zendesk/gists{/gist_id}","starred_url":"https://api.github.com/users/zendesk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zendesk/subscriptions","organizations_url":"https://api.github.com/users/zendesk/orgs","repos_url":"https://api.github.com/users/zendesk/repos","events_url":"https://api.github.com/users/zendesk/events{/privacy}","received_events_url":"https://api.github.com/users/zendesk/received_events","type":"Organization","site_admin":false},"repo":{"id":71839175,"name":"abbreviato","full_name":"zendesk/abbreviato","owner":{"login":"zendesk","id":11525,"avatar_url":"https://avatars.githubusercontent.com/u/11525?v=3","gravatar_id":"","url":"https://api.github.com/users/zendesk","html_url":"https://github.com/zendesk","followers_url":"https://api.github.com/users/zendesk/followers","following_url":"https://api.github.com/users/zendesk/following{/other_user}","gists_url":"https://api.github.com/users/zendesk/gists{/gist_id}","starred_url":"https://api.github.com/users/zendesk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zendesk/subscriptions","organizations_url":"https://api.github.com/users/zendesk/orgs","repos_url":"https://api.github.com/users/zendesk/repos","events_url":"https://api.github.com/users/zendesk/events{/privacy}","received_events_url":"https://api.github.com/users/zendesk/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/zendesk/abbreviato","description":"A tool for truncating HTML strings efficiently","fork":true,"url":"https://api.github.com/repos/zendesk/abbreviato","forks_url":"https://api.github.com/repos/zendesk/abbreviato/forks","keys_url":"https://api.github.com/repos/zendesk/abbreviato/keys{/key_id}","collaborators_url":"https://api.github.com/repos/zendesk/abbreviato/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/zendesk/abbreviato/teams","hooks_url":"https://api.github.com/repos/zendesk/abbreviato/hooks","issue_events_url":"https://api.github.com/repos/zendesk/abbreviato/issues/events{/number}","events_url":"https://api.github.com/repos/zendesk/abbreviato/events","assignees_url":"https://api.github.com/repos/zendesk/abbreviato/assignees{/user}","branches_url":"https://api.github.com/repos/zendesk/abbreviato/branches{/branch}","tags_url":"https://api.github.com/repos/zendesk/abbreviato/tags","blobs_url":"https://api.github.com/repos/zendesk/abbreviato/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/zendesk/abbreviato/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/zendesk/abbreviato/git/refs{/sha}","trees_url":"https://api.github.com/repos/zendesk/abbreviato/git/trees{/sha}","statuses_url":"https://api.github.com/repos/zendesk/abbreviato/statuses/{sha}","languages_url":"https://api.github.com/repos/zendesk/abbreviato/languages","stargazers_url":"https://api.github.com/repos/zendesk/abbreviato/stargazers","contributors_url":"https://api.github.com/repos/zendesk/abbreviato/contributors","subscribers_url":"https://api.github.com/repos/zendesk/abbreviato/subscribers","subscription_url":"https://api.github.com/repos/zendesk/abbreviato/subscription","commits_url":"https://api.github.com/repos/zendesk/abbreviato/commits{/sha}","git_commits_url":"https://api.github.com/repos/zendesk/abbreviato/git/commits{/sha}","comments_url":"https://api.github.com/repos/zendesk/abbreviato/comments{/number}","issue_comment_url":"https://api.github.com/repos/zendesk/abbreviato/issues/comments{/number}","contents_url":"https://api.github.com/repos/zendesk/abbreviato/contents/{+path}","compare_url":"https://api.github.com/repos/zendesk/abbreviato/compare/{base}...{head}","merges_url":"https://api.github.com/repos/zendesk/abbreviato/merges","archive_url":"https://api.github.com/repos/zendesk/abbreviato/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/zendesk/abbreviato/downloads","issues_url":"https://api.github.com/repos/zendesk/abbreviato/issues{/number}","pulls_url":"https://api.github.com/repos/zendesk/abbreviato/pulls{/number}","milestones_url":"https://api.github.com/repos/zendesk/abbreviato/milestones{/number}","notifications_url":"https://api.github.com/repos/zendesk/abbreviato/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/zendesk/abbreviato/labels{/name}","releases_url":"https://api.github.com/repos/zendesk/abbreviato/releases{/id}","deployments_url":"https://api.github.com/repos/zendesk/abbreviato/deployments","created_at":"2016-10-24T23:01:55Z","updated_at":"2016-11-10T00:18:28Z","pushed_at":"2016-12-01T05:58:35Z","git_url":"git://github.com/zendesk/abbreviato.git","ssh_url":"git@github.com:zendesk/abbreviato.git","clone_url":"https://github.com/zendesk/abbreviato.git","svn_url":"https://github.com/zendesk/abbreviato","homepage":null,"size":170,"stargazers_count":0,"watchers_count":0,"language":"Ruby","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"zendesk:master","ref":"master","sha":"923ed6319c55f4c53631d3077bf7aaaf5066f74a","user":{"login":"zendesk","id":11525,"avatar_url":"https://avatars.githubusercontent.com/u/11525?v=3","gravatar_id":"","url":"https://api.github.com/users/zendesk","html_url":"https://github.com/zendesk","followers_url":"https://api.github.com/users/zendesk/followers","following_url":"https://api.github.com/users/zendesk/following{/other_user}","gists_url":"https://api.github.com/users/zendesk/gists{/gist_id}","starred_url":"https://api.github.com/users/zendesk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zendesk/subscriptions","organizations_url":"https://api.github.com/users/zendesk/orgs","repos_url":"https://api.github.com/users/zendesk/repos","events_url":"https://api.github.com/users/zendesk/events{/privacy}","received_events_url":"https://api.github.com/users/zendesk/received_events","type":"Organization","site_admin":false},"repo":{"id":71839175,"name":"abbreviato","full_name":"zendesk/abbreviato","owner":{"login":"zendesk","id":11525,"avatar_url":"https://avatars.githubusercontent.com/u/11525?v=3","gravatar_id":"","url":"https://api.github.com/users/zendesk","html_url":"https://github.com/zendesk","followers_url":"https://api.github.com/users/zendesk/followers","following_url":"https://api.github.com/users/zendesk/following{/other_user}","gists_url":"https://api.github.com/users/zendesk/gists{/gist_id}","starred_url":"https://api.github.com/users/zendesk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zendesk/subscriptions","organizations_url":"https://api.github.com/users/zendesk/orgs","repos_url":"https://api.github.com/users/zendesk/repos","events_url":"https://api.github.com/users/zendesk/events{/privacy}","received_events_url":"https://api.github.com/users/zendesk/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/zendesk/abbreviato","description":"A tool for truncating HTML strings efficiently","fork":true,"url":"https://api.github.com/repos/zendesk/abbreviato","forks_url":"https://api.github.com/repos/zendesk/abbreviato/forks","keys_url":"https://api.github.com/repos/zendesk/abbreviato/keys{/key_id}","collaborators_url":"https://api.github.com/repos/zendesk/abbreviato/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/zendesk/abbreviato/teams","hooks_url":"https://api.github.com/repos/zendesk/abbreviato/hooks","issue_events_url":"https://api.github.com/repos/zendesk/abbreviato/issues/events{/number}","events_url":"https://api.github.com/repos/zendesk/abbreviato/events","assignees_url":"https://api.github.com/repos/zendesk/abbreviato/assignees{/user}","branches_url":"https://api.github.com/repos/zendesk/abbreviato/branches{/branch}","tags_url":"https://api.github.com/repos/zendesk/abbreviato/tags","blobs_url":"https://api.github.com/repos/zendesk/abbreviato/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/zendesk/abbreviato/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/zendesk/abbreviato/git/refs{/sha}","trees_url":"https://api.github.com/repos/zendesk/abbreviato/git/trees{/sha}","statuses_url":"https://api.github.com/repos/zendesk/abbreviato/statuses/{sha}","languages_url":"https://api.github.com/repos/zendesk/abbreviato/languages","stargazers_url":"https://api.github.com/repos/zendesk/abbreviato/stargazers","contributors_url":"https://api.github.com/repos/zendesk/abbreviato/contributors","subscribers_url":"https://api.github.com/repos/zendesk/abbreviato/subscribers","subscription_url":"https://api.github.com/repos/zendesk/abbreviato/subscription","commits_url":"https://api.github.com/repos/zendesk/abbreviato/commits{/sha}","git_commits_url":"https://api.github.com/repos/zendesk/abbreviato/git/commits{/sha}","comments_url":"https://api.github.com/repos/zendesk/abbreviato/comments{/number}","issue_comment_url":"https://api.github.com/repos/zendesk/abbreviato/issues/comments{/number}","contents_url":"https://api.github.com/repos/zendesk/abbreviato/contents/{+path}","compare_url":"https://api.github.com/repos/zendesk/abbreviato/compare/{base}...{head}","merges_url":"https://api.github.com/repos/zendesk/abbreviato/merges","archive_url":"https://api.github.com/repos/zendesk/abbreviato/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/zendesk/abbreviato/downloads","issues_url":"https://api.github.com/repos/zendesk/abbreviato/issues{/number}","pulls_url":"https://api.github.com/repos/zendesk/abbreviato/pulls{/number}","milestones_url":"https://api.github.com/repos/zendesk/abbreviato/milestones{/number}","notifications_url":"https://api.github.com/repos/zendesk/abbreviato/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/zendesk/abbreviato/labels{/name}","releases_url":"https://api.github.com/repos/zendesk/abbreviato/releases{/id}","deployments_url":"https://api.github.com/repos/zendesk/abbreviato/deployments","created_at":"2016-10-24T23:01:55Z","updated_at":"2016-11-10T00:18:28Z","pushed_at":"2016-12-01T05:58:35Z","git_url":"git://github.com/zendesk/abbreviato.git","ssh_url":"git@github.com:zendesk/abbreviato.git","clone_url":"https://github.com/zendesk/abbreviato.git","svn_url":"https://github.com/zendesk/abbreviato","homepage":null,"size":170,"stargazers_count":0,"watchers_count":0,"language":"Ruby","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/zendesk/abbreviato/pulls/1"},"html":{"href":"https://github.com/zendesk/abbreviato/pull/1"},"issue":{"href":"https://api.github.com/repos/zendesk/abbreviato/issues/1"},"comments":{"href":"https://api.github.com/repos/zendesk/abbreviato/issues/1/comments"},"review_comments":{"href":"https://api.github.com/repos/zendesk/abbreviato/pulls/1/comments"},"review_comment":{"href":"https://api.github.com/repos/zendesk/abbreviato/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/zendesk/abbreviato/pulls/1/commits"},"statuses":{"href":"https://api.github.com/repos/zendesk/abbreviato/statuses/433eb815a770c1d9eecf2f3797769e488cb1aece"}}}}
{"actor":{"display_login":"rmartz00"}}