İki belge arasındaki farklı (Silinen, Eklenen, Değişiklik) anlamak için yapılan çalışmadır
Excel dosyada karşılaştırma
Word dosyada karşılaştırma
Text dosyasında karşılaştırma
Fark dosyasını indirerek inceleyebilirsiniz.
Aşağıdaki kodun not defterine yapıştırıp html uzantılı olarak kaydederek çalıştırabilirsiniz.
HTML Kod:
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Belge Karşılaştırma Aracı</title>
<!-- Office ve ZIP dosyalarını tarayıcıda çözmek için JSZip kütüphanesi -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<style>
:root {
--bg: #f4f6f8; --shadow: 0 3px 8px rgba(0,0,0,0.06); --radius: 6px;
--border-color: #dce1e6; --panel-bg: #ffffff;
--primary: #0d6efd; --primary-hover: #0b5ed7;
--success: #198754; --danger: #dc3545; --warning: #ffc107;
--secondary: #6c757d; --secondary-hover: #5c636a;
}
* { margin: 0; padding: 0; box-sizing: border-box; font-family: "Segoe UI", Arial, sans-serif; }
body { background: var(--bg); padding: 12px; color: #333; }
/* --- BAŞLIK ALANI --- */
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 15px;
background: #fff;
border: 2px solid #2c3e50;
border-radius: var(--radius);
box-shadow: var(--shadow);
margin-bottom: 15px;
gap: 15px;
}
.header .left { display: flex; align-items: center; gap: 10px; }
.header .logo img { height: 38px; object-fit: contain; }
.header .title1 { font-size: 16px; font-weight: 700; color: #2c3e50; }
.header .title2 { font-size: 10px; color: #7f8c8d; font-weight: 600; }
.header-center {
display: flex;
align-items: center;
gap: 15px;
flex-grow: 1;
justify-content: center;
}
.file-box {
background: #f8f9fa;
border: 1px solid var(--border-color);
padding: 6px 12px;
border-radius: 6px;
font-size: 12px;
flex: 1;
max-width: 260px;
text-align: center;
}
.file-box label { font-weight: bold; color: #2c3e50; display: block; margin-bottom: 2px; }
.file-box input[type="file"] { font-size: 11px; cursor: pointer; width: 100%; }
.meta-info { font-size: 10px; color: #6c757d; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Sağ Buton Grubu ve AI Tasarımcı İmza Alanı */
.header-right {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 3px;
margin-left: auto;
}
.button-group {
display: flex;
align-items: center;
gap: 8px;
}
.designer-credit {
font-size: 10px;
color: #adb5bd; /* Açık Gri */
white-space: nowrap;
font-weight: 500;
}
.btn { background-color: var(--primary); color: white; border: none; padding: 8px 14px; border-radius: 4px; cursor: pointer; font-weight: 600; font-size: 13px; transition: all 0.2s; white-space: nowrap; }
.btn:hover { background-color: var(--primary-hover); }
.btn-danger { background-color: var(--danger); }
.btn-success { background-color: var(--success); }
.btn-secondary { background-color: var(--secondary); }
.btn-secondary:hover { background-color: var(--secondary-hover); }
.hidden { display: none !important; }
.main-container { width: 100%; display: flex; flex-direction: column; gap: 15px; }
.report-section { background-color: var(--panel-bg); border: 1px solid var(--border-color); border-radius: var(--radius); padding: 20px; box-shadow: var(--shadow); }
.status-banner {
padding: 10px 15px;
margin-bottom: 15px;
border-radius: 4px;
font-size: 14px;
text-align: center;
line-height: 1.3;
background-color: #ffffff;
border: 1px solid #000000;
color: #333333;
}
.status-match { color: #0f5132; border-color: #0f5132; }
/* --- FİLTRELENEBİLİR AÇIKLAMA ALANI (LEGEND) --- */
.legend-box {
display: flex; justify-content: center; gap: 15px; background: #f1f3f5; padding: 8px 10px;
border-bottom: 1px solid #dee2e6; font-size: 12px; color: #333; font-weight: bold;
}
.legend-item {
display: flex; align-items: center; gap: 6px; cursor: pointer; padding: 4px 10px; border-radius: 4px; transition: background 0.2s;
border: 1px solid transparent;
}
.legend-item:hover { background: #e2e6ea; }
.legend-item.active { background: #e2e6ea; border-color: #adb5bd; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.legend-color { width: 12px; height: 12px; border-radius: 3px; }
.bg-red { background-color: #dc3545; }
.bg-green { background-color: #198754; }
.bg-yellow { background-color: #ffc107; }
/* --- BEYAZ ARKA PLANLI YAN YANA GÖRÜNÜM --- */
.diff-container { border: 1px solid var(--border-color); background: #ffffff; border-radius: 6px; overflow: hidden; margin-top: 10px; box-shadow: 0 2px 4px rgba(0,0,0,0.04); }
.diff-header { display: flex; background: #e9ecef; font-weight: bold; border-bottom: 1px solid var(--border-color); }
.diff-header .half { flex: 1; padding: 10px; text-align: center; color: #2c3e50; font-size: 13px; }
.diff-header .half:first-child { border-right: 1px solid var(--border-color); }
.diff-rows { max-height: 550px; overflow-y: auto; }
.diff-row { display: flex; border-bottom: 1px solid #f1f3f5; font-family: 'Consolas', 'Courier New', monospace; font-size: 12px; line-height: 1.4; }
.diff-half { flex: 1; display: flex; min-width: 0; }
.diff-half:first-child { border-right: 1px solid var(--border-color); }
.line-num { width: 45px; min-width: 45px; background: #f8f9fa; color: #adb5bd; text-align: right; padding: 4px 8px; user-select: none; border-right: 1px solid #e9ecef; }
.code-text { padding: 4px 10px; flex: 1; white-space: pre-wrap; word-break: break-all; }
/* KESİN RENK AYRIMLARI VE ARKA PLANLAR */
.diff-del { background-color: rgba(220, 53, 69, 0.25); }
.diff-del .code-text { color: #a71d2a; }
.diff-del .line-num { color: #a71d2a; background-color: rgba(220, 53, 69, 0.15); }
.diff-add { background-color: rgba(40, 167, 69, 0.25); }
.diff-add .code-text { color: #116329; }
.diff-add .line-num { color: #116329; background-color: rgba(40, 167, 69, 0.15); }
.diff-change { background-color: rgba(255, 193, 7, 0.3); }
.diff-change .code-text { color: #533f04; }
.diff-change .line-num { color: #856404; background-color: rgba(255, 193, 7, 0.2); }
/* Noktasal Karakter Vurguları */
.char-del { background-color: rgba(220, 53, 69, 0.4); color: #721c24; font-weight: bold; border-radius: 2px; padding: 0 1px; }
.char-add { background-color: rgba(40, 167, 69, 0.4); color: #155724; font-weight: bold; border-radius: 2px; padding: 0 1px; }
.diff-normal .code-text { color: #333333; }
.diff-empty { background-color: #fafbfc; }
@media (max-width: 1100px) { .header { flex-direction: column; align-items: stretch; } .header-center { flex-direction: column; align-items: stretch; } .header-right { align-items: center; } .file-box { max-width: none; } }
</style>
</head>
<body>
<div class="header">
<div class="left">
<div class="logo"><img src="logo.jpg" alt="Logo" onerror="this.style.display='none'"></div>
<div>
<div class="title1">Belge Karşılaştırma Aracı</div>
<div class="title2">Helpdesk Koordinatörlüğü | MYO MÜDÜRLÜĞÜ</div>
</div>
</div>
<div class="header-center">
<div class="file-box">
<label for="fileInput1">1. Belge (Kaynak / Orijinal)</label>
<input type="file" id="fileInput1" accept=".txt,.html,.htm,.csv,.xml,.json,.js,.py,.docx,.xlsx">
<div class="meta-info" id="meta1">Dosya seçilmedi</div>
</div>
<div class="file-box">
<label for="fileInput2">2. Belge (Hedef / Karşılaştırılacak)</label>
<input type="file" id="fileInput2" accept=".txt,.html,.htm,.csv,.xml,.json,.js,.py,.docx,.xlsx">
<div class="meta-info" id="meta2">Dosya seçilmedi</div>
</div>
</div>
<div class="header-right">
<div class="button-group">
<button id="btnCompare" class="btn btn-success">Karşılaştır</button>
<button id="btnDownloadReport" class="btn btn-secondary hidden">Fark Listesini İndir</button>
<button id="btnReset" class="btn btn-danger">Yeni İşlem</button>
</div>
<div class="designer-credit">AI Web Designer: Muhammed Yusuf OLGUN</div>
</div>
</div>
<div class="main-container">
<div id="comparisonContainer" class="report-section hidden">
<div id="comparisonResult"></div>
</div>
</div>
<script>
let file1Content = "";
let file2Content = "";
let file1Name = "";
let file2Name = "";
let diffTextReport = "";
let currentFilter = 'all';
let rawDiffRows = [];
const fileInput1 = document.getElementById('fileInput1');
const fileInput2 = document.getElementById('fileInput2');
const meta1 = document.getElementById('meta1');
const meta2 = document.getElementById('meta2');
const btnCompare = document.getElementById('btnCompare');
const comparisonContainer = document.getElementById('comparisonContainer');
const comparisonResult = document.getElementById('comparisonResult');
const btnDownloadReport = document.getElementById('btnDownloadReport');
const btnReset = document.getElementById('btnReset');
const formatBytes = (bytes) => {
if (bytes === 0) return '0 Bytes';
const k = 1024, sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};
const downloadTextFile = (filename, content) => {
const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url; a.download = filename; a.click();
URL.revokeObjectURL(url);
};
const escapeHtml = (str) => {
if (str === null || str === undefined) return "";
return String(str).replace(/[&<>'"]/g, tag => ({ '&': '&', '<': '<', '>': '>', "'": ''', '"': '"' }[tag]));
};
async function parseOfficeFile(file) {
const extension = file.name.split('.').pop().toLowerCase();
const arrayBuffer = await file.arrayBuffer();
if (extension === 'docx') {
const zip = new JSZip();
const contents = await zip.loadAsync(arrayBuffer);
const docXml = await contents.file("word/document.xml").async("string");
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(docXml, "text/xml");
const paragraphs = xmlDoc.getElementsByTagName("w:p");
let textLines = [];
for (let p of paragraphs) {
const textNodes = p.getElementsByTagName("w:t");
let pText = "";
for (let t of textNodes) {
pText += t.textContent;
}
textLines.push(pText);
}
return textLines.join("\n");
}
else if (extension === 'xlsx') {
const zip = new JSZip();
const contents = await zip.loadAsync(arrayBuffer);
let sharedStrings = [];
if (contents.file("xl/sharedStrings.xml")) {
const sStrXml = await contents.file("xl/sharedStrings.xml").async("string");
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(sStrXml, "text/xml");
const tNodes = xmlDoc.getElementsByTagName("t");
for (let node of tNodes) {
sharedStrings.push(node.textContent);
}
}
if (contents.file("xl/worksheets/sheet1.xml")) {
const sheetXml = await contents.file("xl/worksheets/sheet1.xml").async("string");
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(sheetXml, "text/xml");
const rows = xmlDoc.getElementsByTagName("row");
let excelRows = [];
for (let row of rows) {
let rowData = [];
let cells = row.getElementsByTagName("c");
for (let cell of cells) {
let vNode = cell.getElementsByTagName("v")[0];
if (vNode) {
let val = vNode.textContent;
let tAttr = cell.getAttribute("t");
if (tAttr === "s" && sharedStrings[parseInt(val)] !== undefined) {
val = sharedStrings[parseInt(val)];
}
rowData.push(val);
}
}
if(rowData.length > 0) excelRows.push(rowData.join(" \t "));
}
return excelRows.join("\n");
}
return "Excel içeriği okunamadı.";
}
else {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => resolve(e.target.result);
reader.onerror = reject;
reader.readAsText(file);
});
}
}
fileInput1.addEventListener('change', async (e) => {
const file = e.target.files[0];
if (file) {
file1Name = file.name;
try {
file1Content = await parseOfficeFile(file);
meta1.innerHTML = `${file1Name} (${formatBytes(file.size)}) - <span style="color:green;">Yüklendi</span>`;
} catch (err) {
meta1.innerHTML = `${file1Name} - <span style="color:red;">Hata!</span>`;
}
}
});
fileInput2.addEventListener('change', async (e) => {
const file = e.target.files[0];
if (file) {
file2Name = file.name;
try {
file2Content = await parseOfficeFile(file);
meta2.innerHTML = `${file2Name} (${formatBytes(file.size)}) - <span style="color:green;">Yüklendi</span>`;
} catch (err) {
meta2.innerHTML = `${file2Name} - <span style="color:red;">Hata!</span>`;
}
}
});
function getCharacterDiff(str1, str2) {
const m = str1.length;
const n = str2.length;
const dp = Array(m + 1).fill(0).map(() => Array(n + 1).fill(0));
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (str1[i - 1] === str2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
let i = m, j = n;
const diff = [];
while (i > 0 || j > 0) {
if (i > 0 && j > 0 && str1[i - 1] === str2[j - 1]) {
diff.unshift({ type: 'equal', val: str1[i - 1] });
i--; j--;
} else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
diff.unshift({ type: 'add', val: str2[j - 1] });
j--;
} else if (i > 0 && (j === 0 || dp[i][j - 1] < dp[i - 1][j])) {
diff.unshift({ type: 'del', val: str1[i - 1] });
i--;
}
}
let html1 = '';
let html2 = '';
for (let item of diff) {
const escaped = escapeHtml(item.val);
if (item.type === 'equal') {
html1 += escaped;
html2 += escaped;
} else if (item.type === 'del') {
html1 += `<span class="char-del">${escaped}</span>`;
} else if (item.type === 'add') {
html2 += `<span class="char-add">${escaped}</span>`;
}
}
return { html1, html2 };
}
function computeGlobalDiff(text1, text2) {
const lines1 = text1.split(/\r?\n/);
const lines2 = text2.split(/\r?\n/);
const m = lines1.length;
const n = lines2.length;
const dp = Array(m + 1).fill(0).map(() => Array(n + 1).fill(0));
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (lines1[i - 1] === lines2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
let i = m, j = n;
const lcsDiff = [];
while (i > 0 || j > 0) {
if (i > 0 && j > 0 && lines1[i - 1] === lines2[j - 1]) {
lcsDiff.unshift({ type: 'equal', l1: lines1[i - 1], l2: lines2[j - 1], line1: i, line2: j });
i--; j--;
} else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
lcsDiff.unshift({ type: 'add', l2: lines2[j - 1], line2: j });
j--;
} else if (i > 0 && (j === 0 || dp[i][j - 1] < dp[i - 1][j])) {
lcsDiff.unshift({ type: 'del', l1: lines1[i - 1], line1: i });
i--;
}
}
const rows = [];
let idx = 0;
while (idx < lcsDiff.length) {
const item = lcsDiff[idx];
if (item.type === 'equal') {
rows.push({ type: 'normal', l1: escapeHtml(item.l1), lineNum1: item.line1, l2: escapeHtml(item.l2), lineNum2: item.line2 });
idx++;
} else {
let dels = [];
let adds = [];
while (idx < lcsDiff.length && lcsDiff[idx].type !== 'equal') {
if (lcsDiff[idx].type === 'del') dels.push(lcsDiff[idx]);
if (lcsDiff[idx].type === 'add') adds.push(lcsDiff[idx]);
idx++;
}
const maxLen = Math.max(dels.length, adds.length);
for (let k = 0; k < maxLen; k++) {
const d = dels[k];
const a = adds[k];
if (d && a) {
const res = getCharacterDiff(d.l1, a.l2);
rows.push({
type: 'change',
l1: res.html1,
lineNum1: d.line1,
l2: res.html2,
lineNum2: a.line2
});
} else if (d && !a) {
const res = getCharacterDiff(d.l1, "");
rows.push({ type: 'del', l1: res.html1, lineNum1: d.line1, l2: "", lineNum2: null });
} else if (!d && a) {
const res = getCharacterDiff("", a.l2);
rows.push({ type: 'add', l1: "", lineNum1: null, l2: res.html2, lineNum2: a.line2 });
}
}
}
}
return rows;
}
btnCompare.addEventListener('click', () => {
if (!file1Content || !file2Content) {
alert("Lütfen karşılaştırma yapmak için her iki belgeyi de seçip yüklenmesini bekleyiniz.");
return;
}
comparisonContainer.classList.remove('hidden');
btnDownloadReport.classList.remove('hidden');
diffTextReport = `BELGE KARŞILAŞTIRMA VE NOKTASAL FARK RAPORU\nTarih: ${new Date().toLocaleString('tr-TR')}\n1. Belge: ${file1Name}\n2. Belge: ${file2Name}\n==================================================\n\n`;
rawDiffRows = computeGlobalDiff(file1Content, file2Content);
let diffCount = rawDiffRows.filter(r => r.type !== 'normal').length;
rawDiffRows.forEach(r => {
if (r.type === 'change') {
diffTextReport += `[DEĞİŞİKLİK]\n 1. Belge: ${r.l1.replace(/<\/?[^>]+(>|$)/g, "")}\n 2. Belge: ${r.l2.replace(/<\/?[^>]+(>|$)/g, "")}\n\n`;
} else if (r.type === 'del') {
diffTextReport += `[SİLİNEN]\n İçerik: ${r.l1.replace(/<\/?[^>]+(>|$)/g, "")}\n\n`;
} else if (r.type === 'add') {
diffTextReport += `[EKLENEN]\n İçerik: ${r.l2.replace(/<\/?[^>]+(>|$)/g, "")}\n\n`;
}
});
currentFilter = 'all';
renderComparisonView(diffCount);
comparisonContainer.scrollIntoView({ behavior: 'smooth' });
});
function renderComparisonView(diffCount) {
let summaryHtml = "";
if (diffCount === 0) {
summaryHtml = `<div class="status-banner status-match"><strong>🟢 İKİ BELGE BİREBİR AYNI</strong><br>Belgeler arasında hiçbir fark bulunamadı.</div>`;
} else {
summaryHtml = `<div class="status-banner"><strong>🔴 NOKTASAL FARK TESPİT EDİLDİ</strong><br>Toplam <b>${diffCount}</b> fark noktası bulundu. Silinenler Kırmızı, Eklenenler Yeşil, Değişiklikler Sarı renkle vurgulanmıştır.</div>`;
}
let rowsHtml = "";
rawDiffRows.forEach(row => {
if (currentFilter !== 'all') {
if (currentFilter === 'del' && row.type !== 'del') return;
if (currentFilter === 'add' && row.type !== 'add') return;
if (currentFilter === 'change' && row.type !== 'change') return;
}
if (row.type === 'normal') {
rowsHtml += `
<div class="diff-row diff-normal">
<div class="diff-half"><span class="line-num">${row.lineNum1 || ''}</span><span class="code-text">${row.l1}</span></div>
<div class="diff-half"><span class="line-num">${row.lineNum2 || ''}</span><span class="code-text">${row.l2}</span></div>
</div>`;
} else if (row.type === 'change') {
rowsHtml += `
<div class="diff-row">
<div class="diff-half diff-change"><span class="line-num">${row.lineNum1 || ''}</span><span class="code-text">${row.l1}</span></div>
<div class="diff-half diff-change"><span class="line-num">${row.lineNum2 || ''}</span><span class="code-text">${row.l2}</span></div>
</div>`;
} else if (row.type === 'del') {
rowsHtml += `
<div class="diff-row">
<div class="diff-half diff-del"><span class="line-num">${row.lineNum1 || ''}</span><span class="code-text">${row.l1}</span></div>
<div class="diff-half diff-empty"><span class="line-num"></span><span class="code-text"></span></div>
</div>`;
} else if (row.type === 'add') {
rowsHtml += `
<div class="diff-row">
<div class="diff-half diff-empty"><span class="line-num"></span><span class="code-text"></span></div>
<div class="diff-half diff-add"><span class="line-num">${row.lineNum2 || ''}</span><span class="code-text">${row.l2}</span></div>
</div>`;
}
});
let diffHtml = `
<div class="diff-container">
<div class="legend-box">
<div class="legend-item ${currentFilter === 'del' ? 'active' : ''}" onclick="filterDiff('del')"><div class="legend-color bg-red"></div> Silinen</div>
<div class="legend-item ${currentFilter === 'add' ? 'active' : ''}" onclick="filterDiff('add')"><div class="legend-color bg-green"></div> Eklenen</div>
<div class="legend-item ${currentFilter === 'change' ? 'active' : ''}" onclick="filterDiff('change')"><div class="legend-color bg-yellow"></div> Değişiklik</div>
<div class="legend-item ${currentFilter === 'all' ? 'active' : ''}" onclick="filterDiff('all')" style="font-weight:bold; color:#0d6efd;">[Tümünü Göster]</div>
</div>
<div class="diff-header">
<div class="half">1. BELGE (Kaynak): ${escapeHtml(file1Name)}</div>
<div class="half">2. BELGE (Hedef): ${escapeHtml(file2Name)}</div>
</div>
<div class="diff-rows">${rowsHtml}</div>
</div>`;
comparisonResult.innerHTML = summaryHtml + diffHtml;
}
window.filterDiff = function(type) {
currentFilter = type;
let diffCount = rawDiffRows.filter(r => r.type !== 'normal').length;
renderComparisonView(diffCount);
}
btnDownloadReport.addEventListener('click', () => {
downloadTextFile(`Noktasal_Belge_Fark_Raporu_${Date.now()}.txt`, diffTextReport);
});
btnReset.addEventListener('click', () => location.reload());
</script>
</body>
</html>



