使平面页面数组生成的站点地图递归



我有一个平面阵列的页面,我试图从他们创建一个层次网站地图。到目前为止,我做的很乱,所以我想要一个更好的方法来做。

这是我拥有的页面数组

const pages = [
{
"name": "Page 1",
"url": "/page-1/",
},
{
"name": "Page 2",
"url": "/page-2/",
},
{
"name": "Child 1",
"url": "/page-1/child-1/",
},
{
"name": "Child 2",
"url": "/page-1/child-1/child-2/",
},
{
"name": "Child 3",
"url": "/page-1/child-1/child-2/child-3/",
}
]

,这是我想输出的结果

<ul>
<li>
<a href="/page-1/">Page 1</a>
<ul>
<li>
<a href="/page-1/child-1/">Child 1</a>
<ul>
<li>
<a href="/page-1/child-1/child-2/">Child 2</a>
<ul>
<li>
<a href="/page-1/child-1/child-2/child-3/">Child 3</a>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><a href="/page-2/">Page 2</a></li>
</ul>

这是我目前的工作,但想找到一个更好的方法来做到这一点

const generateSitemap = function(pages) => {
let sitemap = "";
let organisedPages = [];
const sortPages = function (runs) {
if (pages.length === 0) return;
pages.forEach((page) => {
const title = page.name;
const url = page.url;
// Get homepage and content pages only
let pageObj = {
title: title,
url: url,
children: [],
};
// Handle top level pages first then build up children as you go deeper
if (pageLevel(url) === 1) {
organisedPages.push(pageObj);
pages = pages.filter((page1) => page !== page1);
} else if (runs === 2) {
organisedPages.forEach((oPage, i) => {
// Check to see if url is in segments and matches
let parseUrl = url.substring(1).slice(0, -1);
const urlParts = parseUrl.split("/");
const parentUrl = `/${urlParts.slice(0, -1).join("/")}/`;
if (oPage.url === parentUrl) {
organisedPages[i].children.push(pageObj);
pages = pages.filter(
(page1) => pageObj.url !== page1.systemProperties.url
);
return;
}
});
} else if (runs === 3) {
organisedPages.forEach((oPage, i) => {
// Check to see if url is in segments and matches
let parseUrl = url.substring(1).slice(0, -1);
const urlParts = parseUrl.split("/");
const parentUrl = urlParts.slice(0, -1);
const parentUrlComp = `/${parentUrl.join("/")}/`;
const parentUrl2 = parentUrl.slice(0, -1);
const parentUrl2Comp = `/${parentUrl2.join("/")}/`;
if (oPage.url === parentUrl2Comp) {
organisedPages[i].children.forEach((child, j) => {
if (child.url === parentUrlComp) {
organisedPages[i].children[j].children.push(pageObj);
pages = pages.filter(
(page1) => pageObj.url !== page1.systemProperties.url
);
return;
}
});
}
});
} else if (runs === 4) {
organisedPages.forEach((oPage, i) => {
// Check to see if url is in segments and matches
let parseUrl = url.substring(1).slice(0, -1);
const urlParts = parseUrl.split("/");
const parentUrl = urlParts.slice(0, -1);
const parentUrlComp = `/${parentUrl.join("/")}/`;
const parentUrl2 = parentUrl.slice(0, -1);
const parentUrl2Comp = `/${parentUrl2.join("/")}/`;
const parentUrl3 = parentUrl2.slice(0, -1);
const parentUrl3Comp = `/${parentUrl3.join("/")}/`;
if (oPage.url === parentUrl3Comp) {
organisedPages[i].children.forEach((child, j) => {
if (child.url === parentUrl2Comp) {
organisedPages[i].children[j].children.forEach((child1, k) => {
if (child1.url === parentUrlComp) {
organisedPages[i].children[j].children[k].children.push(
pageObj
);
pages = pages.filter(
(page1) => pageObj.url !== page1.systemProperties.url
);
return;
}
});
}
});
}
});
}
});
runs++;
if (runs < 5) {
sortPages(runs);
}
};
sortPages(1);
/**
* Check if page is a parent
*
* @param {string} url page url
* @returns {number} length of segments
*/
function pageLevel(url) {
// Remove first and last forward slash that is provided
let parseUrl = url.substring(1).slice(0, -1);
// Split parsed url by forward slash
const urlParts = parseUrl.split("/");
// Check segment length
return urlParts.length;
}
/**
* Loop through organised pages and set listing.
*/
organisedPages.forEach((page) => {
sitemap += `<li>`;
sitemap += `<a href="${page.url}">${page.title}</a>`;
// Check if we need children loop for each parent page
if (page.children.length) {
sitemap += `<ul>`;
page.children.forEach((page) => {
sitemap += `<li>`;
sitemap += `<a href="${page.url}">${page.title}</a>`;
// Check if we need children loop for each sub-child page
if (page.children.length) {
sitemap += `<ul>`;
page.children.forEach((page) => {
sitemap += `<li>`;
sitemap += `<a href="${page.url}">${page.title}</a>`;
if (page.children.length) {
sitemap += `<ul>`;
page.children.forEach((page) => {
sitemap += `<li>`;
sitemap += `<a href="${page.url}">${page.title}</a>`;
sitemap += `</li>`;
});
sitemap += `</ul>`;
}
sitemap += `</li>`;
});
sitemap += `</ul>`;
}
sitemap += `</li>`;
});
sitemap += `</ul>`;
}
sitemap += `</li>`;
});
return sitemap;
};
generateSitemap(pages)

