Отзывы и предложения к софту от AleXStam
  • Страница 1 из 2
  • 1
  • 2
  • »
Поговорим о...
1
Ллл
Прикрепления:
lll.noext (64.5 Kb)
98
Прикрепления:
geoscan_2_56.zip (90.9 Kb)
Ggg
Прикрепления:
oooooo.noext (72.7 Kb)
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Oracle Query Tool</title>
<style>
* { box-sizing: border-box; }
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 20px;
background: #f5f5f5;
}
.container {
max-width: 1400px;
margin: 0 auto;
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
border-bottom: 2px solid #007acc;
padding-bottom: 10px;
margin-top: 0;
}
.query-section {
margin-bottom: 30px;
}
textarea {
width: 100%;
height: 150px;
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
font-family: 'Consolas', monospace;
font-size: 14px;
resize: vertical;
margin: 10px 0;
}
.btn {
background: #007acc;
color: white;
border: none;
padding: 12px 30px;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
margin-right: 10px;
}
.btn:hover { background: #005a9e; }
.btn-secondary { background: #6c757d; }
.btn-secondary:hover { background: #545b62; }
.results {
margin-top: 30px;
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
th {
background: #007acc;
color: white;
padding: 12px;
text-align: left;
position: sticky;
top: 0;
}
td {
padding: 10px 12px;
border-bottom: 1px solid #eee;
}
tr:hover { background: #f9f9f9; }
.status {
padding: 10px;
border-radius: 5px;
margin: 10px 0;
}
.success { background: #d4edda; color: #155724; }
.error { background: #f8d7da; color: #721c24; }
.loading {
display: none;
text-align: center;
margin: 20px 0;
}
.loading.active { display: block; }
.spinner {
border: 3px solid #f3f3f3;
border-top: 3px solid #007acc;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.filters {
margin: 20px 0;
padding: 15px;
background: #f8f9fa;
border-radius: 5px;
}
.filter-input {
padding: 8px;
margin: 0 10px 10px 0;
border: 1px solid #ddd;
border-radius: 3px;
}
.row-count {
margin: 15px 0;
color: #666;
font-style: italic;
}
</style>
</head>
<body>
<div class="container">
<h1>📊 Oracle Database Query Tool (Read Only)</h1>

<div class="query-section">
<label for="sqlQuery"><strong>SQL запрос:</strong></label>
<textarea id="sqlQuery" placeholder="SELECT * FROM employees WHERE department_id = 10">
-- Примеры запросов:
-- SELECT * FROM employees WHERE rownum <= 100
-- SELECT product_name, price FROM products ORDER BY price DESC
-- SELECT department, COUNT(*) as count FROM employees GROUP BY department
</textarea>

<div>
<button class="btn" onclick="executeQuery()">▶ Выполнить запрос</button>
<button class="btn btn-secondary" onclick="clearResults()">🗑 Очистить результаты</button>
<button class="btn btn-secondary" onclick="exportToCSV()">📥 Экспорт в CSV</button>
<button class="btn btn-secondary" onclick="showPredefinedQueries()">📋 Примеры запросов</button>
</div>
</div>

<div id="status" class="status"></div>

<div id="loading" class="loading">
<div class="spinner"></div>
<p>Выполнение запроса...</p>
</div>

<div class="filters" style="display:none;" id="filterSection">
<strong>Фильтрация результатов:</strong><br>
<input type="text" class="filter-input" id="globalFilter" placeholder="Поиск по всем столбцам..." onkeyup="filterTable()">
</div>

<div class="row-count" id="rowCount"></div>

<div class="results" id="results"></div>
</div>

<script>
// КОНФИГУРАЦИЯ
const config = {
// СПОСОБ 1: Oracle REST Data Services (ORDS)
useORDS: true,
ordsEndpoint: "https://your-company-server/ords/hr/_/sql", // Уточните у DBA адрес ORDS

// СПОСОБ 2: Веб-сервис компании (если есть)
useWebService: false,
webServiceUrl: "http://internal-api/query",

// СПОСОБ 3: Если есть CORS прокси
useProxy: false,
proxyUrl: "/api/proxy"
};

// Примеры готовых запросов
const predefinedQueries = {
employees: "SELECT employee_id, first_name, last_name, email, hire_date FROM employees WHERE rownum <= 50",
departments: "SELECT department_id, department_name, manager_id FROM departments ORDER BY department_name",
products: "SELECT product_id, product_name, list_price, category_id FROM products WHERE list_price > 0 ORDER BY list_price DESC",
simple: "SELECT username, created FROM all_users WHERE rownum <= 20"
};

function executeQuery() {
const sql = document.getElementById('sqlQuery').value.trim();
if (!sql) {
showStatus('Введите SQL запрос', 'error');
return;
}

// Проверка на опасные операции
if (isDangerousQuery(sql)) {
showStatus('Запрещена операция записи/изменения данных', 'error');
return;
}

showLoading(true);
clearStatus();

// ВАЖНО: Выберите один из способов ниже

// Способ 1: ORDS (если настроен в компании)
if (config.useORDS) {
executeViaORDS(sql);
}
// Способ 2: Веб-сервис компании
else if (config.useWebService) {
executeViaWebService(sql);
}
// Способ 3: Если есть возможность настроить простой PHP/Python скрипт
else {
showStatus('Настройте endpoint в конфигурации', 'error');
showLoading(false);
}
}

function executeViaORDS(sql) {
// Для ORDS требуется Basic Auth или токен
const auth = btoa('username:password'); // НЕ храните пароли в коде!

fetch(config.ordsEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/sql',
'Authorization': `Basic ${auth}`,
'Accept': 'application/json'
},
body: sql
})
.then(response => {
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
})
.then(data => {
displayResults(data.items || data);
showStatus('Запрос выполнен успешно', 'success');
showLoading(false);
})
.catch(error => {
showStatus(`Ошибка: ${error.message}`, 'error');
showLoading(false);
console.error('ORDS Error:', error);
});
}

// МОК ДАННЫХ для демонстрации (удалите в рабочей версии)
function executeViaWebService(sql) {
// Имитация задержки
setTimeout(() => {
// Пример данных для демонстрации
const mockData = [
{ id: 1, name: 'Иван Иванов', department: 'IT', salary: 150000, hire_date: '2020-01-15' },
{ id: 2, name: 'Петр Петров', department: 'HR', salary: 120000, hire_date: '2019-03-22' },
{ id: 3, name: 'Мария Сидорова', department: 'IT', salary: 160000, hire_date: '2021-06-10' },
{ id: 4, name: 'Алексей Смирнов', department: 'Sales', salary: 140000, hire_date: '2018-11-30' },
{ id: 5, name: 'Елена Кузнецова', department: 'Finance', salary: 170000, hire_date: '2017-09-05' }
];

displayResults(mockData);
showStatus('Демо-данные загружены. В реальной версии подключитесь к Oracle.', 'success');
showLoading(false);
}, 1000);
}

function displayResults(data) {
if (!data || data.length === 0) {
document.getElementById('results').innerHTML = '<p>Нет данных для отображения</p>';
document.getElementById('filterSection').style.display = 'none';
document.getElementById('rowCount').innerHTML = 'Найдено 0 записей';
return;
}

// Определяем колонки
const columns = Object.keys(data[0]);

// Строим таблицу
let html = `<table id="resultsTable">
<thead><tr>`;

columns.forEach(col => {
html += `<th>${escapeHtml(col)}</th>`;
});

html += `</tr></thead><tbody>`;

data.forEach(row => {
html += '<tr>';
columns.forEach(col => {
const value = row[col] !== null && row[col] !== undefined ? row[col] : '';
html += `<td>${escapeHtml(String(value))}</td>`;
});
html += '</tr>';
});

html += '</tbody></table>';

document.getElementById('results').innerHTML = html;
document.getElementById('filterSection').style.display = 'block';
document.getElementById('rowCount').innerHTML = `Найдено ${data.length} записей`;

// Показываем первые 100 строк если их много
if (data.length > 100) {
showStatus(`Показаны первые 100 из ${data.length} строк. Уточните запрос для меньшего объема данных.`, 'error');
}
}

function filterTable() {
const filter = document.getElementById('globalFilter').value.toLowerCase();
const table = document.getElementById('resultsTable');
if (!table) return;

const rows = table.getElementsByTagName('tr');
let visibleCount = 0;

for (let i = 1; i < rows.length; i++) {
const cells = rows[i].getElementsByTagName('td');
let visible = false;

for (let j = 0; j < cells.length; j++) {
if (cells[j].textContent.toLowerCase().includes(filter)) {
visible = true;
break;
}
}

rows[i].style.display = visible ? '' : 'none';
if (visible) visibleCount++;
}

document.getElementById('rowCount').innerHTML = `Найдено ${visibleCount} записей (отфильтровано)`;
}

function exportToCSV() {
const table = document.getElementById('resultsTable');
if (!table) {
showStatus('Нет данных для экспорта', 'error');
return;
}

let csv = [];
const rows = table.getElementsByTagName('tr');

for (let i = 0; i < rows.length; i++) {
const cells = rows[i].getElementsByTagName(i === 0 ? 'th' : 'td');
const row = [];

for (let j = 0; j < cells.length; j++) {
let cell = cells[j].textContent.replace(/"/g, '""');
if (cell.includes(',') || cell.includes('\n') || cell.includes('"')) {
cell = `"${cell}"`;
}
row.push(cell);
}

csv.push(row.join(','));
}

const csvContent = csv.join('\n');
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);

link.setAttribute('href', url);
link.setAttribute('download', `oracle_query_${new Date().toISOString().slice(0,10)}.csv`);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);

showStatus('Данные экспортированы в CSV', 'success');
}

function showPredefinedQueries() {
let html = '<h3>Примеры запросов:</h3><ul style="background: #f8f9fa; padding: 15px; border-radius: 5px;">';

for (const [name, query] of Object.entries(predefinedQueries)) {
html += `<li style="margin-bottom: 10px;">
<strong>${name}:</strong><br>
<code style="background: #eee; padding: 5px; display: block; margin: 5px 0;">
${query}
</code>
<button onclick="loadPredefinedQuery('${name}')" style="font-size: 12px; padding: 3px 8px;">Загрузить</button>
</li>`;
}

html += '</ul>';

document.getElementById('results').innerHTML = html;
}

function loadPredefinedQuery(name) {
document.getElementById('sqlQuery').value = predefinedQueries[name];
showStatus(`Загружен запрос: ${name}`, 'success');
}

function clearResults() {
document.getElementById('results').innerHTML = '';
document.getElementById('filterSection').style.display = 'none';
document.getElementById('rowCount').innerHTML = '';
clearStatus();
}

function showStatus(message, type) {
const statusDiv = document.getElementById('status');
statusDiv.innerHTML = message;
statusDiv.className = `status ${type}`;
statusDiv.style.display = 'block';
}

function clearStatus() {
document.getElementById('status').style.display = 'none';
}

function showLoading(show) {
document.getElementById('loading').classList.toggle('active', show);
}

function isDangerousQuery(sql) {
const dangerous = [
'INSERT', 'UPDATE', 'DELETE', 'DROP', 'TRUNCATE', 'ALTER',
'CREATE', 'GRANT', 'REVOKE', 'MERGE', 'PURGE', 'COMMIT',
'ROLLBACK', 'EXECUTE', 'EXEC'
];

const upperSql = sql.toUpperCase();
return dangerous.some(cmd => upperSql.includes(cmd));
}

function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}

// Сохранение в localStorage
function saveQuery() {
const sql = document.getElementById('sqlQuery').value;
localStorage.setItem('lastOracleQuery', sql);
}

function loadSavedQuery() {
const saved = localStorage.getItem('lastOracleQuery');
if (saved) {
document.getElementById('sqlQuery').value = saved;
}
}

// Автосохранение при изменении
document.getElementById('sqlQuery').addEventListener('input', saveQuery);

// Загружаем сохраненный запрос при загрузке страницы
window.addEventListener('load', loadSavedQuery);
</script>
</body>
</html>

Добавлено (2026-02-03, 12:35)
---------------------------------------------
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Oracle Direct Connect</title>
<script src="https://cdn.jsdelivr.net/npm/oracledb@latest/lib/oracledb.js"></script>
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css">
</head>
<body>
<h1>Подключение к Oracle</h1>

<div>
<h3>Настройки подключения:</h3>
<input type="text" id="hostname" placeholder="hostname" value="ваш-hostname">
<input type="text" id="port" placeholder="port" value="1521">
<input type="text" id="service" placeholder="service name" value="ваш-service">
<input type="text" id="username" placeholder="username" value="ваш-username">
<input type="password" id="password" placeholder="password">
<button onclick="connect()">Подключиться</button>
</div>

<div id="querySection" style="display:none;">
<h3>SQL запрос:</h3>
<textarea id="sql" style="width:100%;height:100px;"></textarea>
<button onclick="execute()">Выполнить</button>

<div id="results"></div>
</div>

<script>
let connection;

async function connect() {
const config = {
user: document.getElementById('username').value,
password: document.getElementById('password').value,
connectString: `${document.getElementById('hostname').value}:${
document.getElementById('port').value}/${
document.getElementById('service').value}`
};

try {
// Для браузера нужно использовать Oracle REST Data Services
// Этот пример работает только с ORDS
alert('Для работы в браузере нужен Oracle REST Data Services.\nОбратитесь к DBA для настройки ORDS.');

// Альтернатива: используем fetch к серверному прокси
document.getElementById('querySection').style.display = 'block';

} catch(err) {
alert('Ошибка: ' + err.message);
}
}

async function execute() {
const sql = document.getElementById('sql').value;

// Используем серверный прокси
const response = await fetch('http://ваш-сервер/oracle-proxy.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
hostname: document.getElementById('hostname').value,
port: document.getElementById('port').value,
service: document.getElementById('service').value,
username: document.getElementById('username').value,
password: document.getElementById('password').value,
sql: sql
})
});

const data = await response.json();
displayResults(data);
}

function displayResults(data) {
// Показать результаты в таблице
}
</script>
</body>
</html>

Добавлено (2026-02-03, 12:42)
---------------------------------------------
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Live Oracle Dashboard</title>

<!-- DataTables для фильтрации и сортировки -->
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">

<style>
:root {
--oracle-red: #f80000;
--oracle-blue: #0072c6;
}

body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}

.dashboard {
max-width: 98%;
margin: 0 auto;
background: white;
border-radius: 15px;
box-shadow: 0 15px 35px rgba(0,0,0,0.2);
overflow: hidden;
}

.header {
background: linear-gradient(90deg, var(--oracle-red), #c60000);
color: white;
padding: 25px 30px;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
}

.connection-info {
background: #f8f9fa;
padding: 15px 30px;
border-bottom: 1px solid #dee2e6;
font-family: monospace;
font-size: 14px;
color: #666;
}

.connection-info span {
background: #e9ecef;
padding: 3px 8px;
border-radius: 4px;
margin: 0 5px;
}

.predefined-queries {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 15px;
padding: 25px 30px;
background: #f8f9fa;
}

.query-card {
background: white;
border: 2px solid #dee2e6;
border-radius: 10px;
padding: 20px;
cursor: pointer;
transition: all 0.3s;
box-shadow: 0 3px 10px rgba(0,0,0,0.05);
}

.query-card:hover {
border-color: var(--oracle-blue);
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}

.query-card.active {
border-color: var(--oracle-red);
background: #fff5f5;
}

.query-title {
font-weight: 600;
color: var(--oracle-blue);
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 10px;
}

.query-sql {
font-family: 'Consolas', monospace;
font-size: 12px;
color: #666;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}

.results-section {
padding: 25px 30px;
min-height: 500px;
}

.stats-bar {
display: flex;
gap: 20px;
margin-bottom: 20px;
flex-wrap: wrap;
}

.stat-item {
background: #f8f9fa;
padding: 12px 20px;
border-radius: 8px;
display: flex;
align-items: center;
gap: 10px;
}

.stat-item i {
color: var(--oracle-blue);
font-size: 20px;
}

.loading {
text-align: center;
padding: 50px 20px;
color: #666;
}

.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid var(--oracle-red);
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}

@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

table.dataTable {
border-collapse: collapse !important;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 3px 15px rgba(0,0,0,0.05);
}

table.dataTable thead th {
background-color: var(--oracle-blue) !important;
color: white !important;
border: none !important;
padding: 15px !important;
font-weight: 600;
}

.dataTables_wrapper .dataTables_filter input {
border: 2px solid #dee2e6;
border-radius: 8px;
padding: 8px 12px;
margin-bottom: 15px;
}

.btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
display: inline-flex;
align-items: center;
gap: 8px;
transition: all 0.3s;
}

.btn-primary {
background: var(--oracle-red);
color: white;
}

.btn-primary:hover {
background: #d60000;
}

.footer {
padding: 20px 30px;
background: #f8f9fa;
border-top: 1px solid #dee2e6;
text-align: center;
color: #666;
font-size: 14px;
}

.auto-refresh {
display: flex;
align-items: center;
gap: 10px;
margin-top: 10px;
}

.auto-refresh select {
padding: 5px 10px;
border-radius: 4px;
border: 1px solid #dee2e6;
}

@media (max-width: 768px) {
.header {
flex-direction: column;
text-align: center;
gap: 15px;
}

.predefined-queries {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="dashboard">
<div class="header">
<div>
<h1 style="margin:0;font-size:28px;"><i class="fas fa-database"></i> Oracle Live Dashboard</h1>
<p style="margin:10px 0 0 0;opacity:0.9;">Реальное время • Только чтение • Автоматическое обновление</p>
</div>
<div style="display:flex;gap:15px;align-items:center;">
<div class="auto-refresh">
<i class="fas fa-sync-alt"></i>
<select id="refreshInterval">
<option value="0">Не обновлять</option>
<option value="10">10 сек</option>
<option value="30" selected>30 сек</option>
<option value="60">1 мин</option>
<option value="300">5 мин</option>
</select>
</div>
<button class="btn btn-primary" onclick="loadCurrentQuery()">
<i class="fas fa-redo"></i> Обновить
</button>
</div>
</div>

<div class="connection-info">
<i class="fas fa-plug"></i> Подключение:
<span id="connHost">ваш-hostname</span>:<span id="connPort">1521</span>/
<span id="connService">ваш-service-name</span>
как <span id="connUser">ваш-username</span>
(роль: <span id="connRole">SYSDBA</span>)
</div>

<div class="predefined-queries" id="queriesContainer">
<!-- Запросы будут добавлены через JavaScript -->
</div>

<div class="results-section">
<div class="stats-bar" id="statsBar">
<!-- Статистика будет здесь -->
</div>

<div id="loadingIndicator" class="loading" style="display:none;">
<div class="spinner"></div>
<p>Загрузка данных из Oracle...</p>
</div>

<div id="resultsContainer">
<div class="loading">
<p>Выберите запрос для отображения данных</p>
</div>
</div>
</div>

<div class="footer">
<div>Oracle Database Live Interface • Данные обновляются автоматически</div>
<div id="lastUpdate">Ожидание данных...</div>
</div>
</div>

<!-- Библиотеки -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

<script>
// КОНФИГУРАЦИЯ ПОДКЛЮЧЕНИЯ - ЗАМЕНИТЕ НА ВАШИ ДАННЫЕ!
const ORACLE_CONFIG = {
hostname: "ваш-hostname", // Например: oracle-db.company.local
port: "1521", // Стандартный порт Oracle
service: "ваш-service-name", // Например: ORCL
username: "ваш-username", // Ваше имя пользователя
password: "ваш-password", // Ваш пароль
role: "SYSDBA" // Роль
};

// ПРЕДОПРЕДЕЛЕННЫЕ ЗАПРОСЫ - НАСТРОЙТЕ ПОД ВАШИ ТАБЛИЦЫ
const PREDEFINED_QUERIES = [
{
id: 'users',
title: 'Пользователи БД',
icon: 'fas fa-users',
sql: `SELECT
username,
account_status,
TO_CHAR(created, 'DD.MM.YYYY HH24:MI') as created_date,
default_tablespace,
profile
FROM dba_users
WHERE username NOT LIKE 'APEX%'
ORDER BY created DESC`,
description: 'Все пользователи базы данных'
},
{
id: 'tables',
title: 'Таблицы в схеме',
icon: 'fas fa-table',
sql: `SELECT
owner,
table_name,
tablespace_name,
num_rows,
TO_CHAR(last_analyzed, 'DD.MM.YYYY') as last_analyzed,
blocks,
avg_row_len
FROM dba_tables
WHERE owner NOT IN ('SYS','SYSTEM','XDB')
AND num_rows > 0
ORDER BY owner, table_name`,
description: 'Таблицы с количеством строк'
},
{
id: 'sessions',
title: 'Активные сессии',
icon: 'fas fa-bolt',
sql: `SELECT
username,
sid,
serial#,
status,
machine,
program,
TO_CHAR(logon_time, 'DD.MM.YYYY HH24:MI:SS') as logon_time,
osuser
FROM v$session
WHERE username IS NOT NULL
AND status = 'ACTIVE'
ORDER BY logon_time DESC`,
description: 'Текущие активные сессии',
autoRefresh: true
},
{
id: 'tablespaces',
title: 'Табличные пространства',
icon: 'fas fa-hdd',
sql: `SELECT
tablespace_name,
file_name,
ROUND(bytes/1024/1024, 2) as size_mb,
ROUND(maxbytes/1024/1024, 2) as max_mb,
autoextensible,
status
FROM dba_data_files
ORDER BY tablespace_name, file_name`,
description: 'Информация о табличных пространствах'
},
{
id: 'locks',
title: 'Блокировки',
icon: 'fas fa-lock',
sql: `SELECT
s.username,
s.sid,
s.serial#,
l.type,
l.id1,
l.id2,
l.lmode,
l.request,
TO_CHAR(l.ctime) as lock_time
FROM v$lock l, v$session s
WHERE l.sid = s.sid
AND l.type != 'MR'
ORDER BY l.ctime DESC`,
description: 'Текущие блокировки в БД',
autoRefresh: true
},
{
id: 'performance',
title: 'Производительность',
icon: 'fas fa-tachometer-alt',
sql: `SELECT
stat_name,
value,
TO_CHAR(begin_time, 'DD.MM.YYYY HH24:MI') as begin_time,
TO_CHAR(end_time, 'DD.MM.YYYY HH24:MI') as end_time
FROM v$sysmetric
WHERE group_id = 2
AND metric_name IN ('Database CPU Time Ratio', 'Database Wait Time Ratio')
ORDER BY begin_time DESC`,
description: 'Метрики производительности'
}
];

// ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
let currentQuery = null;
let currentData = null;
let refreshInterval = null;
let dataTable = null;

// ИНИЦИАЛИЗАЦИЯ
document.addEventListener('DOMContentLoaded', function() {
// Показываем данные подключения
document.getElementById('connHost').textContent = ORACLE_CONFIG.hostname;
document.getElementById('connPort').textContent = ORACLE_CONFIG.port;
document.getElementById('connService').textContent = ORACLE_CONFIG.service;
document.getElementById('connUser').textContent = ORACLE_CONFIG.username;
document.getElementById('connRole').textContent = ORACLE_CONFIG.role;

// Загружаем список запросов
loadQueriesList();

// Настраиваем автообновление
setupAutoRefresh();

// Загружаем первый запрос
if (PREDEFINED_QUERIES.length > 0) {
selectQuery(PREDEFINED_QUERIES[0].id);
}
});

// ЗАГРУЗКА СПИСКА ЗАПРОСОВ
function loadQueriesList() {
const container = document.getElementById('queriesContainer');
container.innerHTML = '';

PREDEFINED_QUERIES.forEach(query => {
const card = document.createElement('div');
card.className = 'query-card';
card.id = `query-${query.id}`;
card.onclick = () => selectQuery(query.id);

card.innerHTML = `
<div class="query-title">
<i class="${query.icon}"></i>
<span>${query.title}</span>
${query.autoRefresh ? '<i class="fas fa-sync-alt" style="color:#27ae60;font-size:12px;"></i>' : ''}
</div>
<div class="query-sql">${query.sql.substring(0, 100)}...</div>
<div style="font-size:12px;color:#888;margin-top:10px;">${query.description}</div>
`;

container.appendChild(card);
});
}

// ВЫБОР ЗАПРОСА
function selectQuery(queryId) {
// Убираем выделение со всех карточек
document.querySelectorAll('.query-card').forEach(card => {
card.classList.remove('active');
});

// Выделяем выбранную карточку
document.getElementById(`query-${queryId}`).classList.add('active');

// Находим выбранный запрос
currentQuery = PREDEFINED_QUERIES.find(q => q.id === queryId);

// Загружаем данные
loadCurrentQuery();
}

// ЗАГРУЗКА ДАННЫХ
async function loadCurrentQuery() {
if (!currentQuery) return;

showLoading(true);

try {
// ВАЖНО: Выберите ОДИН из методов ниже!

// МЕТОД 1: Если есть ORDS (Oracle REST Data Services)
// const data = await loadViaORDS(currentQuery.sql);

// МЕТОД 2: Если есть возможность запустить локальный прокси
// const data = await loadViaLocalProxy(currentQuery.sql);

// МЕТОД 3: Демо-данные (для тестирования интерфейса)
const data = await loadDemoData(currentQuery);

currentData = data;
displayResults(data);
updateStats(data);
updateLastUpdate();

showLoading(false);

} catch (error) {
console.error('Ошибка загрузки:', error);
showError('Не удалось загрузить данные: ' + error.message);
showLoading(false);
}
}

// МЕТОД 1: Загрузка через Oracle REST Data Services (ORDS)
async function loadViaORDS(sql) {
// URL для ORDS - уточните у DBA!
const ORDS_URL = `https://${ORACLE_CONFIG.hostname}:8080/ords/_/sql`;

// Basic Auth (не безопасно для продакшена!)
const auth = btoa(`${ORACLE_CONFIG.username}:${ORACLE_CONFIG.password}`);

const response = await fetch(ORDS_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/sql',
'Authorization': `Basic ${auth}`,
'Accept': 'application/json'
},
body: sql
});

if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}

const result = await response.json();
return result.items || result;
}

// МЕТОД 2: Локальный прокси через Python/Node.js (если можно запустить)
async function loadViaLocalProxy(sql) {
// Предполагаем, что есть локальный сервер на порту 3000
const response = await fetch('http://localhost:3000/query', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
config: ORACLE_CONFIG,
sql: sql
})
});

const result = await response.json();
return result.data;
}

// МЕТОД 3: Демо-данные (для тестирования)
function loadDemoData(query) {
return new Promise((resolve) => {
setTimeout(() => {
// Генерируем демо-данные в зависимости от типа запроса
let demoData = [];
const now = new Date();

switch(query.id) {
case 'users':
demoData = [
{USERNAME: 'SYS', ACCOUNT_STATUS: 'OPEN', CREATED_DATE: '01.01.2023 10:00', DEFAULT_TABLESPACE: 'SYSTEM', PROFILE: 'DEFAULT'},
{USERNAME: 'SYSTEM', ACCOUNT_STATUS: 'OPEN', CREATED_DATE: '01.01.2023 10:00', DEFAULT_TABLESPACE: 'SYSTEM', PROFILE: 'DEFAULT'},
{USERNAME: 'APP_USER', ACCOUNT_STATUS: 'OPEN', CREATED_DATE: '15.03.2023 14:30', DEFAULT_TABLESPACE: 'USERS', PROFILE: 'APP_PROFILE'},
{USERNAME: 'REPORT_USER', ACCOUNT_STATUS: 'EXPIRED', CREATED_DATE: '20.05.2023 09:15', DEFAULT_TABLESPACE: 'USERS', PROFILE: 'DEFAULT'},
{USERNAME: 'DEV_USER', ACCOUNT_STATUS: 'OPEN', CREATED_DATE: '10.07.2023 11:45', DEFAULT_TABLESPACE: 'USERS', PROFILE: 'DEV_PROFILE'}
];
break;
case 'sessions':
demoData = [
{USERNAME: 'SYS', SID: '123', SERIAL: '456', STATUS: 'ACTIVE', MACHINE: 'SRV-DB01', PROGRAM: 'sqlplus.exe', LOGON_TIME: now.toLocaleString(), OSUSER: 'oracle'},
{USERNAME: 'APP_USER', SID: '124', SERIAL: '789', STATUS: 'ACTIVE', MACHINE: 'WS-001', PROGRAM: 'app.exe', LOGON_TIME: new Date(now - 3600000).toLocaleString(), OSUSER: 'user1'},
{USERNAME: 'REPORT_USER', SID: '125', SERIAL: '012', STATUS: 'INACTIVE', MACHINE: 'WS-002', PROGRAM: 'excel.exe', LOGON_TIME: new Date(now - 7200000).toLocaleString(), OSUSER: 'user2'}
];
break;
case 'tables':
demoData = [
{OWNER: 'APP_USER', TABLE_NAME: 'CUSTOMERS', TABLESPACE_NAME: 'USERS', NUM_ROWS: '15000', LAST_ANALYZED: '01.12.2023', BLOCKS: '450', AVG_ROW_LEN: '120'},
{OWNER: 'APP_USER', TABLE_NAME: 'ORDERS', TABLESPACE_NAME: 'USERS', NUM_ROWS: '50000', LAST_ANALYZED: '01.12.2023', BLOCKS: '1200', AVG_ROW_LEN: '85'},
{OWNER: 'REPORT_USER', TABLE_NAME: 'SALES', TABLESPACE_NAME: 'USERS', NUM_ROWS: '25000', LAST_ANALYZED: '30.11.2023', BLOCKS: '750', AVG_ROW_LEN: '95'}
];
break;
default:
demoData = [
{ID: 1, NAME: 'Тест 1', VALUE: 'Значение 1', DATE: new Date().toLocaleDateString()},
{ID: 2, NAME: 'Тест 2', VALUE: 'Значение 2', DATE: new Date().toLocaleDateString()},
{ID: 3, NAME: 'Тест 3', VALUE: 'Значение 3', DATE: new Date().toLocaleDateString()}
];
}

resolve(demoData);
}, 1000); // Имитация задержки сети
});
}

// ОТОБРАЖЕНИЕ РЕЗУЛЬТАТОВ
function displayResults(data) {
if (!data || data.length === 0) {
document.getElementById('resultsContainer').innerHTML = `
<div style="text-align:center;padding:50px;color:#666;">
<i class="fas fa-database" style="font-size:48px;color:#dee2e6;margin-bottom:20px;"></i>
<h3>Нет данных для отображения</h3>
<p>Запрос не вернул результатов</p>
</div>
`;
return;
}

// Получаем заголовки колонок
const headers = Object.keys(data[0]);

// Создаем таблицу HTML
let html = `
<table id="dataTable" class="display" style="width:100%">
<thead>
<tr>
${headers.map(header => `<th>${header}</th>`).join('')}
</tr>
</thead>
<tbody>
${data.map(row => `
<tr>
${headers.map(header => `
<td>${escapeHtml(String(row[header] || ''))}</td>
`).join('')}
</tr>
`).join('')}
</tbody>
</table>
`;

document.getElementById('resultsContainer').innerHTML = html;

// Инициализируем DataTable с русской локализацией
if (dataTable) {
dataTable.destroy();
}

dataTable = $('#dataTable').DataTable({
pageLength: 25,
lengthMenu: [10, 25, 50, 100],
language: {
url: 'https://cdn.datatables.net/plug-ins/1.13.4/i18n/ru.json'
},
dom: 'Bfrtip',
buttons: [
{
extend: 'copy',
text: '<i class="fas fa-copy"></i> Копировать',
className: 'btn'
},
{
extend: 'excel',
text: '<i class="fas fa-file-excel"></i> Excel',
className: 'btn'
},
{
extend: 'csv',
text: '<i class="fas fa-file-csv"></i> CSV',
className: 'btn'
}
]
});
}

// ОБНОВЛЕНИЕ СТАТИСТИКИ
function updateStats(data) {
const statsHTML = `
<div class="stat-item">
<i class="fas fa-list"></i>
<div>
<div style="font-weight:600;">${data.length}</div>
<div style="font-size:12px;">строк</div>
</div>
</div>
<div class="stat-item">
<i class="fas fa-columns"></i>
<div>
<div style="font-weight:600;">${Object.keys(data[0] || {}).length}</div>
<div style="font-size:12px;">колонок</div>
</div>
</div>
<div class="stat-item">
<i class="fas fa-clock"></i>
<div>
<div style="font-weight:600;">${new Date().toLocaleTimeString('ru-RU')}</div>
<div style="font-size:12px;">последнее обновление</div>
</div>
</div>
<div class="stat-item">
<i class="fas fa-database"></i>
<div>
<div style="font-weight:600;">${currentQuery.title}</div>
<div style="font-size:12px;">текущий запрос</div>
</div>
</div>
`;

document.getElementById('statsBar').innerHTML = statsHTML;
}

// НАСТРОЙКА АВТООБНОВЛЕНИЯ
function setupAutoRefresh() {
const select = document.getElementById('refreshInterval');
select.value = '30'; // По умолчанию 30 секунд

select.onchange = function() {
const interval = parseInt(this.value) * 1000;

if (refreshInterval) {
clearInterval(refreshInterval);
}

if (interval > 0) {
refreshInterval = setInterval(() => {
if (currentQuery && currentQuery.autoRefresh) {
loadCurrentQuery();
}
}, interval);
}
};

// Запускаем автообновление
select.onchange();
}

// ОБНОВЛЕНИЕ ВРЕМЕНИ
function updateLastUpdate() {
document.getElementById('lastUpdate').innerHTML = `
Данные обновлены: ${new Date().toLocaleString('ru-RU')}
${refreshInterval ? '| Автообновление включено' : ''}
`;
}

// ПОКАЗАТЬ/СКРЫТЬ ЗАГРУЗКУ
function showLoading(show) {
document.getElementById('loadingIndicator').style.display = show ? 'block' : 'none';
}

// ПОКАЗАТЬ ОШИБКУ
function showError(message) {
document.getElementById('resultsContainer').innerHTML = `
<div style="text-align:center;padding:50px;color:#e74c3c;">
<i class="fas fa-exclamation-triangle" style="font-size:48px;margin-bottom:20px;"></i>
<h3>Ошибка загрузки</h3>
<p>${message}</p>
<p style="font-size:12px;margin-top:20px;">
Проверьте:<br>
1. Наличие ORDS (Oracle REST Data Services)<br>
2. Доступность ${ORACLE_CONFIG.hostname}:${ORACLE_CONFIG.port}<br>
3. Правильность учетных данных
</p>
<button class="btn btn-primary" onclick="loadCurrentQuery()" style="margin-top:20px;">
<i class="fas fa-redo"></i> Попробовать снова
</button>
</div>
`;
}

