htmlmetaparser
Version:
A `htmlparser2` handler for parsing rich metadata from HTML. Includes HTML metadata, JSON-LD, RDFa, microdata, OEmbed, Twitter cards and AppLinks.
775 lines • 30.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.copy = exports.Handler = exports.HandlerFlags = exports.HTML_VALUE_MAP = exports.KNOWN_VOCABULARIES = void 0;
const setvalue_1 = require("setvalue");
const oembed_1 = require("./oembed");
const providers = new oembed_1.OEmbedProviders(require("../vendor/providers.json"));
const RDF_VALID_NAME_START_CHAR_RANGE = "A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6" +
"\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F" +
"\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u10000-\uEFFFF";
const RDF_NAME_START_CHAR_REGEXP = new RegExp(`^[${RDF_VALID_NAME_START_CHAR_RANGE}]$`);
const RDF_NAME_CHAR_REGEXP = new RegExp(`^[${RDF_VALID_NAME_START_CHAR_RANGE}\\-\\.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$`);
/**
* Keep track of vocabulary prefixes.
*/
exports.KNOWN_VOCABULARIES = {
// https://www.w3.org/2011/rdfa-context/rdfa-1.1.html
csvw: "http://www.w3.org/ns/csvw#",
dcat: "http://www.w3.org/ns/dcat#",
qb: "http://purl.org/linked-data/cube#",
grddl: "http://www.w3.org/2003/g/data-view#",
ma: "http://www.w3.org/ns/ma-ont#",
org: "http://www.w3.org/ns/org#",
owl: "http://www.w3.org/2002/07/owl#",
prov: "http://www.w3.org/ns/prov#",
rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
rdfa: "http://www.w3.org/ns/rdfa#",
rdfs: "http://www.w3.org/2000/01/rdf-schema#",
rif: "http://www.w3.org/2007/rif#",
rr: "http://www.w3.org/ns/r2rml#",
sd: "http://www.w3.org/ns/sparql-service-description#",
skos: "http://www.w3.org/2004/02/skos/core#",
skosxl: "http://www.w3.org/2008/05/skos-xl#",
wdr: "http://www.w3.org/2007/05/powder#",
void: "http://rdfs.org/ns/void#",
wdrs: "http://www.w3.org/2007/05/powder-s#",
xhv: "http://www.w3.org/1999/xhtml/vocab#",
xml: "http://www.w3.org/XML/1998/namespace",
xsd: "http://www.w3.org/2001/XMLSchema#",
cc: "https://creativecommons.org/ns#",
ctag: "http://commontag.org/ns#",
dc: "http://purl.org/dc/terms/",
dcterms: "http://purl.org/dc/terms/",
dc11: "http://purl.org/dc/elements/1.1/",
foaf: "http://xmlns.com/foaf/0.1/",
gr: "http://purl.org/goodrelations/v1#",
ical: "http://www.w3.org/2002/12/cal/icaltzd#",
og: "http://ogp.me/ns#",
rev: "http://purl.org/stuff/rev#",
sioc: "http://rdfs.org/sioc/ns#",
v: "http://rdf.data-vocabulary.org/#",
vcard: "http://www.w3.org/2006/vcard/ns#",
schema: "http://schema.org/",
// http://ogp.me/
music: "http://ogp.me/ns/music#",
video: "http://ogp.me/ns/video#",
article: "http://ogp.me/ns/article#",
book: "http://ogp.me/ns/book#",
profile: "http://ogp.me/ns/profile#",
website: "http://ogp.me/ns/website#",
fb: "http://ogp.me/ns/fb#",
};
/**
* Wrapper around `URL` for resolving correctly.
*/
function resolveUrl(baseUrl, newUrl) {
try {
return new URL(newUrl, baseUrl).toString();
}
catch (e) {
return;
}
}
/**
* Grab the correct attribute for RDFa support.
*/
exports.HTML_VALUE_MAP = {
meta(attrs) {
return attrs.content;
},
audio(attrs, baseUrl) {
return attrs.src ? resolveUrl(baseUrl, attrs.src) : undefined;
},
a(attrs, baseUrl) {
return attrs.href ? resolveUrl(baseUrl, attrs.href) : undefined;
},
object(attrs, baseUrl) {
return attrs.data ? resolveUrl(baseUrl, attrs.data) : undefined;
},
time(attrs) {
return attrs.datetime;
},
data(attrs) {
return attrs.value;
},
};
exports.HTML_VALUE_MAP["embed"] = exports.HTML_VALUE_MAP["audio"];
exports.HTML_VALUE_MAP["iframe"] = exports.HTML_VALUE_MAP["audio"];
exports.HTML_VALUE_MAP["img"] = exports.HTML_VALUE_MAP["audio"];
exports.HTML_VALUE_MAP["source"] = exports.HTML_VALUE_MAP["audio"];
exports.HTML_VALUE_MAP["track"] = exports.HTML_VALUE_MAP["audio"];
exports.HTML_VALUE_MAP["video"] = exports.HTML_VALUE_MAP["audio"];
exports.HTML_VALUE_MAP["area"] = exports.HTML_VALUE_MAP["a"];
exports.HTML_VALUE_MAP["link"] = exports.HTML_VALUE_MAP["a"];
exports.HTML_VALUE_MAP["meter"] = exports.HTML_VALUE_MAP["data"];
exports.HandlerFlags = {
hasLang: 1 << 0,
rdfaLink: 1 << 1,
rdfaNode: 1 << 2,
rdfaVocab: 1 << 3,
microdataNode: 1 << 4,
microdataVocab: 1 << 5,
microdataScope: 1 << 6,
};
class Handler {
constructor(callback, options) {
this.callback = callback;
this.options = options;
this.result = {
alternate: [],
icons: [],
links: [],
images: [],
};
this.contexts = [
{ tagName: "", text: "", flags: 0, attributes: {} },
];
this.langs = [];
this._rdfaNodes = [{}];
this._rdfaVocabs = [];
this._rdfaRels = [];
this._microdataRefs = {};
this._microdataScopes = [[]];
this._microdataNodes = [{}];
}
onend() {
const oembedProvider = providers.match(this.options.url);
// Add the known OEmbed provider when discovered externally.
if (oembedProvider &&
!this.result.alternate.some((x) => x.type === oembedProvider.type)) {
this.result.alternate.push(oembedProvider);
}
this.callback(null, this.result);
}
onerror(error) {
this.callback(error, this.result);
}
onopentag(tagName, attributes) {
const context = { tagName, text: "", flags: 0, attributes };
this.contexts.push(context);
// HTML attributes.
const relAttr = normalize(attributes["rel"]);
const srcAttr = normalize(attributes["src"]);
const hrefAttr = normalize(attributes["href"]);
const langAttr = normalize(attributes["lang"]);
// RDFa attributes.
const propertyAttr = normalize(attributes["property"]);
const vocabAttr = normalize(attributes["vocab"]);
const prefixAttr = normalize(attributes["prefix"]);
const resourceAttr = normalize(attributes["resource"]);
const typeOfAttr = normalize(attributes["typeof"]);
const aboutAttr = normalize(attributes["about"]);
// Microdata attributes.
const idAttr = normalize(attributes["id"]);
const itempropAttr = normalize(attributes["itemprop"]);
const itemidAttr = normalize(attributes["itemid"]);
const itemtypeAttr = normalize(attributes["itemtype"]);
const itemrefAttr = normalize(attributes["itemref"]);
// Push the language onto the stack.
if (langAttr) {
this.langs.push(langAttr);
context.flags = context.flags | exports.HandlerFlags.hasLang;
(0, setvalue_1.set)(this.result, ["html", "language"], langAttr);
}
// Store `id` references for later (microdata itemrefs).
if (idAttr && !this._microdataRefs.hasOwnProperty(idAttr)) {
this._microdataRefs[idAttr] = {};
}
// Microdata item.
if (attributes.hasOwnProperty("itemscope")) {
const newNode = {};
// Copy item reference data.
if (itemrefAttr) {
const refs = split(itemrefAttr);
for (const ref of refs) {
// Set microdata id reference when it doesn't already exist.
if (this._microdataRefs[ref] !== undefined) {
assignJsonldProperties(newNode, this._microdataRefs[ref]);
}
this._microdataRefs[ref] = newNode;
}
}
// Set child scopes on the root scope.
if (itempropAttr) {
const id = normalize(context.attributes["id"]);
this._addMicrodataProperty(last(this._microdataNodes), id, split(itempropAttr), newNode);
}
else {
this.result.microdata = this.result.microdata || [];
this.result.microdata.push(newNode);
this._microdataScopes.push([]);
context.flags = context.flags | exports.HandlerFlags.microdataScope;
}
// Push the new node as the current scope.
this._microdataNodes.push(newNode);
context.flags = context.flags | exports.HandlerFlags.microdataNode;
}
// Microdata `itemprop=""`.
if (itempropAttr && !(context.flags & exports.HandlerFlags.microdataNode)) {
const value = getValueMap(this.options.url, tagName, attributes);
const props = split(itempropAttr);
if (value !== undefined) {
this._addMicrodataProperty(last(this._microdataNodes), normalize(context.attributes["id"]), props, normalizeJsonLdValue({
"@value": value,
"@language": last(this.langs),
}));
}
else {
context.microdataTextProperty = props;
}
}
// Microdata `itemid=""`.
if (itemidAttr) {
const id = normalize(context.attributes["id"]);
const node = last(this._microdataNodes);
this._setMicrodataProperty(node, id, "@id", itemidAttr);
}
// Microdata `itemtype=""`.
if (itemtypeAttr) {
const [vocab, type] = splitItemtype(itemtypeAttr);
const vocabs = last(this._microdataScopes);
const id = normalize(context.attributes["id"]);
if (type && vocabs && vocab !== last(vocabs)) {
setContext(last(this._microdataNodes), "@vocab", vocab);
vocabs.push(vocab);
context.flags = context.flags | exports.HandlerFlags.microdataVocab;
}
this._addMicrodataProperty(last(this._microdataNodes), id, "@type", type || itemtypeAttr);
}
// RDFa `vocab=""`.
if (vocabAttr) {
setContext(last(this._rdfaNodes), "@vocab", vocabAttr);
this._rdfaVocabs.push(vocabAttr);
context.flags = context.flags | exports.HandlerFlags.rdfaVocab;
}
// RDFa `prefix=""`.
if (prefixAttr) {
const parts = split(prefixAttr);
for (let i = 0; i < parts.length; i += 2) {
const name = parts[i];
const value = parts[i + 1];
const prefix = name.slice(0, -1);
// Detect a valid prefix.
if (!name.endsWith(":") || !isValidName(prefix)) {
continue;
}
setContext(last(this._rdfaNodes), prefix, value);
}
}
// RDFa `rel=""`. Additional care is taken to avoid extranuous output with HTML `rel` attributes.
if (relAttr) {
const links = this._normalizeRdfaProperty(relAttr);
if (links.length) {
this._rdfaRels.push({ links, used: false });
context.flags = context.flags | exports.HandlerFlags.rdfaLink;
}
}
// Handle RDFa rel chaining.
if (this._rdfaRels.length) {
const rel = last(this._rdfaRels);
if (rel && !rel.used) {
const validRelId = resourceAttr || hrefAttr || srcAttr;
if (validRelId) {
const newNode = { "@id": validRelId };
rel.used = true;
this._addRdfaProperty(last(this._rdfaNodes), rel.links, newNode);
if (resourceAttr && !(context.flags & exports.HandlerFlags.rdfaNode)) {
this._rdfaNodes.push(newNode);
context.flags = context.flags | exports.HandlerFlags.rdfaNode;
}
}
// Support property chaining with `rel`.
if (!(context.flags & exports.HandlerFlags.rdfaLink) &&
(propertyAttr || typeOfAttr)) {
rel.used = true;
if (!(context.flags & exports.HandlerFlags.rdfaNode)) {
const newNode = {};
this._rdfaNodes.push(newNode);
this._addRdfaProperty(last(this._rdfaNodes), rel.links, newNode);
context.flags = context.flags | exports.HandlerFlags.rdfaNode;
}
}
}
}
// RDFa `about=""`.
if (aboutAttr) {
this._rdfaNodes.push(this._createRdfaResource(aboutAttr));
context.flags = context.flags | exports.HandlerFlags.rdfaNode;
}
// RDFa `property=""`.
if (propertyAttr) {
const value = getValueMap(this.options.url, tagName, attributes);
const properties = this._normalizeRdfaProperty(propertyAttr);
if (value !== undefined) {
this._addRdfaProperty(last(this._rdfaNodes), properties, normalizeJsonLdValue({
"@value": value,
"@language": last(this.langs),
"@type": normalize(attributes["datatype"]),
}));
}
else {
if ((typeOfAttr || resourceAttr) &&
!(context.flags & exports.HandlerFlags.rdfaLink)) {
const newNode = {};
if (resourceAttr) {
newNode["@id"] = resourceAttr;
}
this._addRdfaProperty(last(this._rdfaNodes), properties, newNode);
if (typeOfAttr && !(context.flags & exports.HandlerFlags.rdfaNode)) {
this._rdfaNodes.push(newNode);
context.flags = context.flags | exports.HandlerFlags.rdfaNode;
}
}
else {
context.rdfaTextProperty = properties;
}
}
}
// RDFa `resource=""`.
if (resourceAttr && !propertyAttr && !relAttr && !aboutAttr) {
this._rdfaNodes.push(this._createRdfaResource(resourceAttr));
context.flags = context.flags | exports.HandlerFlags.rdfaNode;
}
// RDFa `typeof=""`.
if (typeOfAttr) {
// Standalone `typeof` attribute should be treated as a blank resource.
if (!this._rdfaRels.length &&
!propertyAttr &&
!relAttr &&
!resourceAttr &&
!aboutAttr) {
this._rdfaNodes.push(this._createRdfaResource());
context.flags = context.flags | exports.HandlerFlags.rdfaNode;
}
this._addRdfaProperty(last(this._rdfaNodes), "@type", split(typeOfAttr));
}
// Handle meta properties (E.g. HTML, Twitter cards, etc).
if (tagName === "meta") {
const nameAttr = normalize(attributes["name"]);
const contentAttr = normalize(attributes["content"]);
// Catch some bad implementations of Twitter metadata.
if (propertyAttr && contentAttr) {
if (propertyAttr.startsWith("twitter:")) {
(0, setvalue_1.set)(this.result, ["twitter", propertyAttr.substr(8)], contentAttr);
}
else if (propertyAttr.startsWith("al:")) {
(0, setvalue_1.set)(this.result, ["applinks", propertyAttr.substr(3)], contentAttr);
}
}
// It's possible someone will do `<meta name="" property="" content="" />`.
if (nameAttr && contentAttr) {
const name = nameAttr.toLowerCase();
/**
* - Twitter
* - Dublin Core
* - Sailthru
* - HTML
*/
if (name.startsWith("twitter:")) {
(0, setvalue_1.set)(this.result, ["twitter", name.substr(8)], contentAttr);
}
else if (name.startsWith("dc.")) {
(0, setvalue_1.set)(this.result, ["dublincore", name.substr(3)], contentAttr);
}
else if (name.startsWith("dcterms.")) {
(0, setvalue_1.set)(this.result, ["dublincore", name.substr(8)], contentAttr);
}
else if (name.startsWith("sailthru.")) {
(0, setvalue_1.set)(this.result, ["sailthru", name.substr(9)], contentAttr);
}
else if (name === "date" ||
name === "keywords" ||
name === "author" ||
name === "description" ||
name === "language" ||
name === "generator" ||
name === "creator" ||
name === "publisher" ||
name === "robots" ||
name === "viewport" ||
name === "application-name" ||
name === "apple-mobile-web-app-title") {
(0, setvalue_1.set)(this.result, ["html", name], contentAttr);
}
}
}
// Detect external metadata opporunities (E.g. OEmbed).
if (tagName === "link") {
if (relAttr && hrefAttr) {
const rels = split(relAttr);
for (const rel of rels) {
const typeAttr = normalize(attributes["type"]);
const resolvedHref = resolveUrl(this.options.url, hrefAttr);
if (!resolvedHref)
continue;
if (rel === "canonical" || rel === "amphtml" || rel === "pingback") {
(0, setvalue_1.set)(this.result, ["html", rel], resolvedHref);
}
else if (rel === "alternate") {
const mediaAttr = normalize(attributes["media"]);
const hreflangAttr = normalize(attributes["hreflang"]);
if (typeAttr || mediaAttr || hreflangAttr) {
appendAndDedupe(this.result.alternate, ["type", "hreflang", "media", "href"], {
type: typeAttr || "text/html",
media: mediaAttr,
hreflang: hreflangAttr,
title: normalize(attributes["title"]),
href: resolvedHref,
});
}
}
else if (rel === "meta") {
appendAndDedupe(this.result.alternate, ["type"], {
type: typeAttr || "application/rdf+xml",
href: resolvedHref,
});
}
else if (rel === "icon" ||
rel === "apple-touch-icon" ||
rel === "apple-touch-icon-precomposed") {
appendAndDedupe(this.result.icons, ["href"], {
type: typeAttr,
sizes: normalize(attributes["sizes"]),
href: resolvedHref,
});
}
}
}
}
}
ontext(value) {
const currentContext = last(this.contexts);
if (currentContext)
currentContext.text += value;
}
onclosetag() {
const prevContext = this.contexts.pop();
const currentContext = last(this.contexts);
if (!prevContext || !currentContext)
return;
const text = normalize(prevContext.text);
if (prevContext.flags) {
// This context created a new node.
if (prevContext.flags & exports.HandlerFlags.microdataNode) {
this._microdataNodes.pop();
}
// This context used a new vocabulary.
if (prevContext.flags & exports.HandlerFlags.microdataVocab) {
const vocabs = last(this._microdataScopes);
if (vocabs)
vocabs.pop();
}
// This context created a new scope altogether.
if (prevContext.flags & exports.HandlerFlags.microdataScope) {
this._microdataScopes.pop();
}
// This context created a new node.
if (prevContext.flags & exports.HandlerFlags.rdfaNode) {
this._rdfaNodes.pop();
}
// This context used a vocabulary.
if (prevContext.flags & exports.HandlerFlags.rdfaVocab) {
this._rdfaVocabs.pop();
}
// This context used an RDFa link (E.g. `rel=""`).
if (prevContext.flags & exports.HandlerFlags.rdfaLink) {
this._rdfaRels.pop();
}
// This context used language property (E.g. `lang=""`).
if (prevContext.flags & exports.HandlerFlags.hasLang) {
this.langs.pop();
}
}
// Handle parsing significant script elements.
if (prevContext.tagName === "script") {
const type = normalize(prevContext.attributes["type"]);
if (type === "application/ld+json") {
try {
const jsonld = JSON.parse(prevContext.text);
if (typeof jsonld === "object" && jsonld !== null) {
this.result.jsonld = merge(this.result.jsonld, jsonld);
}
}
catch (e) {
/* Ignore. */
}
}
return;
}
if (prevContext.tagName === "a") {
const text = normalize(prevContext.text);
const href = normalize(prevContext.attributes["href"]);
if (text && href) {
const download = prevContext.attributes.hasOwnProperty("download");
const target = normalize(prevContext.attributes["target"]);
const hreflang = normalize(prevContext.attributes["hreflang"]);
const type = normalize(prevContext.attributes["type"]);
const rel = normalize(prevContext.attributes["rel"]);
const resolvedHref = resolveUrl(this.options.url, href);
if (resolvedHref) {
this.result.links.push({
href: resolvedHref,
text,
download,
target,
hreflang,
type,
rel,
});
}
}
}
if (prevContext.tagName === "img") {
const src = normalize(prevContext.attributes["src"]);
if (src) {
const alt = normalize(prevContext.attributes["alt"]);
const longdesc = normalize(prevContext.attributes["longdesc"]);
const sizes = normalize(prevContext.attributes["sizes"]);
const srcset = normalize(prevContext.attributes["srcset"]);
this.result.images.push({
src,
alt,
longdesc,
sizes: typeof sizes === "string" ? sizes.split(/\s*,\s*/) : undefined,
srcset: typeof srcset === "string" ? srcset.split(/\s*,\s*/) : undefined,
});
}
}
// Push the previous context text onto the current context.
currentContext.text += prevContext.text;
if (text) {
// Set RDFa to text value.
if (prevContext.rdfaTextProperty) {
this._addRdfaProperty(last(this._rdfaNodes), prevContext.rdfaTextProperty, normalizeJsonLdValue({
"@value": text,
"@language": last(this.langs),
}));
}
// Set microdata to text value.
if (prevContext.microdataTextProperty) {
this._addMicrodataProperty(last(this._microdataNodes), normalize(prevContext.attributes["id"]), prevContext.microdataTextProperty, normalizeJsonLdValue({
"@value": text,
"@language": last(this.langs),
}));
}
if (prevContext.tagName === "title" &&
(currentContext.tagName === "head" || currentContext.tagName === "html")) {
(0, setvalue_1.set)(this.result, ["html", "title"], text);
}
}
}
/**
* Add a microdata property, with support for `id` references (used via `itemref`).
*/
_addMicrodataProperty(node, id, itemprop, value) {
addJsonldProperty(node, itemprop, value);
if (id && this._microdataRefs.hasOwnProperty(id)) {
addJsonldProperty(this._microdataRefs[id], itemprop, value);
}
if (!this.result.microdata) {
this.result.microdata = [node];
}
}
/**
* Set a microdata property.
*/
_setMicrodataProperty(node, id, key, value) {
node[key] = value;
if (id && this._microdataRefs.hasOwnProperty(id)) {
this._microdataRefs[id][key] = value;
}
}
/**
* Add an RDFa property to the node.
*/
_addRdfaProperty(node, property, value) {
addJsonldProperty(node, property, value);
if (!this.result.rdfa) {
this.result.rdfa = [node];
}
}
/**
* Correct known prefixes in the context.
*/
_normalizeRdfaProperty(propertyList) {
var _a;
const properties = [];
for (const property of split(propertyList)) {
const prefix = getPrefix(property);
if (prefix) {
const node = last(this._rdfaNodes);
if (!((_a = node["@context"]) === null || _a === void 0 ? void 0 : _a.hasOwnProperty(prefix))) {
if (exports.KNOWN_VOCABULARIES.hasOwnProperty(prefix)) {
setContext(node, prefix, exports.KNOWN_VOCABULARIES[prefix]);
}
}
}
else {
if (this._rdfaVocabs.length === 0) {
continue; // Omit relative properties when no vocabulary is used.
}
}
properties.push(property);
}
return properties;
}
/**
* Create an RDFa resource.
*/
_createRdfaResource(id) {
for (const item of this._rdfaNodes) {
if (item["@id"] === id) {
return item;
}
}
const node = {};
if (id)
node["@id"] = id;
this.result.rdfa = this.result.rdfa || [];
this.result.rdfa.push(node);
return node;
}
}
exports.Handler = Handler;
/**
* Set a value to the node context.
*/
function setContext(node, key, value) {
node["@context"] = node["@context"] || {};
node["@context"][key] = value;
}
/**
* Normalize a HTML value, trimming and removing whitespace.
*/
function normalize(value) {
return value === undefined ? undefined : value.trim().replace(/\s+/g, " ");
}
/**
* Set an object property.
*/
function addJsonldProperty(obj, key, value) {
// Skip empty keys.
if (!key)
return;
if (Array.isArray(key)) {
for (const k of key) {
addJsonldProperty(obj, k, value);
}
}
else {
obj[key] = merge(obj[key], value);
}
}
/**
* Merge properties together using regular "set" algorithm.
*/
function assignJsonldProperties(obj, values) {
for (const key of Object.keys(values)) {
addJsonldProperty(obj, key, values[key]);
}
}
/**
* Get the last element in a stack.
*/
function last(arr) {
return arr[arr.length - 1];
}
/**
* Grab the semantic value from HTML.
*/
function getValueMap(url, tagName, attributes) {
const value = normalize(attributes.content);
if (!value && exports.HTML_VALUE_MAP.hasOwnProperty(tagName)) {
return normalize(exports.HTML_VALUE_MAP[tagName](attributes, url));
}
return value;
}
/**
* Merge values together.
*/
function merge(target, value) {
return (Array.isArray(target) ? target : target === undefined ? [] : [target]).concat(value);
}
/**
* Check if a prefix is valid.
*/
function isValidName(value) {
return (value.length > 1 &&
RDF_NAME_START_CHAR_REGEXP.test(value.charAt(0)) &&
RDF_NAME_CHAR_REGEXP.test(value.substr(1)));
}
/**
* Extract the prefix from compact IRIs.
*/
function getPrefix(value) {
const indexOf = value.indexOf(":");
if (indexOf === -1) {
return;
}
if (value.charAt(indexOf + 1) === "/" && value.charAt(indexOf + 2) === "/") {
return;
}
return value.substr(0, indexOf);
}
/**
* Split a space-separated string.
*/
function split(value) {
return value.split(/\s+/g);
}
/**
* Split an `itemtype` microdata property for `@vocab`.
*/
function splitItemtype(value) {
const hashIndexOf = value.lastIndexOf("#");
const slashIndexOf = value.lastIndexOf("/");
if (hashIndexOf > -1) {
return [value.substr(0, hashIndexOf + 1), value.substr(hashIndexOf + 1)];
}
if (slashIndexOf > -1) {
return [value.substr(0, slashIndexOf + 1), value.substr(slashIndexOf + 1)];
}
return [value, ""];
}
/**
* Simplify a JSON-LD value for putting into the graph.
*/
function normalizeJsonLdValue(value) {
if (value["@type"] || value["@language"]) {
const result = {
"@value": value["@value"],
};
if (value["@type"]) {
result["@type"] = value["@type"];
}
else if (value["@language"]) {
result["@language"] = value["@language"];
}
return result;
}
return value["@value"];
}
/**
* Copy properties from `a` to `b`, when "defined".
*/
function copy(a, b) {
for (const prop of Object.keys(b)) {
if (b[prop] !== undefined)
a[prop] = b[prop];
}
}
exports.copy = copy;
/**
* Append/merge a href entry to a list.
*/
function appendAndDedupe(list, props, value) {
for (const entry of list) {
const matches = props.every((x) => entry[x] === value[x]);
if (matches) {
copy(entry, value);
return;
}
}
list.push(value);
}
//# sourceMappingURL=index.js.map