feedsmith
Version:
Robust and fast parser and generator for RSS, Atom, JSON Feed, and RDF feeds, with support for Podcast, iTunes, Dublin Core, and OPML files.
209 lines • 9.83 kB
JavaScript
import { createNamespaceGetter, isObject, isPresent, parseArrayOf, parseNumber, parseSingularOf, parseString, parseTextString, retrieveText, trimObject, } from '../../../common/utils.js';
import { retrieveItemOrFeed as retrieveDcItemOrFeed } from '../../../namespaces/dc/utils.js';
import { retrieveItemOrFeed as retrieveGeoRssItemOrFeed } from '../../../namespaces/georss/utils.js';
import { retrieveFeed as retrieveItunesFeed, retrieveItem as retrieveItunesItem, } from '../../../namespaces/itunes/utils.js';
import { retrieveItemOrFeed as retrieveMediaItemOrFeed } from '../../../namespaces/media/utils.js';
import { retrieveItem as retrieveSlashItem } from '../../../namespaces/slash/utils.js';
import { retrieveFeed as retrieveSyFeed } from '../../../namespaces/sy/utils.js';
import { retrieveItem as retrieveThrItem, retrieveLink as retrieveThrLink, } from '../../../namespaces/thr/utils.js';
export const parseLink = (value, options) => {
if (!isObject(value)) {
return;
}
const link = {
href: parseString(value['@href']),
rel: parseString(value['@rel']),
type: parseString(value['@type']),
hreflang: parseString(value['@hreflang']),
title: parseString(value['@title']),
length: parseNumber(value['@length']),
thr: retrieveThrLink(value),
};
if (isPresent(link.href)) {
return trimObject(link);
}
};
export const retrievePersonUri = (value, options) => {
if (!isObject(value)) {
return;
}
const get = createNamespaceGetter(value, options?.prefix);
const uri = parseSingularOf(get('uri'), parseTextString); // Atom 1.0.
const url = parseSingularOf(get('url'), parseTextString); // Atom 0.3.
return uri || url;
};
export const parsePerson = (value, options) => {
if (!isObject(value)) {
return;
}
const get = createNamespaceGetter(value, options?.prefix);
const person = {
name: parseSingularOf(get('name'), parseTextString),
uri: retrievePersonUri(value, options),
email: parseSingularOf(get('email'), parseTextString),
};
if (isPresent(person.name)) {
return trimObject(person);
}
};
export const parseCategory = (value) => {
if (!isObject(value)) {
return;
}
const category = {
term: parseString(value['@term']),
scheme: parseString(value['@scheme']),
label: parseString(value['@label']),
};
if (isPresent(category.term)) {
return trimObject(category);
}
};
export const retrieveGeneratorUri = (value) => {
if (!isObject(value)) {
return;
}
const uri = parseString(value['@uri']); // Atom 1.0.
const url = parseString(value['@url']); // Atom 0.3.
return uri || url;
};
export const parseGenerator = (value) => {
const generator = {
text: parseString(retrieveText(value)),
uri: retrieveGeneratorUri(value),
version: parseString(value?.['@version']),
};
if (isPresent(generator.text)) {
return trimObject(generator);
}
};
export const parseSource = (value, options) => {
if (!isObject(value)) {
return;
}
const get = createNamespaceGetter(value, options?.prefix);
const source = trimObject({
authors: parseArrayOf(get('author'), (value) => parsePerson(value, options)),
categories: parseArrayOf(get('category'), (value) => parseCategory(value, options)),
contributors: parseArrayOf(get('contributor'), (value) => parsePerson(value, options)),
generator: parseSingularOf(get('generator'), parseGenerator),
icon: parseSingularOf(get('icon'), parseTextString),
id: parseSingularOf(get('id'), parseTextString),
links: parseArrayOf(get('link'), (value) => parseLink(value, options)),
logo: parseSingularOf(get('logo'), parseTextString),
rights: parseSingularOf(get('rights'), parseTextString),
subtitle: parseSingularOf(get('subtitle'), parseTextString),
title: parseSingularOf(get('title'), parseTextString),
updated: retrieveUpdated(value),
});
return source;
};
export const retrievePublished = (value, options) => {
if (!isObject(value)) {
return;
}
const get = createNamespaceGetter(value, options?.prefix);
const published = parseSingularOf(get('published'), parseTextString); // Atom 1.0.
const issued = parseSingularOf(get('issued'), parseTextString); // Atom 0.3.
const created = parseSingularOf(get('created'), parseTextString); // Atom 0.3.
// The "created" date is not entirely valid as "published date", but if it's there when
// no other date is present, it's a good-enough fallback especially that it's not present
// in 1.0 version of the specfication.
return published || issued || created;
};
export const retrieveUpdated = (value, options) => {
if (!isObject(value)) {
return;
}
const get = createNamespaceGetter(value, options?.prefix);
const updated = parseSingularOf(get('updated'), parseTextString); // Atom 1.0.
const modified = parseSingularOf(get('modified'), parseTextString); // Atom 0.3.
return updated || modified;
};
export const retrieveSubtitle = (value, options) => {
if (!isObject(value)) {
return;
}
const get = createNamespaceGetter(value, options?.prefix);
const subtitle = parseSingularOf(get('subtitle'), parseTextString); // Atom 1.0.
const tagline = parseSingularOf(get('tagline'), parseTextString); // Atom 0.3.
return subtitle || tagline;
};
export const parseEntry = (value, options) => {
if (!isObject(value)) {
return;
}
const get = createNamespaceGetter(value, options?.prefix);
const entry = trimObject({
authors: parseArrayOf(get('author'), (value) => parsePerson(value, options)),
categories: parseArrayOf(get('category'), (value) => parseCategory(value, options)),
content: parseSingularOf(get('content'), parseTextString),
contributors: parseArrayOf(get('contributor'), (value) => parsePerson(value, options)),
id: parseSingularOf(get('id'), parseTextString),
links: parseArrayOf(get('link'), (value) => parseLink(value, options)),
published: retrievePublished(value, options),
rights: parseSingularOf(get('rights'), parseTextString),
source: parseSingularOf(get('source'), parseSource),
summary: parseSingularOf(get('summary'), parseTextString),
title: parseSingularOf(get('title'), parseTextString),
updated: retrieveUpdated(value, options),
dc: options?.partial ? undefined : retrieveDcItemOrFeed(value),
slash: options?.partial ? undefined : retrieveSlashItem(value),
itunes: options?.partial ? undefined : retrieveItunesItem(value),
media: options?.partial ? undefined : retrieveMediaItemOrFeed(value),
georss: options?.partial ? undefined : retrieveGeoRssItemOrFeed(value),
thr: options?.partial ? undefined : retrieveThrItem(value),
});
if (options?.partial || !entry) {
return entry;
}
// INFO: Spec also says about required "updated" but this field is
// not always present in entries. We can still parse the entry without it.
if (isPresent(entry.id) && isPresent(entry.title)) {
return entry;
}
};
export const parseFeed = (value, options) => {
if (!isObject(value)) {
return;
}
const get = createNamespaceGetter(value, options?.prefix);
const feed = trimObject({
authors: parseArrayOf(get('author'), (value) => parsePerson(value, options)),
categories: parseArrayOf(get('category'), (value) => parseCategory(value, options)),
contributors: parseArrayOf(get('contributor'), (value) => parsePerson(value, options)),
generator: parseSingularOf(get('generator'), parseGenerator),
icon: parseSingularOf(get('icon'), parseTextString),
id: parseSingularOf(get('id'), parseTextString),
links: parseArrayOf(get('link'), (value) => parseLink(value, options)),
logo: parseSingularOf(get('logo'), parseTextString),
rights: parseSingularOf(get('rights'), parseTextString),
subtitle: retrieveSubtitle(value, options),
title: parseSingularOf(get('title'), parseTextString),
updated: retrieveUpdated(value, options),
entries: parseArrayOf(get('entry'), (value) => parseEntry(value, options)),
dc: options?.partial ? undefined : retrieveDcItemOrFeed(value),
sy: options?.partial ? undefined : retrieveSyFeed(value),
itunes: options?.partial ? undefined : retrieveItunesFeed(value),
media: options?.partial ? undefined : retrieveMediaItemOrFeed(value),
georss: options?.partial ? undefined : retrieveGeoRssItemOrFeed(value),
});
if (options?.partial || !feed) {
return feed;
}
// INFO: Spec says about required "id", "title" and "updated". The thing is "updated" is
// frequently missing from the feeds and since this field is not strictly necessary to make
// the feed make sense, it is not required here. The "ID" field is mostly present, but also
// not in 100% of cases. It's not ideal not to have it, but it's not a dealbreaker either,
// so if either "id" or "title" is present, the feed is treated as valid.
// The "ID" can always fall back to the "title" if it's missing in application's code.
if (isPresent(feed.id) || isPresent(feed.title)) {
return feed;
}
};
export const retrieveFeed = (value) => {
const notNamespaced = parseSingularOf(value?.feed, parseFeed);
const namespaced = parseSingularOf(value?.['atom:feed'], (value) => parseFeed(value, { prefix: 'atom:' }));
return notNamespaced || namespaced;
};
//# sourceMappingURL=utils.js.map