PhoenixKitEcommerce.HtmlToMarkdown (PhoenixKitEcommerce v0.5.0)

Copy Markdown View Source

Converts HTML (as Shopify's body_html product field always is) into Markdown.

The storefront that consumes synced products renders descriptions through a Markdown component. By CommonMark rules, a block starting with a raw <p> tag is treated as opaque raw HTML, so Markdown already written inside Shopify's body_html (sellers routinely hand-write **bold**/- list text inside <p> tags) is emitted byte-for-byte instead of rendering. Converting body_html to Markdown at sync time keeps stored descriptions renderable.

This is a small hand-rolled HTML -> Markdown transform rather than a LazyHTML/Floki-based one: both are declared only: :test in mix.exs, and this module runs on the Shopify sync write path in every environment (not just :test) — pulling a test-only parser into :dev/:prod would be a wider dependency-footprint change than this fix calls for.

Supported tags: p, br, h1-h6, ul/ol/li (including lists nested inside a <li>, rendered as an indented sub-list), table (thead/tbody/tfoot/tr/td/th), strong/b, em/i, a, img, plus a transparent div wrapper and HTML entity decoding (&amp;, &nbsp;, &quot;, &#39;, numeric character references). <script>, <style>, <noscript> and <template> elements are dropped entirely, content included, rather than leaking their raw text. A bare </> that isn't part of a real tag (e.g. "5 < 10") is left as plain text instead of being parsed as a tag boundary. Text with no HTML tag at all is returned byte-for-byte unchanged, which is what makes convert/1 idempotent — converting an already-converted (or always-plain) value is a no-op. Markdown already present in text nodes (**bold**, - item) is never escaped, it is copied through verbatim.

<table> becomes a GitHub-Flavored-Markdown pipe table (header row, --- separator, data rows) rather than dropping the structure — a naive cell-concatenation would silently glue adjacent cells' text together with no separator, which loses information a reader can't recover. The header row is whichever row is inside <thead>, or the first row containing a <th>, or — if neither marker is present — the table's first row, promoted, so the output is always a valid table; any other <thead>-tagged rows are folded into the body instead of being dropped. Cell text has its own line breaks collapsed to spaces and | escaped to |, since a table row is a single Markdown line.

Summary

Functions

Converts html to Markdown. Text that contains no HTML tag at all is returned unchanged.

Functions

convert(html)

@spec convert(String.t() | nil) :: String.t() | nil

Converts html to Markdown. Text that contains no HTML tag at all is returned unchanged.