Hwaro::Content::Processors::InternalLinkResolver
Constants
One attribute of a tag ($1 = name, quoted, or, after minification,
unquoted value in $2/$3/$4). The leading whitespace keeps
data-id= out.
Matches a fragment link in not-yet-resolved content HTML: an
href="@/path#frag" ($1 = "@/path") or a same-page href="#frag".
The lookbehind keeps data-href= / xlink:href= out.
Matches href="@/path" and href="@/path#anchor"
Cheap any-case probe for a link attribute, behind the byte probes.
One href/src/srcset attribute inside a LINK_TAG_REGEX match, in
any case and quote style: $1 = name=, $2 = name, $3 = the
quote (empty when unquoted), $4 = value. An unquoted value ends at
whitespace or >, and never starts with \: href=\"/x/\" is a
JS-escaped quote in a script string, not a value. The first two
alternatives step over every other quoted value whole, so
title="href='/x'" is not a link; \b keeps data-src= (lazy
loaders) in.
An opening tag carrying at least one href/src/srcset outside
its quoted values (any case). As in HTML, a quote opens a value only
right after = (a stray one, alt=don't, is text); quoted values
are skipped whole. The unquoted run stops only at an h/s to test
for a link name, so every other tag fails without a match being
built. A < ends an unquoted run, so a stray < in text cannot
drag a scan to the next >. Possessive, so nothing backtracks.
A URI scheme prefix (e.g. https:, mailto:, tel:, data:).
An opening tag: $1 = tag name, $2 = its attributes. Quoted values
are skipped whole, so a > inside title="a>b" does not end the tag.
Possessive, so a tag with thousands of attributes cannot exhaust the
JIT stack (Regex::Error) on backtracking frames.
Instance methods
Make every relative href/src in html absolute against page_url
(an absolute URL like "https://host/blog/post/").
Feed bodies (RSS content:encoded, Atom <content>) are consumed out
of the page's URL context, so document-relative ("../x/") and
root-relative ("/img.svg") links must be absolutized — otherwise a
reader resolves them against the feed/reader URL and they break.
Absolute URLs, protocol-relative (//host), scheme links
(mailto:/tel:/data:), and pure in-page anchors (#x) are left
untouched. A no-op when page_url is not an absolute URL (no host
to resolve against — e.g. an empty base_url deploy).
Every candidate of a srcset (the responsive-image pass injects
them, authors write them in <img>/<picture>) is resolved too,
descriptors kept.
document_relative_only is for a page that is moved verbatim to a
different directory (the AMP mirror under /amp/…): only the values
that resolve against the document's own location ("cover.png",
"../b2/") are rewritten, root-relative ones keep their bytes, and a
host-less page_url ("/posts/b1/") yields root-relative results.
Index a default-language page whose file spells its suffix out
(about.en.md) under the bare path too (about.md), the way
get_page(path="about.md") finds it — @/about.md otherwise warned
and shipped literally. A literal file of the bare name keeps its key.
Multilingual sites only: elsewhere about.en.md is just a name.
Call on a finished content-path map; returns it.
True when fragment (as written in an href, possibly percent- and
entity-encoded) names an anchor in ids. Like a browser, the raw
fragment is tried before the percent-decoded one, a :~: fragment
directive (scroll-to-text, #:~:text=…) is not part of the id, and
#top always resolves (scrolls to the top when no element has it).
Every anchor target in html, entity-decoded: an id on any element,
or a name on <a> - the only element a browser scrolls to by name.
<meta name="description"> and <input name="q"> are not targets, so
#description must not pass as one.
True when value already carries its own origin: any scheme: URL
(https:, mailto:, tel:, data:, …) or a protocol-relative
//host/…. Such a value must never have a base_url or page-URL
prefix glued onto it.
Single source of truth for every "is this already absolute?" test.
The ad-hoc starts_with?("http") form it replaces was wrong in both
directions: it missed mailto:/data:///cdn… (which then got a
base_url prefix — and //cdn… even took the root-relative branch,
yielding https://site.com//cdn.example.com/og.png), and it
false-positived on a relative path that merely starts with the
letters "http" (http-guide/cover.png), leaving it un-absolutized.
The page an @/ path names. Markd percent-encodes a destination
(@/my note.md arrives as @/my%20note.md), so the decoded path is
tried first: %20 means a space, as it does to Markd. A literal
% file name is still reached when nothing decodes to it (or as
%25). A path captured from rendered HTML carries Markd's &
for a & (@/odd/a&b.md), so that spelling is tried first.
A non-ASCII path that misses verbatim is retried NFC-folded on both sides: macOS, zip and Dropbox hand out decomposed (NFD) file names while links are typed precomposed, and the two never compare equal byte-wise. An exact spelling always wins; the fold scan only runs on a miss, so resolved links cost nothing extra.
Prefix plain root-relative links in rendered content (e.g. a scaffold
or author-written [Posts](/posts/) -> <a href="/posts/">) with the
base_url path component so they resolve under a subpath deployment
(GitHub/GitLab project pages served at https://user.github.io/repo/).
This mirrors Config#with_base_path for content-body anchors that the
template layer never touches. Because page.content is reused by the
feed and search generators, fixing it here also keeps RSS
<content:encoded> and the search index subpath-correct.
A complete no-op when base_url has no subpath (the common
domain-root deploy), so root-relative links are preserved there.
Protocol-relative (//host), absolute (http(s)://), anchor (#),
and already-prefixed links are left untouched.
base_path short-circuits the per-call URI.parse(base_url) when
the caller already holds the memoized Config#base_path (they are
the same value by construction).
Resolve internal @/ links in HTML to actual page URLs.
html— rendered HTML stringpages_by_path— map from content path (e.g. "blog/post.md") to Pagesource_path— path of the page being rendered (for warning messages)base_url— site base_url, used to prepend the path component (e.g. "/noir") so links work when the site is served from a subpath. When empty or root, no prefix is added and behavior matches the previous output.misses— optional accumulator for unresolved links. Each entry is{target, reason}with reason "page not found" or "empty link". Callers running under[links] broken_internal = "error"collect them to fail the build after the render fan-out; warnings are still logged either way.nilkeeps warn-only behavior.