Подготовка скриптов TextTools

Документация по разработке скриптов для TextTools

📋 Оглавление

  1. Введение
  2. Базовое использование скриптов
  3. Работа с CSV и таблицами
  4. Математические вычисления
  5. Практические примеры
  6. Сложные цепочки обработки
  7. Безопасность и лучшие практики
  8. Внешние библиотеки
  9. Ссылки на документацию

🎯 Введение

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`)

Рекомендации по безопасности:

  1. Валидируйте входные данные внутри скрипта
  2. Ограничивайте сложность вычислений
  3. Используйте try-catch для обработки ошибок
  4. Логируйте операции через `console.log`
  5. Тестируйте скрипты на небольших данных

Пример безопасного скрипта

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);

Примечание: Все скрипты выполняются в изолированной среде браузера. Ваши данные никогда не покидают ваш компьютер, что гарантирует максимальную конфиденциальность и безопасность.