我想从 Confluence 页面导出 HTML 内容。这些可以包含带有
<img>
属性的 src
标签,这些属性只是普通的超链接。因为我也想导出这些内容,所以我决定将 src
内容替换为相应的数据 URL,这样就有 src="data:image/png;base64,AEFJEFEF…"
。
这当然需要通过 HTTP 获取图像,而且这只能以异步方式完成。此外,它还包含大量“嵌套”异步调用。
这是我到目前为止的代码:
/**
* @param {HTMLTableCellElement | undefined} cell
*/
async #getCellHtml(cell) {
if (!cell) return undefined;
const srcMap = {}
for await (const imgElement of cell.querySelectorAll('img')) {
if ("attachment" !== imgElement.dataset.linkedResourceType) {
return;
}
const imgUrl =
new URL(imgElement.src, imgElement.dataset.baseUrl);
await fetch(imgUrl)
.then(response => response.blob())
.then(blob => blob.arrayBuffer())
.then(arrayBuffer => {
srcMap[imgElement.src] =
`data:${imgElement.dataset.linkedResourceContentType};base64,`
+ Buffer.from(arrayBuffer).toString('base64');
});
}
const cellHtml = cell.innerHTML;
Object.entries(srcMap).forEach(([imgSrc, dataUrl]) => {
cellHtml.replace(imgSrc, dataUrl)
})
return cellHtml;
}
作为参考,此类 HTML 如下所示:
<p style="text-align: left;"><br/></p>
<p style="text-align: left;"><span
class="confluence-embedded-file-wrapper confluence-embedded-manual-size"><img
class="confluence-embedded-image" draggable="false" width="639"
src="/confluence/download/attachments/2345432345/image-2024-7-11_16-48-22-1.png?version=1&modificationDate=1720709302000&api=v2"
data-image-src="/confluence/download/attachments/235432345/image-2024-7-11_16-48-22-1.png?version=1&modificationDate=1720709302000&api=v2"
data-unresolved-comment-count="0" data-linked-resource-id="345654345"
data-linked-resource-version="1" data-linked-resource-type="attachment"
data-linked-resource-default-alias="image-2024-7-11_16-48-22-1.png"
data-base-url="https://suite.acme.com/confluence"
data-linked-resource-content-type="image/png"
data-linked-resource-container-id="1491043790"
data-linked-resource-container-version="1" alt=""/></span></p>
<p style="text-align: left;"><br/></p>
<p style="text-align: left;"><br/></p>
我的目的是循环遍历所有
<img>
元素,找到相关的 <img>
标签,获取它们的图像数据,并收集替换数组。之后,我只需将所有发现替换为各自的数据 URL。
我想我想要的是这样的:
cell.querySelectorAll('img').map(cell => {
// return a Promise that combines all the fetching etc.
// so that it resolves() with returning the base64 string(!).
return new Promise()…
});
在我
map()
将此数组添加到 Promises 之后,我可以 Promise.all()
然后替换 HTML。
在所有其他承诺都已履行后,我不知道如何“回报”最后一个承诺。我的代码是否应该使用
await
而不是 .then()
调用,这样我就不会进入回调上下文?
使用await而不是链接
.then()
调用:
/**
* @param {HTMLTableCellElement | undefined} cell
*/
async function getCellHtml(cell) {
if (!cell) return undefined;
const imgElements = Array.from(cell.querySelectorAll('img[data-linked-resource-type="attachment"]'));
const srcMapPromises = imgElements.map(async (imgElement) => {
const imgUrl = new URL(imgElement.src, imgElement.dataset.baseUrl);
const response = await fetch(imgUrl);
const blob = await response.blob();
const arrayBuffer = await blob.arrayBuffer();
const base64String = Buffer.from(arrayBuffer).toString('base64');
return {
originalSrc: imgElement.src,
dataUrl: `data:${imgElement.dataset.linkedResourceContentType};base64,${base64String}`
};
});
const srcMap = await Promise.all(srcMapPromises);
let cellHtml = cell.innerHTML;
srcMap.forEach(({ originalSrc, dataUrl }) => {
cellHtml = cellHtml.replace(new RegExp(originalSrc, 'g'), dataUrl);
});
return cellHtml;
}
我首先使用
Array.from()
收集 HTML 单元格元素中的所有图像元素,将 querySelectorAll
返回的 NodeList 转换为数组以使用 map()
。在 map()
回调中,我使用 async
/await
来获取图像,将 blob 转换为数组缓冲区,然后将该缓冲区转换为 base64 字符串。回调返回一个包含原始 src 和新数据 URL 的对象。
通过使用
Promise.all()
,我确保在继续之前完成所有获取操作。
然后,我使用
Promise.all()
的结果来替换 HTML 内容中的图像 src 属性。此方法可确保正确处理所有异步操作,并且仅在获取并转换所有图像后才更新最终 HTML 内容。要使用此函数,请使用适当的 HTML 单元格元素调用它:
const cell = document.querySelector('your-cell-selector');
getCellHtml(cell).then((htmlContent) => {
console.log(htmlContent);
});