Created
June 11, 2025 19:48
-
-
Save MartinMiles/77162ed433da76e1bca002975ca611e4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.SYNOPSIS | |
Download a page and parse Sitecore debug comments into a JSON hierarchy. | |
.DESCRIPTION | |
Scans a Sitecore-rendered HTML page for <!-- start-component='…' --> and <!-- end-component='…' --> | |
markers, extracts metadata (name, id, uid, placeholder, path), and reconstructs a nested | |
component-to-placeholder tree. Strict UID matching ensures proper pairing of start/end tags, | |
while allowing the root layout to remain on the stack at the end. | |
.PARAMETER Url | |
The page URL to download and parse. Defaults to the local Habitat example. | |
.PARAMETER OutputPath | |
Optional. File path to write the resulting JSON. If omitted, JSON is written to the console. | |
.EXAMPLE | |
# Default URL, output to console | |
.\Get-PlaceholdersStructure.ps1 | |
.EXAMPLE | |
# Custom URL, output to file | |
.\Get-PlaceholdersStructure.ps1 -Url "http://example.com" -OutputPath "page.json" | |
#> | |
param( | |
[Parameter(Mandatory=$false)] | |
[string]$Url = 'http://rssbplatform.dev.local/', | |
[Parameter(Mandatory=$false)] | |
[string]$OutputPath | |
) | |
# Download HTML with timeout | |
try { | |
Write-Host "Downloading HTML from $Url" | |
$req = [System.Net.HttpWebRequest]::Create($Url) | |
$req.Timeout = 10000 | |
$res = $req.GetResponse() | |
$reader = New-Object System.IO.StreamReader($res.GetResponseStream()) | |
$content = $reader.ReadToEnd() | |
$reader.Close(); $res.Close() | |
} catch { | |
Write-Error "Error downloading '$Url': $_" | |
exit 1 | |
} | |
# Capture component markers | |
$pattern = "<!--\s*(start-component|end-component)\s*=\s*'(?<json>\{.*?\})'\s*-->" | |
$matches = [regex]::Matches($content, $pattern, [System.Text.RegularExpressions.RegexOptions]::Singleline) | |
if ($matches.Count -eq 0) { | |
Write-Error "No component markers found." | |
exit 1 | |
} | |
# Parse JSON-like component metadata | |
function Convert-ComponentStringToObject { | |
param([string]$str) | |
$json = $str -replace '(\w+)\s*:', '"$1":' | |
try { return $json | ConvertFrom-Json -ErrorAction Stop } catch { throw "Invalid metadata JSON: $str" } | |
} | |
# Define root layout signature | |
$rootUid = '00000000-0000-0000-0000-000000000000' | |
$rootPlaceholder = '' | |
$rootName = 'Default' | |
# Build hierarchy using strict UID matching | |
$stack = @() | |
$root = $null | |
foreach ($m in $matches) { | |
$type = $m.Groups[1].Value | |
$rawJson = $m.Groups['json'].Value | |
$meta = Convert-ComponentStringToObject $rawJson | |
$isRoot = ($meta.uid -eq $rootUid -and $meta.placeholder -eq $rootPlaceholder -and $meta.name -eq $rootName) | |
if ($type -eq 'start-component') { | |
# Create new node | |
$node = [PSCustomObject]@{ | |
name = $meta.name | |
id = $meta.id | |
uid = $meta.uid | |
placeholder = $meta.placeholder | |
path = $meta.path | |
placeholders = @{} | |
} | |
if (-not $root) { | |
# First start defines root | |
$root = $node | |
} else { | |
# Add to parent placeholders | |
$parent = $stack[-1] | |
$ph = $meta.placeholder | |
if (-not $parent.placeholders.ContainsKey($ph)) { | |
$parent.placeholders[$ph] = @() | |
} | |
$parent.placeholders[$ph] += $node | |
} | |
# Always push for matching (including root) | |
$stack += $node | |
} else { | |
# end-component | |
if ($isRoot) { | |
# ignore root end, but do not pop | |
continue | |
} | |
if (-not $stack) { throw "End without matching start (UID: $($meta.uid))" } | |
$top = $stack[-1] | |
if ($top.uid -ne $meta.uid) { throw "UID mismatch: expected '$($top.uid)', got '$($meta.uid)'" } | |
# Pop matched node | |
$stack = $stack[0..($stack.Count-2)] | |
} | |
} | |
# Only root may remain unmatched | |
if ($stack.Count -gt 1) { | |
Write-Error "Unmatched components remain after parsing:" | |
foreach ($n in $stack[0..($stack.Count-2)]) { Write-Error " - $($n.name) [UID: $($n.uid)]" } | |
exit 1 | |
} | |
if (-not $root) { Write-Error "No root component parsed."; exit 1 } | |
# Output JSON | |
$json = $root | ConvertTo-Json -Depth 20 | |
if ($OutputPath) { | |
try { $json | Out-File $OutputPath -Encoding UTF8; Write-Host "JSON written to $OutputPath" } catch { Write-Error "Write error: $_"; exit 1 } | |
} else { | |
Write-Output $json | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "Default", | |
"id": "{FE5D7FDF-89C0-4D99-9AA3-B5FBD009C9F3}", | |
"uid": "00000000-0000-0000-0000-000000000000", | |
"placeholder": "", | |
"path": "/Views/Website/Layouts/Default.cshtml", | |
"placeholders": { | |
"footer": [ | |
{ | |
"name": "Footer", | |
"id": "{299FD8EA-2CFD-4A69-9CA4-7EA24CD34A46}", | |
"uid": "e2040fb7-4151-456d-9315-5973e4eb4d9d", | |
"placeholder": "footer", | |
"path": "/Views/Common/Sublayouts/Footer.cshtml", | |
"placeholders": { | |
"postfooter": [ | |
{ | |
"name": "Copyright", | |
"id": "{7DAE9F23-890F-4E06-99A7-CCEDB74D3D8E}", | |
"uid": "a3bad4d4-8e21-4b30-8c4c-b04163653d62", | |
"placeholder": "postfooter", | |
"path": "/Views/Identity/Copyright.cshtml", | |
"placeholders": {} | |
} | |
], | |
"/footer/section": [ | |
{ | |
"name": "4 Column 2-2-4-4", | |
"id": "{98D6BC34-0185-42A0-B53B-CEB7B17008B6}", | |
"uid": "025588cb-dfc0-4b02-8223-0015c525ae52", | |
"placeholder": "/footer/section", | |
"path": "/Views/Common/Sublayouts/4 Column 2-2-4-4.cshtml", | |
"placeholders": { | |
"/footer/section/col-narrow-4": [ | |
{ | |
"name": "Contact Information", | |
"id": "{CA748838-E6D5-4BAD-9126-CAD10DD1B0DB}", | |
"uid": "84124ee2-e678-4144-8316-0d6874866604", | |
"placeholder": "/footer/section/col-narrow-4", | |
"path": "/Views/Identity/ContactInformation.cshtml", | |
"placeholders": {} | |
} | |
], | |
"/footer/section/col-narrow-2": [ | |
{ | |
"name": "Navigation Links", | |
"id": "{F366FBA9-EF26-4007-988A-63CB64649C46}", | |
"uid": "0e0c6dca-1a29-4975-84d7-e99498a64a72", | |
"placeholder": "/footer/section/col-narrow-2", | |
"path": "~/Views/Navigation/NavigationLinks.cshtml", | |
"placeholders": {} | |
} | |
], | |
"/footer/section/col-narrow-3": [ | |
{ | |
"name": "Content Teaser with Summary", | |
"id": "{781C6B36-4B02-46EA-8ADC-B92A11877F10}", | |
"uid": "47f5a079-6f63-4219-bc45-1a221b200906", | |
"placeholder": "/footer/section/col-narrow-3", | |
"path": "/Views/Teasers/TitleSummaryTeaser.cshtml", | |
"placeholders": {} | |
} | |
], | |
"/footer/section/col-narrow-1": [ | |
{ | |
"name": "Navigation Links", | |
"id": "{F366FBA9-EF26-4007-988A-63CB64649C46}", | |
"uid": "0e0c6dca-1a29-4975-84d7-e99498a64a72", | |
"placeholder": "/footer/section/col-narrow-1", | |
"path": "~/Views/Navigation/NavigationLinks.cshtml", | |
"placeholders": {} | |
} | |
] | |
} | |
} | |
] | |
} | |
} | |
], | |
"page-sidebar": [ | |
{ | |
"name": "xDB Panel", | |
"id": "{CED18F24-62C2-445E-B0D9-045E3325297B}", | |
"uid": "20ab9baa-c112-4567-a701-564423e650d2", | |
"placeholder": "page-sidebar", | |
"path": "~/Views/Demo/ExperienceData.cshtml", | |
"placeholders": { | |
"page-sidebar": [ | |
{ | |
"name": "xDB Panel", | |
"id": "{CED18F24-62C2-445E-B0D9-045E3325297B}", | |
"uid": "20ab9baa-c112-4567-a701-564423e650d2", | |
"placeholder": "page-sidebar", | |
"path": "~/Views/Demo/_ExperienceDataContent.cshtml", | |
"placeholders": { | |
"page-sidebar": [ | |
{ | |
"name": "xDB Panel", | |
"id": "{CED18F24-62C2-445E-B0D9-045E3325297B}", | |
"uid": "20ab9baa-c112-4567-a701-564423e650d2", | |
"placeholder": "page-sidebar", | |
"path": "~/Views/Demo/_Visits.cshtml", | |
"placeholders": {} | |
}, | |
{ | |
"name": "xDB Panel", | |
"id": "{CED18F24-62C2-445E-B0D9-045E3325297B}", | |
"uid": "20ab9baa-c112-4567-a701-564423e650d2", | |
"placeholder": "page-sidebar", | |
"path": "~/Views/Demo/_PersonalInfo.cshtml", | |
"placeholders": {} | |
}, | |
{ | |
"name": "xDB Panel", | |
"id": "{CED18F24-62C2-445E-B0D9-045E3325297B}", | |
"uid": "20ab9baa-c112-4567-a701-564423e650d2", | |
"placeholder": "page-sidebar", | |
"path": "~/Views/Demo/_OnsiteBehavior.cshtml", | |
"placeholders": {} | |
}, | |
{ | |
"name": "xDB Panel", | |
"id": "{CED18F24-62C2-445E-B0D9-045E3325297B}", | |
"uid": "20ab9baa-c112-4567-a701-564423e650d2", | |
"placeholder": "page-sidebar", | |
"path": "~/Views/Demo/_Referral.cshtml", | |
"placeholders": {} | |
} | |
] | |
} | |
} | |
] | |
} | |
} | |
], | |
"navbar": [ | |
{ | |
"name": "Main Navigation", | |
"id": "{E26A158B-FA1E-4E9B-8622-E4458243FF46}", | |
"uid": "07c9fe71-a5e2-4e5f-b276-5f2b73481d44", | |
"placeholder": "navbar", | |
"path": "/Views/Common/Sublayouts/Navbar.cshtml", | |
"placeholders": { | |
"navbar-right": [ | |
{ | |
"name": "Primary Menu", | |
"id": "{53B085DB-6824-4941-9F28-9011CB151832}", | |
"uid": "fbdb4313-35b5-4c26-a586-17f48fd2dc41", | |
"placeholder": "navbar-right", | |
"path": "~/Views/Navigation/PrimaryMenu.cshtml", | |
"placeholders": {} | |
}, | |
{ | |
"name": "Main Navigation Activity", | |
"id": "{8832BA30-99C3-4D20-9B1C-237D910A153F}", | |
"uid": "75551dec-a457-43a3-b76f-91742b5dde93", | |
"placeholder": "navbar-right", | |
"path": "/Views/Common/Sublayouts/NavbarActivity.cshtml", | |
"placeholders": { | |
"navbar-activity": [ | |
{ | |
"name": "Site Menu", | |
"id": "{604ECBB6-2BBF-45AF-AC6E-BA9F853E7A03}", | |
"uid": "462763bc-ec80-4fba-9714-b63a87b43259", | |
"placeholder": "navbar-activity", | |
"path": "~/Views/Multisite/SwitchSite.cshtml", | |
"placeholders": {} | |
}, | |
{ | |
"name": "Language Menu", | |
"id": "{2695013C-DD51-410B-9983-F271F16C727D}", | |
"uid": "3555e24e-fbe9-4d81-aac7-50408aa45ef0", | |
"placeholder": "navbar-activity", | |
"path": "~/Views/Language/LanguageSelector.cshtml", | |
"placeholders": {} | |
}, | |
{ | |
"name": "Login Menu", | |
"id": "{28BD7068-7FF6-4CE6-98E6-85CFE9967E14}", | |
"uid": "79242576-4e0a-4cbf-bc2b-8b0c47fe9b64", | |
"placeholder": "navbar-activity", | |
"path": "~/Views/Accounts/AccountsMenu.cshtml", | |
"placeholders": { | |
"navbar-activity": [ | |
{ | |
"name": "Login Menu", | |
"id": "{28BD7068-7FF6-4CE6-98E6-85CFE9967E14}", | |
"uid": "79242576-4e0a-4cbf-bc2b-8b0c47fe9b64", | |
"placeholder": "navbar-activity", | |
"path": "~/Views/Accounts/_Login.cshtml", | |
"placeholders": {} | |
}, | |
{ | |
"name": "Login Menu", | |
"id": "{28BD7068-7FF6-4CE6-98E6-85CFE9967E14}", | |
"uid": "79242576-4e0a-4cbf-bc2b-8b0c47fe9b64", | |
"placeholder": "navbar-activity", | |
"path": "~/Views/Accounts/_FedAuth.cshtml", | |
"placeholders": {} | |
} | |
] | |
} | |
}, | |
{ | |
"name": "Global Search", | |
"id": "{8C3A2CFC-18FB-4BF5-8584-50EA88978ED6}", | |
"uid": "9c8a45f2-7008-42c1-8b8a-80a88825c1d8", | |
"placeholder": "navbar-activity", | |
"path": "~/Views/Search/GlobalSearch.cshtml", | |
"placeholders": {} | |
} | |
] | |
} | |
} | |
], | |
"navbar-left": [ | |
{ | |
"name": "Logo", | |
"id": "{A47C8288-7D4C-45FF-B75F-4AEDBF58A02D}", | |
"uid": "7bd25bd6-c6c4-4fbe-9828-d29a39a2fec9", | |
"placeholder": "navbar-left", | |
"path": "/Views/Identity/Logo.cshtml", | |
"placeholders": {} | |
} | |
] | |
} | |
} | |
], | |
"header-top": [ | |
{ | |
"name": "Header Topbar", | |
"id": "{C28B9C5F-8E01-42EC-ADF6-3324FAF92C7D}", | |
"uid": "f963aa98-4957-45b5-95cd-b61c242063e1", | |
"placeholder": "header-top", | |
"path": "/Views/Common/Sublayouts/HeaderTop.cshtml", | |
"placeholders": { | |
"left-header-top": [ | |
{ | |
"name": "Menu with links", | |
"id": "{69B9C355-004B-4268-BB1E-2F725B8752B7}", | |
"uid": "05ffb7b4-377c-4c00-9520-0ed7735cadca", | |
"placeholder": "left-header-top", | |
"path": "~/Views/Navigation/LinkMenu.cshtml", | |
"placeholders": {} | |
} | |
], | |
"right-header-top": [ | |
{ | |
"name": "Menu with links", | |
"id": "{69B9C355-004B-4268-BB1E-2F725B8752B7}", | |
"uid": "630f4515-1f19-49a8-b986-d46add5ce024", | |
"placeholder": "right-header-top", | |
"path": "~/Views/Navigation/LinkMenu.cshtml", | |
"placeholders": {} | |
} | |
] | |
} | |
} | |
], | |
"page-layout": [ | |
{ | |
"name": "Page Header", | |
"id": "{A2216CA7-B205-4042-A345-72117D0086C3}", | |
"uid": "1df746c3-2d29-43e6-9de2-05622ded6f97", | |
"placeholder": "page-layout", | |
"path": "/Views/Common/Sublayouts/PageHeader.cshtml", | |
"placeholders": { | |
"/page-layout/page-header": [ | |
{ | |
"name": "Page Header Media Carousel", | |
"id": "{8B28417B-A5B3-44F1-BFCC-FE1D907336E7}", | |
"uid": "66fc1c19-fc71-40ce-9c78-2158011733e1", | |
"placeholder": "/page-layout/page-header", | |
"path": "/Views/MediaFeature/PageHeaderCarousel.cshtml", | |
"placeholders": {} | |
} | |
] | |
} | |
}, | |
{ | |
"name": "Section", | |
"id": "{D201A487-5099-48A1-8189-55944F70C6CB}", | |
"uid": "a3ca17ff-693c-46f1-87aa-b8c1833f1890", | |
"placeholder": "page-layout", | |
"path": "/Views/Common/Sublayouts/Section.cshtml", | |
"placeholders": { | |
"/page-layout/section-{A3CA17FF-693C-46F1-87AA-B8C1833F1890}-0": [ | |
{ | |
"name": "3 Column 4-4-4", | |
"id": "{7074CA39-734D-4953-A3D5-B7D09C37CE3B}", | |
"uid": "d180d469-533f-463c-b817-8d256484d2c4", | |
"placeholder": "/page-layout/section-{A3CA17FF-693C-46F1-87AA-B8C1833F1890}-0", | |
"path": "/Views/Common/Sublayouts/3 Column 4-4-4.cshtml", | |
"placeholders": { | |
"/page-layout/section-{A3CA17FF-693C-46F1-87AA-B8C1833F1890}-0/col-narrow-3-{D180D469-533F-463C-B817-8D256484D2C4}-0": [ | |
{ | |
"name": "Page Teaser", | |
"id": "{07AB411C-F894-4144-B069-48EF67431D32}", | |
"uid": "7a8fcd26-592f-4119-88f1-4401728131d3", | |
"placeholder": "/page-layout/section-{A3CA17FF-693C-46F1-87AA-B8C1833F1890}-0/col-narrow-3-{D180D469-533F-463C-B817-8D256484D2C4}-0", | |
"path": "/Views/PageContent/PageTeaser.cshtml", | |
"placeholders": {} | |
} | |
], | |
"/page-layout/section-{A3CA17FF-693C-46F1-87AA-B8C1833F1890}-0/col-narrow-2-{D180D469-533F-463C-B817-8D256484D2C4}-0": [ | |
{ | |
"name": "Page Teaser", | |
"id": "{07AB411C-F894-4144-B069-48EF67431D32}", | |
"uid": "aa93f815-74d9-4bce-80c3-ab23cafddc29", | |
"placeholder": "/page-layout/section-{A3CA17FF-693C-46F1-87AA-B8C1833F1890}-0/col-narrow-2-{D180D469-533F-463C-B817-8D256484D2C4}-0", | |
"path": "/Views/PageContent/PageTeaser.cshtml", | |
"placeholders": {} | |
} | |
], | |
"/page-layout/section-{A3CA17FF-693C-46F1-87AA-B8C1833F1890}-0/col-narrow-1-{D180D469-533F-463C-B817-8D256484D2C4}-0": [ | |
{ | |
"name": "Page Teaser", | |
"id": "{07AB411C-F894-4144-B069-48EF67431D32}", | |
"uid": "b9a6955b-bed8-4168-9971-2b63dab22860", | |
"placeholder": "/page-layout/section-{A3CA17FF-693C-46F1-87AA-B8C1833F1890}-0/col-narrow-1-{D180D469-533F-463C-B817-8D256484D2C4}-0", | |
"path": "/Views/PageContent/PageTeaser.cshtml", | |
"placeholders": {} | |
} | |
] | |
} | |
} | |
] | |
} | |
}, | |
{ | |
"name": "Section with Media", | |
"id": "{1149A3C7-C69A-4E6B-A6C3-32687EC2F8D4}", | |
"uid": "f4fe27af-b867-4e72-8808-843d5781a355", | |
"placeholder": "page-layout", | |
"path": "~/Views/MediaFeature/SectionMedia.cshtml", | |
"placeholders": { | |
"/page-layout/section-{F4FE27AF-B867-4E72-8808-843D5781A355}-0": [ | |
{ | |
"name": "3 Column 6-3-3", | |
"id": "{B21C8A38-51BC-4866-9589-6062D6390247}", | |
"uid": "0f43a3b5-7236-40e6-b86e-5a52b75bbd8b", | |
"placeholder": "/page-layout/section-{F4FE27AF-B867-4E72-8808-843D5781A355}-0", | |
"path": "/Views/Common/Sublayouts/3 Column 6-3-3.cshtml", | |
"placeholders": { | |
"/page-layout/section-{F4FE27AF-B867-4E72-8808-843D5781A355}-0/col-narrow-1-{0F43A3B5-7236-40E6-B86E-5A52B75BBD8B}-0": [ | |
{ | |
"name": "Quote", | |
"id": "{A1AD34DD-535E-47AF-A911-34C57CCC26F6}", | |
"uid": "afaf655f-81e2-4086-98e7-9aa6fd6720d9", | |
"placeholder": "/page-layout/section-{F4FE27AF-B867-4E72-8808-843D5781A355}-0/col-narrow-1-{0F43A3B5-7236-40E6-B86E-5A52B75BBD8B}-0", | |
"path": "/Views/Person/Quote.cshtml", | |
"placeholders": {} | |
} | |
], | |
"/page-layout/section-{F4FE27AF-B867-4E72-8808-843D5781A355}-0/col-narrow-2-{0F43A3B5-7236-40E6-B86E-5A52B75BBD8B}-0": [ | |
{ | |
"name": "Latest News", | |
"id": "{BDD730EB-F808-4C65-B94A-D2F03672EAFB}", | |
"uid": "6a48c9d4-62d2-4007-a253-8d3d9e6e29e3", | |
"placeholder": "/page-layout/section-{F4FE27AF-B867-4E72-8808-843D5781A355}-0/col-narrow-2-{0F43A3B5-7236-40E6-B86E-5A52B75BBD8B}-0", | |
"path": "~/Views/News/LatestNews.cshtml", | |
"placeholders": {} | |
} | |
], | |
"/page-layout/section-{F4FE27AF-B867-4E72-8808-843D5781A355}-0/col-wide-1-{0F43A3B5-7236-40E6-B86E-5A52B75BBD8B}-0": [ | |
{ | |
"name": "Call to Action", | |
"id": "{A25C9850-AAB7-46A6-84B7-0DFAB32FC357}", | |
"uid": "762dfb70-5d70-4205-be54-c9f364c88e7c", | |
"placeholder": "/page-layout/section-{F4FE27AF-B867-4E72-8808-843D5781A355}-0/col-wide-1-{0F43A3B5-7236-40E6-B86E-5A52B75BBD8B}-0", | |
"path": "/Views/Teasers/Jumbotron.cshtml", | |
"placeholders": {} | |
} | |
] | |
} | |
} | |
] | |
} | |
} | |
], | |
"head": [ | |
{ | |
"name": "HTML Metadata", | |
"id": "{E7AE3F87-CF66-40F2-A9F5-33ECF08BC777}", | |
"uid": "e6ca18d8-321d-4105-9c38-0622e94be400", | |
"placeholder": "head", | |
"path": "~/Views/Metadata/PageMetadata.cshtml", | |
"placeholders": {} | |
}, | |
{ | |
"name": "Facebook Open Graph Metadata", | |
"id": "{3389CAFC-000C-41E5-921C-92821B3DB7B5}", | |
"uid": "ef51b238-7ce2-4e39-ab87-d05424735160", | |
"placeholder": "head", | |
"path": "/Views/Social/OpenGraph.cshtml", | |
"placeholders": {} | |
} | |
] | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Write a Windows PowerShell (5.1 or Core) script that does the following, with 2-space indentation and no Unicode/emojis: | |
1. Accepts two optional parameters: | |
- [string] $Url (default 'https://rssbplatform.dev.local/') | |
- [string] $OutputPath (optional) | |
2. Downloads the HTML at $Url using System.Net.HttpWebRequest with a 10-second timeout (connection and read/write), reads the full page into $content, and exits with error if download fails. | |
3. Uses a single regex to capture all Sitecore debug comments: | |
Pattern: `<!--\s*(start-component|end-component)\s*=\s*'(?<json>\{.*?\})'\s*-->` | |
4. Defines a helper function Convert-ComponentStringToObject that: | |
- Takes the captured `{…}` string, | |
- Quotes unquoted keys via `-replace '(\w+)\s*:', '"$1":'`, | |
- Parses with ConvertFrom-Json or throws on invalid JSON. | |
5. Identifies the outer “Default” layout markers by: | |
- uid == '00000000-0000-0000-0000-000000000000' | |
- placeholder == '' | |
- name == 'Default' | |
It must initialize a root node on that first start, ignore its end, and never report it as unmatched. | |
6. Builds a component tree with a stack: | |
- For each `start-component`: | |
- If it’s the root marker, create the root PSCustomObject: | |
```powershell | |
[PSCustomObject]@{ | |
name = $meta.name | |
id = $meta.id | |
uid = $meta.uid | |
placeholder = $meta.placeholder | |
path = $meta.path | |
placeholders= @{} | |
} | |
``` | |
then continue (do not push it). | |
- Otherwise, create a similar PSCustomObject node, append it to its parent: | |
- Parent = top of stack if stack not empty, else root. | |
- Ensure `$parent.placeholders[$meta.placeholder]` exists as an array, then add the node. | |
- Push the node onto the stack. | |
- For each `end-component`: | |
- If it’s the root marker, continue. | |
- Otherwise, enforce strict UID matching against the top of the stack; pop on match, throw on mismatch or empty stack. | |
7. After processing all markers, throw if the stack still contains any nodes (all non-root starts must match ends). | |
8. Recursively reverse each placeholder’s child array in-place to preserve the original HTML order: | |
```powershell | |
function Reverse-Order { param($node) | |
foreach ($ph in $node.placeholders.Keys) { | |
$rev = $node.placeholders[$ph] | Select-Object -Reverse | |
$node.placeholders[$ph] = $rev | |
foreach ($child in $rev) { Reverse-Order $child } | |
} | |
} | |
Reverse-Order $root | |
``` | |
9. Serialize the final tree as JSON with ConvertTo-Json -Depth 20. If $OutputPath is provided, write to that file (UTF8); otherwise Write-Output the JSON. | |
10. All error messages must use plain ASCII and Write-Error or throw appropriately. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment