<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Offline SQL Formatter</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
}
.editor-container {
display: flex;
gap: 20px;
margin-bottom: 20px;
}
@media (max-width: 768px) {
.editor-container {
flex-direction: column;
}
}
.editor-panel {
flex: 1;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #555;
}
textarea {
width: 100%;
height: 300px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-family: 'Courier New', monospace;
font-size: 14px;
resize: vertical;
}
.controls {
margin: 20px 0;
text-align: center;
}
button {
background-color: #007cba;
color: white;
border: none;
padding: 10px 20px;
margin: 0 5px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
button:hover {
background-color: #005a87;
}
.options {
margin: 10px 0;
text-align: center;
}
select {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
margin: 0 5px;
}
.status {
text-align: center;
margin: 10px 0;
color: #666;
font-style: italic;
}
</style>
</head>
<body>
<div class="container">
<h1>Offline SQL Formatter</h1>
<div class="options">
<label for="dialect">SQL Dialect:</label>
<select id="dialect">
<option value="sql">Standard SQL</option>
<option value="mysql">MySQL</option>
<option value="postgresql">PostgreSQL</option>
</select>
<label for="indent">Indent:</label>
<select id="indent">
<option value="2">2 spaces</option>
<option value="4" selected>4 spaces</option>
<option value="tab">Tabs</option>
</select>
</div>
<div class="editor-container">
<div class="editor-panel">
<label for="inputSQL">Input SQL:</label>
<textarea id="inputSQL" placeholder="Enter your SQL query here..."></textarea>
</div>
<div class="editor-panel">
<label for="outputSQL">Formatted SQL:</label>
<textarea id="outputSQL" readonly placeholder="Formatted SQL will appear here..."></textarea>
</div>
</div>
<div class="controls">
<button id="formatBtn">Format SQL</button>
<button id="clearBtn">Clear</button>
<button id="copyBtn">Copy Formatted</button>
</div>
<div class="status" id="status">Ready</div>
</div>
<script>
// Simple and reliable SQL Formatter
const SQLFormatter = (function() {
'use strict';
const reservedWords = [
'SELECT', 'FROM', 'WHERE', 'GROUP BY', 'HAVING', 'ORDER BY', 'LIMIT',
'INSERT', 'UPDATE', 'DELETE', 'CREATE', 'ALTER', 'DROP', 'VALUES',
'SET', 'AND', 'OR', 'NOT', 'IN', 'BETWEEN', 'LIKE', 'IS', 'NULL',
'JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'INNER JOIN', 'OUTER JOIN', 'ON',
'AS', 'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'EXISTS', 'DISTINCT',
'COUNT', 'SUM', 'AVG', 'MIN', 'MAX', 'ASC', 'DESC', 'UNION', 'ALL'
];
function formatSQL(sql, options = {}) {
const {
indent: indentStr = ' ',
uppercase = true
} = options;
// Step 1: Normalize the SQL - preserve strings and add basic spacing
let formatted = sql
// Normalize whitespace
.replace(/\s+/g, ' ')
// Add space around operators but preserve dots and double operators
.replace(/([!=<>]=?|\|\|)/g, ' $1 ')
.replace(/([+\-*/%])/g, ' $1 ')
// Fix double spaces
.replace(/\s+/g, ' ')
.trim();
// Step 2: Add newlines before major keywords
reservedWords.forEach(keyword => {
const regex = new RegExp(`\\b${keyword}\\b`, 'gi');
formatted = formatted.replace(regex, `\n${keyword}`);
});
// Step 3: Split into lines and apply indentation
const lines = formatted.split('\n').filter(line => line.trim());
let indentLevel = 0;
const result = [];
for (let i = 0; i < lines.length; i++) {
let line = lines[i].trim();
const upperLine = line.toUpperCase();
// Handle indentation levels
if (upperLine.startsWith('FROM') ||
upperLine.startsWith('WHERE') ||
upperLine.startsWith('GROUP BY') ||
upperLine.startsWith('HAVING') ||
upperLine.startsWith('ORDER BY') ||
upperLine.startsWith('LIMIT')) {
indentLevel = 1;
} else if (upperLine.startsWith('SELECT')) {
indentLevel = 1;
} else if (upperLine.startsWith('JOIN') ||
upperLine.startsWith('LEFT JOIN') ||
upperLine.startsWith('RIGHT JOIN') ||
upperLine.startsWith('INNER JOIN') ||
upperLine.startsWith('OUTER JOIN')) {
indentLevel = 2;
} else if (upperLine.startsWith('ON') ||
upperLine.startsWith('AND') ||
upperLine.startsWith('OR')) {
indentLevel = 3;
}
// Apply uppercase to keywords
if (uppercase) {
reservedWords.forEach(keyword => {
const regex = new RegExp(`\\b${keyword}\\b`, 'gi');
line = line.replace(regex, keyword.toUpperCase());
});
}
// Clean up the line
line = line
.replace(/\s*\.\s*/g, '.')
.replace(/\s*,\s*/g, ', ')
.replace(/\s*\(\s*/g, '(')
.replace(/\s*\)\s*/g, ')')
.replace(/\s*=\s*/g, ' = ')
.replace(/\s*!=\s*/g, ' != ')
.replace(/\s*<>\s*/g, ' <> ')
.replace(/\s*<=\s*/g, ' <= ')
.replace(/\s*>=\s*/g, ' >= ')
.replace(/\s*\|\|\s*/g, ' || ')
.replace(/\s*~\s*/g, ' ~ ')
.replace(/\s*\^\s*/g, ' ^ ')
.replace(/\s*&\s*/g, ' & ')
.replace(/\s*\|\s*/g, ' | ')
.replace(/\s*\+\s*/g, ' + ')
.replace(/\s*-\s*/g, ' - ')
.replace(/\s*\*\s*/g, ' * ')
.replace(/\s*\/\s*/g, ' / ')
.replace(/\s*%\s*/g, ' % ')
.replace(/\s+/g, ' ')
.trim();
result.push(indentStr.repeat(indentLevel) + line);
}
return result.join('\n');
}
return {
format: formatSQL
};
})();
// DOM handling
document.addEventListener('DOMContentLoaded', function() {
const inputSQL = document.getElementById('inputSQL');
const outputSQL = document.getElementById('outputSQL');
const formatBtn = document.getElementById('formatBtn');
const clearBtn = document.getElementById('clearBtn');
const copyBtn = document.getElementById('copyBtn');
const dialectSelect = document.getElementById('dialect');
const indentSelect = document.getElementById('indent');
const status = document.getElementById('status');
function formatSQL() {
const sql = inputSQL.value.trim();
if (!sql) {
outputSQL.value = '';
status.textContent = 'Please enter SQL to format';
return;
}
try {
const indent = indentSelect.value === 'tab' ? '\t' : ' '.repeat(parseInt(indentSelect.value));
const formatted = SQLFormatter.format(sql, {
indent: indent,
uppercase: true
});
outputSQL.value = formatted;
status.textContent = 'SQL formatted successfully';
} catch (error) {
outputSQL.value = 'Error formatting SQL: ' + error.message;
status.textContent = 'Formatting failed';
}
}
function clearAll() {
inputSQL.value = '';
outputSQL.value = '';
status.textContent = 'Cleared';
}
function copyFormatted() {
if (!outputSQL.value.trim()) {
status.textContent = 'No formatted SQL to copy';
return;
}
outputSQL.select();
outputSQL.setSelectionRange(0, 99999);
try {
navigator.clipboard.writeText(outputSQL.value);
status.textContent = 'Formatted SQL copied to clipboard';
} catch (err) {
document.execCommand('copy');
status.textContent = 'Formatted SQL copied to clipboard';
}
}
formatBtn.addEventListener('click', formatSQL);
clearBtn.addEventListener('click', clearAll);
copyBtn.addEventListener('click', copyFormatted);
inputSQL.addEventListener('keydown', function(e) {
if (e.ctrlKey && e.key === 'Enter') {
formatSQL();
}
});
// Test SQL
inputSQL.value = `SELECT c.first_name || ' ' || c.last_name as full_name, o.order_date, p.product_name, p.price * o.quantity as total_price FROM customers c INNER JOIN orders o ON c.customer_id = o.customer_id LEFT JOIN products p ON o.product_id = p.product_id WHERE o.order_date >= '2023-01-01' AND p.category = 'Electronics' ORDER BY o.order_date DESC, total_price ASC`;
});
</script>
</body>
</html>
For further actions, you may consider blocking this person and/or reporting abuse
Top comments (0)