Preserve QExcell auto filters in XLSX

This commit is contained in:
Курнат Андрей
2026-06-01 12:53:19 +03:00
parent a1ad101729
commit 5ebc9639dc
4 changed files with 83 additions and 5 deletions
+42 -3
View File
@@ -10,6 +10,7 @@ export interface ImportedExcellWorkbook {
name: string;
visibility?: "hidden" | "veryHidden";
frozenPane?: SheetFrozenPane;
autoFilter?: string;
cells: Record<string, string>;
mergedCells?: MergedCellRange[];
hyperlinks?: Record<string, string>;
@@ -58,6 +59,7 @@ interface ParsedSheet {
name: string;
visibility?: "hidden" | "veryHidden";
frozenPane?: SheetFrozenPane;
autoFilter?: string;
cells: Record<string, string>;
mergedCells?: MergedCellRange[];
hyperlinks?: Record<string, string>;
@@ -254,7 +256,16 @@ export async function exportXlsxBlob(workbook: Workbook): Promise<Blob> {
const drawingRelationshipId = sheetChartParts.length > 0 ? `rId${sortedExternalHyperlinkEntries(sheet.hyperlinks).length + 1}` : "";
zip.file(
`xl/worksheets/sheet${index + 1}.xml`,
buildWorksheetXml(sheet.cells, sheet.mergedCells, sheet.hyperlinks, sheet.cellFormats, styleRegistry, drawingRelationshipId, sheet.frozenPane)
buildWorksheetXml(
sheet.cells,
sheet.mergedCells,
sheet.hyperlinks,
sheet.cellFormats,
styleRegistry,
drawingRelationshipId,
sheet.frozenPane,
sheet.autoFilter
)
);
const relationshipsXml = buildWorksheetRelationshipsXml(
sheet.hyperlinks,
@@ -286,12 +297,14 @@ export function parsedWorksheetToSheet(
styleTable: Array<CellFormat | undefined> = []
): ParsedSheet {
const frozenPane = worksheetXmlToFrozenPane(worksheetXml);
const autoFilter = worksheetXmlToAutoFilter(worksheetXml);
return {
id: sheet.id || `sheet-${index + 1}`,
name: sheet.name || `Лист ${index + 1}`,
...(sheet.visibility ? { visibility: sheet.visibility } : {}),
...(frozenPane ? { frozenPane } : {}),
...(autoFilter ? { autoFilter } : {}),
cells: worksheetXmlToCells(worksheetXml, sharedStrings),
mergedCells: worksheetXmlToMergedCells(worksheetXml),
hyperlinks: worksheetXmlToHyperlinks(worksheetXml, relationshipsXml),
@@ -410,6 +423,12 @@ export function worksheetXmlToFrozenPane(worksheetXml: unknown): SheetFrozenPane
};
}
export function worksheetXmlToAutoFilter(worksheetXml: unknown) {
const worksheet = childRecord(worksheetXml, "worksheet");
const autoFilter = childRecord(worksheet, "autoFilter");
return normalizeCellRange(String(autoFilter.ref ?? ""));
}
async function worksheetXmlToCharts(
zip: ZipInstance,
parser: InstanceType<XmlParserConstructor>,
@@ -581,7 +600,8 @@ export function buildWorksheetXml(
cellFormats: Record<string, CellFormat> = {},
styleRegistry = createCellStyleRegistry([cellFormats]),
drawingRelationshipId = "",
frozenPane?: SheetFrozenPane
frozenPane?: SheetFrozenPane,
autoFilter?: string
) {
const rows = sortedWorksheetEntries(cells, cellFormats).reduce<Record<number, Array<[string, string]>>>((grouped, entry) => {
const rowNumber = cellRowNumber(entry[0]);
@@ -600,12 +620,13 @@ export function buildWorksheetXml(
.join("");
const mergeCellsXml = buildMergeCellsXml(mergedCells);
const autoFilterXml = buildAutoFilterXml(autoFilter);
const hyperlinksXml = buildHyperlinksXml(hyperlinks);
const drawingXml = drawingRelationshipId ? `<drawing r:id="${escapeXmlAttribute(drawingRelationshipId)}"/>` : "";
const sheetViewsXml = buildSheetViewsXml(frozenPane);
return xmlDeclaration(
`<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">${sheetViewsXml}<sheetData>${rowXml}</sheetData>${mergeCellsXml}${hyperlinksXml}${drawingXml}</worksheet>`
`<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">${sheetViewsXml}<sheetData>${rowXml}</sheetData>${autoFilterXml}${mergeCellsXml}${hyperlinksXml}${drawingXml}</worksheet>`
);
}
@@ -681,6 +702,11 @@ export function buildSheetViewsXml(frozenPane?: SheetFrozenPane) {
return `<sheetViews><sheetView workbookViewId="0"><pane ${attributes}/></sheetView></sheetViews>`;
}
export function buildAutoFilterXml(autoFilter?: string) {
const ref = normalizeCellRange(autoFilter ?? "");
return ref ? `<autoFilter ref="${escapeXmlAttribute(ref)}"/>` : "";
}
export function buildWorksheetRelationshipsXml(hyperlinks: Record<string, string> = {}, drawingTarget = "") {
const refs = sortedExternalHyperlinkEntries(hyperlinks);
if (refs.length === 0 && !drawingTarget) {
@@ -1633,6 +1659,19 @@ function normalizeChartRange(range: string) {
return normalized ? `${normalized.start}:${normalized.end}` : `${normalizedStart}:${normalizedStart}`;
}
function normalizeCellRange(range: string) {
const raw = range.trim().replace(/\$/g, "").toUpperCase();
const [start, end = start] = raw.split(":");
const normalizedStart = normalizeCellAddress(start);
const normalizedEnd = normalizeCellAddress(end);
if (!normalizedStart || !normalizedEnd) {
return "";
}
const normalized = normalizeMergeRange({ start: normalizedStart, end: normalizedEnd });
return normalized ? `${normalized.start}:${normalized.end}` : normalizedStart;
}
function uniqueNumbers(values: number[]) {
return [...new Set(values)].sort((first, second) => first - second);
}