Looping LinksWith the WP HTML Processor ![rw-book-cover](https://halfelf.org/wp-content/uploads/sites/2/2023/11/16140940887_c931e12824_b.jpg) ## Metadata - Author: [[Ipstenu (Mika Epstein)]] - Full Title: Looping LinksWith the WP HTML Processor - Category: #articles - Document Note: --- tags: - #regex - #wordpress - #htmlprocessor Author: Mika Epstein (Ipstenu) Source: Half-Elf on Tech (Website) Reference: https://halfelf.org/2023/looping-linkswith-the-wp-html-processor/ date created: 2025-08-23 00:00 --- # Title: Extracting Citations from Post Links with WordPress’s HTML Tag Processor ## Table of Contents: - 🧭 Overview (#-overview) - 🧩 Why Not Regex? (#-why-not-regex) - 🧱 HTML Tag Processor: What It Is (#-html-tag-processor-what-it-is) - 🔁 Looping Through Links (#-looping-through-links) - 🏷️ Getting Attributes (href, title) (#-getting-attributes-href-title) - 🧷 Multiple Passes and Bookmarks (#-multiple-passes-and-bookmarks) - ⚠️ Caveats and Limits (#-caveats-and-limits) - 📚 References (#-references) ### References: Include a list of sources consulted and cited in the article. - WordPress Core: WP_HTML_Tag_Processor class reference - WordPress 6.1+ release notes and developer blog on HTML Processor - Article: “Looping LinksWith The WP HTML Processor” (Half-Elf on Tech) ## 📝 Summary: The article shows two ways to extract citations from links in WordPress post content: a quick-and-dirty regex approach versus a more robust way using WP_HTML_Tag_Processor (introduced in WP 6.1+). The goal is to scan post content for all <a> tags, filter by a target domain (e.g., wikipedia.com), and build a citations array using either the link’s title attribute or the URL as a fallback. The HTML Tag Processor provides a readable, extensible, DOM-like iteration with next_tag(), get_attribute(), and set_bookmark() to support multiple passes without missing nodes. ## 📖 Main Content: The core use case: programmatically gather citations from links embedded in post content. Regex can match <a> tags and capture href/title, but becomes brittle and hard to extend. The HTML Tag Processor offers a safer API: - Instantiate with post content. - Iterate across tags using next_tag('a'). - For each link, check href for the target domain (e.g., contains “wikipedia.com”). - Retrieve and normalize the title via get_attribute('title'), falling back to href where needed. - For multi-pass scans (e.g., also collect spans with class “wikipedia”), use set_bookmark() to return to a known position and avoid skipping elements. Personal take: - The shift from regex to WP_HTML_Tag_Processor mirrors the long-standing advice: don’t parse HTML with regex unless the scope is tiny and controlled. For WordPress, where content is diverse and often imperfectly formed, a tolerant, streaming, DOM-like processor is far safer. - The bookmark pattern is the key mental model: treat the processor as a forward-only cursor; set_bookmark() when you need to branch or make multiple targeted passes. ### 🗒️ Notes - Problem framing - Need: Extract citations by scanning all links in post content. - Filter: Only keep links matching a target domain (e.g., wikipedia.com). - Output: Array of “Source: <title or URL>”. - Regex approach - Pros: Short, familiar to many devs, works for simple cases. - Cons: Fragile with messy HTML; hard to extend; readability suffers; multiple passes are awkward. - Pattern: preg_match_all to capture href/title; conditional on domain; fallback to URL if no title. - HTML Tag Processor (WordPress 6.1+) - Concept: DOM-like, tag-by-tag iteration over post content. - Start: $content_tags = new WP_HTML_Tag_Processor( get_the_content() ); - Loop: while ( $content_tags->next_tag( 'a' ) ) { ... } - Attributes: $href = $content_tags->get_attribute( 'href' ); $title = $content_tags->get_attribute( 'title' ); - Safety: get_attribute() returns null if not present; easy null checks. - Readability: Clear intent; easily extended to other tags/attributes. - Multiple passes and bookmarks - Challenge: Single forward pass means a second pass (e.g., spans) starting after last link. - Risk: Missing earlier elements when changing target tags mid-stream. - Solution: set_bookmark() before a pass; after first scan, go back to bookmark and run another scan criteria. - Tradeoff: Slightly more complex control flow but reliable coverage. - Caveats - Processor iterates forward-only; use bookmarks to avoid misses. - Tag matching is precise; plan queries and pass order carefully. - Still not a full DOM; it’s a focused, streaming-like processor. ## 📖 References: - WordPress Developer Docs: WP_HTML_Tag_Processor (WordPress 6.1+) - Half-Elf on Tech: “Looping LinksWith The WP HTML Processor” https://halfelf.org/2023/looping-linkswith-the-wp-html-processor/ ## 🗣️ Paraphrase: - Original idea: “You need to search all the links in a post and, if the link is to a specific site (wikipedia.com) you want to add it to an array you output at the bottom of your post, as citations.” - Paraphrase: The task is to scan every link within a post’s content, keep only those pointing to a specific domain such as wikipedia.com, and compile them into a citations list rendered at the end of the post. - Original idea: “WordPress now has a better way to do this, that’s both more readable and extendable.” - Paraphrase: WordPress offers a newer, cleaner, and more maintainable method to achieve this that scales better than regex for future enhancements. ## 📰 Newsletter: WordPress devs: tired of wrestling regex to wrangle citations? With WP 6.1+, the WP_HTML_Tag_Processor lets you iterate through post content like a friendly, DOM-ish cursor. Scan every link, filter for domains like wikipedia.com, pull titles with get_attribute('title'), and build a tidy citations list—no cryptic patterns required. Bonus: need multiple passes (e.g., links and spans)? Use set_bookmark() to jump back and avoid missing nodes. It’s cleaner, safer, and easier to extend. While you’re at it, explore modern plugin patterns, keep an eye on core dev notes, and try new accessibility-first components. Further reading: “Looping LinksWith The WP HTML Processor” (Half-Elf on Tech). Ship better citations; save your sanity. ## ✅ Action Items: Prompt for alignment with Open Source Advocacy, Contributor Engagement, Education, and OSPO Goals: - Summarize how this article demonstrates improving developer experience in WordPress by replacing fragile regex with a standardized core API (WP_HTML_Tag_Processor). - Map the technique to open source sustainability: fewer bugs from regex pitfalls reduces maintenance burden on contributors and support teams. - Highlight contributor onboarding potential: this API provides a clear pathway for new contributors to extend parsing logic (e.g., adding attributes, multi-pass scans) with readable code. - Identify educational opportunities: create workshops/labs teaching iteration with next_tag(), safe attribute access, and set_bookmark() for multiple passes; compare against regex pitfalls. - Emphasize transparency/inclusivity: encourage accessible code examples, clear naming, and robust error-handling patterns that help diverse contributor backgrounds. - OSPO trajectory: position this approach as a case study in adopting core, well-documented APIs to reduce tech debt and improve governance of content-processing logic across teams. - Community impact: propose a contributor doc and sample plugin template that demonstrates citation extraction; invite feedback and co-maintainers; explore funding through sponsorships for maintaining documentation and tests. - Speaking/writing: craft talks and posts showing practical migrations from regex to HTML Processor with benchmarks, tests, and accessibility considerations. ## 🧪 Hypotheses: - H1: Using WP_HTML_Tag_Processor reduces citation extraction defects compared to regex in heterogeneous post content. - H2: Developer onboarding time is shorter with the Tag Processor due to clearer APIs and fewer edge-case failures. - H3: Multi-pass parsing with set_bookmark() yields higher coverage of target elements than ad hoc regex re-runs. - H4: Codebases using the Tag Processor have lower maintenance costs over 12 months versus regex-based parsers. ### 📊 Methodology: - Comparative experiment: Implement both methods across a corpus of posts (classic, block, shortcodes, malformed HTML). - Data: Production-like content exports; synthetic edge-case posts. - Summary: The WP HTML Processor is a tool that allows users to search for specific links in a WordPress post and add them to an array for citation purposes. While this task can be accomplished with regex, the HTML Processor class provides a more readable and extendable solution. The class functions by treating HTML tags as nodes and allows users to loop through all the links in a post and perform checks on them. This eliminates the need for complex regex patterns and provides a more efficient way to process HTML tags in WordPress. - URL: https://halfelf.org/2023/looping-linkswith-the-wp-html-processor/ ![Coronal Loops Reveal Magnetic Dance](https://i0.wp.com/halfelf.org/wp-content/uploads/sites/2/2023/11/16140940887_c931e12824_b.jpg?w=1024&ssl=1)Coronal Loops Reveal Magnetic Dance Here’s your backstory. You need to search all the links in a post and, if the link is to a specific site (`wikipedia.com`) you want to add it to an array you output at the bottom of your post, as citations. To do this, you will: 1. Search for tags for every single link (`<a href=....` ) 2. If the link contains our term (Wikipedia), put it in the array 3. If the link also has a title, we’ll use that If we do all that, our output looks something like this: > * Source: `https://en.wikipedia.com/wiki/foobar` > * Source: Foobar2 > > While you can do this with regex, you can also use the (new) [HTML Processor](https://developer.wordpress.org/reference/classes/wp_html_processor/) class to do it for you. #### RegEx As I mentioned, you can do this with regex (I’ll spare you the drama of coming up with this in the first place): | | | --- | | `$citations` `=` `array``();``preg_match_all(` `'#<\s*a[^>]*href="([^"]+)"[^>]*>.*?<\s*/\s*a>#'``, get_the_content(),` `$matches` `);``// Loop through the matches:``foreach` `(` `$matches``[0]` `as` `$i` `=>` `$match` `) {``// If the URL contains WikiPedia, we'll process:``if` `( str_contains(` `$match``,` `'wikipedia.com'``) ) {``// Build initial data:``$current_citation` `=[``'url'` `=>` `$matches``[1][``$i``],``'title'` `=>` `$matches``[1][``$i``],``];``// If there's a title, use it.``if` `( str_contains(` `$match``,` `'title='` `) ) {``$title_match` `= preg_match(` `'#<\s*a[^>]*title="([^"]+)"*[^>]*>.*?<\s*/\s*a>#'``,` `$match``,` `$title_matches` `);``$current_citation``[``'title'``] = ( !` `empty``(` `$title_matches``[1] ) ) ?` `$title_matches``[1] :` `$current_citation``[``'title'``];``}``}``$citations``[] =` `$current_citation``;``}``?>``<ol>``<?php` `foreach` `(` `$citations` `as` `$citation` `): ?>``<li itemprop=``"citation"` `itemscope itemtype=``"https://schema.org/CreativeWork"``>``Source: <a rel=``"noopener noreferrer external"` `itemprop=``"url"` `class``=``"wikipedia-article-citations__url"` `target=``"_blank"` `href=``"<?php echo esc_url( $citation['url'] ) ?>"``><?php` `echo` `esc_html(` `$citation``[``'title'``] ) ?></a>``</li>``<?php` `endforeach``; ?>``</ol>` | This is a very over simplified version, but the basis is sound. This will loop through the whole post, find everything with a URL, check if the URL includes `wikipedia.com` and output a link to it. If the editor added in a link title, it will use that, and if not, it falls back to the URL itself. But … a lot of people will tell you Regex is super powerful and a pain in the ass (it is). And WordPress now has a better way to do this, that’s both more readable and extendable. #### HTML Tag Processor Let’s try this again. What even *is* this processor? Well it’s basically building out something similar to [DOM Nodes](https://developer.mozilla.org/en-US/docs/Web/API/Node) of all your HTMLin a WordPress post and letting us edit them. They’re not *really* DOM nodes, though, they’re a weird little subset, but if you think of each HTML tag as a ‘node’ it may help. To start using it, we’re going to ditch regex entirely, but we still want to process our tags from the whole content, so we’ll ask WordPress to use the new class to build our our tags: `$content_tags = new WP_HTML_Tag_Processor( get_the_content() );` This makes the object which also lets us use all the children functions. In this case, we know we want URLs so we can use `[next\_tag()](https://developer.wordpress.org/reference/classes/wp_html_tag_processor/next_tag/)` to get things: `$content_tags->next_tag( 'a' )`; This finds the next tag matching our query of `a` which is for links. If we were only getting the first item, that would be enough. But we know we have multiple links in posts, so we’re going to need to loop. The good news here is that `next_tag()` in and of itself can keep running! | | | --- | | `while``(` `$content_tags``->next_tag(` `'a'` `) ) {``// Do checks here``}` | That code will actually run through every single link in the post content. Inside the loop, we can check if the URL matches using `[get\_attribute()](https://developer.wordpress.org/reference/classes/wp_html_tag_processor/get_attribute/)`: | | | --- | | `if` `( str_contains(` `$content_tags``->get_attribute(` `'href'` `),` `'wikipedia.com'` `) ) {``// Do stuff here``}` | Since the default of `get_attribute()` is null if it doesn’t exist, this is a safe check, and it means we can reuse it to get the title: | | | --- | | `if` `( !` `is_null``(` `$content_tags``->get_attribute(` `'title'` `) ) ) {``// Change title here``}` | And if we apply all this to our original code, it now looks very different: ##### Example: #### Caveats Since we’re only searching for links, this is pretty easy. There’s a decent example on [looking for multiple items](https://developer.wordpress.org/reference/classes/wp_html_tag_processor/#custom-queries) (say, by class and span) but if you read it, you realize pretty quickly that you have to be doing the exact same thing. If you wanted to do multiple loops though, looking for all the links but also all **span** classes with the class ‘wikipedia’ you’d probably start like this: The problem is that you would only end up looking for any spans that happened **after** the last link! You could go a more complex search and if check, but they’re all risky as you might miss something. To work around this, you’ll use `set_bookmark()` to set a bookmark to loop back to: I admit, I’m not a super fan of that solution, but by gum, it sure works!, Looping LinksWith the WP HTML Processor, Ipstenu (Mika Epstein), 01hg3f84k3n0wdd7nj9gxm75bs.