XML:读写node.js
#node #xml #fastxmlparser #sitemap

这篇文章使用fast-xml-parser在node.js中演示了读写xml。我们将以docusauruses xml站点地图为例。

title image reading "XML: read and write with Node.js" with XML and Docusaurus logos

Docusaurus Sitemap

我是通过想在我的Docusaurus博客上编辑站点地图来撰写这篇文章的。我想从站点地图中删除/page//tag/路由。它们有效地充当重复的内容,我不希望它们被搜索引擎索引。 (还需要更多一点才能将它们从搜索引擎中删除 - 请参阅帖子末尾的部分。)

我能够在我的Docusaurus网站的build文件夹中找到站点地图。它称为sitemap.xml,它位于build文件夹的根部。看起来像这样:

<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
  <url>
    <loc>https://blog.johnnyreilly.com/2012/01/07/standing-on-shoulders-of-giants</loc>
    <changefreq>weekly</changefreq>
    <priority>0.5</priority>
  </url>
  <url>
    <loc>https://blog.johnnyreilly.com/2022/09/20/react-usesearchparamsstate</loc>
    <changefreq>weekly</changefreq>
    <priority>0.5</priority>
  </url>
  <url>
    <loc>https://blog.johnnyreilly.com/page/10</loc>
    <changefreq>weekly</changefreq>
    <priority>0.5</priority>
  </url>
  <url>
    <loc>https://blog.johnnyreilly.com/tags/ajax</loc>
    <changefreq>weekly</changefreq>
    <priority>0.5</priority>
  </url>
  <!-- ... -->
</urlset>

fast-xml-parser

在尝试了几个不同的XML解析器后,我在koude0上定居。它很快,很简单,维护得很好。它还可以很好地处理XML名称空间和属性。 (这在XML解析器中似乎很少见。)

让我们与我们的Docusaurus网站一起进行示例项目:

mkdir trim-xml
cd trim-xml
npx typescript --init
yarn init
yarn add @types/node fast-xml-parser ts-node typescript

和在package.json文件中添加一个start脚本:

{
  "scripts": {
    "start": "ts-node index.ts"
  }
}

最后,创建一个空的index.ts文件。

阅读XML

我们的Docusaurus Sitemap位于我们Docusaurus网站的build文件夹中。让我们阅读并将其解析为一个JavaScript对象:

import { XMLParser, XMLBuilder } from 'fast-xml-parser';
import fs from 'fs';
import path from 'path';

interface Sitemap {
  urlset: {
    url: { loc: string; changefreq: string; priority: number }[];
  };
}

async function trimXML() {
  const sitemapPath = path.resolve(
    '..',
    'blog-website',
    'build',
    'sitemap.xml'
  );

  console.log(`Loading ${sitemapPath}`);
  const sitemapXml = await fs.promises.readFile(sitemapPath, 'utf8');

  const parser = new XMLParser({
    ignoreAttributes: false,
  });
  let sitemap: Sitemap = parser.parse(sitemapXml);

  console.log(sitemap);
}

trimXML();

我们正在使用XMLParser类将XML解析为JavaScript对象。我们还使用ignoreAttributes选项来确保属性包含在解析的对象中。当我们运行此功能时,我们将获得以下输出:

Loading /home/john/code/github/blog.johnnyreilly.com/blog-website/build/sitemap.xml
{
  '?xml': { '@_version': '1.0', '@_encoding': 'UTF-8' },
  urlset: {
    url: [
      [Object], [Object], [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object],
      ... 1481 more items
    ],
    '@_xmlns': 'http://www.sitemaps.org/schemas/sitemap/0.9',
    '@_xmlns:news': 'http://www.google.com/schemas/sitemap-news/0.9',
    '@_xmlns:xhtml': 'http://www.w3.org/1999/xhtml',
    '@_xmlns:image': 'http://www.google.com/schemas/sitemap-image/1.1',
    '@_xmlns:video': 'http://www.google.com/schemas/sitemap-video/1.1'
  }
}

我们可以看到,fast-xml-parser库将XML解析为JavaScript对象。我们可以看到urlset元素具有一系列的url元素。每个url元素都有一个locchangefreqpriority元素。我们还可以看到,urlset元素具有许多属性。这匹配了我们之前看到的XML和我们定义的接口。

