русские функции
This commit is contained in:
@@ -6,83 +6,97 @@ export interface ExcelFunction {
|
|||||||
|
|
||||||
// Волатильные функции - пересчитываются при любом изменении
|
// Волатильные функции - пересчитываются при любом изменении
|
||||||
export const VOLATILE_FUNCTIONS = new Set([
|
export const VOLATILE_FUNCTIONS = new Set([
|
||||||
'RAND',
|
'СЛУЧ',
|
||||||
'RANDBETWEEN',
|
'СЛУЧМЕЖДУ',
|
||||||
'NOW',
|
'СЕЙЧАС',
|
||||||
'TODAY',
|
'СЕГОДНЯ',
|
||||||
])
|
])
|
||||||
|
|
||||||
export const DEFAULT_FUNCTIONS: Record<string, ExcelFunction> = {
|
export const DEFAULT_FUNCTIONS: Record<string, ExcelFunction> = {
|
||||||
RAND: () => Math.random(),
|
СЛУЧ: () => Math.random(),
|
||||||
RANDBETWEEN: (bottom: number, top: number) =>
|
СЛУЧМЕЖДУ: (низ: number, верх: number) =>
|
||||||
Math.floor(Math.random() * (top - bottom + 1)) + bottom,
|
Math.floor(Math.random() * (верх - низ + 1)) + низ,
|
||||||
SUM: (...args: any[]) => {
|
СУММ: (...args: any[]) => {
|
||||||
const flatArgs = args.flat()
|
const flatArgs = args.flat()
|
||||||
return flatArgs.reduce(
|
return flatArgs.reduce(
|
||||||
(sum, val) => sum + (typeof val === 'number' ? val : 0),
|
(sum, val) => sum + (typeof val === 'number' ? val : 0),
|
||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
AVERAGE: (...args: any[]) => {
|
СРЗНАЧ: (...args: any[]) => {
|
||||||
const flatArgs = args.flat()
|
const flatArgs = args.flat()
|
||||||
const validArgs = flatArgs.filter((val) => typeof val === 'number')
|
const validArgs = flatArgs.filter((val) => typeof val === 'number')
|
||||||
return validArgs.length > 0
|
return validArgs.length > 0
|
||||||
? validArgs.reduce((sum, val) => sum + val, 0) / validArgs.length
|
? validArgs.reduce((sum, val) => sum + val, 0) / validArgs.length
|
||||||
: 0
|
: 0
|
||||||
},
|
},
|
||||||
COUNT: (...args: any[]) => {
|
СЧЁТ: (...args: any[]) => {
|
||||||
const flatArgs = args.flat()
|
const flatArgs = args.flat()
|
||||||
return flatArgs.filter((val) => typeof val === 'number').length
|
return flatArgs.filter((val) => typeof val === 'number').length
|
||||||
},
|
},
|
||||||
MAX: (...args: any[]) => {
|
МАКС: (...args: any[]) => {
|
||||||
const flatArgs = args.flat()
|
const flatArgs = args.flat()
|
||||||
const validArgs = flatArgs.filter((val) => typeof val === 'number')
|
const validArgs = flatArgs.filter((val) => typeof val === 'number')
|
||||||
return validArgs.length > 0 ? Math.max(...validArgs) : 0
|
return validArgs.length > 0 ? Math.max(...validArgs) : 0
|
||||||
},
|
},
|
||||||
MIN: (...args: any[]) => {
|
МИН: (...args: any[]) => {
|
||||||
const flatArgs = args.flat()
|
const flatArgs = args.flat()
|
||||||
const validArgs = flatArgs.filter((val) => typeof val === 'number')
|
const validArgs = flatArgs.filter((val) => typeof val === 'number')
|
||||||
return validArgs.length > 0 ? Math.min(...validArgs) : 0
|
return validArgs.length > 0 ? Math.min(...validArgs) : 0
|
||||||
},
|
},
|
||||||
IF: (condition: boolean, trueValue: CellValue, falseValue: CellValue) =>
|
ЕСЛИ: (условие: boolean, истина: CellValue, ложь: CellValue) =>
|
||||||
condition ? trueValue : falseValue,
|
условие ? истина : ложь,
|
||||||
AND: (...args: boolean[]) => args.every(Boolean),
|
И: (...args: boolean[]) => args.every(Boolean),
|
||||||
OR: (...args: boolean[]) => args.some(Boolean),
|
ИЛИ: (...args: boolean[]) => args.some(Boolean),
|
||||||
NOT: (value: boolean) => !value,
|
НЕ: (value: boolean) => !value,
|
||||||
CONCATENATE: (...args: string[]) => args.join(''),
|
СЦЕПИТЬ: (...args: string[]) => args.join(''),
|
||||||
LEN: (text: string) => (text ? text.length : 0),
|
ДЛСТР: (текст: string) => (текст ? текст.length : 0),
|
||||||
UPPER: (text: string) => (text ? text.toUpperCase() : ''),
|
ПРОПИСН: (текст: string) => (текст ? текст.toUpperCase() : ''),
|
||||||
LOWER: (text: string) => (text ? text.toLowerCase() : ''),
|
СТРОЧН: (текст: string) => (текст ? текст.toLowerCase() : ''),
|
||||||
TRIM: (text: string) => (text ? text.trim() : ''),
|
СЖПРОБЕЛЫ: (текст: string) => (текст ? текст.trim() : ''),
|
||||||
|
|
||||||
// Функции округления
|
// Функции округления
|
||||||
ROUND: (number: number, digits: number = 0) => {
|
ОКРУГЛ: (число: number, разряды: number = 0) => {
|
||||||
const factor = Math.pow(10, digits)
|
const factor = Math.pow(10, разряды)
|
||||||
return Math.round(number * factor) / factor
|
return Math.round(число * factor) / factor
|
||||||
},
|
},
|
||||||
ROUNDUP: (number: number, digits: number = 0) => {
|
ОКРУГЛВВЕРХ: (число: number, разряды: number = 0) => {
|
||||||
const factor = Math.pow(10, digits)
|
const factor = Math.pow(10, разряды)
|
||||||
return Math.ceil(number * factor) / factor
|
return Math.ceil(число * factor) / factor
|
||||||
},
|
},
|
||||||
ROUNDDOWN: (number: number, digits: number = 0) => {
|
ОКРУГЛВНИЗ: (число: number, разряды: number = 0) => {
|
||||||
const factor = Math.pow(10, digits)
|
const factor = Math.pow(10, разряды)
|
||||||
return Math.floor(number * factor) / factor
|
return Math.floor(число * factor) / factor
|
||||||
},
|
},
|
||||||
CEILING: (number: number, significance: number = 1) => {
|
ОКРВВЕРХ: (число: number, кратное: number = 1) => {
|
||||||
if (significance === 0) return 0
|
if (кратное === 0) return 0
|
||||||
return Math.ceil(number / significance) * significance
|
return Math.ceil(число / кратное) * кратное
|
||||||
},
|
},
|
||||||
FLOOR: (number: number, significance: number = 1) => {
|
ОКРВНИЗ: (число: number, кратное: number = 1) => {
|
||||||
if (significance === 0) return 0
|
if (кратное === 0) return 0
|
||||||
return Math.floor(number / significance) * significance
|
return Math.floor(число / кратное) * кратное
|
||||||
},
|
},
|
||||||
INT: (number: number) => Math.floor(number),
|
ЦЕЛОЕ: (число: number) => Math.floor(число),
|
||||||
TRUNC: (number: number, digits: number = 0) => {
|
ОТБР: (число: number, разряды: number = 0) => {
|
||||||
const factor = Math.pow(10, digits)
|
const factor = Math.pow(10, разряды)
|
||||||
return Math.trunc(number * factor) / factor
|
return Math.trunc(число * factor) / factor
|
||||||
},
|
},
|
||||||
MROUND: (number: number, multiple: number) => {
|
ОКРУГЛТ: (число: number, кратное: number) => {
|
||||||
if (multiple === 0) return 0
|
if (кратное === 0) return 0
|
||||||
return Math.round(number / multiple) * multiple
|
return Math.round(число / кратное) * кратное
|
||||||
|
},
|
||||||
|
|
||||||
|
// Функции даты и времени
|
||||||
|
СЕЙЧАС: () => Date.now(),
|
||||||
|
СЕГОДНЯ: () => {
|
||||||
|
const date = new Date()
|
||||||
|
date.setHours(0, 0, 0, 0)
|
||||||
|
return date.getTime()
|
||||||
|
},
|
||||||
|
|
||||||
|
// Отладочная функция
|
||||||
|
ТЕСТ: (значение: any = 'Функция работает!') => {
|
||||||
|
console.log('ТЕСТ вызван с:', значение)
|
||||||
|
return значение
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { DEFAULT_FUNCTIONS, ExcelFunction, VOLATILE_FUNCTIONS } from './functions'
|
import {
|
||||||
|
DEFAULT_FUNCTIONS,
|
||||||
|
ExcelFunction,
|
||||||
|
VOLATILE_FUNCTIONS,
|
||||||
|
} from './functions'
|
||||||
|
|
||||||
// Регулярные выражения для ссылок
|
// Регулярные выражения для ссылок
|
||||||
const QUALIFIED_REF_RE = /\b[A-Za-z0-9_]+![A-Z]+[0-9]+\b/g // SheetName!A1
|
const QUALIFIED_REF_RE = /\b[A-Za-z0-9_]+![A-Z]+[0-9]+\b/g // SheetName!A1
|
||||||
@@ -136,9 +140,36 @@ export class Cell {
|
|||||||
|
|
||||||
// Заменяем функции Excel
|
// Заменяем функции Excel
|
||||||
const functions = this.sheet.workbook.getFunctions()
|
const functions = this.sheet.workbook.getFunctions()
|
||||||
|
|
||||||
|
// Отладка
|
||||||
|
if (expr.includes('ТЕСТ')) {
|
||||||
|
console.log('Processing ТЕСТ function')
|
||||||
|
console.log('Available functions:', Object.keys(functions))
|
||||||
|
console.log('Original code:', code)
|
||||||
|
}
|
||||||
|
|
||||||
for (const [funcName] of Object.entries(functions)) {
|
for (const [funcName] of Object.entries(functions)) {
|
||||||
const funcRegex = new RegExp(`\\b${funcName}\\s*\\(`, 'gi')
|
// Экранируем специальные символы в названии функции для регулярного выражения
|
||||||
code = code.replace(funcRegex, `__functions__.${funcName}(`)
|
const escapedFuncName = funcName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||||
|
// Для кириллических символов \b может не работать, используем более универсальное решение
|
||||||
|
const funcRegex = new RegExp(
|
||||||
|
`(?:^|[^\\w])(${escapedFuncName})\\s*\\(`,
|
||||||
|
'gi',
|
||||||
|
)
|
||||||
|
const before = code
|
||||||
|
code = code.replace(funcRegex, (match, p1, offset) => {
|
||||||
|
// p1 - это название функции, нужно сохранить символ перед функцией если он есть
|
||||||
|
const prefix = match.substring(0, match.indexOf(p1))
|
||||||
|
return `${prefix}__functions__.${funcName}(`
|
||||||
|
})
|
||||||
|
|
||||||
|
if (before !== code && expr.includes('ТЕСТ')) {
|
||||||
|
console.log(`Replaced ${funcName}:`, before, '->', code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expr.includes('ТЕСТ')) {
|
||||||
|
console.log('Final code:', code)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Создаём контекст для выполнения
|
// Создаём контекст для выполнения
|
||||||
@@ -163,8 +194,14 @@ export class Cell {
|
|||||||
let hasVolatileFunction = false
|
let hasVolatileFunction = false
|
||||||
|
|
||||||
for (const funcName of Object.keys(functions)) {
|
for (const funcName of Object.keys(functions)) {
|
||||||
if (VOLATILE_FUNCTIONS.has(funcName.toUpperCase())) {
|
if (VOLATILE_FUNCTIONS.has(funcName)) {
|
||||||
const funcRegex = new RegExp(`\\b${funcName}\\s*\\(`, 'gi')
|
// Экранируем специальные символы для регулярного выражения
|
||||||
|
const escapedFuncName = funcName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||||
|
// Для кириллических символов используем более универсальное решение
|
||||||
|
const funcRegex = new RegExp(
|
||||||
|
`(?:^|[^\\w])${escapedFuncName}\\s*\\(`,
|
||||||
|
'gi',
|
||||||
|
)
|
||||||
if (funcRegex.test(expr)) {
|
if (funcRegex.test(expr)) {
|
||||||
hasVolatileFunction = true
|
hasVolatileFunction = true
|
||||||
break
|
break
|
||||||
@@ -288,7 +325,14 @@ export class Sheet {
|
|||||||
export class Workbook {
|
export class Workbook {
|
||||||
sheets: Map<string, Sheet> = new Map()
|
sheets: Map<string, Sheet> = new Map()
|
||||||
private _dirty: Set<string> = new Set()
|
private _dirty: Set<string> = new Set()
|
||||||
private _functions: Record<string, ExcelFunction> = { ...DEFAULT_FUNCTIONS }
|
private _functions: Record<string, ExcelFunction> = {}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// Инициализируем функции через метод addFunction, чтобы обеспечить единообразие
|
||||||
|
Object.entries(DEFAULT_FUNCTIONS).forEach(([name, func]) => {
|
||||||
|
this.addFunction(name, func)
|
||||||
|
})
|
||||||
|
}
|
||||||
private _volatileCells: Set<string> = new Set() // Список волатильных ячеек
|
private _volatileCells: Set<string> = new Set() // Список волатильных ячеек
|
||||||
|
|
||||||
addSheet(name: string): Sheet {
|
addSheet(name: string): Sheet {
|
||||||
@@ -319,7 +363,7 @@ export class Workbook {
|
|||||||
const toVisit = new Set([qualifiedName])
|
const toVisit = new Set([qualifiedName])
|
||||||
|
|
||||||
while (toVisit.size > 0) {
|
while (toVisit.size > 0) {
|
||||||
const current = toVisit.values().next().value
|
const current = toVisit.values().next().value!
|
||||||
toVisit.delete(current)
|
toVisit.delete(current)
|
||||||
|
|
||||||
if (!this._dirty.has(current)) {
|
if (!this._dirty.has(current)) {
|
||||||
@@ -344,7 +388,7 @@ export class Workbook {
|
|||||||
|
|
||||||
// Обрабатываем зависимых от волатильных ячеек
|
// Обрабатываем зависимых от волатильных ячеек
|
||||||
while (toVisit.size > 0) {
|
while (toVisit.size > 0) {
|
||||||
const current = toVisit.values().next().value
|
const current = toVisit.values().next().value!
|
||||||
toVisit.delete(current)
|
toVisit.delete(current)
|
||||||
|
|
||||||
if (!this._dirty.has(current)) {
|
if (!this._dirty.has(current)) {
|
||||||
@@ -360,7 +404,7 @@ export class Workbook {
|
|||||||
recalc(): void {
|
recalc(): void {
|
||||||
// Пересчёт всех "грязных" ячеек
|
// Пересчёт всех "грязных" ячеек
|
||||||
while (this._dirty.size > 0) {
|
while (this._dirty.size > 0) {
|
||||||
const current = this._dirty.values().next().value
|
const current = this._dirty.values().next().value!
|
||||||
this._dirty.delete(current)
|
this._dirty.delete(current)
|
||||||
this.getCell(current).evaluate()
|
this.getCell(current).evaluate()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user