exiftool-vendored
Version:
Efficient, cross-platform access to ExifTool
506 lines • 28.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.TagDescriptions = void 0;
const node_fs_1 = require("node:fs");
const node_os_1 = require("node:os");
const node_path_1 = require("node:path");
const Lazy_1 = require("./Lazy");
const ListXTask_1 = require("./ListXTask");
// Hand-curated descriptions for important tags.
// These take priority over ExifTool's built-in descriptions.
// Extracted from RequiredTags in mktags.ts
const CuratedDescriptions = {
Aperture: {
desc: "Calculated aperture value derived from FNumber or ApertureValue. Read-only composite tag. To write, modify FNumber or ApertureValue instead.",
see: "https://exiftool.org/TagNames/Composite.html",
},
ApertureValue: {
desc: "Lens aperture in APEX units. Secondary source for Aperture composite (FNumber takes priority). Formula: ApertureValue = 2 × log₂(FNumber). To write aperture, prefer FNumber as it's more intuitive.",
see: "https://exiftool.org/TagNames/EXIF.html",
},
Artist: {
desc: "Image creator/photographer name. ExifTool trims trailing whitespace. When MWG module is loaded, this becomes a list-type tag synchronized with XMP-dc:Creator and IPTC:By-line.",
see: "https://exiftool.org/TagNames/EXIF.html",
},
AvgBitrate: {
desc: "Average bitrate for video/audio files, calculated from media data size divided by duration. Read-only composite tag for QuickTime-based formats (MOV, MP4, etc.).",
see: "https://exiftool.org/TagNames/Composite.html",
},
ColorSpace: {
desc: "Color space of image data. EXIF mandatory tag. Standard values: 1 (sRGB), 0xFFFF (Uncalibrated). Non-standard values: 2 (Adobe RGB, some cameras), 0xFFFD (Wide Gamut RGB, Sony), 0xFFFE (ICC Profile, Sony). Adobe RGB is typically indicated by 'Uncalibrated' with InteropIndex='R03'.",
see: "https://exiftool.org/TagNames/EXIF.html",
},
Copyright: {
desc: "Copyright notice for the image. MWG composite tag that reconciles EXIF:Copyright, IPTC:CopyrightNotice, and XMP-dc:Rights. Writing updates all three locations to maintain MWG synchronization.",
see: "https://exiftool.org/TagNames/MWG.html",
},
CreateDate: {
desc: "When an image was digitized (captured by camera sensor). MWG composite tag that reconciles EXIF:CreateDate, IPTC digital creation fields, and XMP-xmp:CreateDate. Distinct from DateTimeOriginal (when photo was taken) - useful for scanned images. For MOV/MP4 videos, use CreateDate instead of DateTimeOriginal.",
see: "https://exiftool.org/TagNames/MWG.html",
},
DateTimeOriginal: {
desc: "When a photo was taken (shutter actuation time). MWG composite tag that reconciles EXIF:DateTimeOriginal, IPTC:DateCreated/TimeCreated, and XMP-photoshop:DateCreated. This is the most commonly used timestamp for photos. Different from CreateDate (digitization) and ModifyDate (file modification).",
see: "https://exiftool.org/TagNames/MWG.html",
},
Description: {
desc: "Image caption or description. MWG composite tag that reconciles EXIF:ImageDescription, IPTC:Caption-Abstract, and XMP-dc:Description. Writing updates all three locations for MWG compliance.",
see: "https://exiftool.org/TagNames/MWG.html",
},
DOF: {
desc: "Calculated depth of field based on focal length, aperture, and focus distance. WARNING: This value may be incorrect if the image has been resized. Read-only composite tag.",
see: "https://exiftool.org/TagNames/Composite.html",
},
Duration: {
desc: "Video/audio duration. QuickTime: stored in time scale units, converted to seconds using TimeScale. ExifTool formats as 'H:MM:SS' or seconds. Some iPhone live-photo MOV videos may show key frame time instead of total duration.",
see: "https://exiftool.org/TagNames/QuickTime.html",
},
ExifVersion: {
desc: "EXIF specification version (e.g., '0232' for EXIF 2.32). EXIF mandatory tag. Stored as 4-byte ASCII without separators. ExifTool accepts '2.32' format when writing. Some files incorrectly include null terminators which ExifTool removes.",
see: "https://exiftool.org/TagNames/EXIF.html",
},
ExposureCompensation: {
desc: "Exposure bias/compensation in EV units (e.g., -0.67, +1.0). Also called ExposureBiasValue in EXIF spec. Signed value indicating deviation from metered exposure.",
see: "https://exiftool.org/TagNames/EXIF.html",
},
ExposureProgram: {
desc: "Camera exposure mode. Values: 0 (Not Defined), 1 (Manual), 2 (Program AE), 3 (Aperture-priority AE), 4 (Shutter speed priority AE), 5 (Creative/Slow speed), 6 (Action/High speed), 7 (Portrait), 8 (Landscape). Value 9 (Bulb) is non-standard but used by some Canon models.",
see: "https://exiftool.org/TagNames/EXIF.html",
},
ExposureTime: {
desc: "Shutter speed in seconds (e.g., '1/250'). Primary source for ShutterSpeed composite. To write shutter speed, use this tag directly. BulbDuration takes priority in ShutterSpeed composite if present and > 0.",
see: "https://exiftool.org/TagNames/EXIF.html",
},
FileAccessDate: {
desc: "File system access date/time. Not stored metadata - file system property. Read-only. Updated when file is read (including by ExifTool).",
see: "https://exiftool.org/TagNames/File.html",
},
FileCreateDate: {
desc: "File system creation date/time. Not stored metadata - file system property. Writable on some systems. Different from image capture date.",
see: "https://exiftool.org/TagNames/File.html",
},
FileModifyDate: {
desc: "File system modification date/time. Not stored metadata - file system property. Writable. Different from EXIF ModifyDate which tracks user edits.",
see: "https://exiftool.org/TagNames/File.html",
},
FileName: {
desc: "Name of the file. Not stored metadata - intrinsic file property. Writable: can rename files. May include full path to set Directory simultaneously.",
see: "https://exiftool.org/TagNames/File.html",
},
FileSize: {
desc: "Size of the file. Not stored metadata - intrinsic file property. Read-only. Uses SI prefixes by default (1 kB = 1000 bytes).",
see: "https://exiftool.org/TagNames/File.html",
},
FileType: {
desc: "File type determined from file content, not extension. Not stored metadata - intrinsic file property. Read-only.",
see: "https://exiftool.org/TagNames/File.html",
},
Flash: {
desc: "Flash status and mode encoded as EXIF bitfield. Combines fired status, return detection, mode (off/on/auto), and red-eye reduction into a single value. ExifTool decodes to human-readable strings.",
see: "https://exiftool.org/TagNames/EXIF.html",
},
FNumber: {
desc: "Lens aperture as f-number (e.g., 2.8, 5.6). Primary source for Aperture composite. To write aperture, use this tag - it's more intuitive than ApertureValue (which uses APEX units).",
see: "https://exiftool.org/TagNames/EXIF.html",
},
FocalLength: {
desc: "Lens focal length in millimeters. Actual focal length, not 35mm equivalent. For 35mm equivalent, see FocalLengthIn35mmFormat or FocalLength35efl composite.",
see: "https://exiftool.org/TagNames/EXIF.html",
},
GPSAltitude: {
desc: "GPS altitude in meters. Always stored as positive value; sign determined by GPSAltitudeRef. Composite GPSAltitude combines this with Ref to return signed value with 'Above/Below Sea Level' text.",
see: "https://exiftool.org/TagNames/GPS.html",
},
GPSAltitudeRef: {
desc: "GPS altitude reference. Values: 0 (Above Sea Level, ellipsoidal), 1 (Below Sea Level, ellipsoidal), 2 (Above Sea Level, sea-level ref), 3 (Below Sea Level, sea-level ref). EXIF 3.0 clarifies 0-1 use ellipsoidal surface, 2-3 use sea-level reference. ExifTool also accepts negative numbers when writing.",
},
GPSDateTime: {
desc: "GPS timestamp combining GPS:GPSDateStamp and GPS:GPSTimeStamp fields into a single UTC datetime. Read-only composite tag.",
see: "https://exiftool.org/TagNames/Composite.html",
},
GPSLatitude: {
desc: "GPS latitude stored as three rationals (degrees, minutes, seconds). Always positive; hemisphere from GPSLatitudeRef. ExifTool accepts decimal degrees, DMS, or mixed formats when writing. Composite GPSLatitude returns signed decimal.",
see: "https://exiftool.org/TagNames/GPS.html",
},
GPSLatitudeRef: {
desc: "GPS latitude hemisphere. Valid values: 'N' (North), 'S' (South). When writing, ExifTool accepts signed numbers or direction strings.",
},
GPSLongitude: {
desc: "GPS longitude stored as three rationals (degrees, minutes, seconds). Always positive; hemisphere from GPSLongitudeRef. ExifTool accepts decimal degrees, DMS, or mixed formats when writing. Composite GPSLongitude returns signed decimal.",
see: "https://exiftool.org/TagNames/GPS.html",
},
GPSLongitudeRef: {
desc: "GPS longitude hemisphere. Valid values: 'E' (East), 'W' (West). When writing, ExifTool accepts signed numbers or direction strings.",
},
GPSPosition: {
desc: "Combined GPS latitude and longitude. Writable composite tag. When written, updates GPSLatitude, GPSLatitudeRef, GPSLongitude, GPSLongitudeRef. Accepts Google Maps coordinates (right-click format).",
see: "https://exiftool.org/TagNames/Composite.html",
},
GPSTimeStamp: {
desc: "UTC time of GPS fix. When writing, date is stripped off if present, and time is adjusted to UTC if it includes a timezone.",
see: "https://exiftool.org/TagNames/GPS.html",
},
History: {
desc: "Tracks editing history of the document. XMP-xmpMM (Media Management) struct type. Flattened fields: HistoryAction, HistoryChanged, HistoryInstanceID, HistoryParameters, HistorySoftwareAgent, HistoryWhen.",
see: "https://exiftool.org/TagNames/XMP.html#xmpMM",
},
ImageDataHash: {
desc: "Hash of image data (excluding metadata). Only generated if specifically requested via -api RequestAll=3. Hash algorithm configurable via ImageHashType option (MD5 default, also SHA256/SHA512). Supports JPEG, TIFF, PNG, RAW formats, and MOV/MP4 videos (includes audio). Excludes thumbnails/previews but includes JpgFromRaw.",
see: "https://exiftool.org/TagNames/Extra.html",
},
ImageHeight: {
desc: "Image height in pixels. File-level property derived from file analysis. Read-only.",
see: "https://exiftool.org/TagNames/File.html",
},
ImageSize: {
desc: "Image dimensions combining width and height from various metadata fields. Read-only composite derived from ImageWidth, ImageHeight, ExifImageWidth, ExifImageHeight, or RawImageCroppedSize.",
see: "https://exiftool.org/TagNames/Composite.html",
},
ImageUniqueID: {
desc: "Unique identifier for the image, typically a 32-character hex string. Useful for image deduplication, tracking identity across edits, and linking related files (e.g., RAW + JPEG pairs). May also appear in MakerNotes and XMP.",
see: "https://exiftool.org/TagNames/EXIF.html",
},
ImageWidth: {
desc: "Image width in pixels. File-level property derived from file analysis. Read-only.",
see: "https://exiftool.org/TagNames/File.html",
},
ISO: {
desc: "Camera ISO sensitivity rating. In EXIF, this is an array (int16u[n]) that can contain multiple values. Historically called ISOSpeedRatings in EXIF 2.2, renamed to PhotographicSensitivity in EXIF 2.3.",
see: "https://exiftool.org/TagNames/EXIF.html",
},
JpgFromRaw: {
desc: "Embedded JPEG preview extracted from RAW files. Binary data type. Writable: can update existing embedded images, but cannot create or delete them. Access via BinaryField to get raw bytes or base64 encoding.",
see: "https://exiftool.org/TagNames/Composite.html",
},
Keywords: {
desc: "Searchable subject terms for image content. MWG composite tag that reconciles IPTC:Keywords and XMP-dc:Subject. Multi-value fields use semicolon-space ('; ') separators when represented as string. IPTC constraint: max 64 characters per keyword.",
see: "https://exiftool.org/TagNames/MWG.html",
},
Lens: {
desc: "Lens information (model name or focal length range). Sources vary by camera manufacturer and file format - may come from MakerNotes, XMP, or standard EXIF fields. For detailed lens identification from lookup tables, see LensID.",
see: "https://exiftool.org/TagNames/Composite.html",
},
LensID: {
desc: "Identifies actual lens model using manufacturer-specific lookup tables from partial type information. Configurable: may be extended with user-defined lenses via ExifTool configuration. Read-only composite.",
see: "https://exiftool.org/TagNames/Composite.html",
},
Make: {
desc: "Camera/device manufacturer. ExifTool automatically removes trailing whitespace. Used internally by ExifTool for vendor-specific tag handling.",
see: "https://exiftool.org/TagNames/EXIF.html",
},
MediaCreateDate: {
desc: "Creation date/time for QuickTime/MOV/MP4 media track. Stored as seconds since 1904-01-01 UTC. WARNING: Many cameras incorrectly store local time instead of UTC. For MOV/MP4 videos, use this tag instead of DateTimeOriginal.",
see: "https://exiftool.org/TagNames/QuickTime.html",
},
Megapixels: {
desc: "Total megapixels calculated from ImageSize composite field. Read-only composite.",
see: "https://exiftool.org/TagNames/Composite.html",
},
MeteringMode: {
desc: "Light metering mode used for exposure calculation (e.g., Average, Center-weighted, Spot, Multi-segment, Partial).",
see: "https://exiftool.org/TagNames/EXIF.html",
},
MIMEType: {
desc: "MIME type of the file. Not stored metadata - intrinsic file property. Read-only, determined from file content.",
see: "https://exiftool.org/TagNames/File.html",
},
Model: {
desc: "Camera/device model name. ExifTool automatically removes trailing whitespace. Used internally by ExifTool for vendor-specific tag handling.",
see: "https://exiftool.org/TagNames/EXIF.html",
},
ModifyDate: {
desc: "When the file was last modified by a user (not automatic processes). MWG composite tag that reconciles EXIF:ModifyDate and XMP-xmp:ModifyDate. Should reflect intentional user edits, not automatic metadata updates.",
see: "https://exiftool.org/TagNames/MWG.html",
},
Orientation: {
desc: "Image orientation. Valid values: 1 (Horizontal/normal), 2 (Mirror horizontal), 3 (Rotate 180°), 4 (Mirror vertical), 5 (Mirror horizontal + rotate 270° CW), 6 (Rotate 90° CW), 7 (Mirror horizontal + rotate 90° CW), 8 (Rotate 270° CW). Most images use values 1, 3, 6, and 8.",
see: "https://exiftool.org/TagNames/EXIF.html",
},
OffsetTime: {
desc: "Timezone offset for ModifyDate (e.g., '+05:30', '-08:00', 'Z'). EXIF 2.31+ tag. Used by SubSecModifyDate composite to produce timezone-aware timestamps. Writing SubSecModifyDate automatically updates this field.",
see: "https://exiftool.org/TagNames/EXIF.html",
},
OffsetTimeDigitized: {
desc: "Timezone offset for CreateDate (e.g., '+05:30', '-08:00', 'Z'). EXIF 2.31+ tag. Used by SubSecCreateDate composite to produce timezone-aware timestamps. Writing SubSecCreateDate automatically updates this field.",
see: "https://exiftool.org/TagNames/EXIF.html",
},
OffsetTimeOriginal: {
desc: "Timezone offset for DateTimeOriginal (e.g., '+05:30', '-08:00', 'Z'). EXIF 2.31+ tag. Used by SubSecDateTimeOriginal composite to produce timezone-aware timestamps. Writing SubSecDateTimeOriginal automatically updates this field.",
see: "https://exiftool.org/TagNames/EXIF.html",
},
PersonInImage: {
desc: "Name(s) of person(s) shown in the image. XMP-iptcExt namespace. Simpler alternative to MWG RegionInfo when face coordinates aren't needed. Multi-value field; array when multiple people identified.",
see: "https://exiftool.org/TagNames/XMP.html#iptcExt",
},
PreviewImage: {
desc: "Embedded preview image data extracted from the file. CRITICAL: Writable for updating existing embedded images, but cannot create or delete previews. Can only modify previews that already exist in the file.",
see: "https://exiftool.org/TagNames/Composite.html",
},
Rating: {
desc: "Star rating for the image. MWG composite tag from XMP-xmp:Rating. Valid values: -1 (rejected), 0 (unrated), 1-5 (star rating). Note: Rating may appear in EXIF but that's non-standard per MWG.",
see: "https://exiftool.org/TagNames/MWG.html",
},
RegionInfo: {
desc: "MWG face/region metadata structure containing AppliedToDimensions and RegionList. With struct=1 (default), contains nested objects. With struct=0, fields are flattened. Used by Lightroom, Picasa, Windows Photo Gallery, digiKam, and other photo organizers.",
see: "https://exiftool.org/TagNames/MWG.html",
},
RegionName: {
desc: "Name(s) of identified region(s), typically person names for face regions. From XMP-mwg-rs namespace. Flattened from RegionInfo struct (requires struct=0). For Lightroom compatibility, also add names to Keywords/Subject.",
see: "https://exiftool.org/TagNames/MWG.html",
},
RegionType: {
desc: "Type(s) of region(s) identified. Valid values: 'Face', 'Pet', 'BarCode', 'Focus'. From XMP-mwg-rs namespace.",
see: "https://exiftool.org/TagNames/MWG.html",
},
Rotation: {
desc: "Degrees of clockwise camera rotation for QuickTime/MP4 video files. Special writable: Writing this tag updates QuickTime MatrixStructure for all tracks with a non-zero image size simultaneously. Different from EXIF Orientation tag.",
see: "https://exiftool.org/TagNames/Composite.html",
},
ShutterSpeed: {
desc: "Shutter speed combining ExposureTime, ShutterSpeedValue, and BulbDuration. Read-only composite tag. Format typically fractional (e.g., '1/250').",
see: "https://exiftool.org/TagNames/Composite.html",
},
SubSecCreateDate: {
desc: "Creation date with subsecond precision, merging EXIF:CreateDate, SubSecTimeDigitized, and OffsetTimeDigitized. Writable composite: writing updates all three fields simultaneously for high-precision timestamps with timezone information.",
see: "https://exiftool.org/TagNames/Composite.html",
},
SubSecDateTimeOriginal: {
desc: "Original datetime with subsecond precision, combining EXIF:DateTimeOriginal, SubSecTimeOriginal, and OffsetTimeOriginal. Writable composite: writing updates all three fields simultaneously. Represents when the photo was originally taken with high precision.",
see: "https://exiftool.org/TagNames/Composite.html",
},
SubSecModifyDate: {
desc: "Modification timestamp with subsecond precision, combining EXIF:ModifyDate, SubSecTime, and OffsetTime. Writable composite: writing updates all three fields simultaneously.",
see: "https://exiftool.org/TagNames/Composite.html",
},
ThumbnailImage: {
desc: "Embedded thumbnail image data. Binary data type. Writable for updating existing thumbnails, but cannot create or delete thumbnails.",
},
Title: {
desc: "Image title. XMP-dc (Dublin Core) namespace - use this standard schema instead of non-standard XMP-xmp:Title. Supports language variants via RFC 3066 codes (e.g., 'Title-fr').",
see: "https://exiftool.org/TagNames/XMP.html#dc",
},
WhiteBalance: {
desc: "White balance mode. Standard EXIF values: 0 (Auto), 1 (Manual). MakerNotes often contain more detailed WhiteBalance with values like Daylight, Cloudy, Tungsten, Fluorescent, Flash, Custom, etc. EXIF:WhiteBalance has lower priority than MakerNotes version when both exist.",
see: "https://exiftool.org/TagNames/EXIF.html",
},
};
/**
* Provides access to human-readable descriptions for ExifTool metadata tags.
*
* Descriptions are sourced from ExifTool's built-in tag database (via `-listx`)
* and merged with hand-curated descriptions for important tags.
*
* **CAUTION**: The first call to an async method (e.g., `getAsync()`, `preload()`,
* `getAll()`) may take several seconds as it launches ExifTool to retrieve tag
* information.
*
* **SECOND CAUTION**: The in-memory cache can consume significant memory (several
* MB).
*
* **THIRD CAUTION**: The on-disk cache can consume disk space (several MB).
*
* **FOURTH CAUTION**: The synchronous `get()` method only works after descriptions
* are loaded! Be sure to call `preload()` during application initialization for
* sync access later.
*
* **FIFTH CAUTION**: Tag descriptions may vary between ExifTool versions and languages.
*
* @example
* ```typescript
* import { exiftool } from "exiftool-vendored";
*
* const descriptions = new TagDescriptions(exiftool);
*
* // Preload during app initialization for sync access later
* await descriptions.preload();
*
* // Sync lookup (instant if preloaded)
* const desc = descriptions.get("DateTimeOriginal");
* // => { desc: "When a photo was taken...", see: "https://..." }
*
* // Or use async lookup (auto-loads if needed)
* const desc2 = await descriptions.getAsync("ISO");
* ```
*/
class TagDescriptions {
#exiftool;
#options;
// Cached value for sync access via get()
#cacheSync = null;
constructor(exiftool, options) {
this.#exiftool = exiftool;
this.#options = {
language: "en",
disableDiskCache: false,
...options,
};
}
/**
* Preload all tag descriptions into memory.
* Call this during application initialization for instant sync access later.
*
* @returns Promise that resolves when descriptions are loaded
*/
async preload() {
await this.#cache();
}
/**
* Whether descriptions have been loaded into memory.
*/
get isLoaded() {
return this.#cacheSync != null;
}
/**
* Get the number of tag descriptions available.
* Returns 0 if not yet loaded.
*/
get size() {
return this.#cacheSync?.size ?? 0;
}
/**
* Returns curated descriptions only when language is English.
*/
get #curated() {
return this.#options.language === "en" ? CuratedDescriptions : {};
}
/**
* Synchronous lookup of a tag description.
* Returns undefined if descriptions haven't been loaded or tag is unknown.
*
* For guaranteed results, call `preload()` first or use `getAsync()`.
*
* @param tagName The tag name (e.g., "DateTimeOriginal", "ISO")
* @returns Tag description or undefined
*/
get(tagName) {
return this.#curated[tagName] ?? this.#cacheSync?.get(tagName);
}
/**
* Asynchronous lookup of a tag description.
* Automatically loads descriptions if not yet loaded.
*
* @param tagName The tag name (e.g., "DateTimeOriginal", "ISO")
* @returns Promise resolving to tag description or undefined
*/
async getAsync(tagName) {
return this.#curated[tagName] ?? (await this.#cache()).get(tagName);
}
/**
* Get all loaded tag descriptions.
* Automatically loads descriptions if not yet loaded.
*
* @returns Promise resolving to a readonly Map of tag names to descriptions
*/
async getAll() {
return await this.#cache();
}
/**
* Clear the in-memory cache.
* The disk cache is preserved; call `preload()` to reload from disk.
*/
clear() {
this.#cacheSync = null;
this.#cache.clear();
}
#cache = (0, Lazy_1.lazy)(async () => {
// Get ExifTool version for cache key
const version = await this.#exiftool.version();
const lang = this.#options.language ?? "en";
// Try to load from disk cache first
if (!this.#options.disableDiskCache) {
const cached = this.#readDiskCache(version, lang);
if (cached != null) {
this.#cacheSync = cached;
return cached;
}
}
// Query ExifTool for tag information
const xml = await this.#exiftool.enqueueTask(() => new ListXTask_1.ListXTask());
// Parse XML and extract descriptions
const parsed = this.#parseListX(xml, lang);
// Merge with curated descriptions (curated takes priority, English only)
for (const [name, desc] of Object.entries(this.#curated)) {
parsed.set(name, desc);
}
// Write to disk cache
if (!this.#options.disableDiskCache) {
this.#writeDiskCache(version, lang, parsed);
}
this.#cacheSync = parsed;
return parsed;
});
#getCacheDir() {
return this.#options.cacheDir ?? (0, node_path_1.join)((0, node_os_1.tmpdir)(), "exiftool-vendored");
}
#getCacheFilename(version, lang) {
return (0, node_path_1.join)(this.#getCacheDir(), `tag-descriptions-${version}-${lang}.json`);
}
#readDiskCache(version, lang) {
try {
const filename = this.#getCacheFilename(version, lang);
if (!(0, node_fs_1.existsSync)(filename))
return;
const data = (0, node_fs_1.readFileSync)(filename, "utf-8");
const parsed = JSON.parse(data);
// Validate cache metadata
if (parsed.version !== version || parsed.language !== lang) {
return;
}
return new Map(Object.entries(parsed.descriptions));
}
catch {
// Cache miss or corrupt - will regenerate
return;
}
}
#writeDiskCache(version, lang, descriptions) {
try {
const dir = this.#getCacheDir();
if (!(0, node_fs_1.existsSync)(dir)) {
(0, node_fs_1.mkdirSync)(dir, { recursive: true });
}
const filename = this.#getCacheFilename(version, lang);
const data = JSON.stringify({
version,
language: lang,
generatedAt: new Date().toISOString(),
descriptions: Object.fromEntries(descriptions),
}, null, 2);
(0, node_fs_1.writeFileSync)(filename, data, "utf-8");
}
catch {
// Non-fatal - cache write failure shouldn't break the app
}
}
#parseListX(xml, lang) {
const descriptions = new Map();
// Simple regex-based parsing - more robust than DOM for large files
// Format: <tag ... name='TagName' ...><desc lang='en'>Description</desc>...</tag>
const tagRegex = /<tag[^>]*\sname=['"]([^'"]+)['"][^>]*>([\s\S]*?)<\/tag>/gi;
const descRegex = new RegExp(`<desc\\s+lang=['"]${lang}['"]>([^<]+)</desc>`, "i");
let match;
while ((match = tagRegex.exec(xml)) !== null) {
const tagName = match[1];
const tagContent = match[2];
if (tagName == null || tagContent == null)
continue;
const descMatch = descRegex.exec(tagContent);
if (descMatch?.[1] != null) {
const desc = this.#decodeXmlEntities(descMatch[1].trim());
if (desc.length > 0) {
descriptions.set(tagName, { desc });
}
}
}
return descriptions;
}
#decodeXmlEntities(str) {
return str
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, '"')
.replace(/'/g, "'")
.replace(/&/g, "&");
}
}
exports.TagDescriptions = TagDescriptions;
//# sourceMappingURL=TagDescriptions.js.map