95 lines
3.0 KiB
JavaScript
95 lines
3.0 KiB
JavaScript
"use strict";
|
|
|
|
function copy(value) {
|
|
return value === undefined ? undefined : JSON.parse(JSON.stringify(value));
|
|
}
|
|
|
|
function normalizeConceptName(value) {
|
|
return String(value || "").trim().toLocaleLowerCase();
|
|
}
|
|
|
|
/** Index lightweight concept placements across stored CMaps. */
|
|
export class CmapReferenceIndex {
|
|
constructor() {
|
|
this.byConceptId = new Map();
|
|
this.byName = new Map();
|
|
this.byPage = new Map();
|
|
}
|
|
|
|
clear() {
|
|
this.byConceptId.clear();
|
|
this.byName.clear();
|
|
this.byPage.clear();
|
|
}
|
|
|
|
load(placements = [], conceptMaps = [], canonicalPageReference = (value) => value,
|
|
normalizeName = normalizeConceptName) {
|
|
this.clear();
|
|
const mapTitles = new Map(conceptMaps
|
|
.filter((map) => map && map.slug)
|
|
.map((map) => [map.slug, map.title || map.slug]));
|
|
const pageByConcept = new Map();
|
|
for (const placement of placements) {
|
|
if (!placement?.conceptId || !placement.pageSlug) continue;
|
|
pageByConcept.set(placement.conceptId,
|
|
canonicalPageReference(placement.pageSlug).toLocaleLowerCase());
|
|
}
|
|
for (const placement of placements) {
|
|
if (!placement?.conceptId || !placement.cmapSlug) continue;
|
|
const conceptId = String(placement.conceptId);
|
|
const slug = String(placement.cmapSlug);
|
|
const nameKey = normalizeName(placement.label);
|
|
if (nameKey) this.byName.set(nameKey, conceptId);
|
|
if (!this.byConceptId.has(conceptId)) this.byConceptId.set(conceptId, new Map());
|
|
const maps = this.byConceptId.get(conceptId);
|
|
const existing = maps.get(slug);
|
|
maps.set(slug, {
|
|
slug,
|
|
title: mapTitles.get(slug) || placement.cmapTitle || slug,
|
|
count: (existing?.count || 0) + (Number(placement.count) || 0)
|
|
});
|
|
|
|
const pageKey = pageByConcept.get(conceptId);
|
|
if (!pageKey) continue;
|
|
if (!this.byPage.has(pageKey)) this.byPage.set(pageKey, new Map());
|
|
const pageConcepts = this.byPage.get(pageKey);
|
|
if (!pageConcepts.has(conceptId)) {
|
|
pageConcepts.set(conceptId, {
|
|
conceptId,
|
|
label: placement.label || "Concept",
|
|
count: 0,
|
|
maps: new Map()
|
|
});
|
|
}
|
|
const concept = pageConcepts.get(conceptId);
|
|
const count = Number(placement.count) || 0;
|
|
concept.count += count;
|
|
const pageMap = concept.maps.get(slug);
|
|
concept.maps.set(slug, {
|
|
slug,
|
|
title: mapTitles.get(slug) || placement.cmapTitle || slug,
|
|
count: count + (pageMap?.count || 0)
|
|
});
|
|
}
|
|
return this;
|
|
}
|
|
|
|
mapsFor(conceptId) {
|
|
return Array.from(this.byConceptId.get(String(conceptId))?.values() || [])
|
|
.map(copy);
|
|
}
|
|
|
|
countFor(conceptId) {
|
|
return this.mapsFor(conceptId).reduce((sum, map) => sum + map.count, 0);
|
|
}
|
|
|
|
conceptIdForName(label) {
|
|
return this.byName.get(normalizeConceptName(label)) || null;
|
|
}
|
|
|
|
pageConcepts(pageReference) {
|
|
return Array.from(this.byPage.get(String(pageReference).toLocaleLowerCase())?.values() || [])
|
|
.map((concept) => ({ ...copy(concept), maps: new Map(concept.maps) }));
|
|
}
|
|
}
|