The Sitemap Cartographer is designed to turn a raw XML sitemap into an interactive table of a website’s Information Architecture (IA).
β Setup Guide
β View Code
How It Works
When run, the code executes client-side JavaScript to scan the active browser tab.Then, a new tab will open. Containing a user-friendly view of each link URL detected, plus additional context critical to foundational SEO.
Media (I/V): Displays the count of images and videos associated with that specific URL in the sitemap.
Type: Distinguishes between a Sitemap Index (a map of other sitemaps) and a standard URL entry.
Path: Find what your looking for with text filtering, allowing you to see the site structure without the clutter of the domain name.
Depth: Indicates how many “clicks” or folders a page is from the root domain.
Top Folder: Automatically identifies the primary directory to help you see which content silos are the largest.
Modified: Pulls the lastmod date from the XML, helping you identify stale content that hasn’t been updated recently.
Use Cases
Check for Depth: Filter by Depth 3+. If critical conversion pages are buried at Depth 4 or 5, they may struggle to rank due to low internal link equity.
Audit Freshness: Scan the Modified column. If your most important “Money Pages” show dates from over a year ago, they are prime candidates for a content refresh.
Export for Strategy: Use the Copy TSV button to move your filtered view into Google Sheets or Excel for further use. Stay tuned for Sheets + AppScript tools.
Disclaimer: some websites prevent the use of bookmarklets, in some cases clearing cache and restarting the browsers fixes it.
Step-by-Step Installation
To install your new Strandfish Sitemap Cartographer, follow these simple steps to add it to your browser’s bookmarks bar. This process works for Chrome and Firefox.
To install your new Strandfish Sitemap Cartographer, follow these simple steps to add it to your browser’s bookmarks bar. This process works for Chrome and Firefox.
-
Show Your Bookmarks Bar
In most browsers, press
Ctrl + Shift + B(Windows) orCmd + Shift + B(Mac) to ensure your bookmarks bar is visible. -
Create a New Bookmark
Right-click any empty space on your bookmarks bar and select “Add Page” or “Add Bookmark”.
-
Configure the Bookmark
Name: Type π² Sitemap Cartographer (or your preferred name).
URL: Paste the entire block of JavaScript code (the “javascript:(function()⦔) into the URL field.
Copy Code
javascript:(function(){ let targetUrl=window.location.href; if(!targetUrl.split('?%27)[0].toLowerCase().endsWith(%27.xml%27)){ targetUrl=window.location.origin+%27/sitemap.xml%27; } fetch(targetUrl).then(res=>{ if(!res.ok) throw new Error(`Could not fetch ${targetUrl} (HTTP ${res.status})`); return res.text(); }).then(str=>new window.DOMParser().parseFromString(str,"text/xml")).then(xml=>{ const isIndex=xml.getElementsByTagName(%27sitemapindex%27).length>0; const items=isIndex?xml.getElementsByTagName(%27sitemap%27):xml.getElementsByTagName(%27url%27); if(items.length===0) throw new Error("No valid sitemap tags found."); const getCount=(node,name)=>{ let count=0; let els=node.getElementsByTagName(%27*%27); for(let i=0;i<els.length;i++){if(els[i].localName===name)count++} return count; }; const rowsData=Array.from(items).map(item=>{ const locNode=item.getElementsByTagName(%27loc%27)[0]||item.getElementsByTagNameNS(%27*%27,%27loc%27)[0]; const loc=locNode?locNode.textContent.trim():%27%27; const lastmodNode=item.getElementsByTagName(%27lastmod%27)[0]||item.getElementsByTagNameNS(%27*%27,%27lastmod%27)[0]; const lastmod=lastmodNode?lastmodNode.textContent.trim():%27%27; const images=getCount(item,%27image%27); const videos=getCount(item,%27video%27); let path; try{path=new URL(loc).pathname}catch(e){path=loc} let segs=path.replace(/^\/|\/$/g,%27%27).split(%27/%27).filter(Boolean); return {type:isIndex?%27Sitemap%27:%27URL%27,loc,path,depth:segs.length,f1:segs[0]||%27root%27,lastmod:lastmod.split(%27T%27)[0],images,videos}; }); const html=`<!doctype html><html><head><meta charset="utf-8"><title>π XML Cartographer | Michael Arrieta | Strandfish</title> <style> :root{--light-font-color:#424242;--light-bg-color:#fff;--light-a-color:#20a39e;--header-bg:#2c302e;--fs:11px;--pad:16px} body{font-family:Calibri,Arial,sans-serif;font-size:var(--fs);margin:0;background:linear-gradient(to right,#00FFCD 0%,#F5CFF4 100%);color:var(--light-font-color)} .wrap{padding:var(--pad);background:#fff;min-height:100vh} header.strandfish-seo-tool-header{background:linear-gradient(to right,#00FFCD 0%,#F5CFF4 100%);display:flex;align-items:center;justify-content:space-between;padding:10px 16px;font-size:12px} header a{text-decoration:none;color:black;font-weight:600} .fish-icon.wave{font-size:5em;background:linear-gradient(90deg,#00FFCD,#F5CFF4,#00FFCD);background-size:200% 100%;-webkit-background-clip:text;-webkit-text-fill-color:transparent;animation:shimmer 3s infinite linear} @keyframes shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}} .control-panel{display:flex; flex-direction:column; gap:10px; margin-bottom:12px; background:#f9f9f9; padding:12px; border:1px solid #000; box-shadow:1px 3px #000;} .filters input[type="radio"]{display:none} .filters label{display:inline-block;background:#fff;border:1px solid #000;padding:6px 10px;margin:2px;cursor:pointer;font-weight:600;box-shadow:1px 2px #000; font-size:10px;} .filters input:checked+label{background:#f5cff4;box-shadow:none;transform:translateY(1px)} .toolbar{display:flex;gap:8px;align-items:center;flex-wrap:wrap;} .toolbar input[type="text"], .toolbar select {padding:6px 8px; border:1px solid #000; font-family:inherit; font-size:11px; box-shadow:1px 2px #000; outline:none;} .toolbar input[type="text"] {flex-grow:1; min-width:200px;} button#copyVisible{background:#00ffcd;font-family:inherit;font-weight:600;border:1px solid #000;padding:6px 12px;box-shadow:1px 2px #000;cursor:pointer; margin-left:auto;} button#copyVisible:active{box-shadow:none;transform:translateY(1px);} .table{width:100%;border:1px solid #ddd;margin-top:10px} .row{display:flex;border-bottom:1px solid #eee} .row.header{background:var(--header-bg);color:#fff;font-weight:bold} .cell{flex:1;padding:8px;word-break:break-all} .colType{flex:0 0 70px} .colDepth{flex:0 0 50px} .colMod{flex:0 0 90px} .colMedia{flex:0 0 80px; text-align:center;} .filtered{display:none !important} footer{text-align:center;margin-top:20px;padding:20px;font-size:10px;color:#666} </style></head> <body> <header class="strandfish-seo-tool-header"> <div>Have a question? <a href="mailto:michael@michaelarrieta.org?subject=Question About Cartographer">Email Michael Arrieta</a></div> <div>Scan XML, Filter Results, Extract, Repeat</a></div> </header> <div class="wrap"> <h1>π² XML Cartographer | ${new URL(targetUrl).hostname}</h1><br>Generated from ${location.hostname} β <a href="${location.protocol}//${location.hostname}/sitemap.xml" target="_blank">View Sitemap</a><br> <div class="control-panel"> <div class="filters"> <span style="font-weight:bold; margin-right:8px;">TYPE:</span> <input type="radio" id="f-all" name="type" checked><label for="f-all">ALL</label> <input type="radio" id="f-index" name="type"><label for="f-index">SITEMAPS</label> <input type="radio" id="f-url" name="type"><label for="f-url">URLs</label> </div> <div class="toolbar"> <input type="text" id="searchInput" placeholder="Search URL path..."> <select id="depthFilter"> <option value="all">All Depths</option> <option value="0">Depth 0</option><option value="1">Depth 1</option><option value="2">Depth 2</option><option value="3+">Depth 3+</option> </select> <select id="mediaFilter"> <option value="all">All Media</option> <option value="images">Has Images</option> <option value="videos">Has Videos</option> </select> <button id="copyVisible">COPY TSV</button> </div> </div> <div class="table"> <div class="row header"> <div class="cell colType">Type</div> <div class="cell">Path</div> <div class="cell colDepth">Depth</div> <div class="cell">Top Folder</div> <div class="cell colMod">Modified</div> <div class="cell colMedia">Media (I/V)</div> </div> ${rowsData.map(r=>%60<div class="row data-row" data-type="${r.type}" data-path="${r.path}" data-depth="${r.depth}" data-imgs="${r.images}" data-vids="${r.videos}"> <div class="cell colType">${r.type}</div> <div class="cell"><a href="${r.loc}" target="_blank">${r.path}</a></div> <div class="cell colDepth">${r.depth}</div> <div class="cell">${r.f1}</div> <div class="cell colMod">${r.lastmod}</div> <div class="cell colMedia" title="Images: ${r.images}, Videos: ${r.videos}">${r.images} / ${r.videos}</div> </div>%60).join('')} </div> </div> <script> function applyFilter(){ const typeVal=document.querySelector('input[name="type"]:checked').id; const q=document.getElementById('searchInput').value.toLowerCase(); const d=document.getElementById('depthFilter').value; const m=document.getElementById('mediaFilter').value; document.querySelectorAll('.data-row').forEach(r=>{ const t = r.dataset.type; const path = r.dataset.path.toLowerCase(); const depth = parseInt(r.dataset.depth); const imgs = parseInt(r.dataset.imgs); const vids = parseInt(r.dataset.vids); const matchType = typeVal==='f-all' || (typeVal==='f-index' && t==='Sitemap') || (typeVal==='f-url' && t==='URL'); const matchQ = path.includes(q); const matchD = d==='all' || (d==='3+' ? depth>=3 : depth==d); const matchM = m==='all' || (m==='images' && imgs>0) || (m==='videos' && vids>0); if(matchType && matchQ && matchD && matchM){ r.classList.remove('filtered'); } else { r.classList.add('filtered'); } }); } document.querySelectorAll('input[name="type"]').forEach(i=>i.addEventListener('change', applyFilter)); document.getElementById('searchInput').addEventListener('input', applyFilter); document.getElementById('depthFilter').addEventListener('change', applyFilter); document.getElementById('mediaFilter').addEventListener('change', applyFilter); document.getElementById('copyVisible').onclick=()=>{ const rows = [].slice.call(document.querySelectorAll('.row:not(.filtered)')); const out = rows.map(r=>[].slice.call(r.querySelectorAll('.cell')).map(c=>c.innerText).join('\\t')).join('\\n'); navigator.clipboard.writeText(out).then(()=>alert('Copied!')); }; </script> </body></html>%60; const w=window.open(); w.document.write(html); w.document.close(); }).catch(err=>alert(err.message));})();