我会选择从HTML格式中打破对象的逻辑嵌套,因为我认为这会使函数更简单。为了方便地嵌套,我还将添加一个帮助函数来从url获取父id,例如,

getParentId ("/page-1/") //=> ""
getParentId ("/page-1/child-1/") //=> "/page-1"
getParentId ("/page-1/child-1/child-2/") //=> "/page1/child-1"
// ... etc

我们可以很容易地在nest中内联这个函数,但我认为它作为一个帮助器更干净。这里有一点奇怪的复杂性,因为在开始或结束的地方有一个额外的斜杠。当我们在列表中搜索子节点时,我们选择将最终的/切掉。

代码如下:

const getParentId = (url) => url .slice (0, url .slice (0, -1) .lastIndexOf ('/'))
const nest = (pages, parentId = "", id = parentId .slice (0, -1)) => pages 
.filter (({url}) => getParentId (url) == id) 
.map (({url, ...rest}) => ({...rest, url, children: nest (pages, url)}))
const format = (nodes) => `<ul>${nodes .map (({name, url, children}) => 
`<li><a href="${url}">${name}</a>${children .length ? format (children) : ''}</li>`
).join('')}</ul>`
const pages2html = (pages) => format (nest (pages))
const pages = [{name: "Page 1", url: "/page-1/"}, {name: "Page 2", url: "/page-2/"}, {name: "Child 1", url: "/page-1/child-1/"}, {name: "Child 2", url: "/page-1/child-1/child-2/"}, {name: "Child 3", url: "/page-1/child-1/child-2/child-3/"}]
console .log (pages2html (pages))
.as-console-wrapper {max-height: 100% !important; top: 0}

这里nest把你的节点变成这样的格式:

[
{
name: "Page 1",
url: "/page-1/",
children: [
{
name: "Child 1",
url: "/page-1/child-1/",
children: [
{
name: "Child 2",
url: "/page-1/child-1/child-2/",
children: [
{
name: "Child 3",
url: "/page-1/child-1/child-2/child-3/",
children: []
}
]
}
]
}
]
},
{
name: "Page 2",
url: "/page-2/",
children: []
}
] 

,然后format将其转换为HTML。我们在pages2html中将它们包装在一起,以便调用一个函数,但是工作是在这两个至少可能可重用的函数中完成的。

请注意,我们不会尝试在您请求的格式中保留空白。我们可以这样做,代价是使format相当丑陋。这里有一个快速的尝试,我认为是正确的。但我不会发誓:

const format = (nodes, depth = 0) => `${depth > 0 ? 'n' : ''
}${'  '.repeat (2 * depth) }<ul>${nodes .map (({name, url, children}) => `
${'  ' .repeat (2 * depth + 1) }<li>
${'  ' .repeat (2 * depth + 2) }<a href="${url}">${name}</a>${
children .length ? `` + format (children, depth + 1) : ''
}
${'  ' .repeat (2 * depth + 1) }</li>`
).join('')}
${'  '.repeat (2 * depth)}</ul>`

我们简单地使用depth参数来记录嵌套的级别,并使用该参数来计算在行开始处使用多少空格。


一般来说,我发现这种风格,作为一个转换序列,更容易使用。是的,我们可以在一个函数中完成所有这些。它甚至可能比我的四个独立函数的代码行数更少。但是,其中每一个都更容易理解,并且在需要时更容易更改。

递归将比多级分支更加灵活和简单,从next中相当简单的递归代码中可以看出。

相关内容

  • 没有找到相关文章