Подготовка скриптов TextTools
Документация по разработке скриптов для TextTools
📋 Оглавление
- Введение
- Базовое использование скриптов
- Работа с CSV и таблицами
- Математические вычисления
- Практические примеры
- Сложные цепочки обработки
- Безопасность и лучшие практики
- Внешние библиотеки
- Ссылки на документацию
🎯 Введение
TextTools предоставляет мощный механизм выполнения пользовательских скриптов для обработки текста. Скрипты выполняются в безопасной изолированной среде с доступом к встроенным функциям и внешним библиотекам.
Доступные глобальные объекты:
- TextTools - Основная библиотека с более 100 функций
- arquero - Библиотека для обработки табличных данных
- formulajs - Функции Excel-подобных вычислений
- slugify - Транслитерация и создание URL-friendly строк
- pluralize - Работа с множественными числами
- console - Безопасное логирование
📝 Базовое использование скриптов
Структура скрипта
// Входной текст доступен как переменная `input`
// Результат должен быть присвоен переменной `output`
output = input;
// Пример: Базовая очистка текста
output = TextTools.stripHtml(output);
output = TextTools.removeExtraSpaces(output);
output = TextTools.changeTextCase(output, 'lower');
// Скрипт автоматически вернет значение output
Шаблон скрипта для быстрого старта
// Скрипт: Очистка и нормализация данных
output = input;
// 1. Очистка
output = TextTools.stripHtml(output);
output = TextTools.removeEmojis(output);
output = TextTools.removeControlChars(output);
// 2. Нормализация
output = TextTools.removeExtraSpaces(output);
output = TextTools.normalizeUnicode(output, 'NFC');
// 3. Форматирование
output = TextTools.wordWrap(output, 80);
📊 Работа с CSV и таблицами
Использование arquero для сложных операций
Пример 1: Анализ CSV данных
// Преобразование CSV в таблицу arquero
output = input;
const table = csvToArqueroTable(output);
// Статистика по колонкам
const stats = table.rollup({
count: arquero.op.count(),
avg: arquero.op.mean('price'),
sum: arquero.op.sum('quantity')
});
// Фильтрация данных
const filtered = table.filter(arquero.escape(d => d.price > 100));
// Сортировка
const sorted = filtered.orderby('price');
// Группировка
const grouped = table.groupby('category')
.rollup({ total: arquero.op.sum('amount') });
// Конвертация обратно в текст
output = arqueroTableToText(sorted, 'csv');
Пример 2: Обработка логических данных
output = input;
const table = csvToArqueroTable(output);
// Добавление вычисляемых колонок
const enhanced = table.derive({
profit: d => d.revenue - d.cost,
margin: d => (d.revenue - d.cost) / d.revenue * 100
});
// Фильтрация по условию
const profitable = enhanced.filter(
arquero.escape(d => d.margin > 20)
);
// Агрегация по дням
const daily = profitable
.derive({ date: d => d.timestamp.substring(0, 10) })
.groupby('date')
.rollup({
total_profit: arquero.op.sum('profit'),
avg_margin: arquero.op.mean('margin'),
count: arquero.op.count()
});
output = arqueroTableToText(daily, 'markdown');
Пример 3: Слияние нескольких CSV
// Предполагаем, что input содержит несколько CSV разделенных "---"
output = input;
const csvs = output.split('---');
const tables = csvs.map(csv => csvToArqueroTable(csv));
// Объединение таблиц
let merged = tables[0];
for (let i = 1; i < tables.length; i++) {
merged = merged.concat(tables[i]);
}
// Удаление дубликатов
merged = merged.dedupe();
// Сортировка по дате
merged = merged.orderby('date', 'desc');
output = arqueroTableToText(merged, 'csv');
🔢 Математические вычисления
Использование formulajs для Excel-подобных вычислений
Пример 1: Финансовые расчеты
output = input;
const table = csvToArqueroTable(output);
// Добавление финансовых показателей
const calculated = table.derive({
npv: d => formulajs.NPV(d.discount_rate, ...d.cash_flows),
irr: d => formulajs.IRR(d.cash_flows),
pmt: d => formulajs.PMT(d.rate, d.periods, d.pv),
fv: d => formulajs.FV(d.rate, d.periods, d.payment, d.pv)
});
output = arqueroTableToText(calculated, 'csv');
Пример 2: Статистический анализ
output = input;
const numbers = TextTools.extractNumbers(output, 'list', true)
.split('\n')
.map(n => parseFloat(n))
.filter(n => !isNaN(n));
if (numbers.length > 0) {
const stats = {
count: numbers.length,
sum: formulajs.SUM(numbers),
average: formulajs.AVERAGE(numbers),
median: formulajs.MEDIAN(numbers),
stdev: formulajs.STDEV.S(numbers),
min: formulajs.MIN(numbers),
max: formulajs.MAX(numbers)
};
output = JSON.stringify(stats, null, 2);
}
🛠️ Практические примеры
Типовые замены и преобразования
Пример 1: Нормализация телефонных номеров
output = input;
// Приведение всех телефонов к единому формату
output = output.replace(/(\+7|8)[\s\-\(\)]*(\d{3})[\s\-\(\)]*(\d{3})[\s\-\(\)]*(\d{2})[\s\-\(\)]*(\d{2})/g,
'+7 ($2) $3-$4-$5');
// Удаление лишних символов
output = output.replace(/[^\d\s\+\-\(\)]/g, '');
Пример 2: Разделение сложных строк
output = input;
// Разделение по нескольким разделителям
const lines = TextTools.splitLines(output, /[;,|]\s*/g, true);
// Очистка каждой строки
const cleaned = lines.map(line =>
line.trim().replace(/\s+/g, ' ')
);
// Удаление пустых и дубликатов
output = cleaned
.filter((line, index, self) =>
line && self.indexOf(line) === index
)
.join('\n');
Пример 3: Преобразование форматов дат
output = input;
// Поддержка различных форматов дат
const datePatterns = [
/(\d{2})\.(\d{2})\.(\d{4})/g, // DD.MM.YYYY
/(\d{4})-(\d{2})-(\d{2})/g, // YYYY-MM-DD
/(\d{1,2})\/(\d{1,2})\/(\d{4})/g, // MM/DD/YYYY
/(\d{4})(\d{2})(\d{2})/g // YYYYMMDD
];
const replacements = [
'$3-$2-$1', // в ISO
'$1-$2-$3', // уже ISO
'$3-$1-$2', // в ISO
'$1-$2-$3' // в ISO
];
datePatterns.forEach((pattern, index) => {
output = output.replace(pattern, replacements[index]);
});
Обработка специфических форматов
Пример 1: Парсинг логов Nginx
output = input;
// Извлечение ключевых полей из логов
const logRegex = /^(\S+) (\S+) (\S+) \[([^\]]+)\] "(\S+) ([^"]+) (\S+)" (\d+) (\d+) "([^"]*)" "([^"]*)"/gm;
const parsed = [];
let match;
while ((match = logRegex.exec(output)) !== null) {
parsed.push({
ip: match[1],
time: match[4],
method: match[5],
url: match[6],
status: match[8],
size: match[9],
referer: match[10],
agent: match[11]
});
}
// Конвертация в CSV
if (parsed.length > 0) {
const headers = Object.keys(parsed[0]);
const csv = [
headers.join(','),
...parsed.map(row => headers.map(h => `"${row[h]}"`).join(','))
];
output = csv.join('\n');
}
Пример 2: Обработка JSON API ответов
output = input;
try {
// Извлечение JSON из текста
const jsonText = TextTools.extractJson(output);
const data = JSON.parse(jsonText);
// Флаттенинг вложенных структур
const flatten = (obj, prefix = '') => {
return Object.keys(obj).reduce((acc, key) => {
const pre = prefix.length ? prefix + '.' : '';
if (typeof obj[key] === 'object' && obj[key] !== null) {
Object.assign(acc, flatten(obj[key], pre + key));
} else {
acc[pre + key] = obj[key];
}
return acc;
}, {});
};
if (Array.isArray(data)) {
const flattened = data.map(item => flatten(item));
output = TextTools.convertJsonTo(JSON.stringify(flattened), 'csv');
} else {
output = JSON.stringify(flatten(data), null, 2);
}
} catch (error) {
// В случае ошибки возвращаем исходный текст
console.error('JSON processing failed:', error.message);
}
🏗️ Сложные цепочки обработки
Data Pipeline для ETL процессов
Пример 1: Полный пайплайн очистки данных
// Шаг 1: Извлечение и валидация
function extractData(text) {
return pipe(
TextTools.stripHtml,
TextTools.removeControlChars,
TextTools.extractJson,
json => {
try {
return JSON.parse(json);
} catch {
return { error: 'Invalid JSON' };
}
}
)(text);
}
// Шаг 2: Трансформация
function transformData(data) {
if (data.error) return data;
const table = arquero.from(data);
return table
.derive({
timestamp: d => new Date(d.created_at).toISOString(),
value_numeric: d => parseFloat(d.value) || 0,
category_normalized: d => TextTools.changeTextCase(d.category, 'lower')
})
.filter(arquero.escape(d => d.value_numeric > 0))
.select('timestamp', 'category_normalized', 'value_numeric')
.objects();
}
// Шаг 3: Загрузка (форматирование)
function loadData(transformed) {
return pipe(
data => JSON.stringify(data),
json => TextTools.convertJsonTo(json, 'csv'),
csv => TextTools.sortCsvByColumn(csv, 0),
csv => TextTools.csvToMarkdownTable(csv)
)(transformed);
}
// Основной скрипт
output = input;
const extracted = extractData(output);
const transformed = transformData(extracted);
output = loadData(transformed);
Пример 2: Анализ текстовых данных с агрегацией
output = input;
// Разделение на документы
const documents = output.split(/\n{3,}/);
// Обработка каждого документа
const results = documents.map(doc => {
// Очистка
const clean = pipe(
TextTools.stripHtml,
TextTools.removeExtraSpaces,
t => TextTools.changeTextCase(t, 'lower')
)(doc);
// Анализ
const stats = TextTools.countStats(clean);
const keywords = TextTools.extractWithRegex(clean, /\b\w{5,}\b/g, '$&')
.split('\n')
.filter(k => k)
.slice(0, 10);
const entities = {
emails: TextTools.extractEmails(clean).split('\n').filter(e => e),
phones: TextTools.extractPhoneNumbers(clean).split('\n').filter(p => p),
urls: TextTools.extractUrls(clean).split('\n').filter(u => u)
};
return {
stats,
keywords,
entities,
sentiment: formulajs.AVERAGE(
...clean.split(/[.!?]+/)
.map(s => s.length)
.filter(l => l > 0)
) // Простая метрика "сложности"
};
});
// Агрегация результатов
const summary = {
total_documents: documents.length,
avg_word_count: formulajs.AVERAGE(results.map(r => r.stats.words)),
total_entities: {
emails: formulajs.SUM(results.map(r => r.entities.emails.length)),
phones: formulajs.SUM(results.map(r => r.entities.phones.length)),
urls: formulajs.SUM(results.map(r => r.entities.urls.length))
},
top_keywords: Array.from(
results.reduce((map, r) => {
r.keywords.forEach(k => map.set(k, (map.get(k) || 0) + 1));
return map;
}, new Map())
)
.sort((a, b) => b[1] - a[1])
.slice(0, 20)
.map(([word, count]) => `${word} (${count})`)
};
output = JSON.stringify(summary, null, 2);
Рецепты для частых задач
Рецепт 1: Обработка пользовательских данных
// Шаблон для очистки и нормализации пользовательского ввода
const userDataPipeline = pipe(
// 1. Санитизация
TextTools.stripHtml,
TextTools.removeEmojis,
TextTools.removeControlChars,
// 2. Нормализация
t => TextTools.normalizeUnicode(t, 'NFC'),
TextTools.removeExtraSpaces,
// 3. Стандартизация
t => TextTools.changeTextCase(t, 'title'),
// 4. Валидация
t => {
const emails = TextTools.extractEmails(t);
const phones = TextTools.extractPhoneNumbers(t);
return `Validated:\nEmails: ${emails.split('\n').length}\nPhones: ${phones.split('\n').length}\n\n${t}`;
}
);
output = userDataPipeline(input);
Рецепт 2: Подготовка данных для машинного обучения
// Подготовка текстовых данных для ML
const mlTextPreprocessor = pipe(
// Очистка
TextTools.stripHtmlTags,
TextTools.removeDiacritics,
t => t.replace(/[^\w\sа-яА-ЯёЁ]/g, ' '),
// Нормализация
TextTools.removeExtraSpaces,
t => TextTools.changeTextCase(t, 'lower'),
// Токенизация
t => t.split(/\s+/).filter(word => word.length > 2).join(' '),
// Дедупликация строк
TextTools.removeDuplicateLines,
// Фильтрация по длине
t => t.split('\n')
.filter(line => line.split(' ').length >= 5 && line.split(' ').length <= 100)
.join('\n')
);
output = mlTextPreprocessor(input);
🔒 Безопасность и лучшие практики
Запрещенные конструкции
Следующие паттерны запрещены в скриптах:
- `eval()`, `Function()` - выполнение динамического кода
- `setTimeout()`, `setInterval()` - асинхронные операции
- `fetch()`, `XMLHttpRequest` - сетевые запросы
- Работа с DOM (`document`, `window`)
- Доступ к хранилищам (`localStorage`, `indexedDB`)
- Системные API (`process`, `require`, `import`)
Рекомендации по безопасности:
- Валидируйте входные данные внутри скрипта
- Ограничивайте сложность вычислений
- Используйте try-catch для обработки ошибок
- Логируйте операции через `console.log`
- Тестируйте скрипты на небольших данных
Пример безопасного скрипта
try {
output = input;
// Валидация входных данных
if (output.length > 100000) {
throw new Error('Input too large (max 100KB)');
}
// Безопасная обработка
const lines = output.split('\n');
const processed = lines
.slice(0, 1000) // Ограничение количества строк
.map(line => {
// Безопасные операции
const clean = TextTools.stripHtml(line);
return TextTools.truncateText(clean, 500);
});
output = processed.join('\n');
} catch (error) {
console.error('Script error:', error.message);
output = `Error: ${error.message}\n\nOriginal input:\n${input}`;
}
📚 Внешние библиотеки
Arquero (обработка табличных данных)
Официальная документация:
Ключевые возможности:
- Фильтрация, сортировка, группировка
- Агрегация и оконные функции
- Соединение таблиц (join, merge)
- Векторизированные операции
FormulaJS (Excel-функции)
Официальная документация:
Поддерживаемые категории:
- Финансовые функции (NPV, IRR, PMT, FV)
- Статистические (AVERAGE, MEDIAN, STDEV)
- Логические (IF, AND, OR, NOT)
- Текстовые (TEXT, CONCAT, LEFT, RIGHT)
- Дата/время (DATE, NOW, DATEDIF)
🚀 Быстрый старт для разработчиков
Шаблон для создания нового скрипта:
/**
* Название: [Название вашего скрипта]
* Описание: [Краткое описание функциональности]
* Автор: [Ваше имя]
* Версия: 1.0
*/
'use strict';
output = input;
try {
// === КОНФИГУРАЦИЯ ===
const CONFIG = {
maxInputSize: 100000, // 100KB
maxRows: 10000,
encoding: 'UTF-8'
};
// === ВАЛИДАЦИЯ ===
if (output.length > CONFIG.maxInputSize) {
throw new Error(`Input exceeds maximum size of ${CONFIG.maxInputSize} bytes`);
}
// === ОБРАБОТКА ===
console.log('Starting processing...');
// Ваша логика здесь
// Используйте TextTools.*, arquero, formulajs
// === РЕЗУЛЬТАТ ===
console.log('Processing completed successfully');
} catch (error) {
console.error('Script execution failed:', error);
output = `❌ Error: ${error.message}\n\nDebug info:\n${JSON.stringify({
inputLength: input.length,
timestamp: new Date().toISOString(),
error: error.message
}, null, 2)}`;
}
// Возвращаем результат
Отладка скриптов:
// Используйте console.log для отладки
console.log('Input type:', typeof input);
console.log('Input length:', input.length);
console.log('First 100 chars:', input.substring(0, 100));
// Проверка доступности библиотек
console.log('TextTools available:', !!TextTools);
console.log('arquero available:', !!arquero);
console.log('formulajs available:', !!formulajs);
// Логирование промежуточных результатов
const intermediate = TextTools.stripHtml(input);
console.log('After stripHtml:', intermediate.length);
Производительность:
// Оптимизация для больших данных
const optimizeForLargeData = (text) => {
// Обработка по строкам, а не целиком
return text.split('\n')
.map(line => {
// Быстрые операции
return line
.replace(/<[^>]*>/g, '') // Быстрее, чем stripHtml для простых случаев
.trim()
.replace(/\s+/g, ' ');
})
.filter(line => line.length > 0)
.join('\n');
};
output = optimizeForLargeData(input);
Примечание: Все скрипты выполняются в изолированной среде браузера. Ваши данные никогда не покидают ваш компьютер, что гарантирует максимальную конфиденциальность и безопасность.