<!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 - be very careful with operators
let formatted = sql
// Normalize whitespace but preserve string content
.replace(/\s+/g, ' ')
// Handle || operator carefully - no spaces inside
.replace(/\s*\|\|\s*/g, '||')
// Add spaces around other operators
.replace(/([!=<>]=?)/g, ' $1 ')
.replace(/([+\-*/%])/g, ' $1 ')
// Fix double spaces
.replace(/\s+/g, ' ')
.trim();
// Step 2: Add newlines before major keywords
reservedWords.forEach(keyword => {
if (keyword !== 'AS') {
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('SELECT')) {
indentLevel = 0;
result.push(indentStr.repeat(indentLevel) + 'SELECT');
// Process SELECT columns - split by commas but keep AS together
const afterSelect = line.substring(6).trim();
if (afterSelect) {
const columns = afterSelect.split(',').map(col => col.trim()).filter(col => col);
columns.forEach((column, index) => {
const cleanColumn = cleanOperators(column);
result.push(indentStr.repeat(indentLevel + 1) + cleanColumn + (index < columns.length - 1 ? ',' : ''));
});
}
continue;
}
else if (upperLine.startsWith('FROM')) {
indentLevel = 0;
}
else if (upperLine.startsWith('WHERE') ||
upperLine.startsWith('GROUP BY') ||
upperLine.startsWith('HAVING') ||
upperLine.startsWith('ORDER BY') ||
upperLine.startsWith('LIMIT')) {
indentLevel = 0;
}
else if (upperLine.startsWith('JOIN') ||
upperLine.startsWith('LEFT JOIN') ||
upperLine.startsWith('RIGHT JOIN') ||
upperLine.startsWith('INNER JOIN') ||
upperLine.startsWith('OUTER JOIN')) {
indentLevel = 1;
}
else if (upperLine.startsWith('ON') ||
upperLine.startsWith('AND') ||
upperLine.startsWith('OR')) {
indentLevel = 2;
}
// 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 operators in the line
line = cleanOperators(line);
result.push(indentStr.repeat(indentLevel) + line);
}
return result.join('\n');
}
function cleanOperators(text) {
return text
// Handle || operator - ensure proper spacing
.replace(/\s*\|\|\s*/g, ' || ')
// Handle dots - no spaces
.replace(/\s*\.\s*/g, '.')
// Handle commas
.replace(/\s*,\s*/g, ', ')
// Handle parentheses
.replace(/\s*\(\s*/g, '(')
.replace(/\s*\)\s*/g, ')')
// Handle comparison operators
.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, ' > ')
// Handle arithmetic operators
.replace(/\s*\*\s*/g, ' * ')
.replace(/\s*\/\s*/g, ' / ')
.replace(/\s*\+\s*/g, ' + ')
.replace(/\s*-\s*/g, ' - ')
.replace(/\s*%\s*/g, ' % ')
// Fix multiple spaces
.replace(/\s+/g, ' ')
.trim();
}
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)