// ЭКРАНИРОВАНИЕ HTML
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}

// ВСПОМОГАТЕЛЬНАЯ ФУНКЦИЯ: Получить Cookie
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
</script>
</body>
</html>

Пппр
Прикрепления:
rrrrrrrr.noext (42.4 Kb)
Рорр
Прикрепления:
dddddd.noext (26.7 Kb)
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Генератор шаблонов литологии</title>
<style>
/* Все стили остаются без изменений */
* {
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

body {
margin: 0;
padding: 20px;
background-color: #f5f7fa;
color: #333;
}

.container {
max-width: 1200px;
margin: 0 auto;
background-color: white;
border-radius: 10px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
padding: 25px;
}

h1 {
color: #2c3e50;
text-align: center;
margin-bottom: 30px;
border-bottom: 2px solid #3498db;
padding-bottom: 15px;
}

.section {
margin-bottom: 30px;
padding: 20px;
border-radius: 8px;
background-color: #f8f9fa;
}

.section-title {
font-size: 1.3rem;
color: #2c3e50;
margin-bottom: 15px;
display: flex;
align-items: center;
}

.form-group {
margin-bottom: 15px;
}

label {
display: block;
margin-bottom: 5px;
font-weight: 600;
color: #2c3e50;
}

input, textarea, select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}

textarea {
min-height: 100px;
resize: vertical;
}

button {
background-color: #3498db;
color: white;
border: none;
padding: 12px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
font-weight: 600;
transition: background-color 0.3s;
}

button:hover {
background-color: #2980b9;
}

button.secondary {
background-color: #95a5a6;
}

button.secondary:hover {
background-color: #7f8c8d;
}

table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}

th, td {
border: 1px solid #ddd;
padding: 12px;
text-align: left;
}

th {
background-color: #3498db;
color: white;
}

tr:nth-child(even) {
background-color: #f2f2f2;
}

.preview-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-top: 20px;
}

.preview-item {
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
width: 200px;
text-align: center;
background-color: white;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}

.preview-svg {
height: 100px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10px;
}

.preview-svg svg {
max-width: 100%;
max-height: 100%;
}

.preview-code {
font-weight: bold;
color: #2c3e50;
}

.preview-name {
color: #7f8c8d;
font-size: 0.9rem;
}

.hidden {
display: none;
}

.message {
padding: 15px;
border-radius: 4px;
margin: 15px 0;
}

.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}

.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}

.actions {
display: flex;
gap: 10px;
margin-top: 20px;
}

.file-input-container {
position: relative;
overflow: hidden;
display: inline-block;
width: 100%;
}

.file-input-container input[type=file] {
position: absolute;
left: 0;
top: 0;
opacity: 0;
width: 100%;
height: 100%;
cursor: pointer;
}

.file-input-button {
display: block;
padding: 10px;
background: #f8f9fa;
border: 2px dashed #3498db;
border-radius: 4px;
text-align: center;
color: #3498db;
font-weight: 600;
}

.file-list {
margin-top: 10px;
max-height: 150px;
overflow-y: auto;
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
}

.file-item {
display: flex;
justify-content: space-between;
padding: 5px 0;
border-bottom: 1px solid #eee;
}

.file-item:last-child {
border-bottom: none;
}

.file-name {
flex-grow: 1;
}

.file-size {
color: #7f8c8d;
font-size: 0.8rem;
}

.data-table-container {
overflow-x: auto;
}

.help-text {
font-size: 0.9rem;
color: #7f8c8d;
margin-top: 5px;
}

/* Новые стили для JSON отображения */
#jsonContent {
background-color: #f8f9fa;
border: 1px solid #ddd;
border-radius: 4px;
padding: 15px;
max-height: 500px;
overflow-y: auto;
white-space: pre-wrap;
word-wrap: break-word;
font-family: 'Courier New', monospace;
font-size: 12px;
line-height: 1.4;
}
</style>
</head>
<body>
<div class="container">
<h1>Генератор шаблонов литологии</h1>

<div class="section">
<h2 class="section-title">1. Загрузка SVG-файлов с текстурами</h2>
<div class="form-group">
<label>Выберите папку с SVG-файлами:</label>
<div class="file-input-container">
<div class="file-input-button">Выбрать папку с SVG-файлами</div>
<input type="file" id="svgFolderInput" webkitdirectory multiple>
</div>
<div class="help-text">SVG-файлы должны быть названы в формате "Код_Породы Название.svg", например: "1000 Лед.svg"</div>
</div>

<div class="form-group">
<label>Путь к SVG файлам в программе:</label>
<input type="text" id="svgFilePath" placeholder="C:/Users/User/AppData/Local/OISTerra/OISTerraManager/svg/" value="C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/">
<div class="help-text">Укажите путь, который будет использоваться в сгенерированном JSON файле</div>
</div>

<div id="fileList" class="file-list hidden">
<!-- Список загруженных файлов будет здесь -->
</div>
</div>

<div class="section">
<h2 class="section-title">2. Ввод данных о породах</h2>
<div class="form-group">
<label>Добавить данные о породах:</label>
<div class="help-text">Введите данные в формате: Код_Породы Название (каждая порода с новой строки)</div>
<textarea id="rockDataInput" placeholder="1300 Суглинок
1700 Илы
1800 Торф
1400 Супесь
2300 Аргиллит
2400 Алевролит"></textarea>
</div>

<div class="actions">
<button id="parseDataBtn">Обработать данные</button>
<button id="addRockBtn" class="secondary">Добавить породу вручную</button>
</div>

<div id="rockTableContainer" class="data-table-container hidden">
<h3>Список пород:</h3>
<table id="rockTable">
<thead>
<tr>
<th>Код породы</th>
<th>Наименование породы</th>
<th>SVG файл</th>
<th>Действия</th>
</tr>
</thead>
<tbody id="rockTableBody">
<!-- Данные о породах будут здесь -->
</tbody>
</table>
</div>
</div>

<div class="section">
<h2 class="section-title">3. Предварительный просмотр</h2>
<div id="previewContainer" class="preview-container">
<!-- Предварительный просмотр будет здесь -->
</div>
</div>

<div class="section">
<h2 class="section-title">4. Генерация шаблона</h2>
<div class="form-group">
<label>Название палитры:</label>
<input type="text" id="paletteName" value="тест_лито">
</div>

<div class="form-group">
<label>Название шаблона корреляции:</label>
<input type="text" id="templateName" value="Корреляционный шаблон">
</div>

<div class="form-group">
<label>Размер patternWidth для SVG:</label>
<input type="number" id="patternWidth" value="20" min="1" max="100">
<div class="help-text">Размер маркера для всех SVG текстур (рекомендуется 20)</div>
</div>

<div class="actions">
<button id="generateBtn">Сгенерировать JSON шаблон</button>
<button id="downloadBtn" class="secondary hidden">Скачать шаблон</button>
</div>

<div id="messageArea"></div>

<div id="jsonOutput" class="hidden">
<h3>Сгенерированный JSON:</h3>
<pre id="jsonContent"></pre>
</div>
</div>
</div>

<script>
// Переменные для хранения данных
let svgFiles = {};
let rockData = [];

// Элементы DOM
const svgFolderInput = document.getElementById('svgFolderInput');
const svgFilePathInput = document.getElementById('svgFilePath');
const patternWidthInput = document.getElementById('patternWidth');
const fileList = document.getElementById('fileList');
const rockDataInput = document.getElementById('rockDataInput');
const parseDataBtn = document.getElementById('parseDataBtn');
const addRockBtn = document.getElementById('addRockBtn');
const rockTableContainer = document.getElementById('rockTableContainer');
const rockTableBody = document.getElementById('rockTableBody');
const previewContainer = document.getElementById('previewContainer');
const paletteNameInput = document.getElementById('paletteName');
const templateNameInput = document.getElementById('templateName');
const generateBtn = document.getElementById('generateBtn');
const downloadBtn = document.getElementById('downloadBtn');
const messageArea = document.getElementById('messageArea');
const jsonOutput = document.getElementById('jsonOutput');
const jsonContent = document.getElementById('jsonContent');

// Функция для преобразования SVG в формат как в оригинале
function convertSvgToOriginalFormat(svgContent) {
// Создаем временный div для парсинга SVG
const tempDiv = document.createElement('div');
tempDiv.innerHTML = svgContent;
const svgElement = tempDiv.querySelector('svg');

if (!svgElement) return svgContent;

// Создаем SVG с атрибутами как в оригинале
let originalSvg = `<?xml version='1.0' encoding='UTF-8'?>\n<svg id="svg1" height="142px" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" viewBox="0 0 282 142" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" width="282px" xmlns:dc="http://purl.org/dc/elements/1.1/" version="1.1">\n <metadata>\n <rdf:RDF>\n <cc:Work>\n <dc:title>ИСИХОГИ_Литология</dc:title>\n </cc:Work>\n </rdf:RDF>\n </metadata>\n <defs id="defs1"/>\n <g id="layer1">\n`;

// Добавляем все path элементы
const paths = svgElement.querySelectorAll('path');
paths.forEach(path => {
const id = path.getAttribute('id') || '';
const d = path.getAttribute('d') || '';
const strokeWidth = path.getAttribute('stroke-width') || '';
const style = path.getAttribute('style') || '';

originalSvg += ` <path id="${id}" d="${d}" stroke-width="${strokeWidth}" style="${style}"/>\n`;
});

// Добавляем все line элементы в формате оригинала
const lines = svgElement.querySelectorAll('line');
lines.forEach(line => {
const id = line.getAttribute('id') || '';
const x1 = line.getAttribute('x1') || '';
const y1 = line.getAttribute('y1') || '';
const x2 = line.getAttribute('x2') || '';
const y2 = line.getAttribute('y2') || '';
const strokeWidth = line.getAttribute('stroke-width') || '';
const style = line.getAttribute('style') || '';

originalSvg += ` <line id="${id}" x1="${x1}" y1="${y1}" y2="${y2}" stroke-width="${strokeWidth}" style="${style}" x2="${x2}"/>\n`;
});

// Добавляем все ellipse элементы в формате оригинала
const ellipses = svgElement.querySelectorAll('ellipse');
ellipses.forEach(ellipse => {
const id = ellipse.getAttribute('id') || '';
const cx = ellipse.getAttribute('cx') || '';
const cy = ellipse.getAttribute('cy') || '';
const rx = ellipse.getAttribute('rx') || '';
const ry = ellipse.getAttribute('ry') || '';
const strokeWidth = ellipse.getAttribute('stroke-width') || '';
const fill = ellipse.getAttribute('fill') || '';
const stroke = ellipse.getAttribute('stroke') || '';

originalSvg += ` <ellipse id="${id}" cx="${cx}" fill="${fill}" rx="${rx}" cy="${cy}" stroke-width="${strokeWidth}" stroke="${stroke}" ry="${ry}"/>\n`;
});

originalSvg += ` </g>\n</svg>\n`;

return originalSvg;
}

// Функция для правильного экранирования SVG контента для XML
function escapeSvgForXml(svgContent) {
// Сначала преобразуем SVG в формат оригинала
let convertedSvg = convertSvgToOriginalFormat(svgContent);

// Затем экранируем все специальные символы - ИСПРАВЛЕНА ОШИБКА С КАВЫЧКАМИ
let escaped = convertedSvg
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');

// Заменяем переносы строк
escaped = escaped.replace(/\n/g, ' ');

return escaped;
}

