UNPKG

@phi-ag/rvt

Version:

Parse Revit file format

117 lines (116 loc) 4.6 kB
const decoder = new TextDecoder("utf-16le"); const findVersionMarker = (data) => { const versionMarker = [0x04, 0x00, 0x00, 0x00]; for (let i = 0; i < data.length - 4; i++) { if (data[i] === versionMarker[0] && data[i + 1] === versionMarker[1] && data[i + 2] === versionMarker[2] && data[i + 3] === versionMarker[3]) { return i + 4; } } }; const parseString = (data, view, position) => { const length = view.getInt32(position, true); if (length > view.byteLength) throw Error(`Invalid string length ${length} at ${position}`); const start = position + 4; const end = start + length * 2; const value = decoder.decode(data.subarray(start, end)); return [value, end]; }; const parseVersion10 = (data, view) => { const versionLengthStart = 14; const [version, versionEnd] = parseString(data, view, versionLengthStart); const $version = version.slice(15, 19); const build = version.slice(28, version.length - 1); return [$version, build, versionEnd]; }; const parseVersion13 = (data, view) => { const versionMarkerEnd = findVersionMarker(data); if (!versionMarkerEnd) throw Error("Failed to find BasicFileInfo version marker"); const versionEnd = versionMarkerEnd + 8; const version = decoder.decode(data.subarray(versionMarkerEnd, versionEnd)); const [build, buildEnd] = parseString(data, view, versionEnd); return [version, build, buildEnd]; }; const parseVersion = (fileVersion, data, view) => { if (fileVersion === 10) { return parseVersion10(data, view); } else { return parseVersion13(data, view); } }; const parseGuids = (data, view, position) => { const padding = 3; const guid1LengthStart = position + 2 + padding; const [guid1, guid1End] = parseString(data, view, guid1LengthStart); const [locale, localeEnd] = parseString(data, view, guid1End); const guid2LengthStart = localeEnd + 5; const [guid2, guid2End] = parseString(data, view, guid2LengthStart); const [_guid3, guid3End] = parseString(data, view, guid2End); const guid4Padding = view.getInt16(guid3End, true); const guid4LengthStart = guid3End + 4 + guid4Padding * 2; const [_guid4, guid4End] = parseString(data, view, guid4LengthStart); return [locale, guid1, guid2, guid4End]; }; const parseAppName = (fileVersion, data, view, position) => { if (fileVersion === 10) return [undefined, position]; const [appName, appNameEnd] = parseString(data, view, position + 1); if (fileVersion === 13) return [appName, appNameEnd]; const [_appName2, appName2End] = parseString(data, view, appNameEnd); return [appName, appName2End]; }; const contentBounds = (fileVersion, data, view, position) => { if (fileVersion === 10 || fileVersion === 13) return [position + 2, data.byteLength - 2]; const unknownPaddingFlag = view.getInt8(position); const padding = unknownPaddingFlag === 4 ? 5 : 2; return [position + padding, data.byteLength - padding]; }; const parseContent = (fileVersion, data, view, position) => { const [start, end] = contentBounds(fileVersion, data, view, position); return decoder.decode(data.subarray(start, end)); }; const parseFileInfo = (data) => { const view = new DataView(data.buffer, data.byteOffset); const fileVersion = view.getInt32(0, true); if (fileVersion !== 10 && fileVersion !== 13 && fileVersion !== 14) throw Error(`Unknown file version ${fileVersion}`); const [version, build, versionEnd] = parseVersion(fileVersion, data, view); const [path, pathEnd] = parseString(data, view, versionEnd); const [locale, identityId, documentId, guidsEnd] = parseGuids(data, view, pathEnd); const [appName, appNameEnd] = parseAppName(fileVersion, data, view, guidsEnd); const content = parseContent(fileVersion, data, view, appNameEnd); return { fileVersion, version, build, path, locale, identityId, documentId, appName, content }; }; export const basicFileInfo = async (cfb) => { const entry = cfb.findEntry("BasicFileInfo"); if (!entry) throw Error("Basic file info not found"); return parseFileInfo(await cfb.entryData(entry)); }; export const tryBasicFileInfo = async (cfb) => { try { return { ok: true, data: await basicFileInfo(cfb) }; } catch (e) { if (e instanceof Error) return { ok: false, error: e.message }; throw e; } };