import React, { useState } from 'react'; import { FileText } from 'lucide-react'; import ToolLayout from '../components/ToolLayout'; import CopyButton from '../components/CopyButton'; const BeautifierTool = () => { const [input, setInput] = useState(''); const [output, setOutput] = useState(''); const [language, setLanguage] = useState('json'); const [mode, setMode] = useState('beautify'); // 'beautify' or 'minify' const beautifyJson = (text) => { try { const parsed = JSON.parse(text); return JSON.stringify(parsed, null, 2); } catch (err) { throw new Error(`Invalid JSON: ${err.message}`); } }; const minifyJson = (text) => { try { const parsed = JSON.parse(text); return JSON.stringify(parsed); } catch (err) { throw new Error(`Invalid JSON: ${err.message}`); } }; const beautifyXml = (text) => { try { // Clean input text first let cleanText = text.trim(); if (!cleanText) { throw new Error('Empty XML input'); } // Parse and validate XML const parser = new DOMParser(); const xmlDoc = parser.parseFromString(cleanText, 'text/xml'); if (xmlDoc.getElementsByTagName('parsererror').length > 0) { throw new Error('Invalid XML syntax'); } // Format XML properly const formatXmlNode = (node, depth = 0) => { const indent = ' '.repeat(depth); let result = ''; if (node.nodeType === Node.ELEMENT_NODE) { const tagName = node.tagName; const attributes = Array.from(node.attributes) .map(attr => `${attr.name}="${attr.value}"`) .join(' '); const openTag = `<${tagName}${attributes ? ' ' + attributes : ''}`; if (node.childNodes.length === 0) { result += `${indent}${openTag} />\n`; } else { const hasTextContent = Array.from(node.childNodes) .some(child => child.nodeType === Node.TEXT_NODE && child.textContent.trim()); if (hasTextContent && node.childNodes.length === 1) { // Single text node - keep on same line result += `${indent}${openTag}>${node.textContent.trim()}\n`; } else { // Multiple children or mixed content result += `${indent}${openTag}>\n`; Array.from(node.childNodes).forEach(child => { if (child.nodeType === Node.ELEMENT_NODE) { result += formatXmlNode(child, depth + 1); } else if (child.nodeType === Node.TEXT_NODE) { const text = child.textContent.trim(); if (text) { result += `${' '.repeat(depth + 1)}${text}\n`; } } }); result += `${indent}\n`; } } } return result; }; let formatted = ''; if (xmlDoc.documentElement) { formatted = formatXmlNode(xmlDoc.documentElement); } return formatted.trim(); } catch (err) { throw new Error(`XML formatting error: ${err.message}`); } }; const minifyXml = (text) => { return text.replace(/>\s+<').replace(/\s+/g, ' ').trim(); }; const beautifyCss = (text) => { let formatted = text .replace(/\{/g, ' {\n ') .replace(/\}/g, '\n}\n') .replace(/;/g, ';\n ') .replace(/,/g, ',\n') .replace(/\n\s*\n/g, '\n') .trim(); // Clean up extra spaces and indentation const lines = formatted.split('\n'); let result = ''; let indent = 0; lines.forEach(line => { const trimmed = line.trim(); if (trimmed) { if (trimmed === '}') { indent--; } result += ' '.repeat(Math.max(0, indent)) + trimmed + '\n'; if (trimmed.endsWith('{')) { indent++; } } }); return result.trim(); }; const minifyCss = (text) => { return text .replace(/\s+/g, ' ') .replace(/;\s*}/g, '}') .replace(/\s*{\s*/g, '{') .replace(/;\s*/g, ';') .replace(/,\s*/g, ',') .trim(); }; const beautifyHtml = (text) => { // Declare formatted variable outside try/catch block let formatted = ''; try { // Clean input text first let cleanText = text.trim(); if (!cleanText) { throw new Error('Empty HTML input'); } // Self-closing tags that don't need closing tags const selfClosingTags = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr']; let indent = 0; const tab = ' '; let formatted = ''; // Better HTML parsing const tokens = cleanText.match(/<\/?[^>]+>|[^<]+/g) || []; for (let i = 0; i < tokens.length; i++) { const token = tokens[i].trim(); if (!token) continue; if (token.startsWith('<')) { // It's a tag if (token.startsWith('')) { // Self-closing tag formatted += tab.repeat(indent) + token + '\n'; } else if (token.startsWith(']+)/); const tagName = tagMatch ? tagMatch[1].toLowerCase() : ''; formatted += tab.repeat(indent) + token + '\n'; if (!selfClosingTags.includes(tagName)) { indent++; } } } else { // It's text content const textContent = token.trim(); if (textContent) { formatted += tab.repeat(indent) + textContent + '\n'; } } } return formatted.trim(); } catch (err) { // Fallback to simple formatting if advanced parsing fails formatted = ''; let indent = 0; const tab = ' '; text = text.replace(/>\n<'); const lines = text.split('\n'); lines.forEach(line => { const trimmed = line.trim(); if (trimmed) { if (trimmed.startsWith('') && !trimmed.includes(' { return text .replace(/>\s+<') .replace(/\s+/g, ' ') .trim(); }; const beautifySql = (text) => { const keywords = ['SELECT', 'FROM', 'WHERE', 'JOIN', 'INNER JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'ORDER BY', 'GROUP BY', 'HAVING', 'INSERT', 'UPDATE', 'DELETE', 'CREATE', 'ALTER', 'DROP']; let formatted = text.toUpperCase(); keywords.forEach(keyword => { const regex = new RegExp(`\\b${keyword}\\b`, 'gi'); formatted = formatted.replace(regex, `\n${keyword}`); }); return formatted .split('\n') .map(line => line.trim()) .filter(line => line) .join('\n') .trim(); }; const minifySql = (text) => { return text.replace(/\s+/g, ' ').trim(); }; const handleProcess = () => { try { let result = ''; if (mode === 'beautify') { switch (language) { case 'json': result = beautifyJson(input); break; case 'xml': result = beautifyXml(input); break; case 'css': result = beautifyCss(input); break; case 'html': result = beautifyHtml(input); break; case 'sql': result = beautifySql(input); break; default: result = input; } } else { switch (language) { case 'json': result = minifyJson(input); break; case 'xml': result = minifyXml(input); break; case 'css': result = minifyCss(input); break; case 'html': result = minifyHtml(input); break; case 'sql': result = minifySql(input); break; default: result = input; } } setOutput(result); } catch (err) { setOutput(`Error: ${err.message}`); } }; const clearAll = () => { setInput(''); setOutput(''); // Force re-render to ensure clean state setTimeout(() => { setInput(''); setOutput(''); }, 10); }; const loadSample = () => { // Clear first to ensure clean state setInput(''); setOutput(''); const samples = { json: '{"name":"John Doe","age":30,"city":"New York","address":{"street":"123 Main St","zipCode":"10001"},"hobbies":["reading","coding","traveling"],"isActive":true}', xml: 'John Doe30john@example.com
123 Main StNew York10001
Jane Smith25jane@example.com
', css: 'body{margin:0;padding:0;font-family:Arial,sans-serif;}h1{color:#333;font-size:24px;margin-bottom:10px;}.container{max-width:1200px;margin:0 auto;padding:20px;}.button{background-color:#007bff;color:white;padding:10px 20px;border:none;border-radius:4px;cursor:pointer;}.button:hover{background-color:#0056b3;}', html: 'Sample Page

Welcome to Our Website

About Us

This is a sample paragraph with bold text and italic text.

Sample Image
', sql: 'SELECT u.id, u.name, u.email, p.title, p.created_at FROM users u INNER JOIN posts p ON u.id = p.user_id WHERE u.age > 18 AND p.status = "published" ORDER BY p.created_at DESC, u.name ASC LIMIT 10;' }; // Set sample with slight delay to ensure clean state setTimeout(() => { setInput(samples[language] || ''); }, 50); }; return ( {/* Language and Mode Selection */}
{/* Controls */}
{/* Input/Output Grid */}
{/* Input */}