diff --git "a/static/script.js" "b/static/script.js" deleted file mode 100644--- "a/static/script.js" +++ /dev/null @@ -1,5925 +0,0 @@ -// Конфигурация API - OpenRouter DeepSeek -const API_CONFIG = { - OPENROUTER_URL: "https://openrouter.ai/api/v1/chat/completions", - OPENROUTER_KEY: "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", - MODEL: "deepseek/deepseek-r1:free" -}; - -// Глобальные переменные -let currentFile = null; -let currentFileId = null; -let accessToken = null; -let jsonData = null; -let isEditMode = false; -let originalTableData = null; -let selectedRows = new Set(); -let selectedColumns = new Set(); -let tokenCache = { value: null, expires: 0 }; -let rawResponse = null; - -// Элементы DOM -const elements = { - uploadArea: document.getElementById('uploadArea'), - pdfFileInput: document.getElementById('pdfFile'), - selectFileBtn: document.getElementById('selectFileBtn'), - fileInfo: document.getElementById('fileInfo'), - fileName: document.getElementById('fileName'), - fileSize: document.getElementById('fileSize'), - removeFileBtn: document.getElementById('removeFileBtn'), - validationAlert: document.getElementById('validationAlert'), - apiKeyInput: document.getElementById('apiKey'), - accessTokenInput: document.getElementById('accessToken'), - getTokenBtn: document.getElementById('getTokenBtn'), - expectedRowsInput: document.getElementById('expectedRows'), - processBtn: document.getElementById('processBtn'), - downloadBtn: document.getElementById('downloadBtn'), - loadingOverlay: document.getElementById('loadingOverlay'), - progressBar: document.getElementById('progressBar'), - statusText: document.getElementById('statusText'), - jsonOutput: document.getElementById('jsonOutput'), - tableView: document.getElementById('tableView'), - jsonView: document.getElementById('jsonView'), - tableHeader: document.getElementById('tableHeader'), - tableBody: document.getElementById('tableBody'), - tableInfo: document.getElementById('tableInfo'), - alertTitle: document.getElementById('alertTitle'), - alertMessage: document.getElementById('alertMessage'), - copyJsonBtn: document.getElementById('copyJsonBtn'), - toggleViewBtn: document.getElementById('toggleViewBtn'), - clearResultsBtn: document.getElementById('clearResultsBtn'), - tokenModal: document.getElementById('tokenModal'), - modalOverlay: document.getElementById('modalOverlay'), - closeTokenModal: document.getElementById('closeTokenModal'), - closeModalBtn: document.getElementById('closeModalBtn'), - tokenStatusContent: document.getElementById('tokenStatusContent'), - tokenResult: document.getElementById('tokenResult'), - tokenError: document.getElementById('tokenError'), - tokenOutput: document.getElementById('tokenOutput'), - copyTokenBtn: document.getElementById('copyTokenBtn'), - errorText: document.getElementById('errorText'), - toggleApiKey: document.getElementById('toggleApiKey') -}; - -function init() { - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', setupEventListeners); - } else { - setupEventListeners(); - } - loadSavedSettings(); - - // Инициализируем dropdown экспорта - setTimeout(setupExportDropdown, 100); -} - -function setupEventListeners() { - // Загрузка файла - elements.selectFileBtn.addEventListener('click', () => elements.pdfFileInput.click()); - elements.pdfFileInput.addEventListener('change', handleFileSelect); - - elements.uploadArea.addEventListener('dragover', (e) => { - e.preventDefault(); - elements.uploadArea.classList.add('dragover'); - }); - - elements.uploadArea.addEventListener('dragleave', () => { - elements.uploadArea.classList.remove('dragover'); - }); - - elements.uploadArea.addEventListener('drop', (e) => { - e.preventDefault(); - elements.uploadArea.classList.remove('dragover'); - if (e.dataTransfer.files.length > 0) { - handleFile(e.dataTransfer.files[0]); - } - }); - - if (document.getElementById('exportTableBtn')) { - document.getElementById('exportTableBtn').addEventListener('click', function() { - // Показываем dropdown экспорта - const dropdown = document.getElementById('exportDropdown'); - if (dropdown) { - dropdown.classList.toggle('show'); - } - }); - } - - elements.uploadArea.addEventListener('click', () => elements.pdfFileInput.click()); - elements.removeFileBtn.addEventListener('click', removeFile); - elements.getTokenBtn.addEventListener('click', getAccessToken); - elements.processBtn.addEventListener('click', processPDF); - elements.downloadBtn.addEventListener('click', downloadJSON); - elements.copyJsonBtn.addEventListener('click', copyJSON); - elements.toggleViewBtn.addEventListener('click', toggleView); - elements.clearResultsBtn.addEventListener('click', clearResults); - - // Модальное окно токена - elements.closeTokenModal.addEventListener('click', () => { - elements.tokenModal.style.display = 'none'; - }); - - elements.copyTokenBtn.addEventListener('click', copyToken); - - // Сохранение настроек - elements.apiKeyInput.addEventListener('input', saveSettings); - elements.accessTokenInput.addEventListener('input', saveSettings); - elements.expectedRowsInput.addEventListener('input', saveSettings); - - // Кнопка редактирования - if (document.getElementById('editTableBtn')) { - document.getElementById('editTableBtn').addEventListener('click', toggleEditMode); - } - - // Кнопка исправления ассимиляции - if (document.getElementById('forceAssimilationBtn')) { - document.getElementById('forceAssimilationBtn').addEventListener('click', function() { - try { - const jsonText = elements.jsonOutput.textContent; - const data = JSON.parse(jsonText); - if (!data.table_data) { - alert('Нет табличных данных'); - return; - } - const originalCount = data.table_data.length; - data.table_data = forceFixAssimilationImproved(data.table_data); - let changes = 0; - data.table_data.forEach(row => { - Object.keys(row).forEach(key => { - if (key !== 'characteristic' && row[key] === '+') { - const originalRow = jsonData ? JSON.parse(jsonData).table_data.find(r => - r.characteristic === row.characteristic - ) : null; - if (originalRow && originalRow[key] !== '+') { - changes++; - } - } - }); - }); - elements.jsonOutput.textContent = JSON.stringify(data, null, 2); - highlightJSON(); - if (elements.tableView.style.display === 'block') { - displayTable(data.table_data); - } - jsonData = JSON.stringify(data); - if (changes > 0) { - showNotification(`Исправлено ${changes} значений ассимиляции`, 'success'); - } else { - alert('Не найдено значений "?" для исправления ассимиляции.'); - } - } catch (error) { - console.error('Ошибка при исправлении ассимиляции:', error); - showNotification('Ошибка при исправлении ассимиляции', 'error'); - } - }); - } - - // Экспорт таблицы - if (document.getElementById('exportTableBtn')) { - document.getElementById('exportTableBtn').addEventListener('click', exportTable); - } - - // Показать сырой ответ - if (document.getElementById('showRawBtn')) { - document.getElementById('showRawBtn').addEventListener('click', showRawResponse); - } - - // Закрытие модального окна при клике вне его - window.addEventListener('click', (e) => { - if (e.target === elements.tokenModal) { - elements.tokenModal.style.display = 'none'; - } - }); -} - -function handleFileSelect(e) { - if (e.target.files.length > 0) { - handleFile(e.target.files[0]); - } -} - -function handleFile(file) { - if (file.type !== 'application/pdf') { - alert('Пожалуйста, выберите файл в формате PDF'); - return; - } - - if (file.size > 50 * 1024 * 1024) { - alert('Размер файла не должен превышать 50 МБ'); - return; - } - - currentFile = file; - updateFileInfo(); - checkProcessButton(); -} - -function updateFileInfo() { - elements.fileName.textContent = currentFile.name; - elements.fileSize.textContent = formatFileSize(currentFile.size); - elements.fileInfo.style.display = 'block'; -} - -function checkRateLimits() { - const lastRequestTime = localStorage.getItem('lastGigaChatRequest'); - const now = Date.now(); - - if (lastRequestTime) { - const timeSinceLastRequest = now - parseInt(lastRequestTime); - // Если прошло меньше 1 секунды с последнего запроса - if (timeSinceLastRequest < 1000) { - showNotification('Слишком частые запросы. Подождите секунду.', 'warning'); - return false; - } - } - - localStorage.setItem('lastGigaChatRequest', now.toString()); - return true; -} - -function formatFileSize(bytes) { - if (bytes === 0) return '0 Bytes'; - - const k = 1024; - const sizes = ['Bytes', 'KB', 'MB', 'GB']; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - - return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; -} - -function removeFile() { - currentFile = null; - currentFileId = null; - elements.pdfFileInput.value = ''; - elements.fileInfo.style.display = 'none'; - checkProcessButton(); -} - -function checkProcessButton() { - const hasFile = currentFile !== null; - const hasApiKey = elements.apiKeyInput.value.trim() !== ''; - const hasToken = elements.accessTokenInput.value.trim() !== ''; - - elements.processBtn.disabled = !hasFile || (!hasApiKey && !hasToken); -} - -async function getAccessToken() { - const apiKey = elements.apiKeyInput.value.trim(); - - if (!apiKey) { - alert('Пожалуйста, введите API ключ'); - return; - } - - // Проверяем кеш - const now = Date.now(); - if (tokenCache.value && tokenCache.expires > now) { - console.log('Используем кешированный токен'); - accessToken = tokenCache.value; - elements.accessTokenInput.value = accessToken; - - // Показываем уведомление - showNotification('Используется кешированный токен', 'info'); - - checkProcessButton(); - return; - } - - // Показываем модальное окно - if (elements.tokenModal) { - elements.tokenModal.style.display = 'flex'; - } - if (elements.tokenStatusContent) { - elements.tokenStatusContent.style.display = 'block'; - } - if (elements.tokenResult) { - elements.tokenResult.style.display = 'none'; - } - if (elements.tokenError) { - elements.tokenError.style.display = 'none'; - } - - try { - const response = await fetch('/api/oauth', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - api_key: apiKey - }) - }); - - console.log('Response status:', response.status); - - if (!response.ok) { - const errorText = await response.text(); - console.error('Error response:', errorText); - - // Проверяем специфичные ошибки - if (response.status === 429) { - throw new Error('Превышен лимит запросов. Подождите 1 минуту и попробуйте снова.'); - } - throw new Error(`HTTP error! status: ${response.status}, details: ${errorText}`); - } - - const result = await response.json(); - console.log('Token response:', result); - - if (result.access_token) { - // Сохраняем токен в кеш (действителен 1 час = 3600000 мс) - tokenCache.value = result.access_token; - tokenCache.expires = Date.now() + 3500000; // 58 минут для запаса - - // Сохраняем токен - accessToken = result.access_token; - elements.accessTokenInput.value = result.access_token; - saveSettings(); - - // Показываем результат - if (elements.tokenStatusContent) { - elements.tokenStatusContent.style.display = 'none'; - } - if (elements.tokenResult) { - elements.tokenResult.style.display = 'block'; - } - if (elements.tokenOutput) { - elements.tokenOutput.value = result.access_token; - } - - // Обновляем статус токена - if (elements.tokenStatus) { - elements.tokenStatus.innerHTML = ' Получен'; - elements.tokenStatus.className = 'status-value status-active'; - } - - checkProcessButton(); - - } else { - throw new Error('Токен не найден в ответе'); - } - - } catch (error) { - console.error('Ошибка при получении токена:', error); - - if (elements.tokenStatusContent) { - elements.tokenStatusContent.style.display = 'none'; - } - if (elements.tokenError) { - elements.tokenError.style.display = 'block'; - } - if (elements.errorText) { - elements.errorText.textContent = `Ошибка: ${error.message}`; - } - - // Показываем уведомление - showNotification(error.message, 'error'); - } -} - -function copyToken() { - navigator.clipboard.writeText(elements.tokenOutput.value) - .then(() => { - const originalText = elements.copyTokenBtn.innerHTML; - elements.copyTokenBtn.innerHTML = ' Скопировано!'; - - setTimeout(() => { - elements.copyTokenBtn.innerHTML = originalText; - }, 2000); - }) - .catch(err => { - console.error('Ошибка при копировании: ', err); - }); -} - -async function processPDF() { - if (!currentFile) { - alert('Пожалуйста, выберите PDF файл'); - return; - } - - // Проверяем лимиты запросов - if (!checkRateLimits()) { - return; - } - - // Проверяем токен - if (elements.accessTokenInput.value.trim()) { - accessToken = elements.accessTokenInput.value.trim(); - } else if (elements.apiKeyInput.value.trim()) { - // Пытаемся получить токен из API ключа - await getAccessToken(); - if (!accessToken) { - return; - } - } else { - alert('Пожалуйста, введите API ключ или токен доступа'); - return; - } - - // Показываем индикатор загрузки - showLoading(true); - updateProgress(10, 'Проверка данных...'); - - try { - // Загружаем файл - updateProgress(30, 'Отправка файла на сервер...'); - const fileId = await uploadPDF(accessToken, currentFile); - currentFileId = fileId; - - // Запрашиваем данные из GigaChat - updateProgress(60, 'Извлечение данных из таблицы...'); - const jsonResult = await askGigaChat(accessToken, fileId); - - // Парсим и отображаем результат - updateProgress(90, 'Обработка результатов...'); - jsonData = jsonResult; - - // Отображаем JSON - displayJSON(jsonResult); - - // Проверяем данные - checkMissingData(jsonResult); - - // Включаем кнопки - elements.downloadBtn.disabled = false; - - updateExportButtons(); - - updateProgress(100, 'Готово!'); - - // Переключаемся на JSON вид - showView('json'); - - // Через секунду скрываем загрузку - setTimeout(() => { - showLoading(false); - }, 1000); - - } catch (error) { - console.error('Ошибка при обработке PDF:', error); - showLoading(false); - - // Более информативное сообщение об ошибке - if (error.message.includes('429') || error.message.includes('лимит')) { - alert(`Ошибка: ${error.message}\n\nРекомендации:\n1. Подождите 1-2 минуты\n2. Проверьте ваш тарифный план GigaChat\n3. Попробуйте позже`); - } else if (error.message.includes('401') || error.message.includes('токен')) { - alert(`Ошибка: ${error.message}\n\nПолучите новый токен через кнопку "Получить токен"`); - } else { - alert(`Ошибка: ${error.message}`); - } - } -} - -if (document.getElementById('clearCacheBtn')) { - document.getElementById('clearCacheBtn').addEventListener('click', function() { - tokenCache.value = null; - tokenCache.expires = 0; - localStorage.removeItem('lastGigaChatRequest'); - showNotification('Кеш очищен', 'info'); - }); -} - -async function uploadPDF(token, file) { - const formData = new FormData(); - formData.append('file', file); - formData.append('purpose', 'general'); - - console.log("DEBUG: Загрузка файла в GigaChat"); - console.log("DEBUG: Имя файла:", file.name); - console.log("DEBUG: Размер файла:", file.size); - - try { - const response = await fetch('/api/files', { - method: 'POST', - headers: { - 'Authorization': `Bearer ${token}` - }, - body: formData - }); - - console.log("DEBUG: Статус ответа загрузки:", response.status); - - if (!response.ok) { - const errorText = await response.text(); - console.error("DEBUG: Ошибка загрузки файла:", errorText); - - let errorMessage = `Ошибка загрузки файла: ${response.status}`; - - if (response.status === 429) { - errorMessage = 'Превышен лимит запросов к GigaChat API. Подождите 1 минуту и попробуйте снова.'; - // Предлагаем пользователю подождать - showNotification('Превышен лимит запросов. Подождите 1 минуту.', 'warning'); - - // Можно автоматически попробовать через 60 секунд - // setTimeout(() => { - // showNotification('Можно попробовать снова', 'info'); - // }, 60000); - } else if (response.status === 401) { - errorMessage = 'Токен устарел или недействителен. Получите новый токен.'; - // Сбрасываем кеш токена - tokenCache.value = null; - tokenCache.expires = 0; - } - - throw new Error(errorMessage); - } - - const result = await response.json(); - console.log("DEBUG: Файл успешно загружен. ID:", result.id); - return result.id; - - } catch (error) { - console.error('Ошибка при загрузке файла:', error); - throw error; - } -} - -async function askGigaChat(token, fileId) { - console.log("DEBUG: Запрос к GigaChat"); - console.log("DEBUG: File ID:", fileId); - - const expectedRows = parseInt(elements.expectedRowsInput?.value) || 30; - const expectedColumns = 24; // Как в Python коде - - const prompt = `ВНИМАТЕЛЬНО ПРОСМОТРИ таблицу из PDF и верни её строго в формате JSON. -ВАЖНО ЧТОБЫ ТЫ ЕЕ ПРОСМОТРЕЛ, ПРОАНАЛИЗИРОВАЛ КАК КАРТИНКУ. СЧИТЫВАНИЕ PDF МОЖЕТ БЫТЬ НЕ КОРРЕКТНЫМ. -ВАЖНО: В таблице должно быть ${expectedColumns} колонок (столбцов)! -Проверь внимательно - если видишь меньше колонок, значит ты пропустил часть данных. - -СТРУКТУРА JSON: -{ - "table_data": [ - { - "characteristic": "Название характеристики", - "column_1": "значение", - "column_2": "значение", - ... - "column_${expectedColumns}": "значение" - } - ] -} - -КРИТИЧЕСКИ ВАЖНО: -1. Должно быть РОВНО ${expectedColumns} колонок. Если какая-то колонка пустая - оставь пустую строку "". -2. Имена колонок должны быть: column_1, column_2, ..., column_${expectedColumns} -3. Сохрани ВСЕ символы как есть: +, -, W, ND, числа, буквы. -4. Если колонок больше, чем ${expectedColumns} - включи все! -5. Верни ТОЛЬКО JSON, без пояснений. -6. Включи все строки таблицы, включая подзаголовки. - -Пример для строки с ${expectedColumns} колонками: -{ - "characteristic": "Название характеристики", - "column_1": "+", - "column_2": "-", - ... - "column_${expectedColumns}": "значение" -} - -Извлеки ВСЕ данные и убедись, что колонок ровно ${expectedColumns}!`; - - const payload = { - "model": "GigaChat", - "messages": [ - { - "role": "user", - "content": prompt, - "attachments": [fileId] - } - ], - "temperature": 0.1, - "max_tokens": 8000 // Увеличиваем для больших таблиц - }; - - console.log("DEBUG: Отправляемый payload"); - - try { - const response = await fetch('/api/chat/completions', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}` - }, - body: JSON.stringify(payload) - }); - - console.log("DEBUG: Статус ответа:", response.status); - - if (!response.ok) { - const errorText = await response.text(); - console.error("DEBUG: Текст ошибки:", errorText); - throw new Error(`Ошибка GigaChat API: ${response.status}, details: ${errorText}`); - } - - const result = await response.json(); - console.log("DEBUG: Получен ответ от GigaChat"); - - // Сохраняем сырой ответ для отладки - rawResponse = result.choices?.[0]?.message?.content || '{"table_data": []}'; - - // В setupEventListeners() замените обработчики: - if (document.getElementById('showRawBtn')) { - document.getElementById('showRawBtn').addEventListener('click', function(e) { - e.stopPropagation(); - safeClick(showRawResponse); - }); - } - - // Очищаем JSON - const cleanedContent = cleanJSON(rawResponse); - console.log("DEBUG: Очищенный контент (первые 500 символов):", cleanedContent.substring(0, 500)); - - // Анализируем данные - const analysis = analyzeExtractedData(cleanedContent, expectedColumns); - console.log("Анализ данных:", analysis); - - // Проверяем, достаточно ли колонок - if (analysis.columns_missing > 10) { - console.warn(`ВНИМАНИЕ: Найдено только ${analysis.total_columns_found} из ${expectedColumns} колонок!`); - // Можно показать уведомление пользователю - showNotification(`Найдено только ${analysis.total_columns_found} из ${expectedColumns} колонок. Возможно, данные неполные.`, 'warning'); - } - - return cleanedContent; - - } catch (error) { - console.error('Ошибка при запросе к GigaChat:', error); - throw error; - } -} - -function setupTableControls() { - console.log('Настройка обработчиков для кнопок управления таблицей'); - - // Маппинг ID кнопок и их обработчиков - const buttonHandlers = { - 'showAllColumnsBtn': showAllColumns, - 'showDataColumnsBtn': showDataColumns, - 'toggleEmptyColumnsBtn': toggleEmptyColumns, - 'deleteEmptyColumnsBtn': deleteEmptyColumnsSimple, // Убедитесь, что используется правильная функция - 'showAllRowsBtn': showAllRows, - 'showDataRowsBtn': showDataRows, - 'toggleEmptyRowsBtn': toggleEmptyRows, - 'deleteEmptyRowsBtn': deleteEmptyRows - }; - - // Назначаем обработчики для каждой кнопки - Object.entries(buttonHandlers).forEach(([id, handler]) => { - const button = document.getElementById(id); - if (button) { - // Удаляем старые обработчики, если есть - const newButton = button.cloneNode(true); - button.parentNode.replaceChild(newButton, button); - - // Добавляем новый обработчик - newButton.addEventListener('click', function(e) { - e.preventDefault(); - e.stopPropagation(); - - // Добавляем визуальную обратную связь - this.classList.add('active'); - setTimeout(() => this.classList.remove('active'), 200); - - // Вызываем обработчик - try { - handler(); - } catch (error) { - console.error(`Ошибка в обработчике ${id}:`, error); - showNotification(`Ошибка: ${error.message}`, 'error'); - } - }); - - console.log(`Обработчик добавлен для кнопки: ${id}`); - } else { - console.warn(`Кнопка с ID "${id}" не найдена`); - } - }); -} - -function displayJSON(jsonStr) { - try { - // Очищаем предыдущие ошибки - const existingError = elements.jsonOutput.parentNode.querySelector('.error-message'); - if (existingError) { - existingError.remove(); - } - - console.log('Начало отображения JSON'); - console.log('Длина исходных данных:', jsonStr.length); - - // Сначала пробуем очистить JSON от возможных проблем - let cleanedJsonStr = cleanJSON(jsonStr); - - console.log('Длина после очистки:', cleanedJsonStr.length); - - // Если очищенная строка слишком короткая, возможно проблема - if (cleanedJsonStr.length < 50) { - console.warn('Очищенный JSON слишком короткий, используем альтернативный метод'); - cleanedJsonStr = extractJSONFromText(jsonStr); - } - - // Парсим JSON - let jsonObj; - try { - jsonObj = JSON.parse(cleanedJsonStr); - console.log('JSON успешно спарсен'); - } catch (parseError) { - console.error('Ошибка при парсинге JSON после очистки:', parseError.message); - - // Пробуем альтернативные методы - jsonObj = tryAlternativeParsing(jsonStr); - } - - // Исправляем символы в таблице - if (jsonObj.table_data && Array.isArray(jsonObj.table_data)) { - console.log('Применяем исправления символов...'); - jsonObj.table_data = fixTableSymbols(jsonObj.table_data); - - // Дополнительная проверка и исправление - jsonObj.table_data = postProcessTableData(jsonObj.table_data); - - // Автоматическое исправление ассимиляции - console.log('Автоматическое исправление ассимиляции...'); - jsonObj.table_data = forceFixAssimilationImproved(jsonObj.table_data); - } - - // Показываем очищенный JSON - elements.jsonOutput.textContent = JSON.stringify(jsonObj, null, 2); - - // Синтаксическая подсветка - highlightJSON(); - - // Отображаем таблицу, если есть данные - console.log('JSON для таблицы:', jsonObj); - if (jsonObj.table_data && Array.isArray(jsonObj.table_data) && jsonObj.table_data.length > 0) { - console.log('Вызываем displayTable с данными:', jsonObj.table_data.length, 'строк'); - displayTable(jsonObj.table_data); - - elements.toggleViewBtn.disabled = false; - elements.toggleViewBtn.setAttribute('data-view', 'json'); - elements.toggleViewBtn.innerHTML = ' Показать таблицу'; - } else { - console.warn('Нет данных table_data или массив пустой'); - elements.tableInfo.textContent = 'Нет табличных данных для отображения'; - elements.toggleViewBtn.disabled = true; - } - - // Включаем кнопки - elements.copyJsonBtn.disabled = false; - elements.clearResultsBtn.disabled = false; - elements.downloadBtn.disabled = false; - - // Сохраняем данные для скачивания - jsonData = JSON.stringify(jsonObj); - - - } catch (error) { - console.error('Критическая ошибка при отображении JSON:', error); - console.error('Исходный текст (первые 500 символов):', jsonStr.substring(0, 500)); - - // Показываем исходный текст - elements.jsonOutput.textContent = jsonStr; - - // Показываем сообщение об ошибке - showJSONError(error, jsonStr); - - // Отключаем кнопки, связанные с таблицей - elements.toggleViewBtn.disabled = true; - elements.copyJsonBtn.disabled = true; - elements.downloadBtn.disabled = false; // все равно можно скачать сырой текст - updateExportButtons(); // <-- Добавьте эту строку - } -} - -function analyzeExtractedData(jsonStr, expectedColumns = 24) { - try { - const data = JSON.parse(jsonStr); - const tableData = data.table_data || []; - - if (!tableData.length) { - return { error: "Таблица пуста", columns_found: 0 }; - } - - // Собираем все уникальные колонки - const allColumns = new Set(); - tableData.forEach(row => { - Object.keys(row).forEach(key => allColumns.add(key)); - }); - - // Убираем characteristic из подсчета колонок с данными - const dataColumns = Array.from(allColumns).filter(col => col !== "characteristic"); - - // Определяем числовые колонки - const numericColumns = []; - const nonNumericColumns = []; - - dataColumns.forEach(col => { - // Проверяем, является ли колонка числовой (column_1, column_2, ...) - if (col.startsWith("column_")) { - try { - const num = parseInt(col.replace("column_", "")); - numericColumns.push({ col, num }); - } catch { - nonNumericColumns.push(col); - } - } - // Или просто число "1", "2", ... - else if (/^\d+$/.test(col)) { - numericColumns.push({ col, num: parseInt(col) }); - } else { - nonNumericColumns.push(col); - } - }); - - // Сортируем числовые колонки - numericColumns.sort((a, b) => a.num - b.num); - const sortedColumns = ["characteristic", ...numericColumns.map(c => c.col), ...nonNumericColumns]; - - return { - total_rows: tableData.length, - total_columns_found: dataColumns.length, - expected_columns: expectedColumns, - columns_missing: Math.max(0, expectedColumns - dataColumns.length), - sorted_columns: sortedColumns, - numeric_columns_count: numericColumns.length, - non_numeric_columns: nonNumericColumns, - sample_columns: dataColumns.slice(0, 10) - }; - - } catch (error) { - console.error('Ошибка при анализе данных:', error); - return { error: error.message, columns_found: 0 }; - } -} - -function extractJSONFromText(text) { - console.log('Извлечение JSON из текста'); - - // Ищем JSON структуру - const jsonPattern = /\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*\}/gs; - const matches = text.match(jsonPattern); - - if (matches && matches.length > 0) { - // Берем самый длинный match (скорее всего это наш JSON) - const longestMatch = matches.reduce((a, b) => a.length > b.length ? a : b); - console.log('Найден JSON длиной:', longestMatch.length); - return longestMatch; - } - - // Если не нашли, создаем минимальный JSON - return '{"table_data": []}'; -} - -function tryAlternativeParsing(jsonStr) { - console.log('Пробуем альтернативные методы парсинга'); - - // Метод 1: Пробуем найти и исправить конкретные ошибки - let fixed = jsonStr; - - // Исправляем распространенные ошибки - const fixes = [ - // Некорректные escape - [/\\([^"\\\/bfnrtu])/g, ''], - // Двойные обратные слеши - [/\\\\/g, '\\'], - // Незакрытые кавычки - [/: ([^",\[\]\{\}\s][^,\]\}]*?)(?=\s*[,}\]])/g, ': "$1"'], - // Ключи без кавычек - [/(\s*)([a-zA-Z_][a-zA-Z0-9_]*)(\s*):/g, '$1"$2"$3:'] - ]; - - fixes.forEach(([pattern, replacement]) => { - fixed = fixed.replace(pattern, replacement); - }); - - try { - return JSON.parse(fixed); - } catch (e) { - console.warn('Альтернативный метод 1 не сработал:', e.message); - } - - // Метод 2: Пробуем извлечь данные построчно - try { - const lines = jsonStr.split('\n'); - const tableData = []; - let currentRow = null; - - for (const line of lines) { - if (line.includes('"characteristic"')) { - if (currentRow) { - tableData.push(currentRow); - } - currentRow = {}; - // Извлекаем characteristic - const charMatch = line.match(/"characteristic"\s*:\s*"([^"]*)"/); - if (charMatch) { - currentRow.characteristic = charMatch[1]; - } - } else if (line.includes('"column_')) { - // Извлекаем column данные - const colMatch = line.match(/"column_(\d+)"\s*:\s*"([^"]*)"/); - if (colMatch && currentRow) { - currentRow[`column_${colMatch[1]}`] = colMatch[2]; - } - } - } - - if (currentRow) { - tableData.push(currentRow); - } - - return { table_data: tableData }; - } catch (e) { - console.warn('Альтернативный метод 2 не сработал:', e.message); - } - - // Метод 3: Возвращаем пустые данные - return { table_data: [] }; -} - -function showJSONError(error, jsonStr) { - const errorDiv = document.createElement('div'); - errorDiv.className = 'error-message'; - errorDiv.style.cssText = ` - background-color: #fef2f2; - border: 1px solid #fecaca; - border-radius: 8px; - padding: 16px; - margin-bottom: 16px; - display: flex; - align-items: flex-start; - gap: 12px; - `; - - // Создаем более информативное сообщение - let errorDetails = error.message; - - // Пытаемся найти позицию ошибки - const positionMatch = error.message.match(/position (\d+)/); - if (positionMatch) { - const position = parseInt(positionMatch[1]); - errorDetails += `\n\nКонтекст ошибки:\n`; - errorDetails += jsonStr.substring(Math.max(0, position - 50), Math.min(jsonStr.length, position + 50)); - } - - errorDiv.innerHTML = ` - -
-

