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
(&, , ", ', 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.