@phi-ag/rvt
Version:
Parse Revit file format
117 lines (116 loc) • 4.6 kB
JavaScript
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;
}
};