Initial module scaffold
This commit is contained in:
102
scripts/core/reference-extractor.js
Normal file
102
scripts/core/reference-extractor.js
Normal file
@@ -0,0 +1,102 @@
|
||||
import { createFileLocator, isMediaPath, normalizePath, parseStoragePath } from "./path-utils.js";
|
||||
|
||||
const ATTRIBUTE_PATTERNS = [
|
||||
/\b(?:src|href|poster)\s*=\s*["']([^"'<>]+)["']/gi,
|
||||
/url\(\s*["']?([^"')]+)["']?\s*\)/gi
|
||||
];
|
||||
|
||||
export function collectStringCandidates(value, visit) {
|
||||
if (typeof value === "string") {
|
||||
visit(value);
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
for (const entry of value) collectStringCandidates(entry, visit);
|
||||
return;
|
||||
}
|
||||
if ((value !== null) && (typeof value === "object")) {
|
||||
for (const entry of Object.values(value)) collectStringCandidates(entry, visit);
|
||||
}
|
||||
}
|
||||
|
||||
export function extractReferencesFromValue(value, source) {
|
||||
const references = [];
|
||||
collectStringCandidates(value, candidate => {
|
||||
const direct = resolveReference(candidate, source);
|
||||
if (direct && isMediaPath(direct.path)) {
|
||||
references.push({
|
||||
sourceType: source.sourceType,
|
||||
sourceScope: source.sourceScope,
|
||||
sourceLabel: source.sourceLabel,
|
||||
rawValue: candidate,
|
||||
normalized: {
|
||||
...direct,
|
||||
targetKind: "local-file"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (const pattern of ATTRIBUTE_PATTERNS) {
|
||||
pattern.lastIndex = 0;
|
||||
let match;
|
||||
while ((match = pattern.exec(candidate))) {
|
||||
const nested = resolveReference(match[1], source);
|
||||
if (!nested || !isMediaPath(nested.path)) continue;
|
||||
references.push({
|
||||
sourceType: source.sourceType,
|
||||
sourceScope: source.sourceScope,
|
||||
sourceLabel: source.sourceLabel,
|
||||
rawValue: match[1],
|
||||
normalized: {
|
||||
...nested,
|
||||
targetKind: "local-file"
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return dedupeReferences(references);
|
||||
}
|
||||
|
||||
function resolveReference(rawValue, source) {
|
||||
const direct = parseStoragePath(rawValue);
|
||||
if (direct) return direct;
|
||||
return resolvePackageRelativeReference(rawValue, source);
|
||||
}
|
||||
|
||||
function resolvePackageRelativeReference(rawValue, source) {
|
||||
if (typeof rawValue !== "string") return null;
|
||||
if (/^(?:https?:|data:|blob:)/i.test(rawValue.trim())) return null;
|
||||
const path = normalizePath(rawValue);
|
||||
if (!path || !path.includes("/")) return null;
|
||||
|
||||
const ownerType = source.sourceScope?.ownerType;
|
||||
const ownerId = source.sourceScope?.ownerId;
|
||||
if (!ownerType || !ownerId) return null;
|
||||
|
||||
if (ownerType === "module") {
|
||||
return createFileLocator("data", `modules/${ownerId}/${path}`);
|
||||
}
|
||||
if (ownerType === "system") {
|
||||
return createFileLocator("data", `systems/${ownerId}/${path}`);
|
||||
}
|
||||
if (ownerType === "world") {
|
||||
return createFileLocator("data", path);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function dedupeReferences(references) {
|
||||
const seen = new Set();
|
||||
return references.filter(reference => {
|
||||
const key = [
|
||||
reference.sourceType,
|
||||
reference.sourceLabel,
|
||||
reference.normalized?.locator ?? "",
|
||||
reference.rawValue
|
||||
].join("|");
|
||||
if (seen.has(key)) return false;
|
||||
seen.add(key);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user