// Обработка загрузки SVG файлов
svgFolderInput.addEventListener('change', function(e) {
const files = Array.from(e.target.files);
svgFiles = {};

// Очистка списка файлов
fileList.innerHTML = '';

// Фильтрация только SVG файлов
const svgFilesList = files.filter(file => file.name.toLowerCase().endsWith('.svg'));

if (svgFilesList.length === 0) {
fileList.innerHTML = '<div>SVG файлы не найдены</div>';
fileList.classList.remove('hidden');
return;
}

// Обработка каждого SVG файла
svgFilesList.forEach(file => {
const fileName = file.name;
const codeMatch = fileName.match(/^(\d+)/);

if (codeMatch) {
const code = codeMatch[1];

const reader = new FileReader();
reader.onload = function(e) {
const svgContent = e.target.result;
svgFiles[code] = {
name: fileName,
content: svgContent
};

// Добавление в список файлов
const fileItem = document.createElement('div');
fileItem.className = 'file-item';
fileItem.innerHTML = `
<div class="file-name">${fileName}</div>
<div class="file-size">${(file.size / 1024).toFixed(2)} KB</div>
`;
fileList.appendChild(fileItem);

// Обновление таблицы пород, если код уже есть
updateRockTableWithSvg(code);
};
reader.readAsText(file);
}
});

fileList.classList.remove('hidden');
showMessage(`Загружено ${svgFilesList.length} SVG файлов`, 'success');
});

// Обработка данных о породах
parseDataBtn.addEventListener('click', function() {
const inputText = rockDataInput.value.trim();
if (!inputText) {
showMessage('Введите данные о породах', 'error');
return;
}

const lines = inputText.split('\n');
rockData = [];

lines.forEach(line => {
const trimmedLine = line.trim();
if (trimmedLine) {
const parts = trimmedLine.split(/\s+/);
if (parts.length >= 2) {
const code = parts[0];
const name = parts.slice(1).join(' ');
rockData.push({
code: code,
name: name,
svgFile: svgFiles[code] ? svgFiles[code].name : null
});
}
}
});

updateRockTable();
updatePreview();
showMessage(`Обработано ${rockData.length} пород`, 'success');
});

// Добавление породы вручную
addRockBtn.addEventListener('click', function() {
const code = prompt('Введите код породы:');
if (!code) return;

const name = prompt('Введите название породы:');
if (!name) return;

rockData.push({
code: code,
name: name,
svgFile: svgFiles[code] ? svgFiles[code].name : null
});

updateRockTable();
updatePreview();
showMessage(`Добавлена порода: ${code} ${name}`, 'success');
});

