An href is a URL reference. guide.html does not identify a complete destination by itself: the document’s base supplies the missing components. A relative path is merged with the base directory; a leading slash starts at the origin’s root. A reference beginning // replaces the authority while retaining the scheme. A fully absolute reference can replace both. None of these operations downloads a resource.
Use urllib.parse.urljoin rather than path concatenation. The URL https://example.test/docs/start.html has a final path segment that acts as a file-like base segment; joining next.html gives /docs/next.html. With a trailing-slash base /docs/start/, the result instead ends /docs/start/next.html. Removing that slash as “cleanup” changes meaning. This rule concerns URL syntax and does not inspect whether a server actually stores a file or directory.
from urllib.parse import urljoin
page = "https://example.test/docs/start.html?old=1"
for reference in ["next.html", "../about", "/help", "?q=2", "#part"]:
print(urljoin(page, reference))A query-only reference replaces the query while keeping the path. A fragment-only reference retains the path and query, changing the fragment. An empty reference keeps the base. Resolution preserves information; removing fragments or deciding whether query order matters belongs to a later identity policy. Also, percent-encoded path bytes are not an invitation to decode and rebuild arbitrary URL text. Encoded delimiters can have a different meaning from literal delimiters.
Documents can declare a base with <base href="...">. For this lesson, extraction and base selection are explicit inputs: resolve_links(document_url, references, base_href=None). If a base href is supplied, first resolve it against the document URL, then resolve every link against that effective base. None means no declared base; "" means an explicitly empty base reference. We do not read files, fetch a page or interpret a dynamic browser DOM to discover the base here.
from urllib.parse import urljoin, urlsplit
document = "https://example.test/docs/page.html"
base = urljoin(document, "../assets/")
result = urljoin(base, "images/logo.svg#shape")
print(result)
print(urlsplit(result).fragment)An absolute or network-path reference can leave the original origin. That is valid resolution behavior. urljoin does not enforce a same-origin rule, verify a scheme or establish that a URL is safe to request. If the next operation is a real request, application security must handle destination policy, DNS/IP behavior and redirects separately. This course performs no network requests and does not claim to implement SSRF defenses.
In the build, preserve reference order and duplicates, leave inputs unchanged and return one resolved string per reference. Work through a two-stage declared base, a root-relative link, a network-path link, a fragment and an empty list. Include a non-HTTP reference such as mailto: to prove resolution has not silently become an HTTP filtering policy. The next lesson will define which resolved URLs belong in our document inventory.