过滤和写XML

现在,我们将XML解析为JavaScript对象,我们可以像其他任何JavaScript对象一样过滤它。我们在指尖具有所有JavaScript的力量!

正如我之前提到的,我想删除代表重复内容的所有URL。这包括“分页” URL。这些是用于在内容页面之间导航的URL。例如,url https://blog.johnnyreilly.com/page/10是分页URL。我想从站点地图中删除这些URL。我也想摆脱“标签” URL。这些URL用于在具有特定标签的帖子之间导航。例如,url https://blog.johnnyreilly.com/tags/ajax是标签URL。我也想从站点地图中删除这些URL。

这是简单的本身,现在我们在JavaScript土地上。我们可以在url阵列上使用filter方法来删除我们不需要的URL:

const rootUrl = 'https://blog.johnnyreilly.com';
const filteredUrls = sitemap.urlset.url.filter(
  (url) =>
    url.loc !== `${rootUrl}/tags` &&
    !url.loc.startsWith(rootUrl + '/tags/') &&
    !url.loc.startsWith(rootUrl + '/page/')
);

然后,我们可以使用过滤的URL更新url数组:

sitemap.urlset.url = filteredUrls;

最后,我们可以将XML写回文件:

const builder = new XMLBuilder({
  ignoreAttributes: false,
});
const xml = builder.buildObject(sitemap);

const outputPath = path.resolve('sitemap.xml');
await fs.promises.writeFile(outputPath, xml);

再次注意,我们正在使用ignoreAttributes选项来确保属性包含在XML中。

让我们把它们全部放在一个文件中:

import { XMLParser, XMLBuilder } from 'fast-xml-parser';
import fs from 'fs';
import path from 'path';

interface Sitemap {
  urlset: {
    url: { loc: string; changefreq: string; priority: number }[];
  };
}

async function trimXML() {
  const sitemapPath = path.resolve(
    '..',
    'blog-website',
    'build',
    'sitemap.xml'
  );

  console.log(`Loading ${sitemapPath}`);
  const sitemapXml = await fs.promises.readFile(sitemapPath, 'utf8');

  const parser = new XMLParser({
    ignoreAttributes: false,
  });
  let sitemap: Sitemap = parser.parse(sitemapXml);

  const rootUrl = 'https://blog.johnnyreilly.com';
  const filteredUrls = sitemap.urlset.url.filter(
    (url) =>
      url.loc !== `${rootUrl}/tags` &&
      !url.loc.startsWith(rootUrl + '/tags/') &&
      !url.loc.startsWith(rootUrl + '/page/')
  );

  console.log(
    `Reducing ${sitemap.urlset.url.length} urls to ${filteredUrls.length} urls`
  );

  sitemap.urlset.url = filteredUrls;

  const builder = new XMLBuilder({ format: false, ignoreAttributes: false });
  const shorterSitemapXml = builder.build(sitemap);

  console.log(`Saving ${sitemapPath}`);
  await fs.promises.writeFile(sitemapPath, shorterSitemapXml);
}

trimXML();

我们已经完成了。我们可以运行脚本并查看结果:

Loading /github/workspace/blog-website/build/sitemap.xml
Reducing 1598 urls to 281 urls
Saving /github/workspace/blog-website/build/sitemap.xml

结论

在这篇文章中,我们看到了如何使用fast-xml-parser库将XML解析为JavaScript对象,在该对象上操作,然后将其写回XML。

如果您要在博客上直接查看我的使用方式,那么可能值得查看this PR

PS noindex

这与XML处理无关,但我不想错过这一点。 Merely editing the sitemap isn't enough to remove them from search engines。我们还将通过调整koude31 file of our Static Web App
来为这些路线提供noindex响应标头

{
  // ...
  "routes": [
    // ...
    {
      "route": "/tags/*",
      "headers": {
        "X-Robots-Tag": "noindex"
      }
    },
    {
      "route": "/page/*",
      "headers": {
        "X-Robots-Tag": "noindex"
      }
    }
  ]
  // ...
}