306 lines
9.0 KiB
TypeScript
306 lines
9.0 KiB
TypeScript
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
|
||
}
|