/*
* This file is part of TREB.
*
* TREB is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* TREB is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along
* with TREB. If not, see .
*
* Copyright 2022-2026 trebco, llc.
* info@treb.app
*
*/
import * as OOXML from 'ooxml-types';
import { IterateTags } from './ooxml';
// import { XMLUtils } from './xml-utils';
/**
* https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.metadatarecord?view=openxml-3.0.1
*
* ah, mixing 0-based and 1-based indexes. that's great. not at all confusing.
*/
export interface MetadataRecord {
//
// A 1-based index to the metadata record type in metadataTypes.
//
t: number;
//
// A zero based index to a specific metadata record. If the corresponding
// metadataType has name="XLMDX", then this is an index to a record in
// mdxMetadata, otherwise this is an index to a record in the futureMetadata
// section whose name matches the name of the metadataType.
//
v: number;
}
/**
* https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.metadatatype?view=openxml-3.0.1
*
* punting on all these attributes for now, except the ones we specifically
* want.
*/
export interface MetadataType {
//
// Represents the name of this particular metadata type. This name shall be
// unique amongst all other metadataTypes.
//
name: string;
//
// A Boolean flag indicating whether metadata is cell metadata. True when
// the metadata is cell metadata, false otherwise - in the false case it
// is considered to be value metadata.
//
cell_meta?: boolean;
}
/**
* the type `Future Feature Data Storage Area` is not actually defined? not
* sure. maybe it's just a standard extension type and I need to look that up?
*
* for the time being we'll use a list of flags...
*/
export interface MetadataFlags {
'dynamic-array'?: boolean;
}
/**
* https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.futuremetadata?view=openxml-3.0.1
* https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.futuremetadatablock?view=openxml-3.0.1
*
* these are named, matching the names in "MetadataType"; we'll use those
* as indexes, and not store them in the object.
*
*/
export interface FutureMetadata {
flags: MetadataFlags;
}
/**
* we're just reflecting the actual data at this point, not simplifying
* (although we should simplify). will require some lookups.
*/
export interface Metadata {
cell_metadata: MetadataRecord[];
metadata_types: MetadataType[];
// TODO
// value_metadata: MetadataRecord[];
future_metadata: Record;
}
export const LookupMetadata = (source: Metadata, type: 'cell'|'value', index: number): FutureMetadata => {
if (type === 'cell') {
//
// the docs say "zero based", but this looks to be one based -- there's
// only one entry when we create a doc, but the cm index in cells is "1".
//
// https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.cell?view=openxml-3.0.1&redirectedfrom=MSDN
//
const metadata_type = source.cell_metadata[index - 1];
if (metadata_type) {
const record_type = source.metadata_types[metadata_type.t - 1]; // 1-based
if (record_type) {
if (record_type.name === 'XLMDX') {
// ...
}
else {
const future_metadata_list = source.future_metadata[record_type.name];
if (future_metadata_list) {
return future_metadata_list[metadata_type.v] || {}; // 0-based
}
}
}
}
}
else {
console.warn('value metadata not implemented')
}
return {flags: {}}; // null, essentially
};
export const ParseMetadataXML = (root: OOXML.Metadata): Metadata => {
const metadata: Metadata = {
metadata_types: [],
cell_metadata: [],
future_metadata: {},
};
IterateTags(root.metadataTypes, metadataTypes => {
IterateTags(metadataTypes.metadataType, entry => {
const name: string = entry.$attributes?.name || '';
const cell_meta = !!entry.$attributes?.cellMeta;
metadata.metadata_types.push({
name, cell_meta
});
});
});
/*
const metadata_types = XMLUtils.FindAll(xml, 'metadata/metadataTypes/metadataType');
for (const entry of metadata_types) {
const name: string = entry.a$?.name || '';
const value = entry.a$?.cellMeta;
const cell_meta: boolean = (value === '1' || value === 'true');
metadata.metadata_types.push({
name, cell_meta
});
}
*/
IterateTags(root.futureMetadata, entry => {
const name = entry.$attributes?.name;
if (name) {
const future_metadata_list: FutureMetadata[] = [];
IterateTags(entry.bk, bk => {
const future_metadata: FutureMetadata = { flags: {} };
IterateTags(bk.extLst?.ext, ext => {
if (ext.dynamicArrayProperties?.$attributes?.fDynamic) {
future_metadata.flags['dynamic-array'] = true;
}
});
future_metadata_list.push(future_metadata);
});
metadata.future_metadata[name] = future_metadata_list;
}
});
/*
const future_metadata_blocks = XMLUtils.FindAll(xml, 'metadata/futureMetadata');
for (const entry of future_metadata_blocks) {
const name: string = entry.a$?.name || '';
if (name) {
const future_metadata_list: FutureMetadata[] = [];
// `extLst` entries can be inside of `bk` entries, but aparently not
// required? what's the case where they are _not_ in `bk` elements?
for (const block of XMLUtils.FindAll(entry, 'bk')) {
const future_metadata: FutureMetadata = { flags: {} };
// I guess metadata attributes are elements? we'll probably
// have to look them up individually
for (const child of XMLUtils.FindAll(block, 'extLst/ext/xda:dynamicArrayProperties')) {
if (child?.a$.fDynamic === '1') {
future_metadata.flags['dynamic-array'] = true;
}
}
future_metadata_list.push(future_metadata);
}
metadata.future_metadata[name] = future_metadata_list;
}
}
*/
IterateTags(root.cellMetadata, cellMetadata => {
IterateTags(cellMetadata.bk, bk => {
IterateTags(bk.rc, rc => {
if (rc.$attributes) {
metadata.cell_metadata.push({
...rc.$attributes
})
}
});
});
});
/*
for (const entry of XMLUtils.FindAll(xml, 'metadata/cellMetadata/bk/rc')) {
metadata.cell_metadata.push({
t: Number(entry.a$?.t || -1),
v: Number(entry.a$?.v || -1),
});
}
*/
// console.info({metadata});
return metadata;
}