// Обновление таблицы пород
function updateRockTable() {
rockTableBody.innerHTML = '';

if (rockData.length === 0) {
rockTableContainer.classList.add('hidden');
return;
}

rockData.forEach((rock, index) => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${rock.code}</td>
<td>${rock.name}</td>
<td>${rock.svgFile || 'Не найден'}</td>
<td>
<button onclick="removeRock(${index})">Удалить</button>
</td>
`;
rockTableBody.appendChild(row);
});

rockTableContainer.classList.remove('hidden');
}

// Обновление таблицы при наличии SVG
function updateRockTableWithSvg(code) {
rockData.forEach(rock => {
if (rock.code === code) {
rock.svgFile = svgFiles[code].name;
}
});

updateRockTable();
updatePreview();
}

// Удаление породы
function removeRock(index) {
rockData.splice(index, 1);
updateRockTable();
updatePreview();
showMessage('Порода удалена', 'success');
}

// Обновление предварительного просмотра
function updatePreview() {
previewContainer.innerHTML = '';

if (rockData.length === 0) {
previewContainer.innerHTML = '<p>Нет данных для предварительного просмотра</p>';
return;
}

rockData.forEach(rock => {
const previewItem = document.createElement('div');
previewItem.className = 'preview-item';

let svgPreview = '<div>SVG не загружен</div>';
if (rock.svgFile && svgFiles[rock.code]) {
svgPreview = svgFiles[rock.code].content;
}

previewItem.innerHTML = `
<div class="preview-svg">${svgPreview}</div>
<div class="preview-code">${rock.code}</div>
<div class="preview-name">${rock.name}</div>
`;

previewContainer.appendChild(previewItem);
});
}

// Генерация JSON шаблона
generateBtn.addEventListener('click', function() {
if (rockData.length === 0) {
showMessage('Нет данных о породах для генерации шаблона', 'error');
return;
}

const paletteName = paletteNameInput.value || 'тест_лито';
const templateName = templateNameInput.value || 'Корреляционный шаблон';
const svgFilePath = svgFilePathInput.value || 'C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/';
const patternWidth = patternWidthInput.value || '20';

// Создание элементов палитры
const paletteItems = rockData.map(rock => {
let svgContent = '';

if (rock.svgFile && svgFiles[rock.code]) {
// Правильное экранирование SVG для XML
svgContent = escapeSvgForXml(svgFiles[rock.code].content);
}

// Генерация случайного ID
const id = Date.now() + Math.floor(Math.random() * 1000);

// Полный путь к файлу SVG
const fullSvgFilePath = svgFilePath + rock.svgFile;

// Точный порядок атрибутов как в оригинале
return {
"color": "#ffffffff",
"data": `<StyledFill id="${id}" name="" objectName="" brushColor="#000000" penColor="#000000" group="">\n <SvgFill patternWidth="${patternWidth}" svgContent="${svgContent}" lineWidth="0.4" objectName="" penWidth="0.4" svgFilePath="${fullSvgFilePath}"/>\n</StyledFill>\n`,
"desc": rock.name,
"value": parseInt(rock.code)
};
});

// Создание основного JSON объекта с фиксированными ID как в рабочем примере
const templateJson = {
"Info": "OISTerra CorrTemplates",
"Palettes": [
{
"Items": paletteItems,
"id": Date.now(),
"name": paletteName,
"transp": true,
"type": 1
}
],
"ScoPaletteVersion": 1,
"corrTemplates": [
{
"Tracks": [
{
"allHeight": false,
"hTxtSet": {
"vis": true
},
"lineStyle": {
"fCol": "#ffc8ffff"
},
"trackId": 1763096947027,
"type": 0
},
{
"Logs": [
{
"logDataVers": 1,
"logId": 1763096947080,
"logParams": [
{
"contId": 1763096947014,
"contName": "РИГИС",
"id": 1763096947013,
"nm": "литология"
}
],
"logType": 3,
"trackId": 1763096947055
}
],
"allHeight": false,
"hTxtSet": {
"vis": false
},
"lineStyle": {
"fCol": "#ffc8ffff"
},
"trackId": 1763096947055,
"width": 50.69166666666666
}
],
"Version": {
"build": 3,
"fillData": 0,
"horizonView": 0,
"logData": 1,
"logRange": 0,
"plastBuild": 0,
"plastData": 0,
"stratBuild": 0,
"stratData": 0,
"stratWidth": 0,
"timeDepth": 0,
"trackWidth": 1,
"view": 7
},
"header": {
"textBlockList": {
"textBlocks": [
{
"blockId": 1763096947023,
"textItems": [
{
"blockId": 1763096947023,
"content": "[WellName]",
"font": {
"b": true
},
"itemId": 1763096947024,
"paramsHash": [
{
"id": -1,
"key": "[WellName]"
}
]
}
]
}
]
}
},
"id": Date.now() + 1000,
"name": templateName,
"plastList": {
"columnId": 1763096947025,
"isPlast": true,
"type": 0
}
}
],
"corrTemplatesVersion": 0
};

// Отображение JSON с правильным форматированием
jsonContent.textContent = JSON.stringify(templateJson, null, 2);
jsonOutput.classList.remove('hidden');
downloadBtn.classList.remove('hidden');

// Сохранение JSON для скачивания
window.generatedJson = templateJson;

showMessage('JSON шаблон успешно сгенерирован', 'success');
});

// Скачивание JSON файла
downloadBtn.addEventListener('click', function() {
if (!window.generatedJson) {
showMessage('Нет сгенерированного JSON для скачивания', 'error');
return;
}

const dataStr = JSON.stringify(window.generatedJson, 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 = 'литология_шаблон.json';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);

showMessage('Файл успешно скачан', 'success');
});

// Функция для отображения сообщений
function showMessage(message, type) {
messageArea.innerHTML = `<div class="message ${type}">${message}</div>`;

// Автоматическое скрытие сообщения через 5 секунд
setTimeout(() => {
messageArea.innerHTML = '';
}, 5000);
}

// Инициализация при загрузке страницы
window.onload = function() {
// Добавляем пример данных для демонстрации
rockDataInput.value = `1300 Суглинок
1700 Илы
1800 Торф
1400 Супесь
2300 Аргиллит
2400 Алевролит`;
};
</script>
</body>
</html>

необходимо при генерации файла убрать"corrTemplates" - не формируем она должна быть пустой вот такой:

"corrTemplates": [
],
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Генератор шаблонов литологии</title>
<style>
/* Все стили остаются без изменений */
* {
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

body {
margin: 0;
padding: 20px;
background-color: #f5f7fa;
color: #333;
}

.container {
max-width: 1200px;
margin: 0 auto;
background-color: white;
border-radius: 10px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
padding: 25px;
}

h1 {
color: #2c3e50;
text-align: center;
margin-bottom: 30px;
border-bottom: 2px solid #3498db;
padding-bottom: 15px;
}

.section {
margin-bottom: 30px;
padding: 20px;
border-radius: 8px;
background-color: #f8f9fa;
}

.section-title {
font-size: 1.3rem;
color: #2c3e50;
margin-bottom: 15px;
display: flex;
align-items: center;
}

.form-group {
margin-bottom: 15px;
}

label {
display: block;
margin-bottom: 5px;
font-weight: 600;
color: #2c3e50;
}

input, textarea, select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}

textarea {
min-height: 100px;
resize: vertical;
}

button {
background-color: #3498db;
color: white;
border: none;
padding: 12px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
font-weight: 600;
transition: background-color 0.3s;
}

button:hover {
background-color: #2980b9;
}

button.secondary {
background-color: #95a5a6;
}

button.secondary:hover {
background-color: #7f8c8d;
}

table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}

th, td {
border: 1px solid #ddd;
padding: 12px;
text-align: left;
}

th {
background-color: #3498db;
color: white;
}

tr:nth-child(even) {
background-color: #f2f2f2;
}

.preview-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-top: 20px;
}

.preview-item {
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
width: 200px;
text-align: center;
background-color: white;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}

.preview-svg {
height: 100px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10px;
}

.preview-svg svg {
max-width: 100%;
max-height: 100%;
}

.preview-code {
font-weight: bold;
color: #2c3e50;
}

.preview-name {
color: #7f8c8d;
font-size: 0.9rem;
}

.hidden {
display: none;
}

.message {
padding: 15px;
border-radius: 4px;
margin: 15px 0;
}

.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}

.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}

.actions {
display: flex;
gap: 10px;
margin-top: 20px;
}

.file-input-container {
position: relative;
overflow: hidden;
display: inline-block;
width: 100%;
}

.file-input-container input[type=file] {
position: absolute;
left: 0;
top: 0;
opacity: 0;
width: 100%;
height: 100%;
cursor: pointer;
}

.file-input-button {
display: block;
padding: 10px;
background: #f8f9fa;
border: 2px dashed #3498db;
border-radius: 4px;
text-align: center;
color: #3498db;
font-weight: 600;
}

.file-list {
margin-top: 10px;
max-height: 150px;
overflow-y: auto;
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
}

.file-item {
display: flex;
justify-content: space-between;
padding: 5px 0;
border-bottom: 1px solid #eee;
}

.file-item:last-child {
border-bottom: none;
}

.file-name {
flex-grow: 1;
}

.file-size {
color: #7f8c8d;
font-size: 0.8rem;
}

.data-table-container {
overflow-x: auto;
}

.help-text {
font-size: 0.9rem;
color: #7f8c8d;
margin-top: 5px;
}

/* Новые стили для JSON отображения */
#jsonContent {
background-color: #f8f9fa;
border: 1px solid #ddd;
border-radius: 4px;
padding: 15px;
max-height: 500px;
overflow-y: auto;
white-space: pre-wrap;
word-wrap: break-word;
font-family: 'Courier New', monospace;
font-size: 12px;
line-height: 1.4;
}
</style>
</head>
<body>
<div class="container">
<h1>Генератор шаблонов литологии</h1>

<div class="section">
<h2 class="section-title">1. Загрузка SVG-файлов с текстурами</h2>
<div class="form-group">
<label>Выберите папку с SVG-файлами:</label>
<div class="file-input-container">
<div class="file-input-button">Выбрать папку с SVG-файлами</div>
<input type="file" id="svgFolderInput" webkitdirectory multiple>
</div>
<div class="help-text">SVG-файлы должны быть названы в формате "Код_Породы Название.svg", например: "1000 Лед.svg"</div>
</div>

<div class="form-group">
<label>Путь к SVG файлам в программе:</label>
<input type="text" id="svgFilePath" placeholder="C:/Users/User/AppData/Local/OISTerra/OISTerraManager/svg/" value="C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/">
<div class="help-text">Укажите путь, который будет использоваться в сгенерированном JSON файле</div>
</div>

<div id="fileList" class="file-list hidden">
<!-- Список загруженных файлов будет здесь -->
</div>
</div>

<div class="section">
<h2 class="section-title">2. Ввод данных о породах</h2>
<div class="form-group">
<label>Добавить данные о породах:</label>
<div class="help-text">Введите данные в формате: Код_Породы Название (каждая порода с новой строки)</div>
<textarea id="rockDataInput" placeholder="1300 Суглинок
1700 Илы
1800 Торф
1400 Супесь
2300 Аргиллит
2400 Алевролит"></textarea>
</div>

<div class="actions">
<button id="parseDataBtn">Обработать данные</button>
<button id="addRockBtn" class="secondary">Добавить породу вручную</button>
</div>

<div id="rockTableContainer" class="data-table-container hidden">
<h3>Список пород:</h3>
<table id="rockTable">
<thead>
<tr>
<th>Код породы</th>
<th>Наименование породы</th>
<th>SVG файл</th>
<th>Действия</th>
</tr>
</thead>
<tbody id="rockTableBody">
<!-- Данные о породах будут здесь -->
</tbody>
</table>
</div>
</div>

<div class="section">
<h2 class="section-title">3. Предварительный просмотр</h2>
<div id="previewContainer" class="preview-container">
<!-- Предварительный просмотр будет здесь -->
</div>
</div>

<div class="section">
<h2 class="section-title">4. Генерация шаблона</h2>
<div class="form-group">
<label>Название палитры:</label>
<input type="text" id="paletteName" value="тест_лито">
</div>

<div class="form-group">
<label>Название шаблона корреляции:</label>
<input type="text" id="templateName" value="Корреляционный шаблон">
</div>

<div class="form-group">
<label>Размер patternWidth для SVG:</label>
<input type="number" id="patternWidth" value="20" min="1" max="100">
<div class="help-text">Размер маркера для всех SVG текстур (рекомендуется 20)</div>
</div>

<div class="actions">
<button id="generateBtn">Сгенерировать JSON шаблон</button>
<button id="downloadBtn" class="secondary hidden">Скачать шаблон</button>
</div>

<div id="messageArea"></div>

<div id="jsonOutput" class="hidden">
<h3>Сгенерированный JSON:</h3>
<pre id="jsonContent"></pre>
</div>
</div>
</div>

<script>
// Переменные для хранения данных
let svgFiles = {};
let rockData = [];

// Элементы DOM
const svgFolderInput = document.getElementById('svgFolderInput');
const svgFilePathInput = document.getElementById('svgFilePath');
const patternWidthInput = document.getElementById('patternWidth');
const fileList = document.getElementById('fileList');
const rockDataInput = document.getElementById('rockDataInput');
const parseDataBtn = document.getElementById('parseDataBtn');
const addRockBtn = document.getElementById('addRockBtn');
const rockTableContainer = document.getElementById('rockTableContainer');
const rockTableBody = document.getElementById('rockTableBody');
const previewContainer = document.getElementById('previewContainer');
const paletteNameInput = document.getElementById('paletteName');
const templateNameInput = document.getElementById('templateName');
const generateBtn = document.getElementById('generateBtn');
const downloadBtn = document.getElementById('downloadBtn');
const messageArea = document.getElementById('messageArea');
const jsonOutput = document.getElementById('jsonOutput');
const jsonContent = document.getElementById('jsonContent');

// Функция для преобразования SVG в формат как в оригинале
function convertSvgToOriginalFormat(svgContent) {
// Создаем временный div для парсинга SVG
const tempDiv = document.createElement('div');
tempDiv.innerHTML = svgContent;
const svgElement = tempDiv.querySelector('svg');

if (!svgElement) return svgContent;

// Создаем SVG с атрибутами как в оригинале
let originalSvg = `<?xml version='1.0' encoding='UTF-8'?>\n<svg id="svg1" height="142px" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" viewBox="0 0 282 142" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" width="282px" xmlns:dc="http://purl.org/dc/elements/1.1/" version="1.1">\n <metadata>\n <rdf:RDF>\n <cc:Work>\n <dc:title>ИСИХОГИ_Литология</dc:title>\n </cc:Work>\n </rdf:RDF>\n </metadata>\n <defs id="defs1"/>\n <g id="layer1">\n`;

// Добавляем все path элементы
const paths = svgElement.querySelectorAll('path');
paths.forEach(path => {
const id = path.getAttribute('id') || '';
const d = path.getAttribute('d') || '';
const strokeWidth = path.getAttribute('stroke-width') || '';
const style = path.getAttribute('style') || '';

originalSvg += ` <path id="${id}" d="${d}" stroke-width="${strokeWidth}" style="${style}"/>\n`;
});

// Добавляем все line элементы в формате оригинала
const lines = svgElement.querySelectorAll('line');
lines.forEach(line => {
const id = line.getAttribute('id') || '';
const x1 = line.getAttribute('x1') || '';
const y1 = line.getAttribute('y1') || '';
const x2 = line.getAttribute('x2') || '';
const y2 = line.getAttribute('y2') || '';
const strokeWidth = line.getAttribute('stroke-width') || '';
const style = line.getAttribute('style') || '';

originalSvg += ` <line id="${id}" x1="${x1}" y1="${y1}" y2="${y2}" stroke-width="${strokeWidth}" style="${style}" x2="${x2}"/>\n`;
});

// Добавляем все ellipse элементы в формате оригинала
const ellipses = svgElement.querySelectorAll('ellipse');
ellipses.forEach(ellipse => {
const id = ellipse.getAttribute('id') || '';
const cx = ellipse.getAttribute('cx') || '';
const cy = ellipse.getAttribute('cy') || '';
const rx = ellipse.getAttribute('rx') || '';
const ry = ellipse.getAttribute('ry') || '';
const strokeWidth = ellipse.getAttribute('stroke-width') || '';
const fill = ellipse.getAttribute('fill') || '';
const stroke = ellipse.getAttribute('stroke') || '';

originalSvg += ` <ellipse id="${id}" cx="${cx}" fill="${fill}" rx="${rx}" cy="${cy}" stroke-width="${strokeWidth}" stroke="${stroke}" ry="${ry}"/>\n`;
});

originalSvg += ` </g>\n</svg>\n`;

return originalSvg;
}

// Функция для правильного экранирования SVG контента для XML
function escapeSvgForXml(svgContent) {
// Сначала преобразуем SVG в формат оригинала
let convertedSvg = convertSvgToOriginalFormat(svgContent);

// Затем экранируем все специальные символы - ИСПРАВЛЕНА ОШИБКА С КАВЫЧКАМИ
let escaped = convertedSvg
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');

// Заменяем переносы строк
escaped = escaped.replace(/\n/g, ' ');

return escaped;
}

// Обработка загрузки SVG файлов
svgFolderInput.addEventListener('change', function(e) {
const files = Array.from(e.target.files);
svgFiles = {};

// Очистка списка файлов
fileList.innerHTML = '';

// Фильтрация только SVG файлов
const svgFilesList = files.filter(file => file.name.toLowerCase().endsWith('.svg'));

if (svgFilesList.length === 0) {
fileList.innerHTML = '<div>SVG файлы не найдены</div>';
fileList.classList.remove('hidden');
return;
}

// Обработка каждого SVG файла
svgFilesList.forEach(file => {
const fileName = file.name;
const codeMatch = fileName.match(/^(\d+)/);

if (codeMatch) {
const code = codeMatch[1];

const reader = new FileReader();
reader.onload = function(e) {
const svgContent = e.target.result;
svgFiles[code] = {
name: fileName,
content: svgContent
};

// Добавление в список файлов
const fileItem = document.createElement('div');
fileItem.className = 'file-item';
fileItem.innerHTML = `
<div class="file-name">${fileName}</div>
<div class="file-size">${(file.size / 1024).toFixed(2)} KB</div>
`;
fileList.appendChild(fileItem);

// Обновление таблицы пород, если код уже есть
updateRockTableWithSvg(code);
};
reader.readAsText(file);
}
});

fileList.classList.remove('hidden');
showMessage(`Загружено ${svgFilesList.length} SVG файлов`, 'success');
});

// Обработка данных о породах
parseDataBtn.addEventListener('click', function() {
const inputText = rockDataInput.value.trim();
if (!inputText) {
showMessage('Введите данные о породах', 'error');
return;
}

const lines = inputText.split('\n');
rockData = [];

lines.forEach(line => {
const trimmedLine = line.trim();
if (trimmedLine) {
const parts = trimmedLine.split(/\s+/);
if (parts.length >= 2) {
const code = parts[0];
const name = parts.slice(1).join(' ');
rockData.push({
code: code,
name: name,
svgFile: svgFiles[code] ? svgFiles[code].name : null
});
}
}
});

updateRockTable();
updatePreview();
showMessage(`Обработано ${rockData.length} пород`, 'success');
});

// Добавление породы вручную
addRockBtn.addEventListener('click', function() {
const code = prompt('Введите код породы:');
if (!code) return;

const name = prompt('Введите название породы:');
if (!name) return;

rockData.push({
code: code,
name: name,
svgFile: svgFiles[code] ? svgFiles[code].name : null
});

updateRockTable();
updatePreview();
showMessage(`Добавлена порода: ${code} ${name}`, 'success');
});

// Обновление таблицы пород
function updateRockTable() {
rockTableBody.innerHTML = '';

if (rockData.length === 0) {
rockTableContainer.classList.add('hidden');
return;
}

rockData.forEach((rock, index) => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${rock.code}</td>
<td>${rock.name}</td>
<td>${rock.svgFile || 'Не найден'}</td>
<td>
<button onclick="removeRock(${index})">Удалить</button>
</td>
`;
rockTableBody.appendChild(row);
});

rockTableContainer.classList.remove('hidden');
}

// Обновление таблицы при наличии SVG
function updateRockTableWithSvg(code) {
rockData.forEach(rock => {
if (rock.code === code) {
rock.svgFile = svgFiles[code].name;
}
});

updateRockTable();
updatePreview();
}

