Files
protoc-frontend/src/component/DualSpreadsheet/utils.ts

306 lines
9.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { MergedCell } from '@/type/template'
import { MergedCellInfo } from './types'
export function getColumnLabel(index: number): string {
let label = ''
let col = index
while (col >= 0) {
label = String.fromCharCode(65 + (col % 26)) + label
col = Math.floor(col / 26) - 1
}
return label
}
// Создает адрес ячейки из координат (например, A1, B2)
export function createCellAddress(row: number, col: number): string {
return getColumnLabel(col) + (row + 1)
}
// Создает квалифицированную ссылку на ячейку с указанием листа
export function createQualifiedCellReference(
sheetType: 'report' | 'calculations',
row: number,
col: number
): string {
const sheetName = sheetType === 'report' ? 'L' : 'R'
const cellAddress = createCellAddress(row, col)
return `${sheetName}!${cellAddress}`
}
export function findMergedCellInfo(
row: number,
col: number,
mergedCells: MergedCell[]
): MergedCellInfo {
for (const mergedCell of mergedCells) {
const { startRow, startCol, endRow, endCol } = mergedCell
// Проверяем, попадает ли ячейка в диапазон объединенных ячеек
if (row >= startRow && row <= endRow && col >= startCol && col <= endCol) {
// Это главная ячейка (левая верхняя)
const isMainCell = row === startRow && col === startCol
return {
isMerged: true,
isMainCell,
mergedCell,
spans: {
rowSpan: endRow - startRow + 1,
colSpan: endCol - startCol + 1,
},
}
}
}
return { isMerged: false, isMainCell: false }
}
// Регулярные выражения для разных типов ссылок на ячейки
const QUALIFIED_REF_RE = /\b[A-Za-z0-9_]+![A-Z]+[0-9]+\b/g // SheetName!A1
const LOCAL_REF_RE = /\b[A-Z]+[0-9]+\b/g // A1, B2, etc.
const RANGE_RE = /\b[A-Z]+[0-9]+:[A-Z]+[0-9]+\b/g // A1:B2, D5:D7, etc.
const QUALIFIED_RANGE_RE = /\b[A-Za-z0-9_]+![A-Z]+[0-9]+:[A-Z]+[0-9]+\b/g // SheetName!A1:B2
// Интерфейс для ссылки на ячейку
export interface CellReference {
sheet: string | null // null для локальных ссылок
address: string // например "A1" или "B2:C3"
isRange: boolean
startRow: number
startCol: number
endRow: number
endCol: number
}
// Парсит адрес ячейки типа "A1" в координаты
export function parseCellAddress(
address: string
): { row: number; col: number } | null {
const match = address.match(/^([A-Z]+)(\d+)$/)
if (!match) return null
const column = match[1]
const row = parseInt(match[2], 10)
// Конвертируем колонку в индекс
let colIndex = 0
for (let i = 0; i < column.length; i++) {
colIndex = colIndex * 26 + (column.charCodeAt(i) - 65 + 1)
}
colIndex -= 1 // Конвертируем в 0-based индекс
return { row: row - 1, col: colIndex } // Конвертируем в 0-based индексы
}
// Парсит диапазон ячеек типа "A1:B2"
export function parseCellRange(range: string): {
startRow: number
startCol: number
endRow: number
endCol: number
} | null {
const parts = range.split(':')
if (parts.length !== 2) return null
const start = parseCellAddress(parts[0])
const end = parseCellAddress(parts[1])
if (!start || !end) return null
return {
startRow: Math.min(start.row, end.row),
startCol: Math.min(start.col, end.col),
endRow: Math.max(start.row, end.row),
endCol: Math.max(start.col, end.col),
}
}
// Извлекает все ссылки на ячейки из формулы
export function extractCellReferences(formula: string): CellReference[] {
if (!formula.startsWith('=')) return []
const expr = formula.substring(1) // Убираем знак =
const references: CellReference[] = []
// Функция для проверки, не является ли ссылка частью имени функции
const isFunction = (ref: string): boolean => {
const commonFunctions = [
'SUM',
'AVERAGE',
'COUNT',
'MAX',
'MIN',
'IF',
'AND',
'OR',
'NOT',
'СУММ',
'СРЕДНЕЕ',
'СЧЁТ',
'МАКС',
'МИН',
'ЕСЛИ',
]
return commonFunctions.includes(ref.toUpperCase())
}
// 1. Обрабатываем квалифицированные диапазоны (SheetName!A1:B2)
const qualifiedRangeMatches = expr.match(QUALIFIED_RANGE_RE)
if (qualifiedRangeMatches) {
for (const match of qualifiedRangeMatches) {
const [sheetPart, rangePart] = match.split('!')
const rangeCoords = parseCellRange(rangePart)
if (rangeCoords) {
references.push({
sheet: sheetPart,
address: rangePart,
isRange: true,
...rangeCoords,
})
}
}
}
// 2. Обрабатываем локальные диапазоны (A1:B2)
let cleanExpr = expr
// Удаляем уже обработанные квалифицированные диапазоны
cleanExpr = cleanExpr.replace(QUALIFIED_RANGE_RE, '')
const rangeMatches = cleanExpr.match(RANGE_RE)
if (rangeMatches) {
for (const match of rangeMatches) {
const rangeCoords = parseCellRange(match)
if (rangeCoords) {
references.push({
sheet: null,
address: match,
isRange: true,
...rangeCoords,
})
}
}
}
// 3. Обрабатываем квалифицированные одиночные ссылки (SheetName!A1)
const qualifiedMatches = expr.match(QUALIFIED_REF_RE)
if (qualifiedMatches) {
for (const match of qualifiedMatches) {
const [sheetPart, cellPart] = match.split('!')
const cellCoords = parseCellAddress(cellPart)
if (cellCoords) {
references.push({
sheet: sheetPart,
address: cellPart,
isRange: false,
startRow: cellCoords.row,
startCol: cellCoords.col,
endRow: cellCoords.row,
endCol: cellCoords.col,
})
}
}
}
// 4. Обрабатываем локальные одиночные ссылки (A1, B2)
// Удаляем уже обработанные квалифицированные ссылки и диапазоны
cleanExpr = expr
.replace(QUALIFIED_RANGE_RE, '')
.replace(QUALIFIED_REF_RE, '')
.replace(RANGE_RE, '')
const localMatches = cleanExpr.match(LOCAL_REF_RE)
if (localMatches) {
for (const match of localMatches) {
// Пропускаем функции
if (isFunction(match)) continue
const cellCoords = parseCellAddress(match)
if (cellCoords) {
references.push({
sheet: null,
address: match,
isRange: false,
startRow: cellCoords.row,
startCol: cellCoords.col,
endRow: cellCoords.row,
endCol: cellCoords.col,
})
}
}
}
return references
}
// Интерфейс для подсвеченной ячейки
export interface HighlightedCell {
sheet: string // нормализованное имя листа ('L' или 'R')
row: number
col: number
color: string // класс CSS для цвета подсветки
}
// Конвертирует ссылки на ячейки в подсвеченные ячейки
export function convertReferencesToHighlights(
references: CellReference[],
currentSheetType: 'report' | 'calculations'
): HighlightedCell[] {
const highlights: HighlightedCell[] = []
const colors = [
'bg-blue-200 border-blue-400',
'bg-green-200 border-green-400',
'bg-purple-200 border-purple-400',
'bg-red-200 border-red-400',
'bg-yellow-200 border-yellow-400',
'bg-indigo-200 border-indigo-400',
'bg-pink-200 border-pink-400',
'bg-orange-200 border-orange-400',
]
let colorIndex = 0
for (const ref of references) {
let sheetName: string
// Определяем имя листа
if (ref.sheet) {
// Квалифицированная ссылка
if (ref.sheet === 'L' || ref.sheet.toLowerCase() === 'report') {
sheetName = 'L'
} else if (
ref.sheet === 'R' ||
ref.sheet.toLowerCase() === 'calculations'
) {
sheetName = 'R'
} else {
// Неизвестный лист, пропускаем
continue
}
} else {
// Локальная ссылка - используем текущий лист
sheetName = currentSheetType === 'report' ? 'L' : 'R'
}
const color = colors[colorIndex % colors.length]
colorIndex++
// Добавляем все ячейки в диапазоне
for (let row = ref.startRow; row <= ref.endRow; row++) {
for (let col = ref.startCol; col <= ref.endCol; col++) {
highlights.push({
sheet: sheetName,
row,
col,
color,
})
}
}
}
return highlights
}