@bliztek/feed-generator
Version:
A simple and lightweight Node.js library for generating RSS 2.0, Atom, and JSON Feed formats.
158 lines (157 loc) • 6.29 kB
JavaScript
import { xmlDeclaration, renderXml, el, nodes, escapeXml } from "../xml.js";
const inferImageType = (url) => {
var _a;
const ext = (_a = url.split(".").pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase();
const types = {
jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png",
gif: "image/gif", webp: "image/webp", svg: "image/svg+xml", avif: "image/avif",
};
return ext ? types[ext] : undefined;
};
const toIso = (date) => new Date(date).toISOString();
/** Ensure a URL used as an Atom ID is in canonical form (trailing slash on origin-only URLs). */
const canonicalizeId = (url) => {
try {
const u = new URL(url);
if (u.pathname === "" || u.pathname === "/") {
u.pathname = "/";
return u.toString();
}
return url;
}
catch (_a) {
return url;
}
};
const renderAuthors = (authors) => (authors !== null && authors !== void 0 ? authors : []).map((a) => ({
tag: "author",
children: nodes(el("name", a.name), a.email ? el("email", a.email) : null, a.url ? el("uri", a.url) : null),
}));
export const renderAtom = (feed, options = { compact: false }) => {
var _a, _b, _c, _d, _e, _f, _g;
const { compact, stylesheet } = options;
// feed.updated is already derived by prepareFeed in index.ts
const feedUpdated = feed.updated
? toIso(feed.updated)
: new Date().toISOString();
const feedId = canonicalizeId((_a = feed.id) !== null && _a !== void 0 ? _a : feed.link);
const feedLinks = [];
if ((_b = feed.feedLinks) === null || _b === void 0 ? void 0 : _b.atom) {
feedLinks.push({
tag: "link",
selfClosing: true,
attributes: {
href: feed.feedLinks.atom,
rel: "self",
type: "application/atom+xml",
},
});
}
feedLinks.push({
tag: "link",
selfClosing: true,
attributes: { href: feed.link, rel: "alternate", type: "text/html" },
});
if (feed.nextUrl) {
feedLinks.push({
tag: "link",
selfClosing: true,
attributes: { href: feed.nextUrl, rel: "next" },
});
}
if ((_c = feed.feedLinks) === null || _c === void 0 ? void 0 : _c.hub) {
feedLinks.push({
tag: "link",
selfClosing: true,
attributes: { href: feed.feedLinks.hub, rel: "hub" },
});
}
const categoryNodes = ((_d = feed.categories) !== null && _d !== void 0 ? _d : []).map((cat) => ({
tag: "category",
selfClosing: true,
attributes: {
term: cat.name,
scheme: cat.domain,
label: cat.label,
},
}));
const entryNodes = feed.items.map((item) => {
var _a, _b, _c, _d;
const entryUpdated = item.updated
? toIso(item.updated)
: item.date
? toIso(item.date)
: feedUpdated;
const entryLinks = [];
if (item.link) {
entryLinks.push({
tag: "link",
selfClosing: true,
attributes: { href: item.link, rel: "alternate", type: "text/html" },
});
}
((_a = item.enclosures) !== null && _a !== void 0 ? _a : []).forEach((enc) => {
entryLinks.push({
tag: "link",
selfClosing: true,
attributes: {
href: enc.url,
rel: "enclosure",
type: enc.type,
length: enc.length,
},
});
});
if (item.image) {
entryLinks.push({
tag: "link",
selfClosing: true,
attributes: {
href: item.image,
rel: "enclosure",
type: inferImageType(item.image),
},
});
}
const entryCategories = ((_b = item.categories) !== null && _b !== void 0 ? _b : []).map((cat) => ({
tag: "category",
selfClosing: true,
attributes: {
term: cat.name,
scheme: cat.domain,
label: cat.label,
},
}));
return {
tag: "entry",
children: nodes(el("title", item.title, { attributes: { type: "text" } }), ...entryLinks, el("id", canonicalizeId(item.id)), el("updated", entryUpdated), item.date ? el("published", toIso(item.date)) : null, ...entryCategories, item.content
? el("content", item.content, { attributes: { type: "html" } })
: null, item.summary
? el("summary", item.summary, { attributes: { type: "text" } })
: null, ...renderAuthors(item.authors), ...((_d = (_c = item.extensions) === null || _c === void 0 ? void 0 : _c.xmlElements) !== null && _d !== void 0 ? _d : [])),
};
});
const generatorNode = feed.generator
? { tag: "generator", children: [feed.generator] }
: null;
const feedAttrs = {
xmlns: "http://www.w3.org/2005/Atom",
};
if ((_e = feed.extensions) === null || _e === void 0 ? void 0 : _e.xmlNamespaces) {
Object.assign(feedAttrs, feed.extensions.xmlNamespaces);
}
const feedNode = {
tag: "feed",
attributes: feedAttrs,
children: nodes(el("title", feed.title, { attributes: { type: "text" } }), feed.description
? el("subtitle", feed.description, { attributes: { type: "text" } })
: null, feed.copyright
? el("rights", feed.copyright, { attributes: { type: "text" } })
: null, ...feedLinks, el("id", feedId), el("updated", feedUpdated), generatorNode, ...categoryNodes, ...renderAuthors(feed.authors), ...((_g = (_f = feed.extensions) === null || _f === void 0 ? void 0 : _f.xmlElements) !== null && _g !== void 0 ? _g : []), ...entryNodes),
};
const sep = compact ? "" : "\n";
const stylesheetPI = stylesheet
? `${sep}xml-stylesheet type="text/xsl" href="${escapeXml(stylesheet)}"`
: "";
return `${xmlDeclaration()}${stylesheetPI}${sep}${renderXml([feedNode], 0, compact)}`;
};