// Удаление породы
function removeRock(index) {
rockData.splice(index, 1);
updateRockTable();
updatePreview();
showMessage('Порода удалена', 'success');
}

// Обновление предварительного просмотра
function updatePreview() {
previewContainer.innerHTML = '';

if (rockData.length === 0) {
previewContainer.innerHTML = '<p>Нет данных для предварительного просмотра</p>';
return;
}

rockData.forEach(rock => {
const previewItem = document.createElement('div');
previewItem.className = 'preview-item';

let svgPreview = '<div>SVG не загружен</div>';
if (rock.svgFile && svgFiles[rock.code]) {
svgPreview = svgFiles[rock.code].content;
}

previewItem.innerHTML = `
<div class="preview-svg">${svgPreview}</div>
<div class="preview-code">${rock.code}</div>
<div class="preview-name">${rock.name}</div>
`;

previewContainer.appendChild(previewItem);
});
}

// Генерация JSON шаблона
generateBtn.addEventListener('click', function() {
if (rockData.length === 0) {
showMessage('Нет данных о породах для генерации шаблона', 'error');
return;
}

const paletteName = paletteNameInput.value || 'тест_лито';
const templateName = templateNameInput.value || 'Корреляционный шаблон';
const svgFilePath = svgFilePathInput.value || 'C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/';
const patternWidth = patternWidthInput.value || '20';

// Создание элементов палитры
const paletteItems = rockData.map(rock => {
let svgContent = '';

if (rock.svgFile && svgFiles[rock.code]) {
// Правильное экранирование SVG для XML
svgContent = escapeSvgForXml(svgFiles[rock.code].content);
}

// Генерация случайного ID
const id = Date.now() + Math.floor(Math.random() * 1000);

// Полный путь к файлу SVG
const fullSvgFilePath = svgFilePath + rock.svgFile;

// Точный порядок атрибутов как в оригинале
return {
"color": "#ffffffff",
"data": `<StyledFill id="${id}" name="" objectName="" brushColor="#000000" penColor="#000000" group="">\n <SvgFill patternWidth="${patternWidth}" svgContent="${svgContent}" lineWidth="0.4" objectName="" penWidth="0.4" svgFilePath="${fullSvgFilePath}"/>\n</StyledFill>\n`,
"desc": rock.name,
"value": parseInt(rock.code)
};
});

// Создание основного JSON объекта с фиксированными ID как в рабочем примере
// corrTemplates теперь пустой массив
const templateJson = {
"Info": "OISTerra CorrTemplates",
"Palettes": [
{
"Items": paletteItems,
"id": Date.now(),
"name": paletteName,
"transp": true,
"type": 1
}
],
"ScoPaletteVersion": 1,
"corrTemplates": [], // Пустой массив, как требуется
"corrTemplatesVersion": 0
};

// Отображение JSON с правильным форматированием
jsonContent.textContent = JSON.stringify(templateJson, null, 2);
jsonOutput.classList.remove('hidden');
downloadBtn.classList.remove('hidden');

// Сохранение JSON для скачивания
window.generatedJson = templateJson;

showMessage('JSON шаблон успешно сгенерирован', 'success');
});

// Скачивание JSON файла
downloadBtn.addEventListener('click', function() {
if (!window.generatedJson) {
showMessage('Нет сгенерированного JSON для скачивания', 'error');
return;
}

const dataStr = JSON.stringify(window.generatedJson, 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 = 'литология_шаблон.json';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);

showMessage('Файл успешно скачан', 'success');
});

// Функция для отображения сообщений
function showMessage(message, type) {
messageArea.innerHTML = `<div class="message ${type}">${message}</div>`;

// Автоматическое скрытие сообщения через 5 секунд
setTimeout(() => {
messageArea.innerHTML = '';
}, 5000);
}

// Инициализация при загрузке страницы
window.onload = function() {
// Добавляем пример данных для демонстрации
rockDataInput.value = `1300 Суглинок
1700 Илы
1800 Торф
1400 Супесь
2300 Аргиллит
2400 Алевролит`;
};
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Генератор шаблонов литологии</title>
<style>
/* Все стили остаются без изменений */
* {
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

body {
margin: 0;
padding: 20px;
background-color: #f5f7fa;
color: #333;
}

.container {
max-width: 1200px;
margin: 0 auto;
background-color: white;
border-radius: 10px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
padding: 25px;
}

h1 {
color: #2c3e50;
text-align: center;
margin-bottom: 30px;
border-bottom: 2px solid #3498db;
padding-bottom: 15px;
}

.section {
margin-bottom: 30px;
padding: 20px;
border-radius: 8px;
background-color: #f8f9fa;
}

.section-title {
font-size: 1.3rem;
color: #2c3e50;
margin-bottom: 15px;
display: flex;
align-items: center;
}

.form-group {
margin-bottom: 15px;
}

label {
display: block;
margin-bottom: 5px;
font-weight: 600;
color: #2c3e50;
}

input, textarea, select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}

textarea {
min-height: 100px;
resize: vertical;
}

button {
background-color: #3498db;
color: white;
border: none;
padding: 12px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
font-weight: 600;
transition: background-color 0.3s;
}

button:hover {
background-color: #2980b9;
}

button.secondary {
background-color: #95a5a6;
}

button.secondary:hover {
background-color: #7f8c8d;
}

table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}

th, td {
border: 1px solid #ddd;
padding: 12px;
text-align: left;
}

th {
background-color: #3498db;
color: white;
}

tr:nth-child(even) {
background-color: #f2f2f2;
}

.preview-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-top: 20px;
}

.preview-item {
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
width: 200px;
text-align: center;
background-color: white;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}

.preview-svg {
height: 100px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10px;
}

.preview-svg svg {
max-width: 100%;
max-height: 100%;
}

.preview-code {
font-weight: bold;
color: #2c3e50;
}

.preview-name {
color: #7f8c8d;
font-size: 0.9rem;
}

.hidden {
display: none;
}

.message {
padding: 15px;
border-radius: 4px;
margin: 15px 0;
}

.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}

.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}

.actions {
display: flex;
gap: 10px;
margin-top: 20px;
}

.file-input-container {
position: relative;
overflow: hidden;
display: inline-block;
width: 100%;
}

.file-input-container input[type=file] {
position: absolute;
left: 0;
top: 0;
opacity: 0;
width: 100%;
height: 100%;
cursor: pointer;
}

.file-input-button {
display: block;
padding: 10px;
background: #f8f9fa;
border: 2px dashed #3498db;
border-radius: 4px;
text-align: center;
color: #3498db;
font-weight: 600;
}

.file-list {
margin-top: 10px;
max-height: 150px;
overflow-y: auto;
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
}

.file-item {
display: flex;
justify-content: space-between;
padding: 5px 0;
border-bottom: 1px solid #eee;
}

.file-item:last-child {
border-bottom: none;
}

.file-name {
flex-grow: 1;
}

.file-size {
color: #7f8c8d;
font-size: 0.8rem;
}

.data-table-container {
overflow-x: auto;
}

.help-text {
font-size: 0.9rem;
color: #7f8c8d;
margin-top: 5px;
}

/* Новые стили для JSON отображения */
#jsonContent {
background-color: #f8f9fa;
border: 1px solid #ddd;
border-radius: 4px;
padding: 15px;
max-height: 500px;
overflow-y: auto;
white-space: pre-wrap;
word-wrap: break-word;
font-family: 'Courier New', monospace;
font-size: 12px;
line-height: 1.4;
}
</style>
</head>
<body>
<div class="container">
<h1>Генератор шаблонов литологии</h1>

<div class="section">
<h2 class="section-title">1. Загрузка SVG-файлов с текстурами</h2>
<div class="form-group">
<label>Выберите папку с SVG-файлами:</label>
<div class="file-input-container">
<div class="file-input-button">Выбрать папку с SVG-файлами</div>
<input type="file" id="svgFolderInput" webkitdirectory multiple>
</div>
<div class="help-text">SVG-файлы должны быть названы в формате "Код_Породы Название.svg", например: "1000 Лед.svg"</div>
</div>

<div class="form-group">
<label>Путь к SVG файлам в программе:</label>
<input type="text" id="svgFilePath" placeholder="C:/Users/User/AppData/Local/OISTerra/OISTerraManager/svg/" value="C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/">
<div class="help-text">Укажите путь, который будет использоваться в сгенерированном JSON файле</div>
</div>

<div id="fileList" class="file-list hidden">
<!-- Список загруженных файлов будет здесь -->
</div>
</div>

<div class="section">
<h2 class="section-title">2. Ввод данных о породах</h2>
<div class="form-group">
<label>Добавить данные о породах:</label>
<div class="help-text">Введите данные в формате: Код_Породы Название (каждая порода с новой строки)</div>
<textarea id="rockDataInput" placeholder="1300 Суглинок
1700 Илы
1800 Торф
1400 Супесь
2300 Аргиллит
2400 Алевролит"></textarea>
</div>

<div class="actions">
<button id="parseDataBtn">Обработать данные</button>
<button id="addRockBtn" class="secondary">Добавить породу вручную</button>
</div>

<div id="rockTableContainer" class="data-table-container hidden">
<h3>Список пород:</h3>
<table id="rockTable">
<thead>
<tr>
<th>Код породы</th>
<th>Наименование породы</th>
<th>SVG файл</th>
<th>Действия</th>
</tr>
</thead>
<tbody id="rockTableBody">
<!-- Данные о породах будут здесь -->
</tbody>
</table>
</div>
</div>

<div class="section">
<h2 class="section-title">3. Предварительный просмотр</h2>
<div id="previewContainer" class="preview-container">
<!-- Предварительный просмотр будет здесь -->
</div>
</div>

<div class="section">
<h2 class="section-title">4. Генерация шаблона</h2>
<div class="form-group">
<label>Название палитры:</label>
<input type="text" id="paletteName" value="тест_лито">
</div>

<div class="form-group">
<label>Размер patternWidth для SVG:</label>
<input type="number" id="patternWidth" value="20" min="1" max="100">
<div class="help-text">Размер маркера для всех SVG текстур (рекомендуется 20)</div>
</div>

<div class="actions">
<button id="generateBtn">Сгенерировать JSON шаблон</button>
<button id="downloadBtn" class="secondary hidden">Скачать шаблон</button>
</div>

<div id="messageArea"></div>

<div id="jsonOutput" class="hidden">
<h3>Сгенерированный JSON:</h3>
<pre id="jsonContent"></pre>
</div>
</div>
</div>

<script>
// Переменные для хранения данных
let svgFiles = {};
let rockData = [];

// Элементы DOM
const svgFolderInput = document.getElementById('svgFolderInput');
const svgFilePathInput = document.getElementById('svgFilePath');
const patternWidthInput = document.getElementById('patternWidth');
const fileList = document.getElementById('fileList');
const rockDataInput = document.getElementById('rockDataInput');
const parseDataBtn = document.getElementById('parseDataBtn');
const addRockBtn = document.getElementById('addRockBtn');
const rockTableContainer = document.getElementById('rockTableContainer');
const rockTableBody = document.getElementById('rockTableBody');
const previewContainer = document.getElementById('previewContainer');
const paletteNameInput = document.getElementById('paletteName');
const generateBtn = document.getElementById('generateBtn');
const downloadBtn = document.getElementById('downloadBtn');
const messageArea = document.getElementById('messageArea');
const jsonOutput = document.getElementById('jsonOutput');
const jsonContent = document.getElementById('jsonContent');

