summaryrefslogtreecommitdiff
path: root/packages/astro-rss/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/astro-rss/src')
-rw-r--r--packages/astro-rss/src/index.ts67
1 files changed, 34 insertions, 33 deletions
diff --git a/packages/astro-rss/src/index.ts b/packages/astro-rss/src/index.ts
index bb6afd562..8779ac007 100644
--- a/packages/astro-rss/src/index.ts
+++ b/packages/astro-rss/src/index.ts
@@ -1,4 +1,4 @@
-import { XMLValidator } from 'fast-xml-parser';
+import { XMLBuilder, XMLParser } from 'fast-xml-parser';
import { createCanonicalURL, isValidURL } from './util.js';
type GlobResult = Record<string, () => Promise<{ [key: string]: any }>>;
@@ -100,15 +100,17 @@ export default async function getRSS(rssOptions: RSSOptions) {
/** Generate RSS 2.0 feed */
export async function generateRSS({ rssOptions, items }: GenerateRSSArgs): Promise<string> {
const { site } = rssOptions;
- let xml = `<?xml version="1.0" encoding="UTF-8"?>`;
+ const xmlOptions = { ignoreAttributes: false };
+ const parser = new XMLParser(xmlOptions);
+ const root: any = { '?xml': { '@_version': '1.0', '@_encoding': 'UTF-8' } };
if (typeof rssOptions.stylesheet === 'string') {
- xml += `<?xml-stylesheet href="${rssOptions.stylesheet}" type="text/xsl"?>`;
+ root['?xml-stylesheet'] = { '@_href': rssOptions.stylesheet, '@_encoding': 'UTF-8' };
}
- xml += `<rss version="2.0"`;
+ root.rss = { '@_version': '2.0' };
if (items.find((result) => result.content)) {
// the namespace to be added to the xmlns:content attribute to enable the <content> RSS feature
const XMLContentNamespace = 'http://purl.org/rss/1.0/modules/content/';
- xml += ` xmlns:content="${XMLContentNamespace}"`;
+ root.rss['@_xmlns:content'] = XMLContentNamespace;
// Ensure that the user hasn't tried to manually include the necessary namespace themselves
if (rssOptions.xmlns?.content && rssOptions.xmlns.content === XMLContentNamespace) {
delete rssOptions.xmlns.content;
@@ -118,29 +120,36 @@ export async function generateRSS({ rssOptions, items }: GenerateRSSArgs): Promi
// xmlns
if (rssOptions.xmlns) {
for (const [k, v] of Object.entries(rssOptions.xmlns)) {
- xml += ` xmlns:${k}="${v}"`;
+ root.rss[`@_xmlns:${k}`] = v;
}
}
- xml += `>`;
- xml += `<channel>`;
// title, description, customData
- xml += `<title><![CDATA[${rssOptions.title}]]></title>`;
- xml += `<description><![CDATA[${rssOptions.description}]]></description>`;
- xml += `<link>${createCanonicalURL(site).href}</link>`;
- if (typeof rssOptions.customData === 'string') xml += rssOptions.customData;
+ root.rss.channel = {
+ title: rssOptions.title,
+ description: rssOptions.description,
+ link: createCanonicalURL(site).href,
+ };
+ if (typeof rssOptions.customData === 'string')
+ Object.assign(
+ root.rss.channel,
+ parser.parse(`<channel>${rssOptions.customData}</channel>`).channel
+ );
// items
- for (const result of items) {
+ root.rss.channel.item = items.map((result) => {
validate(result);
- xml += `<item>`;
- xml += `<title><![CDATA[${result.title}]]></title>`;
// If the item's link is already a valid URL, don't mess with it.
const itemLink = isValidURL(result.link)
? result.link
: createCanonicalURL(result.link, site).href;
- xml += `<link>${itemLink}</link>`;
- xml += `<guid>${itemLink}</guid>`;
- if (result.description) xml += `<description><![CDATA[${result.description}]]></description>`;
+ const item: any = {
+ title: result.title,
+ link: itemLink,
+ guid: itemLink,
+ };
+ if (result.description) {
+ item.description = result.description;
+ }
if (result.pubDate) {
// note: this should be a Date, but if user provided a string or number, we can work with that, too.
if (typeof result.pubDate === 'number' || typeof result.pubDate === 'string') {
@@ -148,26 +157,18 @@ export async function generateRSS({ rssOptions, items }: GenerateRSSArgs): Promi
} else if (result.pubDate instanceof Date === false) {
throw new Error('[${filename}] rss.item().pubDate must be a Date');
}
- xml += `<pubDate>${result.pubDate.toUTCString()}</pubDate>`;
+ item.pubDate = result.pubDate.toUTCString();
}
// include the full content of the post if the user supplies it
if (typeof result.content === 'string') {
- xml += `<content:encoded><![CDATA[${result.content}]]></content:encoded>`;
+ item['content:encoded'] = result.content;
}
- if (typeof result.customData === 'string') xml += result.customData;
- xml += `</item>`;
- }
-
- xml += `</channel></rss>`;
-
- // validate user’s inputs to see if it’s valid XML
- const isValid = XMLValidator.validate(xml);
- if (isValid !== true) {
- // If valid XML, isValid will be `true`. Otherwise, this will be an error object. Throw.
- throw new Error(isValid as any);
- }
+ if (typeof rssOptions.customData === 'string')
+ Object.assign(item, parser.parse(`<item>${rssOptions.customData}</item>`).item);
+ return item;
+ });
- return xml;
+ return new XMLBuilder(xmlOptions).build(root);
}
const requiredFields = Object.freeze(['link', 'title']);