Ошибка при разборе JSON

-

Сообщение: ${error.message}

-

Длина данных: ${jsonStr.length} символов

-
- Начало данных:
- ${jsonStr.substring(0, 200).replace(//g, '>')} -
- -
- `; - - // Вставляем сообщение об ошибке перед JSON - elements.jsonOutput.parentNode.insertBefore(errorDiv, elements.jsonOutput); -} - -function copyRawJSON() { - const jsonText = elements.jsonOutput.textContent; - navigator.clipboard.writeText(jsonText) - .then(() => { - showNotification('Сырые данные скопированы в буфер', 'info'); - }) - .catch(err => { - console.error('Ошибка при копировании:', err); - }); -} - -function cleanJSON(jsonStr) { - try { - console.log('Очистка JSON...'); - console.log('Исходная длина:', jsonStr.length); - console.log('Первые 500 символов:', jsonStr.substring(0, 500)); - - // 1. Удаляем лишние пробелы и переносы в начале/конце - let cleaned = jsonStr.trim(); - - // 2. Удаляем маркдаун обрамление ```json ... ``` - if (cleaned.includes('```json')) { - const start = cleaned.indexOf('```json') + 7; - const end = cleaned.lastIndexOf('```'); - cleaned = cleaned.substring(start, end).trim(); - } else if (cleaned.includes('```')) { - const start = cleaned.indexOf('```') + 3; - const end = cleaned.lastIndexOf('```'); - cleaned = cleaned.substring(start, end).trim(); - } - - // 3. Ищем JSON объект или массив - const jsonMatch = cleaned.match(/(\{[\s\S]*\}|\[[\s\S]*\])/); - if (jsonMatch) { - cleaned = jsonMatch[0]; - } - - // 4. Специальная обработка для исправления некорректных escape-последовательностей - cleaned = cleaned - // Исправляем двойные обратные слэши - .replace(/\\\\/g, '\\') - // Исправляем некорректные escape-последовательности - .replace(/\\([^"\\\/bfnrtu])/g, '$1') - // Заменяем некорректные кавычки - .replace(/[``'']/g, '"') - // Убираем управляющие символы кроме табуляции и переноса строки - .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '') - // Убираем BOM (Byte Order Mark) - .replace(/^\uFEFF/, ''); - - // 5. Исправляем отсутствующие кавычки в ключах - cleaned = cleaned.replace(/(\s*)([a-zA-Z_][a-zA-Z0-9_]*)(\s*):/g, '$1"$2"$3:'); - - // 6. Убираем лишние запятые в конце объектов и массивов - cleaned = cleaned - .replace(/,\s*}/g, '}') - .replace(/,\s*\]/g, ']'); - - // 7. Исправляем незакрытые строки - cleaned = cleaned.replace(/:\s*([^"\[\]\{\}\d,\s][^,\]\}]*?)(?=\s*[,}\]])/g, ': "$1"'); - - // 8. Исправляем распространенные ошибки формата - let fixed = ''; - let inString = false; - let escapeNext = false; - - for (let i = 0; i < cleaned.length; i++) { - const char = cleaned[i]; - const nextChar = cleaned[i + 1] || ''; - - if (escapeNext) { - // Если предыдущий символ был \, добавляем текущий как есть - fixed += char; - escapeNext = false; - } else if (char === '\\') { - // Начинаем escape-последовательность - if (nextChar === 'u') { - // Unicode escape - проверяем формат \uXXXX - if (cleaned.substring(i + 2, i + 6).match(/[0-9a-fA-F]{4}/)) { - fixed += cleaned.substring(i, i + 6); - i += 5; - } else { - // Некорректный Unicode escape - заменяем на пустую строку - fixed += ''; - i += 5; - } - } else if ('"\\/bfnrt'.includes(nextChar)) { - // Корректный escape символ - fixed += char; - escapeNext = true; - } else { - // Некорректный escape - пропускаем \ - fixed += ''; - } - } else if (char === '"') { - inString = !inString; - fixed += char; - } else if (!inString && char === "'") { - // Заменяем одинарные кавычки на двойные вне строк - fixed += '"'; - } else if (char === '\n' && inString) { - // Убираем переносы строк внутри строк - fixed += ' '; - } else { - fixed += char; - } - } - - cleaned = fixed; - - // 9. Проверяем сбалансированность скобок - const stack = []; - for (let i = 0; i < cleaned.length; i++) { - const char = cleaned[i]; - if (char === '{' || char === '[') { - stack.push(char); - } else if (char === '}') { - if (stack.pop() !== '{') { - console.warn('Несбалансированная }'); - // Добавляем недостающую { - cleaned = '{' + cleaned; - } - } else if (char === ']') { - if (stack.pop() !== '[') { - console.warn('Несбалансированная ]'); - // Добавляем недостающую [ - cleaned = '[' + cleaned; - } - } - } - - // Добавляем недостающие закрывающие скобки - while (stack.length > 0) { - const open = stack.pop(); - cleaned += open === '{' ? '}' : ']'; - } - - console.log('Очищенная длина:', cleaned.length); - console.log('Первые 500 символов очищенного:', cleaned.substring(0, 500)); - - // 10. Пробуем спарсить - try { - const parsed = JSON.parse(cleaned); - console.log('JSON успешно спарсен'); - return cleaned; - } catch (parseError) { - console.warn('Ошибка парсинга после очистки:', parseError.message); - - // Попробуем более агрессивную очистку - return cleanJSONAggressive(jsonStr); - } - - } catch (error) { - console.warn('Не удалось очистить JSON, возвращаем исходный с обработкой:', error.message); - return cleanJSONAggressive(jsonStr); - } -} - -function cleanJSONAggressive(jsonStr) { - console.log('Применяем агрессивную очистку JSON'); - - try { - // 1. Находим первый { и последний } - const firstBrace = jsonStr.indexOf('{'); - const lastBrace = jsonStr.lastIndexOf('}'); - - if (firstBrace === -1 || lastBrace === -1) { - throw new Error('Не найдены фигурные скобки'); - } - - let cleaned = jsonStr.substring(firstBrace, lastBrace + 1); - - // 2. Убираем все сложные escape-последовательности - cleaned = cleaned - .replace(/\\\\/g, '\\') - .replace(/\\"/g, '"') - .replace(/\\([^"\\\/bfnrtu])/g, '') - .replace(/\\u[0-9a-fA-F]{4}/g, match => { - try { - return JSON.parse(`"${match}"`); - } catch { - return ''; - } - }); - - // 3. Заменяем все нестандартные кавычки - cleaned = cleaned - .replace(/[`'']/g, '"') - .replace(/„|"|«|»/g, '"'); - - // 4. Убираем управляющие символы - cleaned = cleaned.replace(/[\x00-\x1F\x7F-\x9F]/g, ''); - - // 5. Исправляем ключи без кавычек (ограниченный набор) - cleaned = cleaned.replace(/"table_data":/g, '"table_data":'); - cleaned = cleaned.replace(/"characteristic":/g, '"characteristic":'); - - // Ищем и исправляем ключи column_X - for (let i = 1; i <= 30; i++) { - const pattern1 = new RegExp(`column_${i}(\\s*):`, 'g'); - const pattern2 = new RegExp(`"column_${i}"(\\s*):`, 'g'); - - cleaned = cleaned.replace(pattern1, `"column_${i}"$1:`); - - // Убедимся, что кавычки правильные - if (!pattern2.test(cleaned)) { - // Добавляем кавычки, если их нет - const missingPattern = new RegExp(`column_${i}(\\s*):`, 'g'); - cleaned = cleaned.replace(missingPattern, `"column_${i}"$1:`); - } - } - - // 6. Убираем лишние запятые - cleaned = cleaned - .replace(/,\s*}/g, '}') - .replace(/,\s*\]/g, ']'); - - // 7. Убираем лишние двоеточия - cleaned = cleaned.replace(/::/g, ':'); - - // 8. Добавляем финальную проверку структуры - // Проверяем, что это похоже на наш ожидаемый формат - if (!cleaned.includes('"table_data"') || !cleaned.includes('"characteristic"')) { - throw new Error('Не найден ожидаемый формат данных'); - } - - console.log('Агрессивно очищенный JSON (первые 300 символов):', cleaned.substring(0, 300)); - - return cleaned; - - } catch (error) { - console.error('Агрессивная очистка не удалась:', error.message); - - // Возвращаем минимальный валидный JSON - return '{"table_data": []}'; - } -} - -function highlightJSON() { - const text = elements.jsonOutput.textContent; - let highlighted = text - .replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, - function(match) { - let cls = 'json-value'; - if (/^"/.test(match)) { - if (/:$/.test(match)) { - cls = 'json-key'; - } else { - cls = 'json-string'; - } - } else if (/true|false/.test(match)) { - cls = 'json-boolean'; - } else if (/null/.test(match)) { - cls = 'json-null'; - } else if (/^-?\d/.test(match)) { - cls = 'json-number'; - } - return `${match}`; - }); - - elements.jsonOutput.innerHTML = highlighted; -} - -function displayTable(tableData) { - if (!tableData || !Array.isArray(tableData)) { - console.warn('Нет данных для отображения таблицы:', tableData); - elements.tableInfo.textContent = 'Нет данных для отображения'; - return; - } - - // Очищаем таблицу - elements.tableHeader.innerHTML = ''; - elements.tableBody.innerHTML = ''; - - console.log('Всего строк в данных:', tableData.length); - console.log('Первые 5 строк:', tableData.slice(0, 5)); - - // ОПРЕДЕЛЯЕМ КОЛОНКИ ИЗ ДАННЫХ - const keysFromData = new Set(); - - // Собираем ВСЕ ключи из всех строк - tableData.forEach(row => { - if (row && typeof row === 'object') { - Object.keys(row).forEach(key => { - if (key !== 'characteristic') { - keysFromData.add(key); - } - }); - } - }); - - // Сортируем колонки по номеру - const sortedKeys = Array.from(keysFromData).sort((a, b) => { - const numA = parseInt(a.replace('column_', '')) || 0; - const numB = parseInt(b.replace('column_', '')) || 0; - return numA - numB; - }); - - // Добавляем обработчики для выбора строк и колонок - setupTableSelection(); - - console.log('Найдено колонок:', sortedKeys.length); - console.log('Колонки:', sortedKeys); - - // Если нет колонок, создаем заглушку - if (sortedKeys.length === 0) { - console.warn('Нет колонок для отображения в таблице'); - elements.tableInfo.textContent = 'Нет колонок для отображения'; - return; - } - - // Создаем заголовки - const headerRow = document.createElement('tr'); - - // Первый заголовок - характеристика - const thChar = document.createElement('th'); - thChar.textContent = 'Characteristic'; - thChar.style.backgroundColor = '#2c3e50'; - thChar.style.color = 'white'; - thChar.style.position = 'sticky'; - thChar.style.left = '0'; - thChar.style.zIndex = '3'; - thChar.style.minWidth = '250px'; // Широкая колонка для длинных названий - headerRow.appendChild(thChar); - - // Создаем заголовки для всех колонок - sortedKeys.forEach(key => { - const th = document.createElement('th'); - // Создаем короткое имя для колонки - const colNum = key.replace('column_', ''); - th.textContent = `Col ${colNum}`; - th.title = key; // Полное имя в tooltip - th.style.backgroundColor = '#2c3e50'; - th.style.color = 'white'; - th.style.zIndex = '2'; - th.style.minWidth = '100px'; - th.style.textAlign = 'center'; - headerRow.appendChild(th); - }); - - elements.tableHeader.appendChild(headerRow); - - setTimeout(() => { - try { - if (typeof addTableControls === 'function') { - addTableControls(); - } else { - console.error('addTableControls не определена, пропускаем'); - } - - if (typeof updateRowCounter === 'function') updateRowCounter(); - if (typeof updateColumnCounter === 'function') updateColumnCounter(); - - } catch (error) { - console.error('Ошибка при добавлении контролов таблицы:', error); - } - }, 100); - - // Заполняем таблицу данными - ПОКАЗЫВАЕМ ВСЕ СТРОКИ - let rowCount = 0; - let displayedRows = 0; - - tableData.forEach((row, index) => { - // Проверяем, есть ли строка - if (!row || typeof row !== 'object') { - console.log(`Пропускаем строку ${index}: нет данных или не объект`, row); - return; - } - - const tr = document.createElement('tr'); - rowCount++; - - // Чередуем цвета строк для лучшей читаемости - if (index % 2 === 0) { - tr.style.backgroundColor = '#f8f9fa'; - } - - // Ячейка с характеристикой (закрепленная слева) - const tdChar = document.createElement('td'); - const characteristic = row.characteristic || `Row ${index + 1}`; - tdChar.textContent = characteristic; - tdChar.style.fontWeight = '600'; - tdChar.style.position = 'sticky'; - tdChar.style.left = '0'; - tdChar.style.backgroundColor = index % 2 === 0 ? '#f8f9fa' : 'white'; - tdChar.style.zIndex = '1'; - tdChar.style.borderRight = '2px solid #e2e8f0'; - tdChar.style.minWidth = '250px'; - tdChar.style.maxWidth = '300px'; - tdChar.style.whiteSpace = 'normal'; // Разрешаем перенос текста - tdChar.style.wordBreak = 'break-word'; - tr.appendChild(tdChar); - - // Ячейки с значениями колонок - sortedKeys.forEach(key => { - const td = document.createElement('td'); - const value = row[key] !== undefined ? String(row[key]) : ''; - - // Применяем стили к ячейке - applyCellStyles(td, value); - - tr.appendChild(td); - }); - - elements.tableBody.appendChild(tr); - displayedRows++; - }); - - // Добавляем легенду - addTableLegend(0); - - // Добавляем кнопки управления - addTableControls(); - - // Добавляем информацию о данных - console.log(`Отображено ${displayedRows} из ${tableData.length} строк`); - - // Если отображено не все строки, показываем предупреждение - if (displayedRows < tableData.length) { - console.warn(`Пропущено ${tableData.length - displayedRows} строк!`); - showNotification(`Показано ${displayedRows} из ${tableData.length} строк. Некоторые строки пропущены из-за некорректных данных.`, 'warning'); - } -} - -function setupTableSelection() { - // Выбор строк (по клику на характеристику) - const firstCells = document.querySelectorAll('#tableBody td:first-child'); - firstCells.forEach(cell => { - cell.addEventListener('click', function(e) { - e.stopPropagation(); - const row = this.parentElement; - row.classList.toggle('selected'); - }); - }); - - // Выбор колонок (по клику на заголовок) - const headerCells = document.querySelectorAll('#tableHeader th'); - headerCells.forEach(th => { - th.addEventListener('click', function(e) { - e.stopPropagation(); - if (this.textContent !== 'Characteristic') { - this.classList.toggle('selected'); - } - }); - }); -} - -function addTableControls() { - console.log('Добавление контролов управления таблицей'); - - const oldControls = document.getElementById('tableControls'); - if (oldControls) oldControls.remove(); - - const controls = document.createElement('div'); - controls.id = 'tableControls'; - controls.style.cssText = ` - padding: 12px; - background: #f8fafc; - border-bottom: 1px solid #e2e8f0; - display: flex; - gap: 10px; - flex-wrap: wrap; - align-items: center; - position: sticky; - top: 0; - z-index: 20; - `; - - controls.innerHTML = ` -
- - Колонки: - - - - - -
- -
- - Строки: - - - - - -
- -
- - Строк: ... - - - Колонок: ... - -
- `; - - const tableWrapper = document.querySelector('.table-wrapper'); - if (tableWrapper) { - tableWrapper.parentNode.insertBefore(controls, tableWrapper); - - setTimeout(() => { - try { - setupTableControls(); - updateRowCounter(); - updateColumnCounter(); - } catch (error) { - console.error('Ошибка при настройке контролов таблицы:', error); - } - }, 100); - } -} - -function setupFallbackHandlers() { - console.log('Использование фолбэк обработчиков'); - - const handlers = { - 'showAllColumns': showAllColumns, - 'showDataColumns': showDataColumns, - 'toggleEmptyColumns': toggleEmptyColumns, - 'showAllRows': showAllRows, - 'showDataRows': showDataRows, - 'toggleEmptyRows': toggleEmptyRows, - 'deleteEmptyRows': deleteEmptyRows - }; - - // Простой подход через onclick - Object.entries(handlers).forEach(([name, handler]) => { - const button = document.querySelector(`[onclick*="${name}"]`); - if (button && !button.hasAttribute('data-handler-set')) { - button.setAttribute('data-handler-set', 'true'); - button.onclick = function(e) { - e.preventDefault(); - e.stopPropagation(); - safeClick(handler); - }; - } - }); -} - -function updateColumnCounter() { - const counter = document.getElementById('visibleColumnsCount'); - if (counter) { - const totalColumns = document.querySelectorAll('#tableHeader th').length - 1; // -1 для characteristic - const visibleCount = countVisibleColumns(); - counter.textContent = `${visibleCount}/${totalColumns}`; - } -} - - -let isProcessingClick = false; -const CLICK_DELAY = 300; - -function safeClick(callback, delay = CLICK_DELAY) { - if (isProcessingClick) { - console.log('Клик проигнорирован - обработка предыдущего клика еще идет'); - return; - } - - isProcessingClick = true; - - try { - callback(); - } catch (error) { - console.error('Ошибка при обработке клика:', error); - isProcessingClick = false; - throw error; - } - - setTimeout(() => { - isProcessingClick = false; - }, delay); -} - -function showAllColumns() { - const table = document.getElementById('dataTable'); - if (!table) return; - - const headers = table.querySelectorAll('#tableHeader th'); - const rows = table.querySelectorAll('#tableBody tr'); - - headers.forEach(header => header.style.display = ''); - rows.forEach(row => { - const cells = row.querySelectorAll('td'); - cells.forEach(cell => cell.style.display = ''); - }); - - showNotification('Показаны все колонки', 'info'); - updateColumnCounter(); -} - -function showDataColumns() { - const table = document.getElementById('dataTable'); - if (!table) return; - - const headers = table.querySelectorAll('#tableHeader th'); - const rows = table.querySelectorAll('#tableBody tr'); - - if (rows.length === 0) { - showNotification('Нет данных в таблице', 'warning'); - return; - } - - const columnHasData = new Array(headers.length).fill(false); - columnHasData[0] = true; - - rows.forEach(row => { - const cells = row.querySelectorAll('td'); - cells.forEach((cell, cellIndex) => { - const value = cell.textContent.trim(); - if (value && value !== '—' && value !== '') { - columnHasData[cellIndex] = true; - } - }); - }); - - headers.forEach((header, index) => { - header.style.display = columnHasData[index] ? '' : 'none'; - }); - - rows.forEach(row => { - const cells = row.querySelectorAll('td'); - cells.forEach((cell, index) => { - cell.style.display = columnHasData[index] ? '' : 'none'; - }); - }); - - const visibleCount = columnHasData.filter(Boolean).length - 1; - showNotification(`Показано ${visibleCount} колонок с данными`, 'info'); - updateColumnCounter(); -} - -function toggleEmptyColumns() { - const table = document.getElementById('dataTable'); - if (!table) return; - - const headers = table.querySelectorAll('#tableHeader th'); - const rows = table.querySelectorAll('#tableBody tr'); - - if (rows.length === 0) { - showNotification('Нет данных в таблице', 'warning'); - return; - } - - // Определяем, какие колонки полностью пустые - const columnIsEmpty = new Array(headers.length).fill(true); - columnIsEmpty[0] = false; // Первая колонка никогда не пустая - - rows.forEach(row => { - const cells = row.querySelectorAll('td'); - cells.forEach((cell, cellIndex) => { - const value = cell.textContent.trim(); - if (value && value !== '—' && value !== '') { - columnIsEmpty[cellIndex] = false; - } - }); - }); - - // Проверяем текущее состояние - скрыты ли пустые колонки - const firstEmptyHeader = headers[1]; // Проверяем вторую колонку - const isEmptyHidden = firstEmptyHeader && columnIsEmpty[1] && firstEmptyHeader.style.display === 'none'; - - // Переключаем состояние - headers.forEach((header, index) => { - if (index === 0) return; // characteristic не трогаем - - if (columnIsEmpty[index]) { - if (isEmptyHidden) { - // Если скрыты - показываем - header.style.display = ''; - } else { - // Если показаны - скрываем - header.style.display = 'none'; - } - } - }); - - rows.forEach(row => { - const cells = row.querySelectorAll('td'); - cells.forEach((cell, index) => { - if (index === 0) return; // characteristic не трогаем - - if (columnIsEmpty[index]) { - if (isEmptyHidden) { - cell.style.display = ''; - } else { - cell.style.display = 'none'; - } - } - }); - }); - - const emptyCount = columnIsEmpty.filter((empty, idx) => empty && idx > 0).length; - const action = isEmptyHidden ? 'показаны' : 'скрыты'; - - console.log(`${emptyCount} пустых колонок ${action}`); - showNotification(`${emptyCount} пустых колонок ${action}`, 'info'); - updateColumnCounter(); -} - -function showAllRows() { - const rows = document.querySelectorAll('#tableBody tr'); - rows.forEach(row => row.style.display = ''); - showNotification('Показаны все строки', 'info'); - updateRowCounter(); -} - -function showDataRows() { - const rows = document.querySelectorAll('#tableBody tr'); - if (rows.length === 0) { - showNotification('Нет строк в таблице', 'warning'); - return; - } - - rows.forEach(row => { - const cells = row.querySelectorAll('td'); - let hasData = false; - for (let i = 1; i < cells.length; i++) { - const value = cells[i].textContent.trim(); - if (value && value !== '—' && value !== '') { - hasData = true; - break; - } - } - row.style.display = hasData ? '' : 'none'; - }); - - const visibleCount = Array.from(rows).filter(row => row.style.display !== 'none').length; - showNotification(`Показано ${visibleCount} строк с данными`, 'info'); - updateRowCounter(); -} - -function toggleEmptyRows() { - const rows = document.querySelectorAll('#tableBody tr'); - if (rows.length === 0) { - showNotification('Нет строк в таблице', 'warning'); - return; - } - - const emptyRows = []; - rows.forEach(row => { - const cells = row.querySelectorAll('td'); - let isEmpty = true; - for (let i = 1; i < cells.length; i++) { - const value = cells[i].textContent.trim(); - if (value && value !== '—' && value !== '') { - isEmpty = false; - break; - } - } - if (isEmpty) emptyRows.push(row); - }); - - if (emptyRows.length === 0) { - showNotification('Нет пустых строк', 'info'); - return; - } - - const firstEmptyRow = emptyRows[0]; - const isEmptyHidden = firstEmptyRow && firstEmptyRow.style.display === 'none'; - - emptyRows.forEach(row => { - row.style.display = isEmptyHidden ? '' : 'none'; - }); - - const action = isEmptyHidden ? 'показаны' : 'скрыты'; - showNotification(`${emptyRows.length} пустых строк ${action}`, 'info'); - updateRowCounter(); -} - -function deleteEmptyRows() { - const rows = document.querySelectorAll('#tableBody tr'); - if (rows.length === 0) { - showNotification('Нет строк в таблице', 'warning'); - return; - } - - const emptyRowIndices = []; - rows.forEach((row, index) => { - const cells = row.querySelectorAll('td'); - let isEmpty = true; - for (let i = 1; i < cells.length; i++) { - const value = cells[i].textContent.trim(); - if (value && value !== '—' && value !== '') { - isEmpty = false; - break; - } - } - if (isEmpty) emptyRowIndices.push(index); - }); - - if (emptyRowIndices.length === 0) { - showNotification('Нет пустых строк для удаления', 'info'); - return; - } - - if (!confirm(`Удалить ${emptyRowIndices.length} пустых строк? Это действие нельзя отменить!`)) { - return; - } - - try { - const jsonText = elements.jsonOutput.textContent; - const data = JSON.parse(jsonText); - if (!data.table_data || !Array.isArray(data.table_data)) { - throw new Error('Нет табличных данных'); - } - - emptyRowIndices.sort((a, b) => b - a).forEach(index => { - data.table_data.splice(index, 1); - }); - - elements.jsonOutput.textContent = JSON.stringify(data, null, 2); - highlightJSON(); - - if (elements.tableView.style.display === 'block') { - displayTable(data.table_data); - } - - jsonData = JSON.stringify(data); - showNotification(`Удалено ${emptyRowIndices.length} пустых строк`, 'success'); - } catch (error) { - console.error('Ошибка при удалении пустых строк:', error); - showNotification('Ошибка при удалении пустых строк', 'error'); - } -} - -function countVisibleRows() { - const rows = document.querySelectorAll('#tableBody tr'); - let visibleCount = 0; - - rows.forEach(row => { - if (row.style.display !== 'none') { - visibleCount++; - } - }); - - return visibleCount; -} - -function updateRowCounter() { - const counter = document.getElementById('visibleRowsCount'); - if (counter) { - const totalRows = document.querySelectorAll('#tableBody tr').length; - const visibleCount = countVisibleRows(); - counter.textContent = `${visibleCount}/${totalRows}`; - } -} - -function countVisibleColumns() { - const headers = document.querySelectorAll('#tableHeader th'); - let visibleCount = 0; - - headers.forEach(header => { - if (header.style.display !== 'none') { - visibleCount++; - } - }); - - return visibleCount - 1; // Минус characteristic -} - -function updateColumnCounter() { - const counter = document.getElementById('visibleColumnsCount'); - if (counter) { - const totalColumns = document.querySelectorAll('#tableHeader th').length - 1; // -1 для characteristic - const visibleCount = countVisibleColumns(); - counter.textContent = `${visibleCount}/${totalColumns}`; - } -} - -function setupFallbackHandlers() { - console.log('Использование фолбэк обработчиков'); - - const handlers = { - 'showAllColumns': showAllColumns, - 'showDataColumns': showDataColumns, - 'toggleEmptyColumns': toggleEmptyColumns, - 'showAllRows': showAllRows, - 'showDataRows': showDataRows, - 'toggleEmptyRows': toggleEmptyRows, - 'deleteEmptyRows': deleteEmptyRows - }; - - // Простой подход через onclick - Object.entries(handlers).forEach(([name, handler]) => { - const button = document.querySelector(`[onclick*="${name}"]`); - if (button && !button.hasAttribute('data-handler-set')) { - button.setAttribute('data-handler-set', 'true'); - button.onclick = function(e) { - e.preventDefault(); - e.stopPropagation(); - try { - handler(); - } catch (error) { - console.error(`Ошибка в обработчике ${name}:`, error); - showNotification(`Ошибка: ${error.message}`, 'error'); - } - }; - } - }); -} - -function deleteEmptyRows() { - const rows = document.querySelectorAll('#tableBody tr'); - - if (rows.length === 0) { - showNotification('Нет строк в таблице', 'warning'); - return; - } - - // Находим пустые строки - const emptyRowIndices = []; - rows.forEach((row, index) => { - const cells = row.querySelectorAll('td'); - let isEmpty = true; - - // Проверяем ячейки (начиная со второй) - for (let i = 1; i < cells.length; i++) { - const value = cells[i].textContent.trim(); - if (value && value !== '—' && value !== '') { - isEmpty = false; - break; - } - } - - if (isEmpty) { - emptyRowIndices.push(index); - } - }); - - if (emptyRowIndices.length === 0) { - showNotification('Нет пустых строк для удаления', 'info'); - return; - } - - // Подтверждение удаления - if (!confirm(`Удалить ${emptyRowIndices.length} пустых строк? Это действие нельзя отменить!`)) { - return; - } - - try { - // Получаем текущие данные - const jsonText = elements.jsonOutput.textContent; - const data = JSON.parse(jsonText); - - if (!data.table_data || !Array.isArray(data.table_data)) { - throw new Error('Нет табличных данных'); - } - - // Удаляем строки из данных (в обратном порядке) - emptyRowIndices.sort((a, b) => b - a).forEach(index => { - data.table_data.splice(index, 1); - }); - - // Обновляем JSON - elements.jsonOutput.textContent = JSON.stringify(data, null, 2); - highlightJSON(); - - // Обновляем таблицу - if (elements.tableView.style.display === 'block') { - displayTable(data.table_data); - } - - // Обновляем глобальные данные - jsonData = JSON.stringify(data); - - console.log(`Удалено ${emptyRowIndices.length} пустых строк`); - showNotification(`Удалено ${emptyRowIndices.length} пустых строк`, 'success'); - - } catch (error) { - console.error('Ошибка при удалении пустых строк:', error); - showNotification('Ошибка при удалении пустых строк', 'error'); - } -} - -function countVisibleRows() { - const rows = document.querySelectorAll('#tableBody tr'); - let visibleCount = 0; - - rows.forEach(row => { - if (row.style.display !== 'none') { - visibleCount++; - } - }); - - return visibleCount; -} - -function updateRowCounter() { - const counter = document.getElementById('visibleRowsCount'); - if (counter) { - const totalRows = document.querySelectorAll('#tableBody tr').length; - const visibleCount = document.querySelectorAll('#tableBody tr').length - - document.querySelectorAll('#tableBody tr[style*="display: none"]').length; - counter.textContent = `${visibleCount}/${totalRows}`; - } -} - -function updateColumnCounter() { - const counter = document.getElementById('visibleColumnsCount'); - if (counter) { - const totalColumns = document.querySelectorAll('#tableHeader th').length - 1; - const visibleCount = document.querySelectorAll('#tableHeader th').length - 1 - - document.querySelectorAll('#tableHeader th[style*="display: none"]').length; - counter.textContent = `${visibleCount}/${totalColumns}`; - } -} - - -function countVisibleColumns() { - const headers = document.querySelectorAll('#tableHeader th'); - let visibleCount = 0; - - headers.forEach(header => { - if (header.style.display !== 'none') { - visibleCount++; - } - }); - - return visibleCount - 1; // Минус characteristic -} - -function updateColumnCounter() { - const counter = document.getElementById('visibleColumnsCount'); - if (counter) { - const totalColumns = document.querySelectorAll('#tableHeader th').length - 1; // -1 для characteristic - const visibleCount = countVisibleColumns(); - counter.textContent = `${visibleCount}/${totalColumns}`; - } -} - -function setupFallbackHandlers() { - console.log('Использование фолбэк обработчиков'); - - const handlers = { - 'showAllColumns': showAllColumns, - 'showDataColumns': showDataColumns, - 'toggleEmptyColumns': toggleEmptyColumns, - 'showAllRows': showAllRows, - 'showDataRows': showDataRows, - 'toggleEmptyRows': toggleEmptyRows, - 'deleteEmptyRows': deleteEmptyRows - }; - - // Простой подход через onclick - Object.entries(handlers).forEach(([name, handler]) => { - const button = document.querySelector(`[onclick*="${name}"]`); - if (button && !button.hasAttribute('data-handler-set')) { - button.setAttribute('data-handler-set', 'true'); - button.onclick = function(e) { - e.preventDefault(); - e.stopPropagation(); - try { - handler(); - } catch (error) { - console.error(`Ошибка в обработчике ${name}:`, error); - showNotification(`Ошибка: ${error.message}`, 'error'); - } - }; - } - }); -} - -function showDataColumns() { - const table = document.getElementById('dataTable'); - const headers = table.querySelectorAll('#tableHeader th'); - const rows = table.querySelectorAll('#tableBody tr'); - - if (rows.length === 0) { - showNotification('Нет данных в таблице', 'warning'); - return; - } - - // Определяем, какие колонки имеют данные - const columnHasData = new Array(headers.length).fill(false); - columnHasData[0] = true; // Первая колонка (characteristic) всегда показываем - - rows.forEach(row => { - const cells = row.querySelectorAll('td'); - cells.forEach((cell, cellIndex) => { - const value = cell.textContent.trim(); - if (value && value !== '—' && value !== '') { - columnHasData[cellIndex] = true; - } - }); - }); - - // Показываем/скрываем колонки - headers.forEach((header, index) => { - if (columnHasData[index]) { - header.style.display = ''; - } else { - header.style.display = 'none'; - } - }); - - rows.forEach(row => { - const cells = row.querySelectorAll('td'); - cells.forEach((cell, index) => { - if (columnHasData[index]) { - cell.style.display = ''; - } else { - cell.style.display = 'none'; - } - }); - }); - - const visibleCount = columnHasData.filter(Boolean).length - 1; // -1 для characteristic - console.log(`Показано ${visibleCount} колонок с данными`); - showNotification(`Показано ${visibleCount} колонок с данными`, 'info'); -} - -function toggleEmptyColumns() { - const table = document.getElementById('dataTable'); - if (!table) return; - - const headers = table.querySelectorAll('#tableHeader th'); - const rows = table.querySelectorAll('#tableBody tr'); - - if (rows.length === 0) { - showNotification('Нет данных в таблице', 'warning'); - return; - } - - const columnIsEmpty = new Array(headers.length).fill(true); - columnIsEmpty[0] = false; - - rows.forEach(row => { - const cells = row.querySelectorAll('td'); - cells.forEach((cell, cellIndex) => { - const value = cell.textContent.trim(); - if (value && value !== '—' && value !== '') { - columnIsEmpty[cellIndex] = false; - } - }); - }); - - const firstEmptyHeader = headers[1]; - const isEmptyHidden = firstEmptyHeader && columnIsEmpty[1] && firstEmptyHeader.style.display === 'none'; - - headers.forEach((header, index) => { - if (index === 0) return; - if (columnIsEmpty[index]) { - header.style.display = isEmptyHidden ? '' : 'none'; - } - }); - - rows.forEach(row => { - const cells = row.querySelectorAll('td'); - cells.forEach((cell, index) => { - if (index === 0) return; - if (columnIsEmpty[index]) { - cell.style.display = isEmptyHidden ? '' : 'none'; - } - }); - }); - - const emptyCount = columnIsEmpty.filter((empty, idx) => empty && idx > 0).length; - const action = isEmptyHidden ? 'показаны' : 'скрыты'; - showNotification(`${emptyCount} пустых колонок ${action}`, 'info'); - updateColumnCounter(); -} - -function countVisibleColumns() { - const headers = document.querySelectorAll('#tableHeader th'); - let visibleCount = 0; - - headers.forEach(header => { - if (header.style.display !== 'none') { - visibleCount++; - } - }); - - return visibleCount - 1; // Минус characteristic -} - -function showNotification(message, type = 'info') { - let container = document.getElementById('notificationContainer'); - if (!container) { - // Создаем контейнер, если его нет - const newContainer = document.createElement('div'); - newContainer.id = 'notificationContainer'; - newContainer.className = 'notification-container'; - document.body.appendChild(newContainer); - container = newContainer; - } - - const notification = document.createElement('div'); - notification.className = `notification ${type}`; - notification.innerHTML = ` -
- - ${message} -
- - `; - - container.appendChild(notification); - - // Автоматическое удаление через 5 секунд - setTimeout(() => { - if (notification.parentNode) { - notification.remove(); - } - }, 5000); -} - -const notificationStyle = document.createElement('style'); -notificationStyle.textContent = ` - .notification-container { - position: fixed; - top: 20px; - right: 20px; - z-index: 10000; - display: flex; - flex-direction: column; - gap: 10px; - } - - .notification { - background: white; - border-radius: 8px; - padding: 15px 20px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - display: flex; - align-items: center; - justify-content: space-between; - min-width: 300px; - max-width: 400px; - animation: slideIn 0.3s ease-out; - border-left: 4px solid #3498db; - } - - .notification.success { - border-left-color: #10b981; - } - - .notification.error { - border-left-color: #ef4444; - } - - .notification.warning { - border-left-color: #f59e0b; - } - - .notification-content { - display: flex; - align-items: center; - gap: 10px; - flex: 1; - } - - .notification-content i { - font-size: 20px; - } - - .notification.success .notification-content i { - color: #10b981; - } - - .notification.error .notification-content i { - color: #ef4444; - } - - .notification.warning .notification-content i { - color: #f59e0b; - } - - .notification-close { - background: none; - border: none; - color: #6b7280; - cursor: pointer; - padding: 5px; - margin-left: 10px; - } - - .notification-close:hover { - color: #374151; - } - - @keyframes slideIn { - from { - transform: translateX(100%); - opacity: 0; - } - to { - transform: translateX(0); - opacity: 1; - } - } -`; -document.head.appendChild(notificationStyle); - -function debugSpecificRows() { - try { - const jsonText = elements.jsonOutput.textContent; - const data = JSON.parse(jsonText); - - if (!data.table_data || !Array.isArray(data.table_data)) { - alert('Нет табличных данных'); - return; - } - - console.log('=== ОТЛАДКА КОНКРЕТНЫХ СТРОК ==='); - - // Показываем все строки - data.table_data.forEach((row, index) => { - console.log(`\n=== Строка ${index} ===`); - console.log('Тип:', typeof row); - console.log('Данные:', row); - - if (row && typeof row === 'object') { - console.log('Ключи:', Object.keys(row)); - console.log('characteristic:', row.characteristic || '(пусто)'); - - // Показываем первые 5 колонок - const columns = Object.keys(row).filter(key => key.startsWith('column_')).sort(); - columns.slice(0, 5).forEach(key => { - console.log(` ${key}: "${row[key]}"`); - }); - - if (columns.length > 5) { - console.log(` ... и ещё ${columns.length - 5} колонок`); - } - } - }); - - alert(`Проанализировано ${data.table_data.length} строк. Смотрите консоль (F12) для деталей.`); - - } catch (error) { - console.error('Ошибка при отладке строк:', error); - alert('Ошибка: ' + error.message); - } -} - -function analyzeTableData(tableData) { - console.log('=== АНАЛИЗ ДАННЫХ ТАБЛИЦЫ ==='); - console.log('Всего строк в массиве:', tableData.length); - - let validRows = 0; - let emptyRows = 0; - let invalidRows = 0; - - tableData.forEach((row, index) => { - if (!row) { - console.log(`Строка ${index}: null или undefined`); - invalidRows++; - return; - } - - if (typeof row !== 'object') { - console.log(`Строка ${index}: не объект (${typeof row})`, row); - invalidRows++; - return; - } - - // Проверяем, есть ли характеристика - if (!row.characteristic) { - console.log(`Строка ${index}: нет characteristic`, row); - emptyRows++; - } else { - validRows++; - - // Подсчитываем колонки с данными - const dataColumns = Object.keys(row).filter(key => - key !== 'characteristic' && row[key] !== undefined && row[key] !== '' - ).length; - - console.log(`Строка ${index}: "${row.characteristic.substring(0, 30)}...", колонок с данными: ${dataColumns}`); - } - }); - - console.log(`Итоги анализа:`); - console.log(`- Всего строк: ${tableData.length}`); - console.log(`- Валидных строк: ${validRows}`); - console.log(`- Пустых строк: ${emptyRows}`); - console.log(`- Некорректных строк: ${invalidRows}`); - - return { validRows, emptyRows, invalidRows }; -} - -function applyCellStyles(td, value) { - td.style.textAlign = 'center'; - td.style.verticalAlign = 'middle'; - td.style.padding = '8px 4px'; - td.style.minWidth = '80px'; - td.style.maxWidth = '150px'; - td.style.whiteSpace = 'nowrap'; - td.style.overflow = 'hidden'; - td.style.textOverflow = 'ellipsis'; - - // Очищаем HTML и показываем просто текст - td.textContent = value || ''; - td.title = value || ''; - - // Если значение пустое - if (!value || value.trim() === '') { - td.style.color = '#9ca3af'; - td.style.fontStyle = 'italic'; - td.textContent = '—'; - return; - } - - const cleanValue = value.trim(); - - // Простые стили БЕЗ эмодзи - if (cleanValue === '+') { - td.style.color = '#10b981'; - td.style.fontWeight = 'bold'; - } else if (cleanValue === '-') { - td.style.color = '#ef4444'; - td.style.fontWeight = 'bold'; - } else if (cleanValue === '?' || cleanValue.includes('?')) { - td.style.color = '#f59e0b'; - td.style.fontWeight = 'bold'; - } else if (cleanValue === 'W' || cleanValue === 'w') { - td.style.color = '#8b5cf6'; - td.style.fontWeight = 'bold'; - } else if (cleanValue === 'ND' || cleanValue === 'nd') { - td.style.color = '#9ca3af'; - td.style.fontStyle = 'italic'; - } else if (cleanValue.includes('/')) { - // Значения типа "+/+", "+/-", "W/+" - td.style.fontWeight = 'bold'; - td.textContent = cleanValue; - } else if (cleanValue.includes('-') && !isNaN(parseInt(cleanValue.split('-')[0]))) { - // Числовые диапазоны типа "0-1", "2-4" - td.style.color = '#3b82f6'; - td.textContent = cleanValue; - } else if (!isNaN(parseInt(cleanValue)) || !isNaN(parseFloat(cleanValue))) { - // Числа - td.style.color = '#3b82f6'; - td.textContent = cleanValue; - } else { - // Остальные значения - td.style.color = '#374151'; - td.textContent = cleanValue; - } -} - -function addTableLegend(correctedCells) { - const existingLegend = document.getElementById('tableLegend'); - if (existingLegend) { - existingLegend.remove(); - } - - const legend = document.createElement('div'); - legend.id = 'tableLegend'; - legend.style.cssText = ` - padding: 8px; - background: #f8fafc; - border-top: 1px solid #e2e8f0; - font-size: 0.8rem; - color: #4a5568; - `; - - legend.innerHTML = ` - + Положительный - - Отрицательный - ? Неопределенный - w Слабоположительный - ND Нет данных - `; - - if (correctedCells > 0) { - legend.innerHTML += ` | Исправлено: ${correctedCells}`; - } - - elements.tableInfo.parentNode.insertBefore(legend, elements.tableInfo.nextSibling); -} - -function checkMissingData(jsonStr) { - try { - const data = JSON.parse(jsonStr); - const tableData = data.table_data || []; - const expectedRows = parseInt(elements.expectedRowsInput?.value) || 30; - - // Проверяем, что элементы существуют - if (elements.validationAlert && elements.alertTitle && elements.alertMessage) { - elements.validationAlert.style.display = 'block'; - - if (tableData.length < expectedRows) { - const missing = expectedRows - tableData.length; - elements.alertTitle.textContent = 'ВНИМАНИЕ: Пропущены данные'; - elements.alertMessage.textContent = `Пропущено ${missing} строк! Ожидалось: ${expectedRows}, получено: ${tableData.length}`; - elements.validationAlert.className = 'validation-alert warning'; - } else { - elements.alertTitle.textContent = 'Успешно'; - elements.alertMessage.textContent = `Все данные извлечены успешно. Строк: ${tableData.length}`; - elements.validationAlert.className = 'validation-alert success'; - } - } else { - console.warn('Элементы валидации не найдены'); - // Просто выводим в консоль - if (tableData.length < expectedRows) { - const missing = expectedRows - tableData.length; - console.warn(`ВНИМАНИЕ: Пропущено ${missing} строк!`); - } else { - console.log(`Успешно извлечено ${tableData.length} строк`); - } - } - - } catch (error) { - console.error('Ошибка при проверке данных:', error); - } -} - -function toggleView() { - console.log('toggleView вызвана'); - - // Определяем, что сейчас видно - const tableViewVisible = elements.tableView.style.display === 'block'; - const jsonViewVisible = elements.jsonView.style.display === 'block'; - - console.log('Таблица видна:', tableViewVisible); - console.log('JSON виден:', jsonViewVisible); - - if (tableViewVisible) { - // Если таблица видна, показываем JSON - showView('json'); - } else { - // Иначе показываем таблицу - showView('table'); - } -} - -function showView(view) { - console.log('showView вызывается с параметром:', view); - - if (!elements.tableView || !elements.jsonView || !elements.toggleViewBtn) { - console.error('Элементы для переключения вида не найдены'); - return; - } - - if (view === 'table') { - console.log('Показываем таблицу, скрываем JSON'); - - // Показываем таблицу - elements.tableView.style.display = 'block'; - - // Скрываем JSON - elements.jsonView.style.display = 'none'; - - // Обновляем кнопку - elements.toggleViewBtn.innerHTML = ' Показать JSON'; - elements.toggleViewBtn.setAttribute('data-view', 'json'); - - // Если есть данные, обновляем таблицу - try { - const jsonText = elements.jsonOutput.textContent; - const data = JSON.parse(jsonText); - - if (data.table_data && Array.isArray(data.table_data) && data.table_data.length > 0) { - console.log('Обновляем таблицу с данными'); - displayTable(data.table_data); - } - } catch (error) { - console.error('Ошибка при обновлении таблицы:', error); - } - - } else { - console.log('Показываем JSON, скрываем таблицу'); - - // Показываем JSON - elements.jsonView.style.display = 'block'; - - // Скрываем таблицу - elements.tableView.style.display = 'none'; - - // Обновляем кнопку - elements.toggleViewBtn.innerHTML = ' Показать таблицу'; - elements.toggleViewBtn.setAttribute('data-view', 'table'); - } - - // Логируем конечное состояние - console.log('После showView:'); - console.log('tableView display:', elements.tableView.style.display); - console.log('jsonView display:', elements.jsonView.style.display); - console.log('data-view:', elements.toggleViewBtn.getAttribute('data-view')); -} - -function copyJSON() { - try { - // Пытаемся спарсить JSON, чтобы убедиться в его корректности - const jsonText = elements.jsonOutput.textContent; - JSON.parse(jsonText); - - navigator.clipboard.writeText(jsonText) - .then(() => { - const originalText = elements.copyJsonBtn.innerHTML; - elements.copyJsonBtn.innerHTML = ' Скопировано!'; - - setTimeout(() => { - elements.copyJsonBtn.innerHTML = originalText; - }, 2000); - }) - .catch(err => { - console.error('Ошибка при копировании: ', err); - alert('Ошибка при копировании: ' + err.message); - }); - - } catch (error) { - alert('Невозможно скопировать: JSON содержит ошибки. ' + error.message); - } -} - -function downloadJSON() { - if (!jsonData) return; - - try { - // Пытаемся спарсить JSON - const jsonObj = JSON.parse(jsonData); - const dataStr = JSON.stringify(jsonObj, null, 2); - const dataBlob = new Blob([dataStr], { type: 'application/json' }); - - const url = URL.createObjectURL(dataBlob); - const link = document.createElement('a'); - - link.href = url; - link.download = `table_result_${Date.now()}.json`; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - URL.revokeObjectURL(url); - - } catch (error) { - console.error('Ошибка при создании файла для скачивания:', error); - alert('Ошибка: Невозможно скачать файл. JSON содержит ошибки.'); - } -} - -function showLoading(show) { - if (elements.loadingOverlay) { - elements.loadingOverlay.style.display = show ? 'flex' : 'none'; // было block - } - if (!show && elements.progressBar) { - updateProgress(0, ''); - } -} - -function updateProgress(percent, status) { - if (elements.progressBar) { - elements.progressBar.style.width = `${percent}%`; - } - if (elements.statusText) { - elements.statusText.textContent = status; - } - - // Обновляем шаги прогресса (добавьте эту функцию) - updateProgressSteps(percent); -} - -function updateProgressSteps(percent) { - const steps = document.querySelectorAll('.step'); - steps.forEach((step, index) => { - const stepPercent = (index + 1) * 25; - if (percent >= stepPercent) { - step.classList.add('active'); - } else { - step.classList.remove('active'); - } - }); -} - -function saveSettings() { - const settings = { - apiKey: elements.apiKeyInput.value, - accessToken: elements.accessTokenInput.value, - expectedRows: elements.expectedRowsInput.value - }; - - localStorage.setItem('pdfExtractorSettings', JSON.stringify(settings)); - checkProcessButton(); -} - -function loadSavedSettings() { - const saved = localStorage.getItem('pdfExtractorSettings'); - - if (saved) { - try { - const settings = JSON.parse(saved); - - if (settings.apiKey) elements.apiKeyInput.value = settings.apiKey; - if (settings.accessToken) elements.accessTokenInput.value = settings.accessToken; - if (settings.expectedRows) elements.expectedRowsInput.value = settings.expectedRows; - - checkProcessButton(); - } catch (error) { - console.error('Ошибка при загрузке настроек:', error); - } - } -} - -document.addEventListener('DOMContentLoaded', init); - -const style = document.createElement('style'); -style.textContent = ` - .json-key { color: #005cc5; font-weight: bold; } - .json-string { color: #032f62; } - .json-number { color: #d73a49; } - .json-boolean { color: #6f42c1; } - .json-null { color: #d73a49; font-weight: bold; } - .special-char { font-weight: bold; color: #d73a49; } -`; -document.head.appendChild(style); - -console.log("Проверка элементов:"); -Object.keys(elements).forEach(key => { - console.log(`${key}:`, elements[key] ? '✓' : '✗'); -}); - -if (elements.clearResultsBtn) { - elements.clearResultsBtn.addEventListener('click', clearResults); -} - -function clearResults() { - // Очищаем JSON - elements.jsonOutput.textContent = '{\n "table_data": []\n}'; - - // Очищаем таблицу - elements.tableHeader.innerHTML = 'characteristic'; - elements.tableBody.innerHTML = ''; - elements.tableInfo.textContent = 'Загрузите файл для просмотра данных'; - - // Скрываем таблицу, показываем JSON - showView('json'); - - if (document.getElementById('exportDropdownBtn')) { - document.getElementById('exportDropdownBtn').disabled = true; - } - - // Очищаем валидационное сообщение - if (elements.validationAlert) { - elements.validationAlert.style.display = 'none'; - } - - // Отключаем кнопки - elements.copyJsonBtn.disabled = true; - elements.toggleViewBtn.disabled = true; - elements.clearResultsBtn.disabled = true; - elements.downloadBtn.disabled = true; - - // Очищаем глобальные переменные - jsonData = null; - currentFileId = null; - - console.log('Результаты очищены'); -} - -if (document.getElementById('showRawBtn')) { - document.getElementById('showRawBtn').addEventListener('click', showRawResponse); -} - -rawResponse = content; - -function showRawResponse() { - if (rawResponse) { - alert('Сырой ответ от GigaChat (первые 1000 символов):\n\n' + rawResponse.substring(0, 1000)); - } -} - -if (document.getElementById('showRawBtn')) { - document.getElementById('showRawBtn').style.display = 'inline-block'; -} - -function validateTableData(tableData) { - if (!tableData || !Array.isArray(tableData)) { - console.error('tableData должен быть массивом:', tableData); - return false; - } - - if (tableData.length === 0) { - console.warn('Массив tableData пустой'); - return false; - } - - // Проверяем первую строку на наличие необходимых полей - const firstRow = tableData[0]; - if (!firstRow || typeof firstRow !== 'object') { - console.error('Первая строка не является объектом:', firstRow); - return false; - } - - // Проверяем наличие хотя бы characteristic - if (!firstRow.hasOwnProperty('characteristic')) { - console.warn('Нет поля characteristic в данных'); - } - - return true; -} - -if (document.getElementById('exportTableBtn')) { - document.getElementById('exportTableBtn').addEventListener('click', exportTable); -} - -if (document.getElementById('scrollToTopBtn')) { - document.getElementById('scrollToTopBtn').addEventListener('click', scrollTableToTop); -} - -if (document.getElementById('scrollToBottomBtn')) { - document.getElementById('scrollToBottomBtn').addEventListener('click', scrollTableToBottom); -} - -function exportTable() { - if (!jsonData) return; - - try { - const data = JSON.parse(jsonData); - if (!data.table_data || !Array.isArray(data.table_data)) { - throw new Error('Нет табличных данных для экспорта'); - } - - // Создаем CSV - let csv = ''; - const headers = ['Characteristic']; - - // Определяем колонки - const firstRow = data.table_data[0]; - if (firstRow) { - Object.keys(firstRow).forEach(key => { - if (key !== 'characteristic') { - headers.push(key.toUpperCase()); - } - }); - } - - csv += headers.join(',') + '\n'; - - // Добавляем данные - data.table_data.forEach(row => { - const rowData = [row.characteristic || '']; - headers.slice(1).forEach(header => { - const key = header.toLowerCase(); - rowData.push(row[key] || ''); - }); - csv += rowData.map(cell => `"${cell}"`).join(',') + '\n'; - }); - - // Скачиваем CSV - const blob = new Blob([csv], { type: 'text/csv' }); - const url = URL.createObjectURL(blob); - const link = document.createElement('a'); - link.href = url; - link.download = `table_${Date.now()}.csv`; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - URL.revokeObjectURL(url); - - } catch (error) { - console.error('Ошибка при экспорте таблицы:', error); - alert('Ошибка при экспорте: ' + error.message); - } -} - -function scrollTableToTop() { - const tableWrapper = document.querySelector('.table-wrapper'); - if (tableWrapper) { - tableWrapper.scrollTop = 0; - } -} - -function scrollTableToBottom() { - const tableWrapper = document.querySelector('.table-wrapper'); - if (tableWrapper) { - tableWrapper.scrollTop = tableWrapper.scrollHeight; - } -} - -if (elements.toggleViewBtn) { - console.log('Добавляем обработчик для toggleViewBtn'); - elements.toggleViewBtn.addEventListener('click', function(e) { - console.log('Кнопка toggleViewBtn нажата', e); - toggleView(); - }); -} else { - console.error('Кнопка toggleViewBtn не найдена!'); -} - -window.testTableView = function() { - console.log('=== Тест переключения вида ==='); - console.log('Текущее состояние:'); - console.log('tableView:', elements.tableView.style.display); - console.log('jsonView:', elements.jsonView.style.display); - console.log('data-view:', elements.toggleViewBtn.getAttribute('data-view')); - - // Принудительно показываем таблицу - elements.tableView.style.display = 'block'; - elements.jsonView.style.display = 'none'; - elements.toggleViewBtn.setAttribute('data-view', 'json'); - elements.toggleViewBtn.innerHTML = ' Показать JSON'; - - console.log('После принудительного переключения:'); - console.log('tableView:', elements.tableView.style.display); - console.log('jsonView:', elements.jsonView.style.display); - console.log('data-view:', elements.toggleViewBtn.getAttribute('data-view')); -}; - -function fixTableSymbols(tableData) { - if (!Array.isArray(tableData)) return tableData; - - console.log('Исправление символов в таблице...'); - - return tableData.map(row => { - if (!row || typeof row !== 'object') return row; - - const fixedRow = { ...row }; - - Object.keys(fixedRow).forEach(key => { - if (key === 'characteristic') return; - - const value = String(fixedRow[key] || ''); - - // Исправляем символы - let fixedValue = value; - - // Преобразуем похожие на плюс символы - if (/[?�•∗·∙⋅◦]/.test(value)) { - // Если в строке есть символы, похожие на плюс - fixedValue = value.replace(/[?�•∗·∙⋅◦]/g, match => { - // Проверяем контекст - const context = JSON.stringify(row).toLowerCase(); - const isLikelyPlus = context.includes('assimilation') || - context.includes('enzyme') || - context.includes('test') || - context.includes('фермент') || - context.includes('ассимиляция'); - - if (isLikelyPlus) { - console.log(`Исправляем символ "${match}" в "${row.characteristic}" на "+"`); - return '+'; - } - return match; - }); - } - - // Преобразуем разные типы минусов/тире - if (/[―–—~~˗‐‑‒–—―﹘﹣-]/.test(fixedValue)) { - fixedValue = fixedValue.replace(/[―–—~~˗‐‑‒–—―﹘﹣-]/g, '-'); - } - - // Убираем лишние пробелы - fixedValue = fixedValue.trim(); - - // Заменяем множественные пробелы на один - fixedValue = fixedValue.replace(/\s+/g, ' '); - - if (value !== fixedValue) { - console.log(`Исправлено: "${value}" → "${fixedValue}" в ${row.characteristic}`); - } - - fixedRow[key] = fixedValue; - }); - - return fixedRow; - }); -} - -function postProcessTableData(tableData) { - if (!Array.isArray(tableData)) return tableData; - - console.log('Дополнительная постобработка данных...'); - - // Определяем контекст строк - const assimilationRows = new Set(); - const enzymeRows = new Set(); - - // Расширенные ключевые слова для разных типов строк - const assimilationKeywords = [ - 'assimilation', 'ассимиляция', 'assimilates', 'assimilate', - 'углевод', 'углеводы', 'сахар', 'сахара', - 'glucose', 'глюкоза', 'glucos', 'глюкоз', - 'fructose', 'фруктоза', 'fructos', 'фруктоз', - 'maltose', 'мальтоза', 'maltos', 'мальтоз', - 'dextrin', 'декстрин', 'dextr', 'декстри', - 'cellobiose', 'целлобиоза', 'cellobios', 'целлобиоз', - 'mannose', 'манноза', 'mannos', 'манноз', - 'lactose', 'лактоза', 'lactos', 'лактоз', - 'sucrose', 'сахароза', 'sucros', 'сахароз', - 'galactose', 'галактоза', 'galactos', 'галактоз', - 'sorbose', 'сорбоза', 'sorbos', 'сорбоз', - 'xylose', 'ксилоза', 'xylos', 'ксилоз', - 'arabinose', 'арабиноза', 'arabinos', 'арабиноз', - 'ribose', 'рибоза', 'ribos', 'рибоз', - 'rhamnose', 'рамноза', 'rhamnos', 'рамноз', - 'trehalose', 'трегалоза', 'trehalos', 'трегалоз', - 'raffinose', 'раффиноза', 'raffinos', 'раффиноз', - 'melezitose', 'мелецитоза', 'melezitos', 'мелецитоз', - 'starch', 'крахмал', 'starc', 'крахма', - 'glycogen', 'гликоген', 'glycoge', 'гликоге' - ]; - - const enzymeKeywords = [ - 'enzyme', 'фермент', 'activity', 'активность', - 'lipase', 'липаза', 'lipas', 'липаз', - 'arylamidase', 'ариламидаза', 'arylamidas', 'ариламидаз', - 'trypsin', 'трипсин', 'trypsi', 'трипси', - 'glucosaminidase', 'глюкозаминидаза', 'glucosaminidas', 'глюкозаминидаз', - 'phosphatase', 'фосфатаза', 'phosphatas', 'фосфатаз', - 'protease', 'протеаза', 'proteas', 'протеаз', - 'catalase', 'каталаза', 'catalas', 'каталаз', - 'oxidase', 'оксидаза', 'oxidas', 'оксидаз', - 'urease', 'уреаза', 'ureas', 'уреаз', - 'amylase', 'амилаза', 'amylas', 'амилаз', - 'cellulase', 'целлюлаза', 'cellulas', 'целлюлаз', - 'hemolysin', 'гемолизин', 'hemolysi', 'гемолизи', - 'coagulase', 'коагулаза', 'coagulas', 'коагулаз' - ]; - - // Классифицируем строки - tableData.forEach(row => { - if (!row.characteristic) return; - - const characteristic = row.characteristic.toLowerCase().trim(); - - // Проверяем ассимиляцию - const isAssimilation = assimilationKeywords.some(keyword => { - // Точное совпадение или вхождение слова - return characteristic === keyword || - characteristic.includes(keyword) || - characteristic.split(/\s+/).some(word => word === keyword); - }); - - if (isAssimilation) { - assimilationRows.add(row.characteristic); - } - - // Проверяем ферменты - const isEnzyme = enzymeKeywords.some(keyword => { - return characteristic === keyword || - characteristic.includes(keyword) || - characteristic.split(/\s+/).some(word => word === keyword); - }); - - if (isEnzyme) { - enzymeRows.add(row.characteristic); - } - }); - - console.log('Классификация строк:'); - console.log('Ассимиляция:', Array.from(assimilationRows)); - console.log('Ферменты:', Array.from(enzymeRows)); - - // Правила преобразования в зависимости от контекста - return tableData.map(row => { - const newRow = { ...row }; - const characteristic = row.characteristic ? row.characteristic.toLowerCase().trim() : ''; - - Object.keys(newRow).forEach(key => { - if (key === 'characteristic') return; - - let value = String(newRow[key] || '').trim(); - let originalValue = value; - - // ПРАВИЛО 1: Ассимиляция углеводов - "?" ВСЕГДА преобразуется в "+" - if (assimilationRows.has(row.characteristic) && value === '?') { - console.log(`Ассимиляция углеводов: "${row.characteristic}" - заменяем "?" на "+"`); - value = '+'; - } - - // ПРАВИЛО 2: Для ферментов - "?" может быть "+" или "-" в зависимости от типа - else if (enzymeRows.has(row.characteristic) && value === '?') { - const enzymeName = characteristic; - - // Ферменты, которые обычно дают положительную реакцию - const positiveEnzymes = [ - 'lipase', 'липаза', - 'trypsin', 'трипсин', - 'glucosaminidase', 'глюкозаминидаза', - 'catalase', 'каталаза', - 'oxidase', 'оксидаза' - ]; - - const isPositiveEnzyme = positiveEnzymes.some(enzyme => - enzymeName.includes(enzyme) - ); - - if (isPositiveEnzyme) { - console.log(`Фермент ${row.characteristic}: обычно положительный, заменяем "?" на "+"`); - value = '+'; - } else { - console.log(`Фермент ${row.characteristic}: обычно отрицательный, заменяем "?" на "-"`); - value = '-'; - } - } - - // ПРАВИЛО 3: Общее правило для остальных случаев - else if (value === '?') { - // Анализируем столбец - const columnData = tableData - .map(r => r[key]) - .filter(v => v !== undefined) - .map(v => String(v).trim()); - - const plusCount = columnData.filter(v => v === '+').length; - const minusCount = columnData.filter(v => v === '-').length; - const questionCount = columnData.filter(v => v === '?').length; - const total = columnData.length; - - if (total > 5) { // Только если достаточно данных - const plusRatio = plusCount / total; - const minusRatio = minusCount / total; - const questionRatio = questionCount / total; - - // Если в столбце явное большинство "+" - if (plusRatio > 0.7 && questionRatio < 0.3) { - console.log(`Столбец ${key}: доминируют "+" (${plusCount}/${total}), заменяем "?" на "+" в "${row.characteristic}"`); - value = '+'; - } - // Если в столбце явное большинство "-" - else if (minusRatio > 0.7 && questionRatio < 0.3) { - console.log(`Столбец ${key}: доминируют "-" (${minusCount}/${total}), заменяем "?" на "-" в "${row.characteristic}"`); - value = '-'; - } - } - } - - // Исправляем другие похожие символы - if (value && !['+', '-', '?'].includes(value)) { - // Любой символ, похожий на плюс - if (/[+﹢⁺₊†‡ᐩ•∗·∙⋅◦]/.test(value)) { - console.log(`Исправляем похожий на плюс символ "${value}" на "+" в "${row.characteristic}"`); - value = '+'; - } - // Любой символ, похожий на минус - else if (/[−–—―‑‒–—―]/.test(value)) { - console.log(`Исправляем похожий на минус символ "${value}" на "-" в "${row.characteristic}"`); - value = '-'; - } - } - - // Убираем лишние пробелы - value = value.trim(); - - // Если значение изменилось, сохраняем - if (originalValue !== value) { - newRow[key] = value; - console.log(`Исправлено: "${row.characteristic}"[${key}] "${originalValue}" → "${value}"`); - } - }); - - return newRow; - }); -} - -function addTableLegend(correctedCells) { - const existingLegend = document.getElementById('tableLegend'); - if (existingLegend) { - existingLegend.remove(); - } - - const legend = document.createElement('div'); - legend.id = 'tableLegend'; - legend.style.cssText = ` - padding: 12px; - background: #f8fafc; - border-top: 1px solid #e2e8f0; - display: flex; - flex-wrap: wrap; - gap: 16px; - align-items: center; - font-size: 0.85rem; - color: #4a5568; - `; - - if (correctedCells > 0) { - legend.innerHTML += ` -
- - Исправленный символ - - Автоматически исправлено: ${correctedCells} -
- `; - } - - elements.tableInfo.parentNode.insertBefore(legend, elements.tableInfo.nextSibling); -} - -if (document.getElementById('fixSymbolsBtn')) { - document.getElementById('fixSymbolsBtn').addEventListener('click', manualFixSymbols); -} - -function manualFixSymbols() { - try { - const jsonText = elements.jsonOutput.textContent; - const data = JSON.parse(jsonText); - - if (!data.table_data || !Array.isArray(data.table_data)) { - alert('Нет табличных данных для исправления'); - return; - } - - console.log('Ручное исправление символов...'); - - // Сохраняем оригинальные данные - const originalData = JSON.parse(JSON.stringify(data.table_data)); - - // Применяем исправления - data.table_data = fixTableSymbols(data.table_data); - data.table_data = postProcessTableData(data.table_data); - - // Считаем изменения - let changes = 0; - originalData.forEach((originalRow, index) => { - const fixedRow = data.table_data[index]; - if (!originalRow || !fixedRow) return; - - Object.keys(originalRow).forEach(key => { - if (key === 'characteristic') return; - - const originalValue = String(originalRow[key] || ''); - const fixedValue = String(fixedRow[key] || ''); - - if (originalValue !== fixedValue) { - changes++; - console.log(`Изменение: ${originalRow.characteristic}[${key}] "${originalValue}" → "${fixedValue}"`); - } - }); - }); - - // Обновляем отображение - elements.jsonOutput.textContent = JSON.stringify(data, null, 2); - highlightJSON(); - - if (elements.tableView.style.display === 'block') { - displayTable(data.table_data); - } - - // Показываем результат - if (changes > 0) { - alert(`Исправлено ${changes} символов �� таблице`); - } else { - alert('Символы не нуждаются в исправлении'); - } - - // Сохраняем исправленные данные - jsonData = JSON.stringify(data); - - } catch (error) { - console.error('Ошибка при ручном исправлении символов:', error); - alert('Ошибка: ' + error.message); - } -} - -if (document.getElementById('convertQuestionsBtn')) { - document.getElementById('convertQuestionsBtn').addEventListener('click', convertQuestionsToPlus); -} - -function convertQuestionsToPlus() { - try { - const jsonText = elements.jsonOutput.textContent; - const data = JSON.parse(jsonText); - - if (!data.table_data || !Array.isArray(data.table_data)) { - alert('Нет табличных данных для обработки'); - return; - } - - let changes = 0; - const assimilationKeywords = ['dextrin', 'cellobiose', 'fructose', 'glucose', 'maltose', 'mannose', 'sugar', 'углевод', 'сахар']; - - data.table_data = data.table_data.map(row => { - const newRow = { ...row }; - const characteristic = (row.characteristic || '').toLowerCase(); - - // Проверяем, относится ли строка к ассимиляции углеводов - const isAssimilation = assimilationKeywords.some(keyword => - characteristic.includes(keyword) - ); - - if (isAssimilation) { - Object.keys(newRow).forEach(key => { - if (key === 'characteristic') return; - - const value = String(newRow[key] || ''); - if (value === '?') { - newRow[key] = '+'; - changes++; - console.log(`Преобразовано: ${row.characteristic}[${key}] "?" → "+"`); - } - }); - } - - return newRow; - }); - - // Обновляем отображение - elements.jsonOutput.textContent = JSON.stringify(data, null, 2); - highlightJSON(); - - if (elements.tableView.style.display === 'block') { - displayTable(data.table_data); - } - - alert(`Преобразовано ${changes} символов "?" в "+" для ассимиляции углеводов`); - - // Сохраняем исправленные данные - jsonData = JSON.stringify(data); - - } catch (error) { - console.error('Ошибка при преобразовании:', error); - alert('Ошибка: ' + error.message); - } -} - -function forceFixAssimilation(tableData) { - if (!Array.isArray(tableData)) return tableData; - - // Расширенный список углеводов для ассимиляции - const carbohydrateKeywords = [ - // Английские названия - 'glucose', 'fructose', 'maltose', 'dextrin', 'cellobiose', - 'mannose', 'lactose', 'sucrose', 'galactose', 'sorbose', - 'xylose', 'arabinose', 'ribose', 'rhamnose', 'trehalose', - 'raffinose', 'melezitose', 'starch', 'glycogen', - // Русские названия - 'глюкоза', 'фруктоза', 'мальтоза', 'декстрин', 'целлобиоза', - 'манноза', 'лактоза', 'сахароза', 'галактоза', 'сорбоза', - 'ксилоза', 'арабиноза', 'рибоза', 'рамноза', 'трегалоза', - 'раффиноза', 'мелецитоза', 'крахмал', 'гликоген', - // Общие термины - 'sugar', 'углевод', 'сахар', 'carbohydrate', 'углеводы', - 'assimilation', 'ассимиляция' - ]; - - return tableData.map(row => { - if (!row || typeof row !== 'object' || !row.characteristic) return row; - - const characteristic = row.characteristic.toLowerCase().trim(); - const isCarbohydrate = carbohydrateKeywords.some(keyword => - characteristic.includes(keyword.toLowerCase()) - ); - - if (!isCarbohydrate) return row; - - const newRow = { ...row }; - let changes = 0; - - Object.keys(newRow).forEach(key => { - if (key === 'characteristic') return; - - const value = String(newRow[key] || '').trim(); - if (value === '?') { - newRow[key] = '+'; - changes++; - } - }); - - if (changes > 0) { - console.log(`Принудительно исправлено ${changes} символов "?" в "+" для: ${row.characteristic}`); - } - - return newRow; - }); -} - -function manualFixSymbols() { - try { - const jsonText = elements.jsonOutput.textContent; - const data = JSON.parse(jsonText); - - if (!data.table_data || !Array.isArray(data.table_data)) { - alert('Нет табличных данных для исправления'); - return; - } - - console.log('Ручное исправление символов...'); - - // Сохраняем оригинальные данные - const originalData = JSON.parse(JSON.stringify(data.table_data)); - - // Применяем все исправления - data.table_data = fixTableSymbols(data.table_data); - data.table_data = postProcessTableData(data.table_data); - data.table_data = forceFixAssimilation(data.table_data); // Добавляем принудительное исправление - - // Считаем изменения - let changes = 0; - originalData.forEach((originalRow, index) => { - const fixedRow = data.table_data[index]; - if (!originalRow || !fixedRow) return; - - Object.keys(originalRow).forEach(key => { - if (key === 'characteristic') return; - - const originalValue = String(originalRow[key] || ''); - const fixedValue = String(fixedRow[key] || ''); - - if (originalValue !== fixedValue) { - changes++; - console.log(`Изменение: ${originalRow.characteristic}[${key}] "${originalValue}" → "${fixedValue}"`); - } - }); - }); - - // Обновляем отображение - elements.jsonOutput.textContent = JSON.stringify(data, null, 2); - highlightJSON(); - - if (elements.tableView.style.display === 'block') { - displayTable(data.table_data); - } - - // Показываем результат - if (changes > 0) { - alert(`Исправлено ${changes} символов в таблице`); - } else { - alert('Символы не нуждаются в исправлении'); - } - - // Сохраняем исправленные данные - jsonData = JSON.stringify(data); - - } catch (error) { - console.error('Ошибка при ручном исправлении символов:', error); - alert('Ошибка: ' + error.message); - } -} - -function forceFixAssimilationImproved(tableData) { - if (!Array.isArray(tableData)) return tableData; - - console.log('=== УЛУЧШЕННОЕ ИСПРАВЛЕНИЕ АССИМИЛЯЦИИ ==='); - - // Шаблоны для распознавания углеводов - const carbPatterns = [ - // Основные углеводы (точные совпадения) - /^dextrin$/i, - /^cellobiose$/i, - /^fructose$/i, - /^glucose$/i, - /^maltose$/i, - /^mannose$/i, - /^lactose$/i, - /^sucrose$/i, - /^galactose$/i, - /^sorbose$/i, - /^xylose$/i, - /^arabinose$/i, - /^ribose$/i, - /^rhamnose$/i, - /^trehalose$/i, - /^raffinose$/i, - /^melezitose$/i, - /^starch$/i, - /^glycogen$/i, - - // С префиксами - /^d-.*glucose$/i, - /^d-.*fructose$/i, - /^d-.*mannose$/i, - /^d-.*maltose$/i, - /^d-.*cellobiose$/i, - /^α-d-.*glucose$/i, - /^d-glucose$/i, - /^d-fructose$/i, - /^d-mannose$/i, - /^d-maltose$/i, - - // Русские варианты - /^декстрин$/i, - /^целлобиоза$/i, - /^фруктоза$/i, - /^глюкоза$/i, - /^мальтоза$/i, - /^манноза$/i, - /^лактоза$/i, - /^сахароза$/i, - /^галактоза$/i, - /^сорбоза$/i, - /^ксилоза$/i, - /^арабиноза$/i, - /^рибоза$/i, - /^рамноза$/i, - /^трегалоза$/i, - /^раффиноза$/i, - /^мелецитоза$/i, - /^крахмал$/i, - /^гликоген$/i, - - // Частичные совпадения - /glucos/i, - /fructos/i, - /maltos/i, - /mannos/i, - /cellobios/i, - /dextr/i, - /сахар/i, - /углевод/i - ]; - - let totalChanges = 0; - const changedRows = []; - - const result = tableData.map((row, rowIndex) => { - if (!row || typeof row !== 'object' || !row.characteristic) return row; - - const characteristic = row.characteristic.trim(); - const charLower = characteristic.toLowerCase(); - - // Проверяем, является ли строка углеводом - const isCarbohydrate = carbPatterns.some(pattern => - pattern.test(characteristic) || pattern.test(charLower) - ); - - console.log(`Строка ${rowIndex}: "${characteristic}" - углевод: ${isCarbohydrate}`); - - const newRow = { ...row }; - let rowChanges = 0; - - if (isCarbohydrate) { - Object.keys(newRow).forEach(key => { - if (key === 'characteristic') return; - - const originalValue = String(newRow[key] || ''); - - // Очищаем значение для проверки - const cleanValue = originalValue - .replace(/[❓❌✅⭐—–\s↵↵]/g, '') // Убираем эмодзи и спецсимволы - .trim(); - - // Проверяем различные варианты "?" - const isQuestionMark = - cleanValue === '?' || - originalValue.includes('?') || - originalValue.includes('❓') || - cleanValue === '' && originalValue !== ''; // Пустые значения тоже меняем - - if (isQuestionMark) { - const oldValue = originalValue; - newRow[key] = '+'; - rowChanges++; - - console.log(` Изменение [${key}]: "${oldValue}" → "+"`); - } - }); - } - - if (rowChanges > 0) { - totalChanges += rowChanges; - changedRows.push({ - index: rowIndex, - characteristic: characteristic, - changes: rowChanges - }); - } - - return newRow; - }); - - // Логируем результаты - console.log('\n=== ИТОГИ ИСПРАВЛЕНИЯ ==='); - console.log(`Обработано строк: ${tableData.length}`); - console.log(`Изменено строк: ${changedRows.length}`); - console.log(`Всего изменений: ${totalChanges}`); - - if (changedRows.length > 0) { - console.log('\nИзмененные строки:'); - changedRows.forEach(item => { - console.log(` ${item.index}. "${item.characteristic}": ${item.changes} ячеек`); - }); - } else { - console.log('Нет изменений. Возможные причины:'); - console.log('1. Нет строк углеводов в данных'); - console.log('2. В строках углеводов нет знаков "?"'); - console.log('3. Знаки "?" уже исправлены'); - } - - return result; -} - -function handleCellClick(e) { - e.target.style.backgroundColor = '#e8f4fd'; - e.target.focus(); -} - -function handleCellBlur(e) { - e.target.style.backgroundColor = ''; - updateTableDataFromDOM(); -} - -function handleCellKeydown(e) { - if (e.key === 'Enter') { - e.preventDefault(); - e.target.blur(); - } -} - -function updateTableDataFromDOM() { - try { - const table = document.getElementById('dataTable'); - const rows = table.querySelectorAll('#tableBody tr'); - const headers = table.querySelectorAll('#tableHeader th'); - - if (!jsonData) return; - - const data = JSON.parse(jsonData); - if (!data.table_data) return; - - // Обновляем данные из DOM - rows.forEach((row, rowIndex) => { - const cells = row.querySelectorAll('td'); - const characteristic = cells[0].textContent; - - // Находим соответствующую строку в данных - const dataRow = data.table_data.find(r => r.characteristic === characteristic); - if (dataRow) { - cells.forEach((cell, cellIndex) => { - if (cellIndex > 0) { // Пропускаем характеристику - const columnName = headers[cellIndex].textContent; - const cleanColumnName = columnName.replace('Col ', 'column_'); - dataRow[cleanColumnName] = cell.textContent.trim(); - } - }); - } - }); - - // Сохраняем обновленные данные - jsonData = JSON.stringify(data); - elements.jsonOutput.textContent = JSON.stringify(data, null, 2); - highlightJSON(); - - } catch (error) { - console.error('Ошибка при обновлении данных:', error); - } -} - -function deleteSelectedRows() { - const selectedRows = document.querySelectorAll('#tableBody tr.selected'); - if (selectedRows.length === 0) { - alert('Выберите строки для удаления (кликните на номер строки)'); - return; - } - - if (!confirm(`Удалить выбранные ${selectedRows.length} строк?`)) { - return; - } - - try { - const data = JSON.parse(jsonData); - const rowsToDelete = []; - - selectedRows.forEach(row => { - const characteristic = row.querySelector('td:first-child').textContent; - const rowIndex = data.table_data.findIndex(r => r.characteristic === characteristic); - if (rowIndex !== -1) { - rowsToDelete.push(rowIndex); - } - }); - - // Удаляем строки в обратном порядке - rowsToDelete.sort((a, b) => b - a).forEach(index => { - data.table_data.splice(index, 1); - }); - - // Обновляем данные - jsonData = JSON.stringify(data); - displayJSON(jsonData); - - showNotification(`Удалено ${rowsToDelete.length} строк`, 'success'); - - } catch (error) { - console.error('Ошибка при удалении строк:', error); - showNotification('Ошибка при удалении строк', 'error'); - } -} - -function deleteSelectedColumns() { - const selectedColumns = document.querySelectorAll('#tableHeader th.selected'); - if (selectedColumns.length === 0) { - alert('Выберите колонки для удаления (кликните на заголовок колонки)'); - return; - } - - if (!confirm(`Удалить выбранные ${selectedColumns.length} колонок?`)) { - return; - } - - try { - const data = JSON.parse(jsonData); - const columnsToDelete = []; - - selectedColumns.forEach(th => { - const colName = th.textContent.replace('Col ', 'column_'); - columnsToDelete.push(colName); - }); - - // Удаляем колонки из всех строк - data.table_data.forEach(row => { - columnsToDelete.forEach(col => { - delete row[col]; - }); - }); - - // Обновляем данные - jsonData = JSON.stringify(data); - displayJSON(jsonData); - - showNotification(`Удалено ${columnsToDelete.length} колонок`, 'success'); - - } catch (error) { - console.error('Ошибка при удалении колонок:', error); - showNotification('Ошибка при удалении колонок', 'error'); - } -} - -function addNewRow() { - try { - const data = JSON.parse(jsonData); - const headers = document.querySelectorAll('#tableHeader th'); - - // Создаем новую строку - const newRow = { characteristic: `Новая строка ${data.table_data.length + 1}` }; - - // Добавляем все колонки - headers.forEach((th, index) => { - if (index > 0) { // Пропускаем характеристику - const colName = th.textContent.replace('Col ', 'column_'); - newRow[colName] = ''; - } - }); - - data.table_data.push(newRow); - - // Обновляем данные - jsonData = JSON.stringify(data); - displayJSON(jsonData); - - showNotification('Добавлена новая строка', 'success'); - - } catch (error) { - console.error('Ошибка при добавлении строки:', error); - showNotification('Ошибка при добавлении строки', 'error'); - } -} - -function addNewColumn() { - const colName = prompt('Введите имя новой колонки (например: column_31):', 'column_31'); - if (!colName) return; - - try { - const data = JSON.parse(jsonData); - - // Добавляем новую колонку всем строкам - data.table_data.forEach(row => { - row[colName] = ''; - }); - - // Обновляем данные - jsonData = JSON.stringify(data); - displayJSON(jsonData); - - showNotification(`Добавлена новая колонка: ${colName}`, 'success'); - - } catch (error) { - console.error('Ошибка при добавлении колонки:', error); - showNotification('Ошибка при добавлении колонки', 'error'); - } -} - -function showEditPanel() { - const editPanel = document.createElement('div'); - editPanel.id = 'editPanel'; - editPanel.style.cssText = ` - position: fixed; - top: 20px; - left: 50%; - transform: translateX(-50%); - background: white; - padding: 15px 20px; - border-radius: 10px; - box-shadow: 0 4px 20px rgba(0,0,0,0.15); - z-index: 1000; - display: flex; - gap: 10px; - align-items: center; - border: 2px solid #3b82f6; - `; - - editPanel.innerHTML = ` - - Режим редактирования - - - - - - `; - - document.body.appendChild(editPanel); -} - -function hideEditPanel() { - const editPanel = document.getElementById('editPanel'); - if (editPanel) { - editPanel.remove(); - } -} - -function saveChanges() { - updateTableDataFromDOM(); - disableEditMode(); - showNotification('Изменения сохранены', 'success'); -} - -function cancelEdit() { - // Восстанавливаем исходные данные - if (originalTableData) { - jsonData = JSON.stringify(originalTableData); - displayJSON(jsonData); - } - disableEditMode(); - showNotification('Изменения отменены', 'info'); -} - -function toggleEditMode() { - console.log('toggleEditMode вызвана, текущее состояние:', isEditMode); - - if (!jsonData) { - showNotification('Нет данных для редактирования', 'warning'); - return; - } - - isEditMode = !isEditMode; - - if (isEditMode) { - enableEditMode(); - } else { - disableEditMode(); - } -} - -function enableEditMode() { - console.log('Включение режима редактирования...'); - - try { - // Сохраняем оригинальные данные - originalTableData = JSON.parse(jsonData); - - isEditMode = true; - - // Добавляем класс для редактируемых ячеек - const cells = document.querySelectorAll('#tableBody td:not(:first-child)'); - cells.forEach(cell => { - cell.classList.add('editable-cell'); - cell.contentEditable = true; - cell.addEventListener('focus', handleCellFocus); - cell.addEventListener('blur', handleCellBlur); - cell.addEventListener('keydown', handleCellKeydown); - - // Добавляем возможность выбора ячейки - cell.addEventListener('click', function(e) { - if (e.ctrlKey || e.metaKey) { - this.classList.toggle('selected-cell'); - } - }); - }); - - // Добавляем номера строк - addRowNumbers(); - - // Показываем панель редактирования - showEditPanel(); - - showNotification('Режим редактирования включен', 'success'); - - } catch (e) { - console.error('Ошибка при включении режима редактирования:', e); - showNotification('Ошибка включения режима редактирования', 'error'); - isEditMode = false; - } -} - -function disableEditMode() { - console.log('Отключение режима редактирования...'); - - isEditMode = false; - - // Убираем классы и обработчики - const cells = document.querySelectorAll('#tableBody td.editable-cell'); - cells.forEach(cell => { - cell.classList.remove('editable-cell', 'selected-cell'); - cell.contentEditable = false; - cell.removeEventListener('focus', handleCellFocus); - cell.removeEventListener('blur', handleCellBlur); - cell.removeEventListener('keydown', handleCellKeydown); - }); - - // Убираем номера строк - removeRowNumbers(); - - // Скрываем панель редактирования - hideEditPanel(); - - // Сбрасываем выбранные строки и колонки - selectedRows.clear(); - selectedColumns.clear(); - - showNotification('Режим редактирования отключен', 'info'); -} - -function handleCellFocus(e) { - e.target.style.backgroundColor = '#e8f4fd'; - e.target.style.outline = '2px solid #3b82f6'; -} - -function handleCellBlur(e) { - const cell = e.target; - cell.style.backgroundColor = ''; - cell.style.outline = ''; - - // Обновляем данные в JSON - updateCellValue(cell); -} - -function handleCellKeydown(e) { - if (e.key === 'Enter') { - e.preventDefault(); - e.target.blur(); - } - - // Ctrl+A для выбора всех ячеек - if ((e.ctrlKey || e.metaKey) && e.key === 'a') { - e.preventDefault(); - selectAllCells(); - } -} - -function updateCellValue(cell) { - try { - const rowElement = cell.parentElement; - const rowIndex = Array.from(rowElement.parentElement.children).indexOf(rowElement); - const cellIndex = Array.from(rowElement.children).indexOf(cell); - - const data = JSON.parse(jsonData); - const row = data.table_data[rowIndex]; - - if (!row) return; - - // Получаем имя колонки из заголовка - const headers = document.querySelectorAll('#tableHeader th'); - if (cellIndex >= headers.length) return; - - const header = headers[cellIndex]; - let columnName = header.textContent.trim(); - - // Преобразуем заголовок в имя колонки - if (columnName === 'Characteristic') { - columnName = 'characteristic'; - } else if (columnName.startsWith('Col ')) { - columnName = columnName.toLowerCase().replace('col ', 'column_'); - } - - // Обновляем значение - const newValue = cell.textContent.trim(); - const oldValue = row[columnName] || ''; - - if (newValue !== oldValue) { - row[columnName] = newValue; - - // Обновляем JSON - jsonData = JSON.stringify(data, null, 2); - elements.jsonOutput.textContent = jsonData; - highlightJSON(); - - // Обновляем стиль ячейки - applyCellStyles(cell, newValue); - - console.log(`Обновлено: строка ${rowIndex}, колонка ${columnName}: "${oldValue}" → "${newValue}"`); - - // Показываем кнопку сохранения - showSaveButton(); - } - - } catch (error) { - console.error('Ошибка при обновлении ячейки:', error); - showNotification('Ошибка обновления ячейки', 'error'); - } -} - -function addRowNumbers() { - const rows = document.querySelectorAll('#tableBody tr'); - rows.forEach((row, index) => { - if (!row.querySelector('.row-number')) { - const firstCell = row.querySelector('td:first-child'); - const rowNumber = document.createElement('span'); - rowNumber.className = 'row-number'; - rowNumber.textContent = `${index + 1}. `; - rowNumber.style.marginRight = '5px'; - rowNumber.style.fontWeight = 'bold'; - rowNumber.style.color = '#6b7280'; - - if (firstCell && firstCell.firstChild) { - firstCell.insertBefore(rowNumber, firstCell.firstChild); - } - } - }); -} - -function removeRowNumbers() { - const rowNumbers = document.querySelectorAll('.row-number'); - rowNumbers.forEach(number => { - number.remove(); - }); -} - -function showEditPanel() { - // Удаляем старую панель, если есть - hideEditPanel(); - - const editPanel = document.createElement('div'); - editPanel.id = 'editPanel'; - editPanel.className = 'edit-panel'; - - editPanel.innerHTML = ` -
-
- - Режим редактирования -
- -
- - - - - - - - - - - - - -
- -
- Кликните на ячейку для редактирования - Ctrl+клик для выбора нескольких ячеек -
-
- `; - - document.body.appendChild(editPanel); -} - -function hideEditPanel() { - const editPanel = document.getElementById('editPanel'); - if (editPanel) { - editPanel.remove(); - } -} - -function showSaveButton() { - const saveBtn = document.querySelector('#editPanel .btn-success'); - if (saveBtn) { - saveBtn.innerHTML = ' Сохранить*'; - saveBtn.style.backgroundColor = '#f59e0b'; - saveBtn.style.borderColor = '#f59e0b'; - } -} - -function saveChanges() { - if (!originalTableData) return; - - // Просто обновляем данные - они уже обновляются при изменении ячеек - originalTableData = JSON.parse(jsonData); - - const saveBtn = document.querySelector('#editPanel .btn-success'); - if (saveBtn) { - saveBtn.innerHTML = ' Сохранено'; - saveBtn.style.backgroundColor = ''; - saveBtn.style.borderColor = ''; - - setTimeout(() => { - saveBtn.innerHTML = ' Сохранить'; - }, 2000); - } - updateExportButtons(); - showNotification('Изменения сохранены', 'success'); -} - -function cancelEdit() { - if (!originalTableData) return; - - if (!confirm('Отменить все изменения? Это действие нельзя отменить.')) { - return; - } - - // Восстанавливаем оригинальные данные - jsonData = JSON.stringify(originalTableData, null, 2); - - // Обновляем отображение - displayJSON(jsonData); - - // Показываем уведомление - showNotification('Изменения отменены', 'info'); -} - -function addNewRow() { - try { - const data = JSON.parse(jsonData); - if (!data.table_data) return; - - // Получаем колонки из первой строки - const columns = data.table_data.length > 0 ? - Object.keys(data.table_data[0]) : - ['characteristic']; - - // Создаем новую строку - const newRow = {}; - columns.forEach(col => { - if (col === 'characteristic') { - newRow[col] = `Новая строка ${data.table_data.length + 1}`; - } else { - newRow[col] = ''; - } - }); - - // Добавляем строку в данные - data.table_data.push(newRow); - - // Обновляем данные - jsonData = JSON.stringify(data, null, 2); - displayJSON(jsonData); - - showNotification('Добавлена новая строка', 'success'); - - } catch (error) { - console.error('Ошибка при добавлении строки:', error); - showNotification('Ошибка при добавлении строки', 'error'); - } -} - -function deleteSelectedRows() { - const selectedCells = document.querySelectorAll('#tableBody td.selected-cell'); - const rowsToDelete = new Set(); - - selectedCells.forEach(cell => { - const row = cell.parentElement; - rowsToDelete.add(row); - }); - - // Если нет выбранных ячеек, проверяем выбранные строки через клик - if (rowsToDelete.size === 0) { - const selectedRows = document.querySelectorAll('#tableBody tr.selected-row'); - selectedRows.forEach(row => rowsToDelete.add(row)); - } - - if (rowsToDelete.size === 0) { - alert('Выберите строки для удаления (Ctrl+клик на ячейки или клик на номер строки)'); - return; - } - - if (!confirm(`Удалить выбранные ${rowsToDelete.size} строк?`)) { - return; - } - - try { - const data = JSON.parse(jsonData); - const rowsArray = Array.from(rowsToDelete); - const indicesToDelete = []; - - // Получаем индексы строк для удаления - rowsArray.forEach(row => { - const rowIndex = Array.from(row.parentElement.children).indexOf(row); - if (rowIndex !== -1) { - indicesToDelete.push(rowIndex); - } - }); - - // Сортируем индексы по убыванию и удаляем - indicesToDelete.sort((a, b) => b - a).forEach(index => { - data.table_data.splice(index, 1); - }); - - // Обновляем данные - jsonData = JSON.stringify(data, null, 2); - displayJSON(jsonData); - - showNotification(`Удалено ${indicesToDelete.length} строк`, 'success'); - - } catch (error) { - console.error('Ошибка при удалении строк:', error); - showNotification('Ошибка при удалении строк', 'error'); - } -} - -function addNewColumn() { - const colName = prompt('Введите имя новой колонки (например: column_31):', 'column_31'); - if (!colName) return; - - try { - const data = JSON.parse(jsonData); - - // Добавляем новую колонку всем строкам - data.table_data.forEach(row => { - row[colName] = ''; - }); - - // Обновляем данные - jsonData = JSON.stringify(data, null, 2); - displayJSON(jsonData); - - showNotification(`Добавлена новая колонка: ${colName}`, 'success'); - - } catch (error) { - console.error('Ошибка при добавлении колонки:', error); - showNotification('Ошибка при добавлении колонки', 'error'); - } -} - -function deleteSelectedColumns() { - const selectedCells = document.querySelectorAll('#tableBody td.selected-cell'); - const columnsToDelete = new Set(); - - selectedCells.forEach(cell => { - const cellIndex = Array.from(cell.parentElement.children).indexOf(cell); - if (cellIndex !== -1) { - columnsToDelete.add(cellIndex); - } - }); - - // Получаем имена колонок для удаления - const headers = document.querySelectorAll('#tableHeader th'); - const columnNames = []; - - columnsToDelete.forEach(index => { - if (index < headers.length) { - let colName = headers[index].textContent.trim(); - if (colName === 'Characteristic') { - colName = 'characteristic'; - } else if (colName.startsWith('Col ')) { - colName = colName.toLowerCase().replace('col ', 'column_'); - } - columnNames.push(colName); - } - }); - - if (columnNames.length === 0) { - alert('Выберите колонки для удаления (Ctrl+клик на ячейки в нужных колонках)'); - return; - } - - if (!confirm(`Удалить выбранные ${columnNames.length} колонок?`)) { - return; - } - - try { - const data = JSON.parse(jsonData); - - // Удаляем колонки из всех строк - data.table_data.forEach(row => { - columnNames.forEach(colName => { - delete row[colName]; - }); - }); - - // Обновляем данные - jsonData = JSON.stringify(data, null, 2); - displayJSON(jsonData); - - showNotification(`Удалено ${columnNames.length} колонок`, 'success'); - - } catch (error) { - console.error('Ошибка при удалении колонок:', error); - showNotification('Ошибка при удалении колонок', 'error'); - } -} - -function selectAllCells() { - const cells = document.querySelectorAll('#tableBody td.editable-cell'); - cells.forEach(cell => { - cell.classList.add('selected-cell'); - }); -} - -function addEditButtonToTable() { - // Удаляем старую кнопку, если есть - const oldBtn = document.querySelector('.edit-mode-btn'); - if (oldBtn) oldBtn.remove(); - - if (!jsonData) return; - - const editBtn = document.createElement('button'); - editBtn.className = 'btn btn-primary edit-mode-btn'; - editBtn.innerHTML = ' Режим редактирования'; - editBtn.onclick = toggleEditMode; - editBtn.style.position = 'absolute'; - editBtn.style.top = '10px'; - editBtn.style.right = '10px'; - editBtn.style.zIndex = '10'; - - const tableHeader = document.querySelector('.table-header'); - if (tableHeader) { - tableHeader.style.position = 'relative'; - tableHeader.appendChild(editBtn); - } -} - -const originalDisplayTable = displayTable; -displayTable = function(tableData) { - // Вызываем оригинальную функцию - originalDisplayTable(tableData); - - // Добавляем кнопку редактирования - setTimeout(() => { - addEditButtonToTable(); - }, 100); -}; - -if (document.getElementById('editTableBtn')) { - document.getElementById('editTableBtn').addEventListener('click', function() { - toggleEditMode(); - }); -} - -function analyzeRows() { - const rows = document.querySelectorAll('#tableBody tr'); - const result = { - total: rows.length, - empty: 0, - withData: 0, - details: [] - }; - - rows.forEach((row, index) => { - const cells = row.querySelectorAll('td'); - let isEmpty = true; - const values = []; - - // Проверяем все ячейки кроме characteristic - for (let i = 1; i < cells.length; i++) { - const value = cells[i].textContent.trim(); - values.push(value); - if (value && value !== '—' && value !== '') { - isEmpty = false; - } - } - - result.details.push({ - index: index, - characteristic: cells[0].textContent.trim(), - isEmpty: isEmpty, - values: values - }); - - if (isEmpty) { - result.empty++; - } else { - result.withData++; - } - }); - - console.log('Анализ строк:', result); - return result; -} - -function deleteEmptyColumns() { - const table = document.getElementById('dataTable'); - if (!table) { - showNotification('Таблица не найдена', 'warning'); - return; - } - - const headers = table.querySelectorAll('#tableHeader th'); - const rows = table.querySelectorAll('#tableBody tr'); - - if (rows.length === 0) { - showNotification('Нет данных в таблице', 'warning'); - return; - } - - // Находим пустые колонки - const emptyColumnIndices = []; - - // Проверяем каждую колонку (кроме characteristic) - for (let colIndex = 1; colIndex < headers.length; colIndex++) { - let isEmpty = true; - - // Проверяем все строки в этой колонке - for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) { - const cells = rows[rowIndex].querySelectorAll('td'); - if (colIndex < cells.length) { - const value = cells[colIndex].textContent.trim(); - if (value && value !== '—' && value !== '') { - isEmpty = false; - break; - } - } - } - - if (isEmpty) { - emptyColumnIndices.push(colIndex); - } - } - - if (emptyColumnIndices.length === 0) { - showNotification('Нет пустых колонок для удаления', 'info'); - return; - } - - // Подтверждение удаления - if (!confirm(`Удалить ${emptyColumnIndices.length} пустых колонок? Это действие нельзя отменить!`)) { - return; - } - - try { - // Получаем текущие данные - const jsonText = elements.jsonOutput.textContent; - const data = JSON.parse(jsonText); - - if (!data.table_data || !Array.isArray(data.table_data)) { - throw new Error('Нет табличных данных'); - } - - // Получаем имена колонок для удаления - const columnsToDelete = []; - emptyColumnIndices.forEach(colIndex => { - const header = headers[colIndex]; - if (header) { - let colName = header.textContent.trim(); - if (colName.startsWith('Col ')) { - colName = colName.toLowerCase().replace('col ', 'column_'); - } - columnsToDelete.push(colName); - } - }); - - // Удаляем колонки из всех строк - data.table_data.forEach(row => { - columnsToDelete.forEach(colName => { - delete row[colName]; - }); - }); - - // Обновляем JSON - elements.jsonOutput.textContent = JSON.stringify(data, null, 2); - highlightJSON(); - - // Обновляем таблицу - if (elements.tableView.style.display === 'block') { - displayTable(data.table_data); - } - - // Обновляем глобальные данные - jsonData = JSON.stringify(data); - - console.log(`Удалено ${emptyColumnIndices.length} пустых колонок:`, columnsToDelete); - showNotification(`Удалено ${emptyColumnIndices.length} пустых колонок`, 'success'); - - } catch (error) { - console.error('Ошибка при удалении пустых колонок:', error); - showNotification('Ошибка при удалении пустых колонок', 'error'); - } -} - -function analyzeColumns() { - const table = document.getElementById('dataTable'); - if (!table) return null; - - const headers = table.querySelectorAll('#tableHeader th'); - const rows = table.querySelectorAll('#tableBody tr'); - - const columnAnalysis = []; - - // Проверяем каждую колонку - for (let colIndex = 0; colIndex < headers.length; colIndex++) { - const header = headers[colIndex]; - const columnName = header.textContent.trim(); - - let totalCells = 0; - let emptyCells = 0; - let nonEmptyCells = 0; - - // Анализируем данные в колонке - for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) { - const cells = rows[rowIndex].querySelectorAll('td'); - if (colIndex < cells.length) { - totalCells++; - const value = cells[colIndex].textContent.trim(); - if (value && value !== '—' && value !== '') { - nonEmptyCells++; - } else { - emptyCells++; - } - } - } - - columnAnalysis.push({ - index: colIndex, - name: columnName, - totalCells: totalCells, - emptyCells: emptyCells, - nonEmptyCells: nonEmptyCells, - isEmpty: nonEmptyCells === 0, - emptyPercentage: totalCells > 0 ? (emptyCells / totalCells * 100).toFixed(1) : 100 - }); - } - - console.log('Анализ колонок:', columnAnalysis); - return columnAnalysis; -} - -function showColumnStats() { - const analysis = analyzeColumns(); - if (!analysis) return; - - let message = '📊 Статистика колонок:\n\n'; - - analysis.forEach((col, index) => { - if (index === 0) return; // Пропускаем characteristic - - const status = col.isEmpty ? '🔴 ПУСТАЯ' : '🟢 С данными'; - message += `${index}. ${col.name}: ${status}\n`; - message += ` Заполнено: ${col.nonEmptyCells}/${col.totalCells} (${col.emptyPercentage}% пустых)\n`; - message += ` ${col.isEmpty ? '✅ Может быть удалена' : '❌ Не может быть удалена'}\n\n`; - }); - - // Подсчитываем пустые колонки - const emptyCols = analysis.filter(col => col.index > 0 && col.isEmpty); - const totalCols = analysis.length - 1; // Минус characteristic - - message += `\n📈 Итоги:\n`; - message += `Всего колонок: ${totalCols}\n`; - message += `Пустых колонок: ${emptyCols.length}\n`; - message += `Процент пустых: ${(emptyCols.length / totalCols * 100).toFixed(1)}%\n`; - - alert(message); - - if (emptyCols.length > 0) { - const colNames = emptyCols.map(col => col.name).join(', '); - console.log('Пустые колонки для удаления:', colNames); - - if (confirm(`Найдено ${emptyCols.length} пустых колонок. Удалить их?`)) { - deleteEmptyColumns(); - } - } -} - -function addStatsButton() { - const controls = document.getElementById('tableControls'); - if (!controls) return; - - const statsBtn = document.createElement('button'); - statsBtn.className = 'btn btn-small btn-info'; - statsBtn.id = 'showStatsBtn'; - statsBtn.innerHTML = ' Статистика'; - statsBtn.title = 'Показать статистику колонок и строк'; - statsBtn.onclick = showColumnStats; - - const columnGroup = controls.querySelector('.control-group'); - if (columnGroup) { - columnGroup.appendChild(statsBtn); - } -} - -function deleteEmptyColumnsDOM() { - const table = document.getElementById('dataTable'); - if (!table) { - showNotification('Таблица не найдена', 'warning'); - return; - } - - const headers = table.querySelectorAll('#tableHeader th'); - const rows = table.querySelectorAll('#tableBody tr'); - - if (rows.length === 0) { - showNotification('Таблица пуста', 'warning'); - return; - } - - // Находим пустые колонки (кроме characteristic) - const emptyColumnIndices = []; - - for (let colIndex = 1; colIndex < headers.length; colIndex++) { - let isEmpty = true; - - for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) { - const cells = rows[rowIndex].querySelectorAll('td'); - if (colIndex < cells.length) { - const value = cells[colIndex].textContent.trim(); - if (value && value !== '—' && value !== '') { - isEmpty = false; - break; - } - } - } - - if (isEmpty) { - emptyColumnIndices.push(colIndex); - } - } - - if (emptyColumnIndices.length === 0) { - showNotification('Нет пустых колонок', 'info'); - return; - } - - if (!confirm(`Удалить ${emptyColumnIndices.length} пустых колонок?`)) { - return; - } - - try { - // Удаляем из DOM - // Заголовки - emptyColumnIndices.reverse().forEach(colIndex => { - const header = headers[colIndex]; - if (header && header.parentNode) { - header.parentNode.removeChild(header); - } - }); - - // Ячейки - rows.forEach(row => { - const cells = row.querySelectorAll('td'); - emptyColumnIndices.forEach(colIndex => { - if (colIndex < cells.length) { - const cell = cells[colIndex]; - if (cell && cell.parentNode) { - cell.parentNode.removeChild(cell); - } - } - }); - }); - - // Обновляем данные JSON - updateJSONFromTable(); - - showNotification(`Удалено ${emptyColumnIndices.length} пустых колонок`, 'success'); - - } catch (error) { - console.error('Ошибка при удалении колонок:', error); - showNotification('Ошибка при удалении колонок', 'error'); - } -} - -function updateJSONFromTable() { - const table = document.getElementById('dataTable'); - const headers = table.querySelectorAll('#tableHeader th'); - const rows = table.querySelectorAll('#tableBody tr'); - - const tableData = []; - - // Собираем данные из таблицы - rows.forEach(row => { - const cells = row.querySelectorAll('td'); - const rowData = {}; - - cells.forEach((cell, index) => { - if (index === 0) { - rowData.characteristic = cell.textContent.trim(); - } else if (index < headers.length) { - const headerText = headers[index].textContent; - const columnName = headerText.startsWith('Col ') ? - `column_${headerText.replace('Col ', '')}` : - headerText.toLowerCase(); - rowData[columnName] = cell.textContent.trim(); - } - }); - - tableData.push(rowData); - }); - - // Обновляем JSON - const data = { table_data: tableData }; - jsonData = JSON.stringify(data, null, 2); - elements.jsonOutput.textContent = jsonData; - highlightJSON(); -} - -function deleteEmptyColumnsSimple() { - console.log('Функция deleteEmptyColumnsSimple вызвана'); - - try { - // Получаем текущие данные - const jsonText = elements.jsonOutput.textContent; - const data = JSON.parse(jsonText); - - if (!data.table_data || !Array.isArray(data.table_data)) { - showNotification('Нет табличных данных', 'warning'); - return; - } - - if (data.table_data.length === 0) { - showNotification('Таблица пуста', 'warning'); - return; - } - - // Находим все уникальные колонки (кроме characteristic) - const allColumns = new Set(); - data.table_data.forEach(row => { - Object.keys(row).forEach(key => { - if (key !== 'characteristic') { - allColumns.add(key); - } - }); - }); - - // Находим пустые колонки - const emptyColumns = []; - allColumns.forEach(colName => { - let isEmpty = true; - - data.table_data.forEach(row => { - const value = row[colName]; - if (value !== undefined && value !== null && value !== '' && String(value).trim() !== '') { - isEmpty = false; - } - }); - - if (isEmpty) { - emptyColumns.push(colName); - } - }); - - if (emptyColumns.length === 0) { - showNotification('Нет пустых колонок для удаления', 'info'); - return; - } - - // Подтверждение - if (!confirm(`Удалить ${emptyColumns.length} пустых колонок?\n\n${emptyColumns.join(', ')}`)) { - return; - } - - // Удаляем пустые колонки - const cleanedData = data.table_data.map(row => { - const newRow = { characteristic: row.characteristic }; - - // Копируем только непустые колонки - Object.keys(row).forEach(key => { - if (key !== 'characteristic' && !emptyColumns.includes(key)) { - newRow[key] = row[key]; - } - }); - - return newRow; - }); - - // Обновляем данные - data.table_data = cleanedData; - - // Обновляем JSON - elements.jsonOutput.textContent = JSON.stringify(data, null, 2); - highlightJSON(); - - // Обновляем таблицу, если она видна - if (elements.tableView.style.display === 'block') { - displayTable(data.table_data); - } - - // Сохраняем глобальные данные - jsonData = JSON.stringify(data); - - showNotification(`Удалено ${emptyColumns.length} пустых колонок`, 'success'); - console.log('Удалены колонки:', emptyColumns); - - } catch (error) { - console.error('Ошибка при удалении пустых колонок:', error); - showNotification('Ошибка при удалении колонок: ' + error.message, 'error'); - } -} - -const buttonHandlers = { - // ... существующие обработчики - 'deleteEmptyColumnsBtn': deleteEmptyColumnsSimple, // или deleteEmptyColumns - // ... остальные обработчики -}; - -console.log('Кнопка удаления колонок:', document.getElementById('deleteEmptyColumnsBtn')); -console.log('Функция deleteEmptyColumnsSimple:', typeof deleteEmptyColumnsSimple); -document.getElementById('deleteEmptyColumnsBtn')?.click(); - -document.addEventListener('DOMContentLoaded', function() { - // Ждем полной загрузки страницы - setTimeout(function() { - const deleteBtn = document.getElementById('deleteEmptyColumnsBtn'); - if (deleteBtn) { - console.log('Кнопка "Удалить пустые колонки" найдена, добавляем прямой обработчик'); - - // Удаляем все старые обработчики - const newBtn = deleteBtn.cloneNode(true); - deleteBtn.parentNode.replaceChild(newBtn, deleteBtn); - - // Добавляем новый обработчик - document.getElementById('deleteEmptyColumnsBtn').addEventListener('click', function(e) { - e.preventDefault(); - e.stopPropagation(); - console.log('Кнопка "Удалить пустые колонки" нажата (прямой обработчик)'); - deleteEmptyColumnsSimple(); - }); - } - }, 2000); -}); - -// Запуск приложения -document.addEventListener('DOMContentLoaded', init); - -// Функции экспорта таблицы -function setupExportDropdown() { - const exportBtn = document.getElementById('exportDropdownBtn'); - const dropdown = document.getElementById('exportDropdown'); - - if (!exportBtn || !dropdown) return; - - // Показываем/скрываем dropdown - exportBtn.addEventListener('click', function(e) { - e.stopPropagation(); - dropdown.classList.toggle('show'); - }); - - // Закрываем dropdown при клике вне его - document.addEventListener('click', function(e) { - if (!dropdown.contains(e.target) && !exportBtn.contains(e.target)) { - dropdown.classList.remove('show'); - } - }); - - // Обработчики для кнопок экспорта - document.getElementById('exportExcelBtn')?.addEventListener('click', exportToExcel); - document.getElementById('exportPdfBtn')?.addEventListener('click', exportToPDF); - document.getElementById('exportCsvBtn')?.addEventListener('click', exportToCSV); - document.getElementById('exportJsonBtn')?.addEventListener('click', exportToJSON); -} - -function exportToExcel() { - try { - const data = JSON.parse(jsonData); - if (!data.table_data || !Array.isArray(data.table_data) || data.table_data.length === 0) { - throw new Error('Нет табличных данных для экспорта'); - } - - // Создаем новую книгу Excel - const wb = XLSX.utils.book_new(); - - // Подготавливаем данные - const wsData = []; - - // Заголовки - const headers = ['Characteristic']; - const firstRow = data.table_data[0]; - - // Получаем все колонки кроме characteristic - const columns = Object.keys(firstRow || {}).filter(key => key !== 'characteristic'); - - // Сортируем колонки по номеру - columns.sort((a, b) => { - const numA = parseInt(a.replace('column_', '')) || 0; - const numB = parseInt(b.replace('column_', '')) || 0; - return numA - numB; - }); - - headers.push(...columns); - wsData.push(headers); - - // Данные - data.table_data.forEach(row => { - const rowData = [row.characteristic || '']; - columns.forEach(col => { - rowData.push(row[col] || ''); - }); - wsData.push(rowData); - }); - - // Создаем worksheet - const ws = XLSX.utils.aoa_to_sheet(wsData); - - // Настраиваем ширину колонок - const colWidths = headers.map((header, index) => { - let maxLength = header.length; - data.table_data.forEach(row => { - const cellValue = index === 0 - ? String(row.characteristic || '') - : String(row[columns[index-1]] || ''); - maxLength = Math.max(maxLength, cellValue.length); - }); - return { wch: Math.min(Math.max(maxLength, 10), 50) }; - }); - ws['!cols'] = colWidths; - - // Добавляем стили (если нужно) - // Для более продвинутого форматирования можно использовать xlsx-style - - // Добавляем worksheet в книгу - XLSX.utils.book_append_sheet(wb, ws, 'Table Data'); - - // Генерируем файл - const fileName = `table_export_${new Date().toISOString().slice(0,10)}.xlsx`; - XLSX.writeFile(wb, fileName); - - showNotification('Таблица экспортирована в Excel', 'success'); - - } catch (error) { - console.error('Ошибка при экспорте в Excel:', error); - showNotification('Ошибка экспорта: ' + error.message, 'error'); - } -} - -function exportToPDF() { - try { - const data = JSON.parse(jsonData); - if (!data.table_data || !Array.isArray(data.table_data) || data.table_data.length === 0) { - throw new Error('Нет данных для экспорта'); - } - - // Импортируем jsPDF - const { jsPDF } = window.jspdf; - - // Создаем PDF с поддержкой кириллицы - const pdf = new jsPDF({ - orientation: 'landscape', - unit: 'mm', - format: 'a4' - }); - - // Добавляем поддержку кириллицы (стандартный шрифт поддерживает русские буквы) - pdf.setFont("helvetica"); - - const pageWidth = pdf.internal.pageSize.width; - const pageHeight = pdf.internal.pageSize.height; - const margin = 10; - let y = margin; - - // Заголовок - используем английский или простой текст - pdf.setFontSize(16); - pdf.setFont("helvetica", "bold"); - pdf.text("TABLE EXPORT", pageWidth / 2, y, { align: "center" }); - y += 8; - - // Информация на английском - pdf.setFontSize(10); - pdf.setFont("helvetica", "normal"); - const exportDate = new Date().toLocaleDateString('en-GB'); - pdf.text(`Export date: ${exportDate}`, margin, y); - pdf.text(`Total rows: ${data.table_data.length}`, pageWidth - margin, y, { align: "right" }); - y += 10; - - // Получаем все колонки из данных - const allColumns = new Set(); - data.table_data.forEach(row => { - Object.keys(row).forEach(key => { - if (key !== 'characteristic') { - allColumns.add(key); - } - }); - }); - - // Сортируем колонки по номеру - const sortedColumns = Array.from(allColumns).sort((a, b) => { - const numA = parseInt(a.replace('column_', '')) || 0; - const numB = parseInt(b.replace('column_', '')) || 0; - return numA - numB; - }); - - // Ограничиваем количество колонок для читаемости - const maxColumns = Math.min(sortedColumns.length, 15); - const displayColumns = sortedColumns.slice(0, maxColumns); - - // Рассчитываем ширину колонок - const tableWidth = pageWidth - (2 * margin); - const charColWidth = 35; // Ширина для характеристики - const dataColWidth = (tableWidth - charColWidth - 10) / Math.max(displayColumns.length, 1); - - // Заголовки таблицы (на английском) - const headers = ['#', 'Characteristic', ...displayColumns.map(col => - `Col ${col.replace('column_', '')}` - )]; - - // Подготавливаем данные - const tableData = []; - const maxRows = Math.min(data.table_data.length, 100); - - for (let i = 0; i < maxRows; i++) { - const row = data.table_data[i]; - const rowData = [ - (i + 1).toString(), - row.characteristic || '', - ...displayColumns.map(col => row[col] || '') - ]; - tableData.push(rowData); - } - - // Стили для заголовков - pdf.setFillColor(44, 62, 80); // Темно-синий - pdf.setTextColor(255, 255, 255); - pdf.setFontSize(8); - pdf.setFont("helvetica", "bold"); - - // Рисуем заголовки таблицы - let x = margin; - const headerHeight = 6; - - // Номер - pdf.rect(x, y, 10, headerHeight, 'F'); - pdf.text('#', x + 3, y + 4); - x += 10; - - // Характеристика - pdf.rect(x, y, charColWidth, headerHeight, 'F'); - pdf.text('Characteristic', x + 2, y + 4); - x += charColWidth; - - // Колонки - displayColumns.forEach((col, index) => { - pdf.rect(x, y, dataColWidth, headerHeight, 'F'); - const colNum = col.replace('column_', ''); - pdf.text(colNum, x + dataColWidth/2, y + 4, { align: 'center' }); - x += dataColWidth; - }); - - y += headerHeight; - - // Рисуем данные - pdf.setFont("helvetica", "normal"); - pdf.setFontSize(7); - pdf.setTextColor(0, 0, 0); - - const rowHeight = 5; - - tableData.forEach((row, rowIndex) => { - // Проверяем, нужна ли новая страница - if (y + rowHeight > pageHeight - margin - 10) { - pdf.addPage(); - y = margin; - - // Рисуем заголовки на новой странице - x = margin; - pdf.setFillColor(44, 62, 80); - pdf.setTextColor(255, 255, 255); - pdf.setFont("helvetica", "bold"); - - // Номер - pdf.rect(x, y, 10, headerHeight, 'F'); - pdf.text('#', x + 3, y + 4); - x += 10; - - // Характеристика - pdf.rect(x, y, charColWidth, headerHeight, 'F'); - pdf.text('Characteristic', x + 2, y + 4); - x += charColWidth; - - // Колонки - displayColumns.forEach((col, index) => { - pdf.rect(x, y, dataColWidth, headerHeight, 'F'); - const colNum = col.replace('column_', ''); - pdf.text(colNum, x + dataColWidth/2, y + 4, { align: 'center' }); - x += dataColWidth; - }); - - y += headerHeight; - - // Возвращаем стили для данных - pdf.setFont("helvetica", "normal"); - pdf.setTextColor(0, 0, 0); - } - - // Рисуем строку - x = margin; - - // Чередующийся цвет фона - if (rowIndex % 2 === 0) { - pdf.setFillColor(248, 249, 250); // Светло-серый - let fillX = margin; - - // Номер - pdf.rect(fillX, y, 10, rowHeight, 'F'); - fillX += 10; - - // Характеристика - pdf.rect(fillX, y, charColWidth, rowHeight, 'F'); - fillX += charColWidth; - - // Колонки - displayColumns.forEach(() => { - pdf.rect(fillX, y, dataColWidth, rowHeight, 'F'); - fillX += dataColWidth; - }); - } - - // Номер строки - pdf.text(row[0], x + 3, y + 3.5, { align: 'center' }); - x += 10; - - // Характеристика (обрезаем если слишком длинная) - const characteristic = row[1] || ''; - const maxCharLength = 30; - const displayChar = characteristic.length > maxCharLength - ? characteristic.substring(0, maxCharLength - 3) + '...' - : characteristic; - pdf.text(displayChar, x + 2, y + 3.5); - x += charColWidth; - - // Значения колонок - row.slice(2).forEach((cell, cellIndex) => { - const cellValue = String(cell || ''); - - // Устанавливаем цвет в зависимости от значения - if (cellValue === '+') { - pdf.setTextColor(16, 185, 129); // Green - } else if (cellValue === '-') { - pdf.setTextColor(239, 68, 68); // Red - } else if (cellValue === '?') { - pdf.setTextColor(245, 158, 11); // Orange - } else if (cellValue.toUpperCase() === 'W') { - pdf.setTextColor(139, 92, 246); // Purple - } else if (cellValue.toUpperCase() === 'ND') { - pdf.setTextColor(156, 163, 175); // Gray - } else { - pdf.setTextColor(0, 0, 0); // Black - } - - // Центрируем текст - pdf.text(cellValue, x + dataColWidth/2, y + 3.5, { align: 'center' }); - x += dataColWidth; - - // Сбрасываем цвет - pdf.setTextColor(0, 0, 0); - }); - - y += rowHeight; - }); - - // Легенда на английском - y = pageHeight - margin - 10; - pdf.setFontSize(7); - pdf.setTextColor(102, 102, 102); - pdf.text("Legend: + (positive) - (negative) ? (unknown) W (weak positive) ND (no data)", - margin, y); - - // Номер страницы - pdf.text("Page 1", pageWidth - margin, pageHeight - margin, { align: "right" }); - - // Сохраняем PDF - const fileName = `table_export_${new Date().toISOString().slice(0, 10)}.pdf`; - pdf.save(fileName); - - showNotification('PDF successfully created', 'success'); - - } catch (error) { - console.error('Error creating PDF:', error); - showNotification('Error: ' + error.message, 'error'); - } -} - -// Функция для рисования таблицы -function drawTable(pdf, tableData, headers, options) { - const { - startX, - startY, - tableWidth, - charColWidth, - dataColWidth, - headerStyle, - cellStyle, - margin, - pageHeight - } = options; - - const colCount = headers.length; - const rowHeight = 6; - let x = startX; - let y = startY; - let currentPage = 1; - - // Рисуем заголовки - pdf.setFillColor(...headerStyle.fillColor); - pdf.setTextColor(...headerStyle.textColor); - pdf.setFont("helvetica", headerStyle.fontStyle); - pdf.setFontSize(headerStyle.fontSize); - - headers.forEach((header, colIndex) => { - // Рассчитываем ширину колонки - let colWidth; - if (colIndex === 0) { - colWidth = 8; // Для номера - } else if (colIndex === 1) { - colWidth = charColWidth; // Для характеристики - } else { - colWidth = dataColWidth; // Для остальных колонок - } - - // Рисуем ячейку заголовка - pdf.rect(x, y, colWidth, rowHeight, 'F'); - - // Текст заголовка - const text = colIndex === 0 ? '№' : - colIndex === 1 ? 'Characteristic' : - header; - - const maxTextWidth = colWidth - 4; // Минус отступы - const textWidth = pdf.getTextWidth(text); - let displayText = text; - - if (textWidth > maxTextWidth) { - // Укорачиваем текст если не помещается - for (let i = text.length - 1; i > 0; i--) { - const shortText = text.substring(0, i) + '...'; - if (pdf.getTextWidth(shortText) <= maxTextWidth) { - displayText = shortText; - break; - } - } - } - - pdf.text(displayText, x + 2, y + rowHeight - 2); - x += colWidth; - }); - - y += rowHeight; - - // Рисуем данные - pdf.setFont("helvetica", "normal"); - pdf.setFontSize(cellStyle.fontSize); - pdf.setTextColor(...cellStyle.textColor); - - tableData.forEach((row, rowIndex) => { - // Проверяем, нужна ли новая страница - if (y + rowHeight > pageHeight - margin - 10) { - pdf.addPage(); - currentPage++; - y = margin; - - // Повторяем заголовки на новой странице - x = startX; - pdf.setFillColor(...headerStyle.fillColor); - pdf.setTextColor(...headerStyle.textColor); - pdf.setFont("helvetica", headerStyle.fontStyle); - pdf.setFontSize(headerStyle.fontSize); - - headers.forEach((header, colIndex) => { - let colWidth; - if (colIndex === 0) { - colWidth = 8; - } else if (colIndex === 1) { - colWidth = charColWidth; - } else { - colWidth = dataColWidth; - } - - pdf.rect(x, y, colWidth, rowHeight, 'F'); - - const text = colIndex === 0 ? '№' : - colIndex === 1 ? 'Characteristic' : - header; - - const maxTextWidth = colWidth - 4; - const textWidth = pdf.getTextWidth(text); - let displayText = text; - - if (textWidth > maxTextWidth) { - for (let i = text.length - 1; i > 0; i--) { - const shortText = text.substring(0, i) + '...'; - if (pdf.getTextWidth(shortText) <= maxTextWidth) { - displayText = shortText; - break; - } - } - } - - pdf.text(displayText, x + 2, y + rowHeight - 2); - x += colWidth; - }); - - y += rowHeight; - } - - // Рисуем строку данных - x = startX; - - row.forEach((cell, colIndex) => { - // Рассчитываем ширину колонки - let colWidth; - if (colIndex === 0) { - colWidth = 8; - } else if (colIndex === 1) { - colWidth = charColWidth; - } else { - colWidth = dataColWidth; - } - - // Чередуем цвет фона строк - if (rowIndex % 2 === 0) { - pdf.setFillColor(248, 249, 250); - pdf.rect(x, y, colWidth, rowHeight, 'F'); - } - - // Рисуем границу - pdf.setDrawColor(200, 200, 200); - pdf.rect(x, y, colWidth, rowHeight); - - // Устанавливаем цвет текста в зависимости от значения - const cellValue = String(cell || ''); - if (cellValue === '+') { - pdf.setTextColor(16, 185, 129); // Зеленый - } else if (cellValue === '-') { - pdf.setTextColor(239, 68, 68); // Красный - } else if (cellValue === '?') { - pdf.setTextColor(245, 158, 11); // Оранжевый - } else if (cellValue.toUpperCase() === 'W') { - pdf.setTextColor(139, 92, 246); // Фиолетовый - } else if (cellValue.toUpperCase() === 'ND') { - pdf.setTextColor(156, 163, 175); // Серый - } else { - pdf.setTextColor(0, 0, 0); // Черный - } - - // Отображаем текст с переносом - const maxTextWidth = colWidth - 4; - let displayText = cellValue; - - // Если текст слишком длинный, обрезаем его - if (pdf.getTextWidth(cellValue) > maxTextWidth) { - for (let i = cellValue.length - 1; i > 0; i--) { - const shortText = cellValue.substring(0, i); - if (pdf.getTextWidth(shortText) <= maxTextWidth) { - displayText = shortText; - break; - } - } - } - - // Центрируем текст для числовых колонок, левый край для характеристик - const textX = colIndex <= 1 ? x + 2 : x + (colWidth / 2); - const align = colIndex <= 1 ? 'left' : 'center'; - - pdf.text(displayText, textX, y + rowHeight - 2, { align: align }); - - x += colWidth; - }); - - y += rowHeight; - - // Сбрасываем цвет текста - pdf.setTextColor(0, 0, 0); - }); - - // Информация о страницах - pdf.setFontSize(7); - pdf.setTextColor(102, 102, 102); - pdf.text(`Страница ${currentPage}`, margin, pageHeight - margin); -} - -// Альтернативная упрощенная версия для больших таблиц -function exportToPDFSimple() { - try { - const data = JSON.parse(jsonData); - if (!data.table_data || !Array.isArray(data.table_data) || data.table_data.length === 0) { - throw new Error('Нет данных для экспорта'); - } - - // Используем jsPDF autotable если доступен - if (typeof pdf.autoTable !== 'undefined') { - exportWithAutoTable(data); - return; - } - - const { jsPDF } = window.jspdf; - const pdf = new jsPDF('l', 'mm', 'a4'); - - const margin = 15; - let y = margin; - - // Заголовок - pdf.setFontSize(18); - pdf.setFont("helvetica", "bold"); - pdf.text("ЭКСПОРТ ТАБЛИЦЫ", 148.5, y, { align: "center" }); - y += 8; - - pdf.setFontSize(10); - pdf.setFont("helvetica", "normal"); - pdf.text(`Экспортировано: ${new Date().toLocaleString()}`, margin, y); - pdf.text(`Строк: ${data.table_data.length}`, 280, y, { align: "right" }); - y += 15; - - // Получаем колонки - const columns = []; - const sampleRow = data.table_data[0]; - Object.keys(sampleRow).forEach(key => { - if (key !== 'characteristic') { - const colNum = parseInt(key.replace('column_', '')) || 0; - columns.push({ key, num: colNum }); - } - }); - - columns.sort((a, b) => a.num - b.num); - const displayColumns = columns.slice(0, 15); // Ограничиваем 15 колонками - - // Создаем заголовки - const headers = ['№', 'Characteristic', ...displayColumns.map(col => `C${col.num}`)]; - - // Создаем данные - const tableData = []; - const maxRows = Math.min(data.table_data.length, 100); - - for (let i = 0; i < maxRows; i++) { - const row = data.table_data[i]; - const rowData = [ - (i + 1).toString(), - row.characteristic || '', - ...displayColumns.map(col => row[col.key] || '') - ]; - tableData.push(rowData); - } - - // Простая таблица без сложной логики - const colWidths = [10, 40, ...Array(displayColumns.length).fill(15)]; - const rowHeight = 6; - - // Заголовки - pdf.setFillColor(44, 62, 80); - pdf.setTextColor(255, 255, 255); - pdf.setFontSize(9); - - let x = margin; - headers.forEach((header, i) => { - pdf.rect(x, y, colWidths[i], rowHeight, 'F'); - pdf.text(header.substring(0, 8), x + 2, y + 4); - x += colWidths[i]; - }); - y += rowHeight; - - // Данные - pdf.setFontSize(8); - pdf.setTextColor(0, 0, 0); - - tableData.forEach((row, rowIndex) => { - if (y > 190) { // Конец страницы - pdf.addPage(); - y = margin; - - // Повторяем заголовки - x = margin; - pdf.setFillColor(44, 62, 80); - pdf.setTextColor(255, 255, 255); - headers.forEach((header, i) => { - pdf.rect(x, y, colWidths[i], rowHeight, 'F'); - pdf.text(header.substring(0, 8), x + 2, y + 4); - x += colWidths[i]; - }); - y += rowHeight; - - pdf.setFontSize(8); - pdf.setTextColor(0, 0, 0); - } - - // Цвет фона для четных строк - if (rowIndex % 2 === 0) { - x = margin; - pdf.setFillColor(248, 249, 250); - colWidths.forEach(width => { - pdf.rect(x, y, width, rowHeight, 'F'); - x += width; - }); - } - - // Текст - x = margin; - row.forEach((cell, cellIndex) => { - // Форматирование значений - let displayCell = String(cell || ''); - if (displayCell.length > 8) { - displayCell = displayCell.substring(0, 7) + '...'; - } - - pdf.text(displayCell, - cellIndex === 1 ? x + 2 : x + colWidths[cellIndex] / 2, - y + 4, - { align: cellIndex === 1 ? 'left' : 'center' } - ); - x += colWidths[cellIndex]; - }); - - y += rowHeight; - }); - - // Сохраняем - pdf.save(`table_${Date.now()}.pdf`); - showNotification('PDF успешно создан', 'success'); - - } catch (error) { - console.error('Ошибка при создании PDF:', error); - showNotification('Ошибка: ' + error.message, 'error'); - } -} - -// Если у вас есть autotable, используйте эту функцию -function exportWithAutoTable(data) { - const { jsPDF } = window.jspdf; - const pdf = new jsPDF('l', 'mm', 'a4'); - - // Получаем колонки - const columns = []; - const sampleRow = data.table_data[0]; - Object.keys(sampleRow).forEach(key => { - if (key !== 'characteristic') { - const colNum = parseInt(key.replace('column_', '')) || 0; - columns.push({ key, num: colNum, name: `C${colNum}` }); - } - }); - - columns.sort((a, b) => a.num - b.num); - const displayColumns = columns.slice(0, 20); - - // Заголовки - const headers = ['№', 'Characteristic', ...displayColumns.map(col => col.name)]; - - // Данные - const tableData = []; - const maxRows = Math.min(data.table_data.length, 200); - - for (let i = 0; i < maxRows; i++) { - const row = data.table_data[i]; - const rowData = [ - (i + 1).toString(), - row.characteristic || '', - ...displayColumns.map(col => row[col.key] || '') - ]; - tableData.push(rowData); - } - - // Создаем таблицу - pdf.autoTable({ - head: [headers], - body: tableData, - startY: 20, - theme: 'grid', - styles: { - fontSize: 7, - cellPadding: 2, - overflow: 'linebreak', - lineColor: [200, 200, 200], - lineWidth: 0.1 - }, - headStyles: { - fillColor: [44, 62, 80], - textColor: [255, 255, 255], - fontStyle: 'bold', - fontSize: 8 - }, - alternateRowStyles: { - fillColor: [248, 249, 250] - }, - columnStyles: { - 0: { cellWidth: 10, halign: 'center' }, - 1: { cellWidth: 40, halign: 'left' } - }, - margin: { top: 20 }, - didDrawPage: function(data) { - // Заголовок на каждой странице - pdf.setFontSize(10); - pdf.text(`Страница ${data.pageNumber}`, data.settings.margin.left, 10); - } - }); - - // Сохраняем - pdf.save(`table_autotable_${Date.now()}.pdf`); - showNotification('PDF с использованием AutoTable создан', 'success'); -} - -// Альтернативная функция для создания простого PDF -function createSimplePDF(data) { - try { - const pdf = new window.jspdf.jsPDF({ - orientation: 'landscape', - unit: 'mm', - format: 'a4' - }); - - // Заголовок - pdf.setFontSize(16); - pdf.setTextColor(44, 62, 80); - pdf.text('Экспорт таблицы', 20, 20); - - pdf.setFontSize(10); - pdf.setTextColor(127, 140, 141); - pdf.text(`Создано: ${new Date().toLocaleString()}`, 20, 30); - - // Получаем колонки - const columns = new Set(); - data.table_data.forEach(row => { - Object.keys(row).forEach(key => { - if (key !== 'characteristic') { - columns.add(key); - } - }); - }); - - const sortedColumns = Array.from(columns).sort((a, b) => { - const numA = parseInt(a.replace('column_', '')) || 0; - const numB = parseInt(b.replace('column_', '')) || 0; - return numA - numB; - }); - - // Ограничиваем количество колонок для читаемости - const displayColumns = sortedColumns.slice(0, 15); - const colWidth = 180 / (displayColumns.length + 1); // мм на колонку - - // Заголовки таблицы - pdf.setFillColor(44, 62, 80); - pdf.setTextColor(255, 255, 255); - pdf.rect(20, 40, 180, 8, 'F'); - - pdf.setFontSize(9); - pdf.text('Characteristic', 22, 46); - - displayColumns.forEach((col, index) => { - pdf.text(`Col ${col.replace('column_', '')}`, 22 + (colWidth * (index + 1)), 46); - }); - - // Данные таблицы (ограничиваем количество строк) - const maxRows = Math.min(data.table_data.length, 50); - pdf.setFontSize(8); - pdf.setTextColor(0, 0, 0); - - for (let i = 0; i < maxRows; i++) { - const row = data.table_data[i]; - const yPos = 50 + (i * 5); - - // Рисуем строку - if (i % 2 === 0) { - pdf.setFillColor(248, 249, 250); - pdf.rect(20, yPos - 4, 180, 5, 'F'); - } - - // Характеристика - const characteristic = row.characteristic || ''; - pdf.text(characteristic.substring(0, 30), 22, yPos); - - // Значения колонок - displayColumns.forEach((col, index) => { - const value = row[col] || ''; - pdf.text(value, 22 + (colWidth * (index + 1)), yPos); - }); - } - - // Информация - pdf.setFontSize(8); - pdf.setTextColor(102, 102, 102); - pdf.text(`Показано ${maxRows} из ${data.table_data.length} строк`, 20, 50 + (maxRows * 5) + 10); - - // Сохраняем PDF - const fileName = `table_simple_${new Date().toISOString().slice(0,10)}.pdf`; - pdf.save(fileName); - - showNotification('Таблица экспортирована в упрощенный PDF', 'info'); - - } catch (error) { - console.error('Ошибка при создании простого PDF:', error); - showNotification('Не удалось создать PDF файл', 'error'); - } -} - -function exportToCSV() { - try { - const data = JSON.parse(jsonData); - if (!data.table_data || !Array.isArray(data.table_data)) { - throw new Error('Нет табличных данных для экспорта'); - } - - // Создаем CSV - let csv = ''; - - // Находим все колонки - const allColumns = new Set(); - data.table_data.forEach(row => { - Object.keys(row).forEach(key => { - if (key !== 'characteristic') { - allColumns.add(key); - } - }); - }); - - // Сортируем колонки - const sortedColumns = Array.from(allColumns).sort((a, b) => { - const numA = parseInt(a.replace('column_', '')) || 0; - const numB = parseInt(b.replace('column_', '')) || 0; - return numA - numB; - }); - - // Заголовки - const headers = ['Characteristic', ...sortedColumns]; - csv += headers.map(h => `"${h}"`).join(',') + '\n'; - - // Данные - data.table_data.forEach(row => { - const rowData = [row.characteristic || '']; - sortedColumns.forEach(col => { - rowData.push(row[col] || ''); - }); - csv += rowData.map(cell => `"${String(cell).replace(/"/g, '""')}"`).join(',') + '\n'; - }); - - // Создаем и скачиваем файл - const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); - const link = document.createElement('a'); - const url = URL.createObjectURL(blob); - - link.setAttribute('href', url); - link.setAttribute('download', `table_export_${new Date().toISOString().slice(0,10)}.csv`); - link.style.visibility = 'hidden'; - - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - - showNotification('Таблица экспортирована в CSV', 'success'); - - } catch (error) { - console.error('Ошибка при экспорте в CSV:', error); - showNotification('Ошибка экспорта: ' + error.message, 'error'); - } -} - -function exportToJSON() { - try { - if (!jsonData) { - throw new Error('Нет данных для экспорта'); - } - - const data = JSON.parse(jsonData); - const formattedData = JSON.stringify(data, null, 2); - - const blob = new Blob([formattedData], { type: 'application/json' }); - const link = document.createElement('a'); - const url = URL.createObjectURL(blob); - - link.setAttribute('href', url); - link.setAttribute('download', `table_export_${new Date().toISOString().slice(0,10)}.json`); - link.style.visibility = 'hidden'; - - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - - showNotification('Таблица экспортирована в JSON', 'success'); - - } catch (error) { - console.error('Ошибка при экспорте в JSON:', error); - showNotification('Ошибка экспорта: ' + error.message, 'error'); - } -} - -// Улучшенная функция для Excel с форматированием -function exportToExcelWithFormatting() { - try { - const data = JSON.parse(jsonData); - if (!data.table_data || !Array.isArray(data.table_data)) { - throw new Error('Нет табличных данных для экспорта'); - } - - // Создаем новую книгу - const wb = XLSX.utils.book_new(); - - // Подготавливаем данные - const wsData = []; - - // Заголовки - const headers = ['№', 'Characteristic']; - const columns = []; - - // Находим все колонки - data.table_data.forEach(row => { - Object.keys(row).forEach(key => { - if (key !== 'characteristic' && !columns.includes(key)) { - columns.push(key); - } - }); - }); - - // Сортируем колонки - columns.sort((a, b) => { - const numA = parseInt(a.replace('column_', '')) || 0; - const numB = parseInt(b.replace('column_', '')) || 0; - return numA - numB; - }); - - headers.push(...columns); - wsData.push(headers); - - // Данные с номерами строк - data.table_data.forEach((row, index) => { - const rowData = [index + 1, row.characteristic || '']; - columns.forEach(col => { - rowData.push(row[col] || ''); - }); - wsData.push(rowData); - }); - - // Создаем worksheet - const ws = XLSX.utils.aoa_to_sheet(wsData); - - // Автоматическая ширина колонок - const colWidths = headers.map((header, idx) => { - let maxLength = header.length; - wsData.forEach((row, rowIdx) => { - if (rowIdx > 0) { // Пропускаем заголовки - const cellValue = String(row[idx] || ''); - maxLength = Math.max(maxLength, cellValue.length); - } - }); - return { wch: Math.min(Math.max(maxLength + 2, 10), 30) }; - }); - - ws['!cols'] = colWidths; - - // Добавляем фильтры - ws['!autofilter'] = { ref: XLSX.utils.encode_range({ - s: { r: 0, c: 0 }, - e: { r: wsData.length - 1, c: headers.length - 1 } - })}; - - // Добавляем закрепление первой строки - ws['!freeze'] = { xSplit: 0, ySplit: 1, topLeftCell: 'A2', activePane: 'bottomRight' }; - - // Добавляем worksheet в книгу - XLSX.utils.book_append_sheet(wb, ws, 'Table Data'); - - // Добавляем информационный лист - const infoData = [ - ['Информация об экспорте'], - [''], - ['Дата экспорта:', new Date().toLocaleString()], - ['Количество строк:', data.table_data.length], - ['Количество колонок:', columns.length + 1], - [''], - ['Обозначения:'], - ['+', 'Положительный результат'], - ['-', 'Отрицательный результат'], - ['?', 'Неопределенный результат'], - ['W', 'Слабоположительный'], - ['ND', 'Нет данных'] - ]; - - const infoWs = XLSX.utils.aoa_to_sheet(infoData); - infoWs['!cols'] = [{ wch: 20 }, { wch: 40 }]; - XLSX.utils.book_append_sheet(wb, infoWs, 'Информация'); - - // Сохраняем файл - const fileName = `table_${new Date().toISOString().slice(0,10)}.xlsx`; - XLSX.writeFile(wb, fileName); - - showNotification('Файл Excel успешно создан', 'success'); - - } catch (error) { - console.error('Ошибка при создании Excel файла:', error); - showNotification('Ошибка: ' + error.message, 'error'); - } -} - -// Функция для включения/выключения кнопок экспорта -function updateExportButtons() { - const exportBtn = document.getElementById('exportDropdownBtn'); - if (!exportBtn) return; - - // Проверяем, есть ли данные для экспорта - const hasData = jsonData && jsonData !== '{"table_data": []}' && jsonData !== ''; - - console.log('updateExportButtons вызвана. hasData:', hasData, 'jsonData:', jsonData?.substring(0, 100)); - - // Включаем/выключаем кнопку - exportBtn.disabled = !hasData; - - // Добавляем визуальную обратную связь - if (hasData) { - exportBtn.title = 'Экспорт табличных данных'; - exportBtn.classList.remove('disabled'); - } else { - exportBtn.title = 'Нет данных для экспорта'; - exportBtn.classList.add('disabled'); - } -} - -// Обновляем функцию displayJSON для включения кнопок экспорта -const originalDisplayJSON = displayJSON; -displayJSON = function(jsonStr) { - originalDisplayJSON(jsonStr); - updateExportButtons(); -}; - -function forceEnableExport() { - const exportBtn = document.getElementById('exportDropdownBtn'); - if (exportBtn) { - exportBtn.disabled = false; - console.log('Кнопка экспорта принудительно включена'); - } -} - -// Добавьте эту функцию в обработчик клика по кнопке редактирования -if (document.getElementById('editTableBtn')) { - document.getElementById('editTableBtn').addEventListener('click', function() { - // Включаем кнопку экспорта при входе в режим редактирования - forceEnableExport(); - toggleEditMode(); - }); -} \ No newline at end of file