// Функция для преобразования SVG в формат как в оригинале
function convertSvgToOriginalFormat(svgContent) {
// Создаем временный div для парсинга SVG
const tempDiv = document.createElement('div');
tempDiv.innerHTML = svgContent;
const svgElement = tempDiv.querySelector('svg');

if (!svgElement) return svgContent;

// Создаем SVG с атрибутами как в оригинале
let originalSvg = `<?xml version='1.0' encoding='UTF-8'?>\n<svg id="svg1" height="142px" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" viewBox="0 0 282 142" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" width="282px" xmlns:dc="http://purl.org/dc/elements/1.1/" version="1.1">\n <metadata>\n <rdf:RDF>\n <cc:Work>\n <dc:title>ИСИХОГИ_Литология</dc:title>\n </cc:Work>\n </rdf:RDF>\n </metadata>\n <defs id="defs1"/>\n <g id="layer1">\n`;

// Добавляем все path элементы
const paths = svgElement.querySelectorAll('path');
paths.forEach(path => {
const id = path.getAttribute('id') || '';
const d = path.getAttribute('d') || '';
const strokeWidth = path.getAttribute('stroke-width') || '';
const style = path.getAttribute('style') || '';

originalSvg += ` <path id="${id}" d="${d}" stroke-width="${strokeWidth}" style="${style}"/>\n`;
});

// Добавляем все line элементы в формате оригинала
const lines = svgElement.querySelectorAll('line');
lines.forEach(line => {
const id = line.getAttribute('id') || '';
const x1 = line.getAttribute('x1') || '';
const y1 = line.getAttribute('y1') || '';
const x2 = line.getAttribute('x2') || '';
const y2 = line.getAttribute('y2') || '';
const strokeWidth = line.getAttribute('stroke-width') || '';
const style = line.getAttribute('style') || '';

originalSvg += ` <line id="${id}" x1="${x1}" y1="${y1}" y2="${y2}" stroke-width="${strokeWidth}" style="${style}" x2="${x2}"/>\n`;
});

// Добавляем все ellipse элементы в формате оригинала
const ellipses = svgElement.querySelectorAll('ellipse');
ellipses.forEach(ellipse => {
const id = ellipse.getAttribute('id') || '';
const cx = ellipse.getAttribute('cx') || '';
const cy = ellipse.getAttribute('cy') || '';
const rx = ellipse.getAttribute('rx') || '';
const ry = ellipse.getAttribute('ry') || '';
const strokeWidth = ellipse.getAttribute('stroke-width') || '';
const fill = ellipse.getAttribute('fill') || '';
const stroke = ellipse.getAttribute('stroke') || '';

originalSvg += ` <ellipse id="${id}" cx="${cx}" fill="${fill}" rx="${rx}" cy="${cy}" stroke-width="${strokeWidth}" stroke="${stroke}" ry="${ry}"/>\n`;
});

originalSvg += ` </g>\n</svg>\n`;

return originalSvg;
}

// Функция для правильного экранирования SVG контента для XML
function escapeSvgForXml(svgContent) {
// Сначала преобразуем SVG в формат оригинала
let convertedSvg = convertSvgToOriginalFormat(svgContent);

// Затем экранируем все специальные символы - ИСПРАВЛЕНА ОШИБКА С КАВЫЧКАМИ
let escaped = convertedSvg
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');

// Заменяем переносы строк
escaped = escaped.replace(/\n/g, ' ');

return escaped;
}

// Обработка загрузки SVG файлов
svgFolderInput.addEventListener('change', function(e) {
const files = Array.from(e.target.files);
svgFiles = {};

// Очистка списка файлов
fileList.innerHTML = '';

// Фильтрация только SVG файлов
const svgFilesList = files.filter(file => file.name.toLowerCase().endsWith('.svg'));

if (svgFilesList.length === 0) {
fileList.innerHTML = '<div>SVG файлы не найдены</div>';
fileList.classList.remove('hidden');
return;
}

// Обработка каждого SVG файла
svgFilesList.forEach(file => {
const fileName = file.name;
const codeMatch = fileName.match(/^(\d+)/);

if (codeMatch) {
const code = codeMatch[1];

const reader = new FileReader();
reader.onload = function(e) {
const svgContent = e.target.result;
svgFiles[code] = {
name: fileName,
content: svgContent
};

// Добавление в список файлов
const fileItem = document.createElement('div');
fileItem.className = 'file-item';
fileItem.innerHTML = `
<div class="file-name">${fileName}</div>
<div class="file-size">${(file.size / 1024).toFixed(2)} KB</div>
`;
fileList.appendChild(fileItem);

// Обновление таблицы пород, если код уже есть
updateRockTableWithSvg(code);
};
reader.readAsText(file);
}
});

fileList.classList.remove('hidden');
showMessage(`Загружено ${svgFilesList.length} SVG файлов`, 'success');
});

// Обработка данных о породах
parseDataBtn.addEventListener('click', function() {
const inputText = rockDataInput.value.trim();
if (!inputText) {
showMessage('Введите данные о породах', 'error');
return;
}

const lines = inputText.split('\n');
rockData = [];

lines.forEach(line => {
const trimmedLine = line.trim();
if (trimmedLine) {
const parts = trimmedLine.split(/\s+/);
if (parts.length >= 2) {
const code = parts[0];
const name = parts.slice(1).join(' ');
rockData.push({
code: code,
name: name,
svgFile: svgFiles[code] ? svgFiles[code].name : null
});
}
}
});

updateRockTable();
updatePreview();
showMessage(`Обработано ${rockData.length} пород`, 'success');
});

// Добавление породы вручную
addRockBtn.addEventListener('click', function() {
const code = prompt('Введите код породы:');
if (!code) return;

const name = prompt('Введите название породы:');
if (!name) return;

rockData.push({
code: code,
name: name,
svgFile: svgFiles[code] ? svgFiles[code].name : null
});

updateRockTable();
updatePreview();
showMessage(`Добавлена порода: ${code} ${name}`, 'success');
});

// Обновление таблицы пород
function updateRockTable() {
rockTableBody.innerHTML = '';

if (rockData.length === 0) {
rockTableContainer.classList.add('hidden');
return;
}

rockData.forEach((rock, index) => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${rock.code}</td>
<td>${rock.name}</td>
<td>${rock.svgFile || 'Не найден'}</td>
<td>
<button onclick="removeRock(${index})">Удалить</button>
</td>
`;
rockTableBody.appendChild(row);
});

rockTableContainer.classList.remove('hidden');
}

// Обновление таблицы при наличии SVG
function updateRockTableWithSvg(code) {
rockData.forEach(rock => {
if (rock.code === code) {
rock.svgFile = svgFiles[code].name;
}
});

updateRockTable();
updatePreview();
}

// Удаление породы
function removeRock(index) {
rockData.splice(index, 1);
updateRockTable();
updatePreview();
showMessage('Порода удалена', 'success');
}

// Обновление предварительного просмотра
function updatePreview() {
previewContainer.innerHTML = '';

if (rockData.length === 0) {
previewContainer.innerHTML = '<p>Нет данных для предварительного просмотра</p>';
return;
}

rockData.forEach(rock => {
const previewItem = document.createElement('div');
previewItem.className = 'preview-item';

let svgPreview = '<div>SVG не загружен</div>';
if (rock.svgFile && svgFiles[rock.code]) {
svgPreview = svgFiles[rock.code].content;
}

previewItem.innerHTML = `
<div class="preview-svg">${svgPreview}</div>
<div class="preview-code">${rock.code}</div>
<div class="preview-name">${rock.name}</div>
`;

previewContainer.appendChild(previewItem);
});
}

// Генерация JSON шаблона
generateBtn.addEventListener('click', function() {
if (rockData.length === 0) {
showMessage('Нет данных о породах для генерации шаблона', 'error');
return;
}

const paletteName = paletteNameInput.value || 'тест_лито';
const svgFilePath = svgFilePathInput.value || 'C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/';
const patternWidth = patternWidthInput.value || '20';

// Создание элементов палитры
const paletteItems = rockData.map(rock => {
let svgContent = '';

if (rock.svgFile && svgFiles[rock.code]) {
// Правильное экранирование SVG для XML
svgContent = escapeSvgForXml(svgFiles[rock.code].content);
}

// Генерация случайного ID
const id = Date.now() + Math.floor(Math.random() * 1000);

// Полный путь к файлу SVG
const fullSvgFilePath = svgFilePath + rock.svgFile;

// Точный порядок атрибутов как в оригинале
return {
"color": "#ffffffff",
"data": `<StyledFill id="${id}" name="" objectName="" brushColor="#000000" penColor="#000000" group="">\n <SvgFill patternWidth="${patternWidth}" svgContent="${svgContent}" lineWidth="0.4" objectName="" penWidth="0.4" svgFilePath="${fullSvgFilePath}"/>\n</StyledFill>\n`,
"desc": rock.name,
"value": parseInt(rock.code)
};
});

// Создание основного JSON объекта с фиксированными ID как в рабочем примере
// corrTemplates теперь пустой массив
const templateJson = {
"Info": "OISTerra CorrTemplates",
"Palettes": [
{
"Items": paletteItems,
"id": Date.now(),
"name": paletteName,
"transp": true,
"type": 1
}
],
"ScoPaletteVersion": 1,
"corrTemplates": [],
"corrTemplatesVersion": 0
};

// Отображение JSON с правильным форматированием
jsonContent.textContent = JSON.stringify(templateJson, null, 2);
jsonOutput.classList.remove('hidden');
downloadBtn.classList.remove('hidden');

// Сохранение JSON для скачивания
window.generatedJson = templateJson;

showMessage('JSON шаблон успешно сгенерирован', 'success');
});

// Скачивание JSON файла
downloadBtn.addEventListener('click', function() {
if (!window.generatedJson) {
showMessage('Нет сгенерированного JSON для скачивания', 'error');
return;
}

const dataStr = JSON.stringify(window.generatedJson, 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 = 'литология_шаблон.json';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);

showMessage('Файл успешно скачан', 'success');
});

// Функция для отображения сообщений
function showMessage(message, type) {
messageArea.innerHTML = `<div class="message ${type}">${message}</div>`;

// Автоматическое скрытие сообщения через 5 секунд
setTimeout(() => {
messageArea.innerHTML = '';
}, 5000);
}

// Инициализация при загрузке страницы
window.onload = function() {
// Добавляем пример данных для демонстрации
rockDataInput.value = `1300 Суглинок
1700 Илы
1800 Торф
1400 Супесь
2300 Аргиллит
2400 Алевролит`;
};
</script>
</body>
</html>
SELECT
class.node_name node_name,
class.node_id,
spck.lcode_spck LCODE_SPECK
FROM
mr_dba.classifier class
LEFT JOIN mr_dba.speck_spck spck ON class.lcode_speck = id_speck_spck
WHERE
class.node_id LIKE 'A0301%'
AND length(class.node_id) >= 5
AND REGEXP_LIKE(class.node_id, '^A0301[0-9]+$')
and class.node_name <> '-'
SELECT
class.node_name,
class.node_id,
class.lcode_speck,
spck.id_speck_spck,
spck.lcode_spck
FROM mr_dba.classifier class
LEFT JOIN mr_dba.speck_spck spck
ON class.lcode_speck = spck.id_speck_spck
WHERE class.node_id LIKE 'A0301%'
AND length(class.node_id) >= 5
AND REGEXP_LIKE(class.node_id, '^A0301[0-9]+$')
AND class.node_name <> '-'
ORDER BY class.node_id;
Ошибка: The requested file could not be read, typically due to permission problems that have occurred after a reference to a file was acquired.
11111
Прикрепления:
547.txt (92.3 Kb)
тттт
Прикрепления:
6619215.txt (116.1 Kb)
  • Страница 1 из 2
  • 1
  • 2
  • »
Поиск:
Новый ответ
Имя:
Текст сообщения: