Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions php-transformer/src/HtmlToBlocks/HtmlTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4080,18 +4080,14 @@ private function hasSidebarAndContentChildren(DOMElement $element): bool
*/
private function navigationSectionBlockFromElement(DOMElement $element): ?array
{
if ( ! $this->hasNavigationContainerSignal($element) ) {
return null;
}

$heading = null;
$anchors = array();
foreach ( $element->childNodes as $child ) {
if ( XML_TEXT_NODE === $child->nodeType && '' === trim($child->textContent ?? '') ) {
continue;
}

if ( $child instanceof DOMElement && preg_match('/^h[1-6]$/i', $child->tagName) ) {
if ( $child instanceof DOMElement && $this->isNavigationSectionHeading($child) ) {
if ( $heading instanceof DOMElement ) {
return null;
}
Expand All @@ -4111,6 +4107,10 @@ private function navigationSectionBlockFromElement(DOMElement $element): ?array
return null;
}

if ( ! $this->hasNavigationContainerSignal($element) && ! $this->hasSoftNavigationSectionHeadingSignal($heading) ) {
return null;
}

$sectionFallbacks = array();
$blocks = array( $this->convertElement($heading, $sectionFallbacks, true) );
$links = array();
Expand All @@ -4126,6 +4126,25 @@ private function navigationSectionBlockFromElement(DOMElement $element): ?array
return $this->createBlock('core/group', $this->presentationAttributes($element), array_values(array_filter($blocks)), $element);
}

private function isNavigationSectionHeading(DOMElement $element): bool
{
if ( preg_match('/^h[1-6]$/i', $element->tagName) ) {
return true;
}

if ( ! in_array(strtolower($element->tagName), array( 'div', 'p', 'span' ), true) || '' === trim($element->textContent ?? '') ) {
return false;
}

$name = strtolower(trim($this->attr($element, 'class') . ' ' . $this->attr($element, 'id') . ' ' . $this->attr($element, 'role') . ' ' . $this->attr($element, 'aria-label')));
return (bool) preg_match('/(?:^|[\s_-])(?:heading|label|title)(?:$|[\s_-])/', $name);
}

private function hasSoftNavigationSectionHeadingSignal(DOMElement $element): bool
{
return ! preg_match('/^h[1-6]$/i', $element->tagName) && $this->isNavigationSectionHeading($element);
}

private function hasNavigationContainerSignal(DOMElement $element): bool
{
if ( 'navigation' === strtolower($this->attr($element, 'role')) ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ private function navigationLinkBlock(DOMElement $anchor, callable $presentationA

private function navigationLabel(string $html): string
{
$html = preg_replace('/<svg\b[^>]*>.*?<\/svg>/is', '', $html) ?? $html;
$html = preg_replace('/<span\b[^>]*>\s*<\/span>/i', '', $html) ?? $html;
$html = preg_replace('/<([a-z][a-z0-9]*)\b[^>]*\baria-hidden\s*=\s*(["\'])?true\2[^>]*>\s*<\/\1>/i', '', $html) ?? $html;
$html = preg_replace('/<\/?(?:' . self::BLOCK_LEVEL_LABEL_TAGS . ')\b[^>]*>/i', '', $html) ?? $html;
return trim($html);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"schema": "blocks-engine/php-transformer/parity-fixture/v1",
"name": "html-footer-labeled-link-clusters",
"description": "Converts footer and mobile navigation link clusters introduced by labeled span headings into grouped navigation blocks.",
"source_reference": {
"repo": "php-transformer",
"path": "tests/fixtures/parity/html-footer-labeled-link-clusters.json",
"notes": "Generic fixture based on SSI footer CTA/navigation columns and mobile drawer sections."
},
"legacy_comparison": {
"skip": true,
"reason": "This upstream primitive fixture has no downstream legacy comparison."
},
"operation": "html_transformer.transform",
"input": {
"content": "<footer class=\"site-footer\" role=\"contentinfo\"><div class=\"footer-top\"><div><span class=\"footer-nav__heading\">Shop</span><a href=\"shop-all.html\" class=\"footer-nav__link\">Shop All</a><a href=\"bundles.html\" class=\"footer-nav__link\">Bundles</a><a href=\"by-outing.html\" class=\"footer-nav__link\">By Outing</a></div><div class=\"mobile-nav__section\"><span class=\"mobile-nav__label\">Support</span><a href=\"help.html\" class=\"mobile-nav__link\">Help Center</a><a href=\"returns.html\" class=\"mobile-nav__link\">Returns</a></div></div></footer>"
},
"expected_blocks": [
{ "path": "blocks.0", "name": "core/group", "attrs": { "className": "site-footer" } },
{ "path": "blocks.0.innerBlocks.0", "name": "core/group", "attrs": { "className": "footer-top" } },
{ "path": "blocks.0.innerBlocks.0.innerBlocks.0", "name": "core/group" },
{ "path": "blocks.0.innerBlocks.0.innerBlocks.0.innerBlocks.0", "name": "core/paragraph" },
{ "path": "blocks.0.innerBlocks.0.innerBlocks.0.innerBlocks.1", "name": "core/navigation" },
{ "path": "blocks.0.innerBlocks.0.innerBlocks.0.innerBlocks.1.innerBlocks.0", "name": "core/navigation-link", "attrs": { "label": "Shop All", "url": "shop-all.html", "kind": "custom" } },
{ "path": "blocks.0.innerBlocks.0.innerBlocks.1", "name": "core/group", "attrs": { "className": "mobile-nav__section" } },
{ "path": "blocks.0.innerBlocks.0.innerBlocks.1.innerBlocks.0", "name": "core/paragraph" },
{ "path": "blocks.0.innerBlocks.0.innerBlocks.1.innerBlocks.1", "name": "core/navigation" },
{ "path": "blocks.0.innerBlocks.0.innerBlocks.1.innerBlocks.1.innerBlocks.0", "name": "core/navigation-link", "attrs": { "label": "Help Center", "url": "help.html", "kind": "custom" } }
],
"expected_fallbacks": [],
"expect": [
{ "path": "status", "assert": "equals", "value": "success" },
{ "path": "blocks.0.innerBlocks.0.innerBlocks", "assert": "count", "count": 2 },
{ "path": "blocks.0.innerBlocks.0.innerBlocks.0.innerBlocks.1.innerBlocks", "assert": "count", "count": 3 },
{ "path": "blocks.0.innerBlocks.0.innerBlocks.1.innerBlocks.1.innerBlocks", "assert": "count", "count": 2 },
{ "path": "fallbacks", "assert": "count", "count": 0 },
{ "path": "serialized_blocks", "assert": "contains", "value": "<!-- wp:navigation" },
{ "path": "serialized_blocks", "assert": "not_contains", "value": "<!-- wp:html" },
{ "path": "coverage.0.fallback_count", "assert": "equals", "value": 0 }
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"schema": "blocks-engine/php-transformer/parity-fixture/v1",
"name": "html-nav-decorative-labels",
"description": "Strips decorative SVG and empty icon spans from navigation labels while preserving the link URLs and submenu structure.",
"source_reference": {
"repo": "php-transformer",
"path": "tests/fixtures/parity/html-nav-decorative-labels.json",
"notes": "Generic fixture based on static-site desktop dropdown navigation with chevrons and icon dots in anchors."
},
"legacy_comparison": {
"skip": true,
"reason": "This upstream primitive fixture has no downstream legacy comparison."
},
"operation": "html_transformer.transform",
"input": {
"content": "<nav class=\"main-nav\" aria-label=\"Main navigation\"><div class=\"nav-item\"><a href=\"by-outing.html\" class=\"nav-link\">By Outing <svg class=\"nav-chevron\" viewBox=\"0 0 10 6\"><path d=\"M1 1l4 4 4-4\"></path></svg></a><div class=\"dropdown\"><a href=\"by-outing.html#day-hike\" class=\"dropdown__link\"><span class=\"dropdown__dot\"></span>Day Hike</a><a href=\"by-outing.html#camp\" class=\"dropdown__link\"><span class=\"dropdown__dot\"></span>Weekend Camp</a></div></div><div class=\"nav-item\"><a href=\"bundles.html\" class=\"nav-link\">Bundles</a></div></nav>"
},
"expected_blocks": [
{ "path": "blocks.0", "name": "core/navigation", "attrs": { "className": "main-nav" } },
{ "path": "blocks.0.innerBlocks.0", "name": "core/navigation-submenu", "attrs": { "label": "By Outing", "url": "by-outing.html", "kind": "custom" } },
{ "path": "blocks.0.innerBlocks.0.innerBlocks.0", "name": "core/navigation-link", "attrs": { "label": "Day Hike", "url": "by-outing.html#day-hike", "kind": "custom" } },
{ "path": "blocks.0.innerBlocks.0.innerBlocks.1", "name": "core/navigation-link", "attrs": { "label": "Weekend Camp", "url": "by-outing.html#camp", "kind": "custom" } },
{ "path": "blocks.0.innerBlocks.1", "name": "core/navigation-link", "attrs": { "label": "Bundles", "url": "bundles.html", "kind": "custom" } }
],
"expected_fallbacks": [],
"expect": [
{ "path": "status", "assert": "equals", "value": "success" },
{ "path": "blocks.0.innerBlocks", "assert": "count", "count": 2 },
{ "path": "blocks.0.innerBlocks.0.innerBlocks", "assert": "count", "count": 2 },
{ "path": "fallbacks", "assert": "count", "count": 0 },
{ "path": "serialized_blocks", "assert": "contains", "value": "\"label\":\"By Outing\"" },
{ "path": "serialized_blocks", "assert": "contains", "value": "\"label\":\"Day Hike\"" },
{ "path": "serialized_blocks", "assert": "not_contains", "value": "nav-chevron" },
{ "path": "serialized_blocks", "assert": "not_contains", "value": "dropdown__dot" },
{ "path": "coverage.0.fallback_count", "assert": "equals", "value": 0 }
]
}
Loading