{"id":13416381,"url":"https://github.com/Mange/roadie","last_synced_at":"2025-03-14T23:31:40.475Z","repository":{"id":1143706,"uuid":"1024839","full_name":"Mange/roadie","owner":"Mange","description":"Making HTML emails comfortable for the Ruby rockstars","archived":false,"fork":false,"pushed_at":"2024-05-13T05:48:49.000Z","size":1063,"stargazers_count":1342,"open_issues_count":5,"forks_count":83,"subscribers_count":20,"default_branch":"master","last_synced_at":"2025-03-12T07:08:36.702Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Mange.png","metadata":{"files":{"readme":"README.md","changelog":"Changelog.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2010-10-26T08:08:59.000Z","updated_at":"2025-02-18T21:45:35.000Z","dependencies_parsed_at":"2024-01-31T05:06:39.008Z","dependency_job_id":"1b116e18-f298-4ced-b07a-74de66e435b4","html_url":"https://github.com/Mange/roadie","commit_stats":{"total_commits":561,"total_committers":47,"mean_commits":"11.936170212765957","dds":"0.24420677361853838","last_synced_commit":"4144e9e5729601b6a81a96208f1351ddedc0b295"},"previous_names":[],"tags_count":52,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Mange%2Froadie","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Mange%2Froadie/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Mange%2Froadie/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Mange%2Froadie/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Mange","download_url":"https://codeload.github.com/Mange/roadie/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243663516,"owners_count":20327300,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-30T21:00:57.922Z","updated_at":"2025-03-14T23:31:40.452Z","avatar_url":"https://github.com/Mange.png","language":"Ruby","funding_links":[],"categories":["Ruby","Email"],"sub_categories":[],"readme":"Roadie\n======\n\n[![Code Climate](https://codeclimate.com/github/Mange/roadie.png)](https://codeclimate.com/github/Mange/roadie)\n[![Code coverage status](https://codecov.io/github/Mange/roadie/coverage.svg?branch=master)](https://codecov.io/github/Mange/roadie?branch=master)\n[![Gem](https://img.shields.io/gem/v/roadie.svg)](https://rubygems.org/gems/roadie)\n[![Passive maintenance](https://img.shields.io/badge/maintenance-Passive-yellow.svg)][passive]\n\n\n|||\n|---|---|\n| :warning: | This gem is now in [passive maintenance mode][passive]. [(more)][passive] |\n\n\u003e Making HTML emails comfortable for the Ruby rockstars\n\nRoadie tries to make sending HTML emails a little less painful by inlining stylesheets and rewriting relative URLs for you inside your emails.\n\n\nHow does it work?\n-----------------\n\nEmail clients have bad support for stylesheets, and some of them blocks stylesheets from downloading. The easiest way to handle this is to work with inline styles (`style=\"...\"`), but that is error prone and hard to work with as you cannot use classes and/or reuse styling over your HTML.\n\nThis gem makes this easier by automatically inlining stylesheets into the document. You give Roadie your CSS, or let it find it by itself from the `\u003clink\u003e` and `\u003cstyle\u003e` tags in the markup, and it will go through all of the selectors assigning the styles to the matching elements. Careful attention has been put into selectors being applied in the correct order, so it should behave just like in the browser.\n\n\"Dynamic\" selectors (`:hover`, `:visited`, `:focus`, etc.), or selectors not understood by Nokogiri will be inlined into a single `\u003cstyle\u003e` element for those email clients that support it. This changes specificity a great deal for these rules, so it might not work 100% out of the box. (See more about this below)\n\nRoadie also rewrites all relative URLs in the email to an absolute counterpart,\nmaking images you insert and those referenced in your stylesheets work. No more\nheadaches about how to write the stylesheets while still having them work with\nemails from your acceptance environments. You can disable this on specific\nelements using a `data-roadie-ignore` marker.\n\nFeatures\n--------\n\n* Writes CSS styles inline.\n  * Respects `!important` styles.\n  * Does not overwrite styles already present in the `style` attribute of tags.\n  * Supports the same CSS selectors as [Nokogiri](http://nokogiri.org/); use CSS3 selectors in your emails!\n  * Keeps `:hover`, `@media { ... }` and friends around in a separate `\u003cstyle\u003e` element.\n* Makes image urls absolute.\n  * Hostname and port configurable on a per-environment basis.\n  * Can be disabled on individual elements.\n* Makes link `href`s and `img` `src`s absolute.\n* Automatically adds proper HTML skeleton when missing; you don't have to create a layout for emails.\n  * Also supports HTML fragments / partial documents, where layout is not added.\n* Allows you to inject stylesheets in a number of ways, at runtime.\n* Removes `data-roadie-ignore` markers before finishing the HTML.\n\nInstall \u0026 Usage\n---------------\n\n[Add this gem to your Gemfile as recommended by Rubygems](http://rubygems.org/gems/roadie) and run `bundle install`.\n\n```ruby\ngem 'roadie', '~\u003e 5.2'\n```\n\nYou can then create a new instance of a Roadie document:\n\n```ruby\n# Transform full documents with the #transform method.\ndocument = Roadie::Document.new \"\u003chtml\u003e\u003cbody\u003e\u003c/body\u003e\u003c/html\u003e\"\ndocument.add_css \"body { color: green; }\"\ndocument.transform\n    # =\u003e \"\u003chtml\u003e\u003cbody style=\\\"color:green;\\\"\u003e\u003c/body\u003e\u003c/html\u003e\"\n\n# Transform partial documents with #transform_partial.\ndocument = Roadie::Document.new \"\u003cdiv\u003eHello world!\u003c/div\u003e\"\ndocument.add_css \"div { color: green; }\"\ndocument.transform_partial\n    # =\u003e \"\u003cdiv style=\\\"color:green;\\\"\u003eHello world!\u003c/div\u003e\"\n```\n\nYour document instance can be configured with several options:\n\n* `url_options` - Dictates how absolute URLs should be built.\n* `keep_uninlinable_css` - Set to false to skip CSS that cannot be inlined.\n* `merge_media_queries` - Set to false to not group media queries. Some users might prefer to not group rules within media queries because\n  it will result in rules getting reordered.\n  e.g.\n  ```css\n  @media(max-width: 600px) { .col-6 { display: block; } }\n  @media(max-width: 400px) { .col-12 { display: inline-block; } }\n  @media(max-width: 600px) { .col-12 { display: block; } }\n  ```\n  will become\n  ```css\n  @media(max-width: 600px) { .col-6 { display: block; } .col-12 { display: block; } }\n  @media(max-width: 400px) { .col-12 { display: inline-block; } }\n  ```\n* `asset_providers` - A list of asset providers that are invoked when CSS files are referenced. See below.\n* `external_asset_providers` - A list of asset providers that are invoked when absolute CSS URLs are referenced. See below.\n* `before_transformation` - A callback run before transformation starts.\n* `after_transformation` - A callback run after transformation is completed.\n* `serialization_options` - An integer bitmap of options passed along to Nokogiri during serialization.\nBy default it's `Nokogiri::XML::Node::SaveOptions::NO_DECLARATION | Nokogiri::XML::Node::SaveOptions::NO_EMPTY_TAGS`.\n\n### Making URLs absolute ###\n\nIn order to make URLs absolute you need to first configure the URL options of the document.\n\n```ruby\nhtml = '... \u003ca href=\"/about-us\"\u003eRead more!\u003c/a\u003e ...'\ndocument = Roadie::Document.new html\ndocument.url_options = {host: \"myapp.com\", protocol: \"https\"}\ndocument.transform\n  # =\u003e \"... \u003ca href=\\\"https://myapp.com/about-us\\\"\u003eRead more!\u003c/a\u003e ...\"\n```\n\nThe following URLs will be rewritten for you:\n* `a[href]` (HTML)\n* `img[src]` (HTML)\n* `url()` (CSS)\n\nYou can disable individual elements by adding an `data-roadie-ignore` marker on\nthem. CSS will still be inlined on those elements, but URLs will not be\nrewritten.\n\n```html\n\u003ca href=\"|UNSUBSCRIBE_URL|\" data-roadie-ignore\u003eUnsubscribe\u003c/a\u003e\n```\n\n### Referenced stylesheets ###\n\nBy default, `style` and `link` elements in the email document's `head` are processed along with the stylesheets and removed from the `head`.\n\nYou can set a special `data-roadie-ignore` attribute on `style` and `link` tags that you want to ignore (the attribute will be removed, however). This is the place to put things like `:hover` selectors that you want to have for email clients allowing them.\n\nStyle and link elements with `media=\"print\"` are also ignored.\n\n```html\n\u003chead\u003e\n  \u003clink rel=\"stylesheet\" type=\"text/css\" href=\"/assets/emails/rock.css\"\u003e         \u003c!-- Will be inlined with normal providers --\u003e\n  \u003clink rel=\"stylesheet\" type=\"text/css\" href=\"http://www.metal.org/metal.css\"\u003e  \u003c!-- Will be inlined with external providers, *IF* specified; otherwise ignored. --\u003e\n  \u003clink rel=\"stylesheet\" type=\"text/css\" href=\"/assets/jazz.css\" media=\"print\"\u003e  \u003c!-- Will NOT be inlined; print style --\u003e\n  \u003clink rel=\"stylesheet\" type=\"text/css\" href=\"/ambient.css\" data-roadie-ignore\u003e \u003c!-- Will NOT be inlined; ignored --\u003e\n  \u003cstyle\u003e\u003c/style\u003e                    \u003c!-- Will be inlined --\u003e\n  \u003cstyle data-roadie-ignore\u003e\u003c/style\u003e \u003c!-- Will NOT be inlined; ignored --\u003e\n\u003c/head\u003e\n```\n\nRoadie will use the given asset providers to look for the actual CSS that is referenced. If you don't change the default, it will use the `Roadie::FilesystemProvider` which looks for stylesheets on the filesystem, relative to the current working directory.\n\nExample:\n\n```ruby\n# /home/user/foo/stylesheets/primary.css\nbody { color: green; }\n\n# /home/user/foo/script.rb\nhtml = \u003c\u003c-HTML\n\u003chtml\u003e\n  \u003chead\u003e\n  \u003clink rel=\"stylesheet\" type=\"text/css\" href=\"/stylesheets/primary.css\"\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\nHTML\n\nDir.pwd # =\u003e \"/home/user/foo\"\ndocument = Roadie::Document.new html\ndocument.transform # =\u003e\n                   # \u003c!DOCTYPE html\u003e\n                   # \u003chtml\u003e\n                   #   \u003chead\u003e\u003cmeta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"\u003e\u003c/head\u003e\n                   #   \u003cbody style=\"color:green;\"\u003e\u003c/body\u003e\n                   # \u003c/html\u003e\n```\n\nIf a referenced stylesheet cannot be found, the `#transform` method will raise an `Roadie::CssNotFound` error. If you instead want to ignore missing stylesheets, you can use the `NullProvider`.\n\n### Configuring providers ###\n\nYou can write your own providers if you need very specific behavior for your app, or you can use the built-in providers. Providers come in two groups: normal and external. Normal providers handle paths without host information (`/style/foo.css`) while external providers handle URLs with host information (`//example.com/foo.css`, `localhost:3001/bar.css`, and so on).\n\nThe default configuration is to not have any external providers configured, which will cause those referenced stylesheets to be ignored. Adding one or more providers for external assets causes all of them to be searched and inlined, so if you only want this to happen to specific stylesheets you need to add ignore markers to every other styleshheet (see above).\n\nIncluded providers:\n* `FilesystemProvider` – Looks for files on the filesystem, relative to the given directory unless otherwise specified.\n* `ProviderList` – Wraps a list of other providers and searches them in order. The `asset_providers` setting is an instance of this. It behaves a lot like an array, so you can push, pop, shift and unshift to it.\n* `NullProvider` – Does not actually provide anything, it always finds empty stylesheets. Use this in tests or if you want to ignore stylesheets that cannot be found by your other providers (or if you want to force the other providers to never run).\n* `NetHttpProvider` – Downloads stylesheets using `Net::HTTP`. Can be given a whitelist of hosts to download from.\n* `CachedProvider` – Wraps another provider (or `ProviderList`) and caches responses inside the provided cache store.\n* `PathRewriterProvider` – Rewrites the passed path and then passes it on to another provider (or `ProviderList`).\n\nIf you want to search several locations on the filesystem, you can declare that:\n\n```ruby\ndocument.asset_providers = [\n  Roadie::FilesystemProvider.new(App.root.join(\"resources\", \"stylesheets\")),\n  Roadie::FilesystemProvider.new(App.root.join(\"system\", \"uploads\", \"stylesheets\")),\n]\n```\n\n#### `NullProvider` ####\n\nIf you want to ignore stylesheets that cannot be found instead of crashing, push the `NullProvider` to the end:\n\n```ruby\n# Don't crash on missing assets\ndocument.asset_providers \u003c\u003c Roadie::NullProvider.new\n\n# Don't download assets in tests\ndocument.external_asset_providers.unshift Roadie::NullProvider.new\n```\n\n**Note:** This will cause the referenced stylesheet to be removed from the source code, so email client will never see it either.\n\n#### `NetHttpProvider` ####\n\nThe `NetHttpProvider` will download the URLs that is is given using Ruby's standard `Net::HTTP` library.\n\nYou can give it a whitelist of hosts that downloads are allowed from:\n\n```ruby\ndocument.external_asset_providers \u003c\u003c Roadie::NetHttpProvider.new(\n  whitelist: [\"myapp.com\", \"assets.myapp.com\", \"cdn.cdnnetwork.co.jp\"],\n)\ndocument.external_asset_providers \u003c\u003c Roadie::NetHttpProvider.new # Allows every host\n```\n\n#### `CachedProvider` ####\n\nYou might want to cache providers from working several times. If you are sending several emails quickly from the same process, this might also save a lot of time on parsing the stylesheets if you use in-memory storage such as a hash.\n\nYou can wrap any other kind of providers with it, even a `ProviderList`:\n\n```ruby\ndocument.external_asset_providers = Roadie::CachedProvider.new(document.external_asset_providers, my_cache)\n```\n\nIf you don't pass a cache backend, it will use a normal `Hash`. The cache store must follow this protocol:\n\n```ruby\nmy_cache[\"key\"] = some_stylesheet_instance # =\u003e #\u003cRoadie::Stylesheet instance\u003e\nmy_cache[\"key\"]                            # =\u003e #\u003cRoadie::Stylesheet instance\u003e\nmy_cache[\"missing\"]                        # =\u003e nil\n```\n\n**Warning:** The default `Hash` store will never be cleared, so make sure you don't allow the number of unique asset paths to grow too large in a single run. This is especially important if you run Roadie in a daemon that accepts arbritary documents, and/or if you use hash digests in your filenames. Making a new instance of `CachedProvider` will use a new `Hash` instance.\n\nYou can implement your own custom cache store by implementing the `[]` and `[]=` methods.\n\n```ruby\nclass MyRoadieMemcacheStore\n  def initialize(memcache)\n    @memcache = memcache\n  end\n\n  def [](path)\n    css = memcache.read(\"assets/#{path}/css\")\n    if css\n      name = memcache.read(\"assets/#{path}/name\") || \"cached #{path}\"\n      Roadie::Stylesheet.new(name, css)\n    end\n  end\n\n  def []=(path, stylesheet)\n    memcache.write(\"assets/#{path}/css\", stylesheet.to_s)\n    memcache.write(\"assets/#{path}/name\", stylesheet.name)\n    stylesheet # You need to return the set Stylesheet\n  end\nend\n\ndocument.external_asset_providers = Roadie::CachedProvider.new(\n  document.external_asset_providers,\n  MyRoadieMemcacheStore.new(MemcacheClient.instance)\n)\n```\n\nIf you are using Rspec, you can test your implementation by using the shared examples for the \"roadie cache store\" role:\n\n```ruby\nrequire \"roadie/rspec\"\n\ndescribe MyRoadieMemcacheStore do\n  let(:memcache_client) { MemcacheClient.instance }\n  subject { MyRoadieMemcacheStore.new(memcache_client) }\n\n  it_behaves_like \"roadie cache store\" do\n    before { memcache_client.clear }\n  end\nend\n```\n\n#### `PathRewriterProvider` ####\n\nWith this provider, you can rewrite the paths that are searched in order to more easily support another provider. Examples could include rewriting absolute URLs into something that can be found on the filesystem, or to access internal hosts instead of external ones.\n\n```ruby\nfilesystem = Roadie::FilesystemProvider.new(\"assets\")\ndocument.asset_providers \u003c\u003c Roadie::PathRewriterProvider.new(filesystem) do |path|\n  path.sub('stylesheets', 'css').downcase\nend\n\ndocument.external_asset_providers = Roadie::PathRewriterProvider.new(filesystem) do |url|\n  if url =~ /myapp\\.com/\n    URI.parse(url).path.sub(%r{^/assets}, '')\n  else\n    url\n  end\nend\n```\n\nYou can also wrap a list, for example to implement `external_asset_providers` by composing the normal `asset_providers`:\n\n```ruby\ndocument.external_asset_providers =\n  Roadie::PathRewriterProvider.new(document.asset_providers) do |url|\n    URI.parse(url).path\n  end\n```\n\n### Writing your own provider ###\n\nWriting your own provider is also easy. You need to provide:\n * `#find_stylesheet(name)`, returning either a `Roadie::Stylesheet` or `nil`.\n * `#find_stylesheet!(name)`, returning either a `Roadie::Stylesheet` or raising `Roadie::CssNotFound`.\n\n```ruby\nclass UserAssetsProvider\n  def initialize(user_collection)\n    @user_collection = user_collection\n  end\n\n  def find_stylesheet(name)\n    if name =~ %r{^/users/(\\d+)\\.css$}\n      user = @user_collection.find_user($1)\n      Roadie::Stylesheet.new(\"user #{user.id} stylesheet\", user.stylesheet)\n    end\n  end\n\n  def find_stylesheet!(name)\n    find_stylesheet(name) or\n      raise Roadie::CssNotFound.new(\n        css_name: name, message: \"does not match a user stylesheet\", provider: self\n      )\n  end\n\n  # Instead of implementing #find_stylesheet!, you could also:\n  #     include Roadie::AssetProvider\n  # That will give you a default implementation without any error message. If\n  # you have multiple error cases, it's recommended that you implement\n  # #find_stylesheet! without #find_stylesheet and raise with an explanatory\n  # error message.\nend\n\n# Try to look for a user stylesheet first, then fall back to normal filesystem lookup.\ndocument.asset_providers = [\n  UserAssetsProvider.new(app),\n  Roadie::FilesystemProvider.new('./stylesheets'),\n]\n```\n\nYou can test for compliance by using the built-in RSpec examples:\n\n```ruby\nrequire 'spec_helper'\nrequire 'roadie/rspec'\n\ndescribe MyOwnProvider do\n  # Will use the default `subject` (MyOwnProvider.new)\n  it_behaves_like \"roadie asset provider\", valid_name: \"found.css\", invalid_name: \"does_not_exist.css\"\n\n  # Extra setup just for these tests:\n  it_behaves_like \"roadie asset provider\", valid_name: \"found.css\", invalid_name: \"does_not_exist.css\" do\n    subject { MyOwnProvider.new(...) }\n    before { stub_dependencies }\n  end\nend\n```\n\n### Keeping CSS that is impossible to inline\n\nSome CSS is impossible to inline properly. `:hover` and `::after` comes to\nmind. Roadie tries its best to keep these around by injecting them inside a new\n`\u003cstyle\u003e` element in the `\u003chead\u003e` (or at the beginning of the partial if\ntransforming a partial document).\n\nThe problem here is that Roadie cannot possible adjust the specificity for you,\nso they will not apply the same way as they did before the styles were inlined.\n\nAnother caveat is that a lot of email clients does not support this (which is\nthe entire point of inlining in the first place), so don't put anything\nimportant in here. Always handle the case of these selectors not being part of\nthe email.\n\n#### Specificity problems ####\n\nInlined styles will have much higher specificity than styles in a `\u003cstyle\u003e`. Here's an example:\n\n```html\n\u003cstyle\u003ep:hover { color: blue; }\u003c/style\u003e\n\u003cp style=\"color: green;\"\u003eHello world\u003c/p\u003e\n```\n\nWhen hovering over this `\u003cp\u003e`, the color will not change as the `color: green` rule takes precedence. You can get it to work by adding `!important` to the `:hover` rule.\n\nIt would be foolish to try to automatically inject `!important` on every rule automatically, so this is a manual process.\n\n#### Turning it off ####\n\nIf you'd rather skip this and have the styles not possible to inline disappear, you can turn off this feature by setting the `keep_uninlinable_css` option to false.\n\n```ruby\ndocument.keep_uninlinable_css = false\n```\n\n### Callbacks ###\n\nCallbacks allow you to do custom work on documents before they are transformed. The Nokogiri document tree is passed to the callable along with the `Roadie::Document` instance:\n\n```ruby\nclass TrackNewsletterLinks\n  def call(dom, document)\n    dom.css(\"a\").each { |link| fix_link(link) }\n  end\n\n  def fix_link(link)\n    divider = (link['href'] =~ /?/ ? '\u0026' : '?')\n    link['href'] = link['href'] + divider + 'source=newsletter'\n  end\nend\n\ndocument.before_transformation = -\u003e(dom, document) {\n  logger.debug \"Inlining document with title #{dom.at_css('head \u003e title').try(:text)}\"\n}\ndocument.after_transformation = TrackNewsletterLinks.new\n```\n\n### XHTML vs HTML ###\n\nYou can configure the underlying HTML/XML engine to output XHTML or HTML (which\nis the default). One usecase for this is that `{` tokens usually gets escaped\nto `\u0026#123;`, which would be a problem if you then pass the resulting HTML on to\nsome other templating engine that uses those tokens (like Handlebars or Mustache).\n\n```ruby\ndocument.mode = :xhtml\n```\n\nThis will also affect the emitted `\u003c!DOCTYPE\u003e` if transforming a full document.\nPartial documents does not have a `\u003c!DOCTYPE\u003e`.\n\nBuild Status\n------------\n\nTested with Github CI using:  \nSee [Workflow Matrix](/.github/workflows/main.yml)\n\nLet me know if you want any other runtime supported officially.\n\n### Versioning ###\n\nThis project follows [Semantic Versioning](http://semver.org/) and has been since version 1.0.0.\n\nFAQ\n---\n\n### Why is my markup changed in subtle ways?\n\nRoadie uses Nokogiri to parse and regenerate the HTML of your email, which means that some unintentional changes might show up.\n\nOne example would be that Nokogiri might remove your `\u0026nbsp;`s in some cases.\n\nAnother example is Nokogiri's lack of HTML5 support, so certain new element might have spaces removed. I recommend you don't use HTML5 in emails anyway because of bad email client support (that includes web mail!).\n\n### I'm getting segmentation faults (or other C-like problems)! What should I do? ###\n\nRoadie uses Nokogiri to parse the HTML of your email, so any C-like problems like segfaults are likely in that end. The best way to fix this is to first upgrade libxml2 on your system and then reinstall Nokogiri.\nInstructions on how to do this on most platforms, see [Nokogiri's official install guide](http://nokogiri.org/tutorials/installing_nokogiri.html).\n\n### What happened to my `@keyframes`?\n\nThe CSS Parser used in Roadie does not handle keyframes. I don't think any email clients do either, but if you want to keep on trying you can add them manually to a `\u003cstyle\u003e` element (or a separate referenced stylesheet) and [tell Roadie not to touch them](#referenced-stylesheets).\n\n### My `@media` queries are reordered, how can I fix this?\n\nDifferent `@media` query blocks with the same conditions are merged by default,\nwhich will change the order in some cases. You can disable this by setting\n`merge_media_queries` to `false`. (See *Install \u0026 Usage* section above).\n\n### How do I get rid of the `\u003cbody\u003e` elements that are added?\n\nIt sounds like you want to transform a partial document. Maybe you are building\npartials or template fragments to later place in other documents. Use\n`Document#transform_partial` instead of `Document#transform` in order to treat\nthe HTML as a partial document.\n\n### Can I skip URL rewriting on a specific element?\n\nIf you add the `data-roadie-ignore` attribute on an element, URL rewriting will\nnot be performed on that element. This could be really useful for you if you\nintend to send the email through some other rendering pipeline that replaces\nsome placeholders/variables.\n\n```html\n\u003ca href=\"/about-us\"\u003eAbout us\u003c/a\u003e\n\u003ca href=\"|UNSUBSCRIBE_URL|\" data-roadie-ignore\u003eUnsubscribe\u003c/a\u003e\n```\n\nNote that this will not skip CSS inlining on the element; it will still get the\ncorrect styles applied.\n\n### What should I do about \"Invalid URL\" errors?\n\nIf the URL is invalid on purpose, see _Can I skip URL rewriting on a specific\nelement?_ above. Otherwise, you can try to parse it yourself using Ruby's `URI`\nclass and see if you can figure it out.\n\n```ruby\nrequire \"uri\"\nURI.parse(\"https://example.com/best image.jpg\") # raises\nURI.parse(\"https://example.com/best%20image.jpg\") # Works!\n```\n\nDocumentation\n-------------\n\n* [Online documentation for gem](https://www.omniref.com/ruby/gems/roadie)\n* [Online documentation for master](https://www.omniref.com/github/Mange/roadie)\n* [Changelog](https://github.com/Mange/roadie/blob/master/Changelog.md)\n\nRunning specs\n-------------\n\n```bash\nbundle install\nrake\n```\n\nSecurity\n--------\n\nRoadie is set up with the assumption that all CSS and HTML passing through it is under your control. It is not recommended to run arbritary HTML with the default settings.\n\nCare has been given to try to secure all file system accesses, but it is never guaranteed that someone cannot access something they should not be able to access.\n\nIn order to secure Roadie against file system access, only use your own asset providers that you yourself can secure against your particular environment.\n\nIf you have found any security vulnerability, please email me at `magnus.bergmark+security@gmail.com` to disclose it. [For very sensitive issues, please use my public GPG key.][gpg] You can also encrypt your message with my public key and open an issue if you do not want to email me directly. Thank you.\n\nHistory and contributors\n------------------------\n\nThis gem was previously tied to Rails. It is now framework-agnostic and supports any type of HTML documents. If you want to use it with Rails, check out [roadie-rails](https://github.com/Mange/roadie-rails).\n\nMajor contributors to Roadie:\n\n* [Arttu Tervo (arttu)](https://github.com/arttu) - Original Asset pipeline support\n* [Ryunosuke SATO (tricknotes)](https://github.com/tricknotes) - Initial Rails 4 support\n* [Leung Ho Kuen (PikachuEXE)](https://github.com/PikachuEXE) - A lot of bug reporting and triaging.\n\nYou can [see all contributors](https://github.com/Mange/roadie/contributors) on GitHub.\n\nLicense\n-------\n\n(The MIT License)\n\nCopyright (c) 2009-2022 Magnus Bergmark, Jim Neath / Purify, and contributors.\n\n* [Magnus Bergmark](https://github.com/Mange) \u003cmagnus.bergmark@gmail.com\u003e\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n[gpg]: https://gist.github.com/Mange/baf25e23e653a206ec2d#file-keybase-md\n[passive]: https://github.com/Mange/roadie/issues/155\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FMange%2Froadie","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FMange%2Froadie","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FMange%2Froadie/lists"}