Update index.html
Browse files- index.html +127 -323
index.html
CHANGED
@@ -4,9 +4,8 @@
|
|
4 |
<meta charset="UTF-8">
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
<title>نظام المقارنة والتحليل المتقدم - شركة موندو لينجوا</title>
|
7 |
-
<!-- استيراد مكتبات Tailwind وFont Awesome
|
8 |
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
|
9 |
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/mammoth/1.6.0/mammoth.browser.min.js"></script>
|
10 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
11 |
<style>
|
12 |
/* ================================
|
@@ -35,14 +34,14 @@
|
|
35 |
تنسيقات النص والتظليل
|
36 |
================================= */
|
37 |
.text-comparison { line-height: 1.8; white-space: pre-wrap; }
|
38 |
-
/*
|
39 |
.highlight-minor {
|
40 |
background-color: #FFF9C4; /* أصفر فاتح */
|
41 |
padding: 0 4px;
|
42 |
border-radius: 3px;
|
43 |
font-weight: bold;
|
44 |
}
|
45 |
-
/*
|
46 |
.highlight-missing {
|
47 |
background-color: #FECACA; /* أحمر فاتح */
|
48 |
color: #B91C1C;
|
@@ -50,7 +49,7 @@
|
|
50 |
border-radius: 3px;
|
51 |
font-style: italic;
|
52 |
}
|
53 |
-
/*
|
54 |
.highlight-partial {
|
55 |
background-color: #FFCDD2; /* أحمر باهت */
|
56 |
color: #B91C1C;
|
@@ -58,9 +57,9 @@
|
|
58 |
border-radius: 3px;
|
59 |
font-weight: bold;
|
60 |
}
|
61 |
-
/* تظليل
|
62 |
.highlight-number {
|
63 |
-
background-color: #FDE68A;
|
64 |
padding: 0 4px;
|
65 |
border-radius: 3px;
|
66 |
font-weight: bold;
|
@@ -72,7 +71,7 @@
|
|
72 |
font-weight: bold;
|
73 |
}
|
74 |
.highlight-doubt {
|
75 |
-
background-color: #DBEAFE;
|
76 |
color: #1E3A8A;
|
77 |
padding: 0 4px;
|
78 |
border-radius: 3px;
|
@@ -113,9 +112,7 @@
|
|
113 |
/* ================================
|
114 |
شاشة الأخطاء والنتائج
|
115 |
================================= */
|
116 |
-
#errorsList div {
|
117 |
-
margin-bottom: 0.5rem;
|
118 |
-
}
|
119 |
</style>
|
120 |
</head>
|
121 |
<body class="bg-gradient-to-br from-gray-100 via-blue-100 to-indigo-100 min-h-screen">
|
@@ -127,7 +124,7 @@
|
|
127 |
<i class="fas fa-chart-line icon"></i> نظام المقارنة والتحليل المتقدم
|
128 |
</h1>
|
129 |
<p class="text-xl opacity-90">
|
130 |
-
<i class="fas fa-info-circle icon"></i> استخراج كافة
|
131 |
</p>
|
132 |
</div>
|
133 |
</header>
|
@@ -249,251 +246,100 @@
|
|
249 |
</div>
|
250 |
|
251 |
<!-- ================================
|
252 |
-
جافا سكريبت:
|
253 |
================================= -->
|
254 |
<script>
|
255 |
-
//
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
مهمتك مقارنة النص المصدر والنص الهدف واستخراج كافة المشاكل وفقاً للأنواع التالية:
|
260 |
-
1. إذا كانت الجملة موجودة في النصين ولكن بها اختلاف بسيط (مثال: اختلاف كلمة أو تاريخ أو معنى بسيط) فاستخدم العلامة [MINOR] ... [/MINOR].
|
261 |
-
2. إذا كانت الجملة مختلفة تماماً (أي بها 3 اختلافات أو أكثر) فتعتبر مفقودة في النص الهدف، فاستخدم العلامة [MISSING] ... [/MISSING].
|
262 |
-
3. إذا كانت الجملة موجودة في النص المصدر ولكنها ناقصة أو بها كلمات مفقودة في النص الهدف، فاستخدم العلامة [PARTIAL] ... [/PARTIAL].
|
263 |
-
يُرجى إظهار العلامات في النص المصدر لتبيان مكان الخطأ.
|
264 |
-
|
265 |
-
النص المصدر:
|
266 |
-
{source}
|
267 |
-
|
268 |
-
النص الهدف:
|
269 |
-
{target}`;
|
270 |
-
|
271 |
-
// دوال مساعدة للتعامل مع النصوص
|
272 |
-
function countWords(text) {
|
273 |
-
return text.trim().split(/\s+/).filter(word => word !== "").length;
|
274 |
}
|
275 |
-
|
276 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
277 |
}
|
278 |
-
|
279 |
-
|
280 |
-
|
281 |
-
|
282 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
283 |
}
|
284 |
-
|
285 |
-
})
|
286 |
-
|
287 |
-
function getLineNumber(text, substring) {
|
288 |
-
const index = text.indexOf(substring);
|
289 |
-
if (index === -1) return "غير محدد";
|
290 |
-
return text.substring(0, index).split("\n").length;
|
291 |
}
|
292 |
|
293 |
-
// دالة
|
294 |
-
function
|
295 |
-
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
if
|
302 |
-
|
303 |
-
const phraseRegex = new RegExp(escapeRegExp(phrase), 'g');
|
304 |
-
highlightedText = highlightedText.replace(phraseRegex, replacement);
|
305 |
-
}
|
306 |
-
}
|
307 |
-
// جملة مفقودة [MISSING]
|
308 |
-
const missingRegex = /\[MISSING\](.*?)\[\/MISSING\]/g;
|
309 |
-
while ((match = missingRegex.exec(analysisOutput)) !== null) {
|
310 |
-
const phrase = match[1].trim();
|
311 |
-
if (phrase) {
|
312 |
-
const replacement = `<span class="highlight-missing">${phrase}</span>`;
|
313 |
-
const phraseRegex = new RegExp(escapeRegExp(phrase), 'g');
|
314 |
-
highlightedText = highlightedText.replace(phraseRegex, replacement);
|
315 |
-
}
|
316 |
-
}
|
317 |
-
// جملة ناقصة [PARTIAL]
|
318 |
-
const partialRegex = /\[PARTIAL\](.*?)\[\/PARTIAL\]/g;
|
319 |
-
while ((match = partialRegex.exec(analysisOutput)) !== null) {
|
320 |
-
const phrase = match[1].trim();
|
321 |
-
if (phrase) {
|
322 |
-
const replacement = `<span class="highlight-partial">${phrase}</span>`;
|
323 |
-
const phraseRegex = new RegExp(escapeRegExp(phrase), 'g');
|
324 |
-
highlightedText = highlightedText.replace(phraseRegex, replacement);
|
325 |
-
}
|
326 |
-
}
|
327 |
-
// يمكن الإبقاء على التظليل السابق للأرقام والتواريخ واختلاف المعنى وعلامات الشك إن وجدت
|
328 |
-
const numberRegex = /<([^<>]+)>/g;
|
329 |
-
while ((match = numberRegex.exec(analysisOutput)) !== null) {
|
330 |
-
const phrase = match[1].trim();
|
331 |
-
if (phrase) {
|
332 |
-
const replacement = `<span class="highlight-number">${phrase}</span>`;
|
333 |
-
const phraseRegex = new RegExp(escapeRegExp(phrase), 'g');
|
334 |
-
highlightedText = highlightedText.replace(phraseRegex, replacement);
|
335 |
-
}
|
336 |
-
}
|
337 |
-
const meaningRegex = /\[MEANING\](.*?)\[\/MEANING\]/g;
|
338 |
-
while ((match = meaningRegex.exec(analysisOutput)) !== null) {
|
339 |
-
const phrase = match[1].trim();
|
340 |
-
if (phrase) {
|
341 |
-
const replacement = `<span class="highlight-meaning">${phrase}</span>`;
|
342 |
-
const phraseRegex = new RegExp(escapeRegExp(phrase), 'g');
|
343 |
-
highlightedText = highlightedText.replace(phraseRegex, replacement);
|
344 |
}
|
345 |
-
|
346 |
-
|
347 |
-
while ((match = doubtRegex.exec(analysisOutput)) !== null) {
|
348 |
-
const phrase = match[1].trim();
|
349 |
-
if (phrase) {
|
350 |
-
const replacement = `<span class="highlight-doubt">${phrase}</span>`;
|
351 |
-
const phraseRegex = new RegExp(escapeRegExp(phrase), 'g');
|
352 |
-
highlightedText = highlightedText.replace(phraseRegex, replacement);
|
353 |
-
}
|
354 |
-
}
|
355 |
-
return highlightedText;
|
356 |
}
|
357 |
|
358 |
-
// دالة توليد شرح تفصيلي
|
359 |
-
function generateExplanation(
|
360 |
-
let steps = [];
|
361 |
-
let match;
|
362 |
const iconMinor = `<i class="fas fa-edit text-orange-500 mr-1"></i>`;
|
363 |
const iconMissing = `<i class="fas fa-exclamation-triangle text-red-500 mr-1"></i>`;
|
364 |
const iconPartial = `<i class="fas fa-minus-circle text-red-500 mr-1"></i>`;
|
365 |
-
const iconNumber = `<i class="fas fa-hashtag text-yellow-600 mr-1"></i>`;
|
366 |
-
const iconMeaning = `<i class="fas fa-info-circle text-blue-600 mr-1"></i>`;
|
367 |
-
const iconDoubt = `<i class="fas fa-question-circle text-indigo-600 mr-1"></i>`;
|
368 |
|
369 |
-
|
370 |
-
|
371 |
-
|
372 |
-
|
373 |
-
|
374 |
-
|
375 |
-
|
376 |
-
}
|
377 |
-
|
378 |
-
// استخراج جملة مفقودة [MISSING]
|
379 |
-
const missingRegex2 = /\[MISSING\](.*?)\[\/MISSING\]/g;
|
380 |
-
while ((match = missingRegex2.exec(analysisOutput)) !== null) {
|
381 |
-
const phrase = match[1].trim();
|
382 |
-
if(phrase) {
|
383 |
-
const lineNum = getLineNumber(sourceText, phrase);
|
384 |
-
steps.push(`<li>${iconMissing} في السطر ${lineNum}: جملة مفقودة <span class="highlight-missing">${phrase}</span> غير موجودة في النص الهدف.</li>`);
|
385 |
-
}
|
386 |
-
}
|
387 |
-
// استخراج جملة ناقصة [PARTIAL]
|
388 |
-
const partialRegex2 = /\[PARTIAL\](.*?)\[\/PARTIAL\]/g;
|
389 |
-
while ((match = partialRegex2.exec(analysisOutput)) !== null) {
|
390 |
-
const phrase = match[1].trim();
|
391 |
-
if(phrase) {
|
392 |
-
const lineNum = getLineNumber(sourceText, phrase);
|
393 |
-
steps.push(`<li>${iconPartial} في السطر ${lineNum}: جملة ناقصة أو بها كلمات مفقودة <span class="highlight-partial">${phrase}</span> في النص الهدف.</li>`);
|
394 |
-
}
|
395 |
-
}
|
396 |
-
// استخراج اختلافات الأرقام/التواريخ إن وجدت
|
397 |
-
const numberRegex2 = /<([^<>]+)>/g;
|
398 |
-
while ((match = numberRegex2.exec(analysisOutput)) !== null) {
|
399 |
-
const phrase = match[1].trim();
|
400 |
-
if (phrase) {
|
401 |
-
const lineNum = getLineNumber(sourceText, phrase);
|
402 |
-
steps.push(`<li>${iconNumber} في السطر ${lineNum}: الرقم/التاريخ <span class="highlight-number">${phrase}</span> لا يتطابق بين المصدر والهدف.</li>`);
|
403 |
}
|
404 |
-
|
405 |
-
|
406 |
-
|
407 |
-
|
408 |
-
const phrase = match[1].trim();
|
409 |
-
if (phrase) {
|
410 |
-
const lineNum = getLineNumber(sourceText, phrase);
|
411 |
-
steps.push(`<li>${iconMeaning} في السطر ${lineNum}: اختلاف في المعنى مع التعبير <span class="highlight-meaning">${phrase}</span>.</li>`);
|
412 |
-
}
|
413 |
-
}
|
414 |
-
// استخراج علامات الشك أو الأخطاء البسيطة إن وجدت
|
415 |
-
const doubtRegex2 = /\[DOUBT\](.*?)\[\/DOUBT\]/g;
|
416 |
-
while ((match = doubtRegex2.exec(analysisOutput)) !== null) {
|
417 |
-
const phrase = match[1].trim();
|
418 |
-
if (phrase) {
|
419 |
-
const lineNum = getLineNumber(sourceText, phrase);
|
420 |
-
steps.push(`<li>${iconDoubt} في السطر ${lineNum}: علامة شك أو خطأ بسيط <span class="highlight-doubt">${phrase}</span> تحتاج مراجعة.</li>`);
|
421 |
-
}
|
422 |
-
}
|
423 |
-
if (steps.length === 0) {
|
424 |
return `<p class="text-green-700"><i class="fas fa-check-circle mr-2"></i> لا توجد اختلافات ملحوظة بين النصين.</p>`;
|
425 |
}
|
426 |
-
return `<ol class="list-decimal ml-6 space-y-2">${
|
427 |
}
|
428 |
|
429 |
-
// دالة
|
430 |
-
|
431 |
-
|
432 |
-
|
433 |
-
|
434 |
-
form.append('image', file);
|
435 |
-
const response = await fetch('https://demo.api4ai.cloud/ocr/v1/results', {
|
436 |
-
method: 'POST',
|
437 |
-
body: form,
|
438 |
-
headers: { 'A4A-CLIENT-APP-ID': 'sample' }
|
439 |
-
});
|
440 |
-
const data = await response.json();
|
441 |
-
text = data.results[0].entities[0].objects[0].entities[0].text;
|
442 |
-
} else if (file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
|
443 |
-
const arrayBuffer = await file.arrayBuffer();
|
444 |
-
const result = await mammoth.extractRawText({ arrayBuffer });
|
445 |
-
text = result.value;
|
446 |
-
} else {
|
447 |
-
throw new Error('نوع الملف غير مدعوم');
|
448 |
-
}
|
449 |
-
return text;
|
450 |
}
|
451 |
|
452 |
-
//
|
453 |
-
document.getElementById('sourceFile')?.addEventListener('change', async (event) => {
|
454 |
-
const file = event.target.files[0];
|
455 |
-
if (!file) return;
|
456 |
-
document.getElementById('processStatus').classList.remove('hidden');
|
457 |
-
try {
|
458 |
-
const text = await processFile(file);
|
459 |
-
document.getElementById('sourceText').value = text;
|
460 |
-
} catch (error) {
|
461 |
-
console.error('Error processing source file:', error);
|
462 |
-
addError('خطأ في معالجة ملف المصدر');
|
463 |
-
} finally {
|
464 |
-
document.getElementById('processStatus').classList.add('hidden');
|
465 |
-
}
|
466 |
-
});
|
467 |
-
document.getElementById('targetFile')?.addEventListener('change', async (event) => {
|
468 |
-
const file = event.target.files[0];
|
469 |
-
if (!file) return;
|
470 |
-
document.getElementById('processStatus').classList.remove('hidden');
|
471 |
-
try {
|
472 |
-
const text = await processFile(file);
|
473 |
-
document.getElementById('targetText').value = text;
|
474 |
-
} catch (error) {
|
475 |
-
console.error('Error processing target file:', error);
|
476 |
-
addError('خطأ في معالجة ملف الهدف');
|
477 |
-
} finally {
|
478 |
-
document.getElementById('processStatus').classList.add('hidden');
|
479 |
-
}
|
480 |
-
});
|
481 |
-
document.getElementById('sourceExtraFile')?.addEventListener('change', async (event) => {
|
482 |
-
const file = event.target.files[0];
|
483 |
-
if (!file) return;
|
484 |
-
document.getElementById('processStatus').classList.remove('hidden');
|
485 |
-
try {
|
486 |
-
const text = await processFile(file);
|
487 |
-
document.getElementById('sourceExtraText').value = text;
|
488 |
-
} catch (error) {
|
489 |
-
console.error('Error processing extra source file:', error);
|
490 |
-
addError('خطأ في معالجة ملف المصدر الإضافي');
|
491 |
-
} finally {
|
492 |
-
document.getElementById('processStatus').classList.add('hidden');
|
493 |
-
}
|
494 |
-
});
|
495 |
-
|
496 |
-
// دالة عرض الأخطاء والرسائل
|
497 |
function addError(message, type = 'error') {
|
498 |
const errorsList = document.getElementById('errorsList');
|
499 |
if (!errorsList) return;
|
@@ -506,8 +352,8 @@
|
|
506 |
errorsList.appendChild(errorDiv);
|
507 |
}
|
508 |
|
509 |
-
//
|
510 |
-
document.getElementById('submitBtn').addEventListener('click',
|
511 |
const sourceText = document.getElementById('sourceText').value;
|
512 |
const targetText = document.getElementById('targetText').value;
|
513 |
document.getElementById('errorsList').innerHTML = '';
|
@@ -519,95 +365,53 @@
|
|
519 |
return;
|
520 |
}
|
521 |
|
522 |
-
|
523 |
-
const
|
|
|
524 |
if (sourceWordCount !== targetWordCount) {
|
525 |
addError(`عدد كلمات النص المصدر (${sourceWordCount}) يختلف عن النص الهدف (${targetWordCount})`, 'warning');
|
526 |
}
|
527 |
|
528 |
-
|
529 |
-
|
530 |
-
|
531 |
-
|
532 |
-
|
533 |
-
|
534 |
-
|
535 |
-
|
536 |
-
|
537 |
-
|
538 |
-
|
539 |
-
|
540 |
-
|
541 |
-
|
542 |
-
|
543 |
-
|
544 |
-
|
545 |
-
|
546 |
-
|
547 |
-
|
548 |
-
|
549 |
-
|
550 |
-
|
551 |
-
|
552 |
-
|
553 |
-
|
554 |
-
|
555 |
-
|
556 |
-
|
557 |
-
|
558 |
-
|
559 |
-
|
560 |
-
|
561 |
-
|
562 |
-
|
563 |
-
|
564 |
-
|
565 |
-
|
566 |
-
|
567 |
-
|
568 |
-
const checkDiv = document.createElement('div');
|
569 |
-
checkDiv.className = "p-4 rounded-xl bg-green-50 text-green-700 flex items-center";
|
570 |
-
checkDiv.innerHTML = `<i class="fas fa-check-circle mr-2"></i> <span>النصوص متطابقة تماماً</span>`;
|
571 |
-
document.getElementById('errorsList').appendChild(checkDiv);
|
572 |
-
document.getElementById('sourceTextReview').innerHTML = splitIntoLines(sourceText);
|
573 |
-
document.getElementById('targetTextReview').innerHTML = splitIntoLines(targetText);
|
574 |
-
document.getElementById('explanationText').innerHTML = `<p>النصوص متطابقة ولا توجد فروقات يجب الإشارة إليها.</p>`;
|
575 |
-
} else {
|
576 |
-
// تطبيق التظليل على النص المصدر فقط
|
577 |
-
const sourceHighlighted = applyHighlights(sourceText, analysisOutput);
|
578 |
-
document.getElementById('sourceTextReview').innerHTML = splitIntoLines(sourceHighlighted);
|
579 |
-
// عرض النص الهدف بدون تظليل
|
580 |
-
document.getElementById('targetTextReview').innerHTML = splitIntoLines(targetText);
|
581 |
-
// توليد شرح تفصيلي لكل مشكلة تم استخراجها باستخدام العلامات الجديدة
|
582 |
-
const explanationHTML = generateExplanation(sourceText, analysisOutput);
|
583 |
-
document.getElementById('explanationText').innerHTML = explanationHTML;
|
584 |
-
|
585 |
-
// عرض ملخص للإختلافات بشكل عام إن وُجدت
|
586 |
-
const numDiffCount = (analysisOutput.match(/<([^<>]+)>/g) || []).length;
|
587 |
-
const minorDiffCount = (analysisOutput.match(/\[MINOR\](.*?)\[\/MINOR\]/g) || []).length;
|
588 |
-
const missingDiffCount = (analysisOutput.match(/\[MISSING\](.*?)\[\/MISSING\]/g) || []).length;
|
589 |
-
const partialDiffCount = (analysisOutput.match(/\[PARTIAL\](.*?)\[\/PARTIAL\]/g) || []).length;
|
590 |
-
const meaningDiffCount = (analysisOutput.match(/\[MEANING\](.*?)\[\/MEANING\]/g) || []).length;
|
591 |
-
const doubtDiffCount = (analysisOutput.match(/\[DOUBT\](.*?)\[\/DOUBT\]/g) || []).length;
|
592 |
-
|
593 |
-
if (minorDiffCount || missingDiffCount || partialDiffCount || numDiffCount || meaningDiffCount || doubtDiffCount) {
|
594 |
-
const summaryDiv = document.createElement('div');
|
595 |
-
summaryDiv.className = "p-4 rounded-xl bg-yellow-50 text-gray-800";
|
596 |
-
let summaryText = '<div class="font-bold mb-2">ملخص الاختلافات:</div><ul class="list-disc ml-4 space-y-1">';
|
597 |
-
if (minorDiffCount) summaryText += `<li>اختلاف بسيط: ${minorDiffCount}</li>`;
|
598 |
-
if (missingDiffCount) summaryText += `<li>جمل مفقودة: ${missingDiffCount}</li>`;
|
599 |
-
if (partialDiffCount) summaryText += `<li>جمل ناقصة: ${partialDiffCount}</li>`;
|
600 |
-
if (numDiffCount) summaryText += `<li>اختلاف في الأرقام/التواريخ: ${numDiffCount}</li>`;
|
601 |
-
if (meaningDiffCount) summaryText += `<li>اختلاف في المعنى: ${meaningDiffCount}</li>`;
|
602 |
-
if (doubtDiffCount) summaryText += `<li>علامات الشك: ${doubtDiffCount}</li>`;
|
603 |
-
summaryText += '</ul>';
|
604 |
-
summaryDiv.innerHTML = summaryText;
|
605 |
-
document.getElementById('errorsList').appendChild(summaryDiv);
|
606 |
-
}
|
607 |
-
}
|
608 |
-
} catch (error) {
|
609 |
-
document.getElementById('errorsList').innerHTML = '';
|
610 |
-
addError(`خطأ في التحليل: ${error.message}`);
|
611 |
}
|
612 |
});
|
613 |
</script>
|
|
|
4 |
<meta charset="UTF-8">
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
<title>نظام المقارنة والتحليل المتقدم - شركة موندو لينجوا</title>
|
7 |
+
<!-- استيراد مكتبات Tailwind وFont Awesome -->
|
8 |
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
|
|
|
9 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
10 |
<style>
|
11 |
/* ================================
|
|
|
34 |
تنسيقات النص والتظليل
|
35 |
================================= */
|
36 |
.text-comparison { line-height: 1.8; white-space: pre-wrap; }
|
37 |
+
/* اختلاف بسيط */
|
38 |
.highlight-minor {
|
39 |
background-color: #FFF9C4; /* أصفر فاتح */
|
40 |
padding: 0 4px;
|
41 |
border-radius: 3px;
|
42 |
font-weight: bold;
|
43 |
}
|
44 |
+
/* جملة مفقودة بالكامل */
|
45 |
.highlight-missing {
|
46 |
background-color: #FECACA; /* أحمر فاتح */
|
47 |
color: #B91C1C;
|
|
|
49 |
border-radius: 3px;
|
50 |
font-style: italic;
|
51 |
}
|
52 |
+
/* جملة ناقصة */
|
53 |
.highlight-partial {
|
54 |
background-color: #FFCDD2; /* أحمر باهت */
|
55 |
color: #B91C1C;
|
|
|
57 |
border-radius: 3px;
|
58 |
font-weight: bold;
|
59 |
}
|
60 |
+
/* تظليل إضافي للأرقام والتواريخ واختلاف المعنى وعلامات الشك */
|
61 |
.highlight-number {
|
62 |
+
background-color: #FDE68A;
|
63 |
padding: 0 4px;
|
64 |
border-radius: 3px;
|
65 |
font-weight: bold;
|
|
|
71 |
font-weight: bold;
|
72 |
}
|
73 |
.highlight-doubt {
|
74 |
+
background-color: #DBEAFE;
|
75 |
color: #1E3A8A;
|
76 |
padding: 0 4px;
|
77 |
border-radius: 3px;
|
|
|
112 |
/* ================================
|
113 |
شاشة الأخطاء والنتائج
|
114 |
================================= */
|
115 |
+
#errorsList div { margin-bottom: 0.5rem; }
|
|
|
|
|
116 |
</style>
|
117 |
</head>
|
118 |
<body class="bg-gradient-to-br from-gray-100 via-blue-100 to-indigo-100 min-h-screen">
|
|
|
124 |
<i class="fas fa-chart-line icon"></i> نظام المقارنة والتحليل المتقدم
|
125 |
</h1>
|
126 |
<p class="text-xl opacity-90">
|
127 |
+
<i class="fas fa-info-circle icon"></i> استخراج كافة الاختلافات مع التركيز على النصوص المفقودة في المصدر
|
128 |
</p>
|
129 |
</div>
|
130 |
</header>
|
|
|
246 |
</div>
|
247 |
|
248 |
<!-- ================================
|
249 |
+
جافا سكريبت: التحليل المحلي للمقارنة
|
250 |
================================= -->
|
251 |
<script>
|
252 |
+
// دالة تقسيم النص إلى جمل (مع التأكد من انتهاء كل جملة بنقطة)
|
253 |
+
function splitSentences(text) {
|
254 |
+
// تقسيم النص بواسطة النقطة (يمكن تحسينها بتقسيم علامات الاستفهام والتعجب)
|
255 |
+
return text.split('.').map(s => s.trim()).filter(s => s.length > 0).map(s => s + '.');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
256 |
}
|
257 |
+
|
258 |
+
// دالة لحساب درجة التشابه بين جملتين باستخدام تشابه جاكارد (Jaccard similarity)
|
259 |
+
function sentenceSimilarity(s1, s2) {
|
260 |
+
const words1 = s1.toLowerCase().split(/\s+/);
|
261 |
+
const words2 = s2.toLowerCase().split(/\s+/);
|
262 |
+
const set1 = new Set(words1);
|
263 |
+
const set2 = new Set(words2);
|
264 |
+
const intersection = new Set([...set1].filter(x => set2.has(x)));
|
265 |
+
const union = new Set([...set1, ...set2]);
|
266 |
+
return intersection.size / union.size;
|
267 |
}
|
268 |
+
|
269 |
+
// دالة مقارنة الجمل بين المصدر والهدف
|
270 |
+
function compareSentences(source, target) {
|
271 |
+
const sourceSentences = splitSentences(source);
|
272 |
+
const targetSentences = splitSentences(target);
|
273 |
+
let results = [];
|
274 |
+
|
275 |
+
sourceSentences.forEach(sentence => {
|
276 |
+
let maxSim = 0;
|
277 |
+
targetSentences.forEach(tSentence => {
|
278 |
+
const sim = sentenceSimilarity(sentence, tSentence);
|
279 |
+
if (sim > maxSim) maxSim = sim;
|
280 |
+
});
|
281 |
+
let type = 'match';
|
282 |
+
// تحديد النوع بناءً على درجة التشابه
|
283 |
+
if(maxSim < 0.4) {
|
284 |
+
type = 'MISSING';
|
285 |
+
} else if(maxSim < 0.8) {
|
286 |
+
type = 'PARTIAL';
|
287 |
+
} else if(maxSim < 0.95) {
|
288 |
+
type = 'MINOR';
|
289 |
}
|
290 |
+
results.push({ sentence, similarity: maxSim, type });
|
291 |
+
});
|
292 |
+
return results;
|
|
|
|
|
|
|
|
|
293 |
}
|
294 |
|
295 |
+
// دالة بناء النص المصدر مع التظليل بناءً على النتائج
|
296 |
+
function buildAnnotatedText(results) {
|
297 |
+
return results.map((item, i) => {
|
298 |
+
let annotated = item.sentence;
|
299 |
+
if(item.type === 'MINOR') {
|
300 |
+
annotated = `<span class="highlight-minor">${annotated} [MINOR]</span>`;
|
301 |
+
} else if(item.type === 'PARTIAL') {
|
302 |
+
annotated = `<span class="highlight-partial">${annotated} [PARTIAL]</span>`;
|
303 |
+
} else if(item.type === 'MISSING') {
|
304 |
+
annotated = `<span class="highlight-missing">${annotated} [MISSING]</span>`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
305 |
}
|
306 |
+
return `<div class="line-item"><span class="line-number">${i+1}:</span> <span class="line-text">${annotated}</span></div>`;
|
307 |
+
}).join('');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
308 |
}
|
309 |
|
310 |
+
// دالة توليد شرح تفصيلي لكل اختلاف
|
311 |
+
function generateExplanation(results) {
|
|
|
|
|
312 |
const iconMinor = `<i class="fas fa-edit text-orange-500 mr-1"></i>`;
|
313 |
const iconMissing = `<i class="fas fa-exclamation-triangle text-red-500 mr-1"></i>`;
|
314 |
const iconPartial = `<i class="fas fa-minus-circle text-red-500 mr-1"></i>`;
|
|
|
|
|
|
|
315 |
|
316 |
+
let explanations = results.map((item, i) => {
|
317 |
+
if(item.type === 'match') return "";
|
318 |
+
let explanation = "";
|
319 |
+
if(item.type === 'MINOR') {
|
320 |
+
explanation = `${iconMinor} السطر ${i+1}: اختلاف بسيط (درجة التشابه: ${(item.similarity*100).toFixed(1)}%)`;
|
321 |
+
} else if(item.type === 'PARTIAL') {
|
322 |
+
explanation = `${iconPartial} السطر ${i+1}: الجملة ناقصة أو بها كلمات مفقودة (درجة التشابه: ${(item.similarity*100).toFixed(1)}%)`;
|
323 |
+
} else if(item.type === 'MISSING') {
|
324 |
+
explanation = `${iconMissing} السطر ${i+1}: الجملة مفقودة بالكامل في النص ال��دف (درجة التشابه: ${(item.similarity*100).toFixed(1)}%)`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
325 |
}
|
326 |
+
return `<li>${explanation}</li>`;
|
327 |
+
}).filter(exp => exp !== "");
|
328 |
+
|
329 |
+
if(explanations.length === 0) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
330 |
return `<p class="text-green-700"><i class="fas fa-check-circle mr-2"></i> لا توجد اختلافات ملحوظة بين النصين.</p>`;
|
331 |
}
|
332 |
+
return `<ol class="list-decimal ml-6 space-y-2">${explanations.join('')}</ol>`;
|
333 |
}
|
334 |
|
335 |
+
// دالة عرض النص الهدف مع تقسيم الجمل (بدون تظليل)
|
336 |
+
function buildTargetText(text) {
|
337 |
+
return splitSentences(text).map((sentence, i) => {
|
338 |
+
return `<div class="line-item"><span class="line-number">${i+1}:</span> <span class="line-text">${sentence}</span></div>`;
|
339 |
+
}).join('');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
340 |
}
|
341 |
|
342 |
+
// دالة عرض الأخطاء والرسائل (نفس الدالة السابقة)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
343 |
function addError(message, type = 'error') {
|
344 |
const errorsList = document.getElementById('errorsList');
|
345 |
if (!errorsList) return;
|
|
|
352 |
errorsList.appendChild(errorDiv);
|
353 |
}
|
354 |
|
355 |
+
// عند الضغط على زر التحليل يتم إجراء المقارنة وعرض النتائج
|
356 |
+
document.getElementById('submitBtn').addEventListener('click', () => {
|
357 |
const sourceText = document.getElementById('sourceText').value;
|
358 |
const targetText = document.getElementById('targetText').value;
|
359 |
document.getElementById('errorsList').innerHTML = '';
|
|
|
365 |
return;
|
366 |
}
|
367 |
|
368 |
+
// تنبيه إذا اختلف عدد الكلمات (تحذير بسيط)
|
369 |
+
const sourceWordCount = sourceText.trim().split(/\s+/).filter(w => w).length;
|
370 |
+
const targetWordCount = targetText.trim().split(/\s+/).filter(w => w).length;
|
371 |
if (sourceWordCount !== targetWordCount) {
|
372 |
addError(`عدد كلمات النص المصدر (${sourceWordCount}) يختلف عن النص الهدف (${targetWordCount})`, 'warning');
|
373 |
}
|
374 |
|
375 |
+
// إظهار مؤشر التحليل
|
376 |
+
const progressDiv = document.createElement('div');
|
377 |
+
progressDiv.className = "bg-indigo-100 p-4 rounded-xl mb-4";
|
378 |
+
progressDiv.innerHTML = `<div class="flex items-center">
|
379 |
+
<div class="animate-spin h-6 w-6 border-4 border-indigo-600 rounded-full border-t-transparent mr-3"></div>
|
380 |
+
<span>جارٍ التحليل...</span>
|
381 |
+
</div>`;
|
382 |
+
document.getElementById('errorsList').appendChild(progressDiv);
|
383 |
+
|
384 |
+
// إجراء مقارنة الجمل
|
385 |
+
const results = compareSentences(sourceText, targetText);
|
386 |
+
|
387 |
+
// بناء النص المصدر المُعلّم
|
388 |
+
const annotatedSource = buildAnnotatedText(results);
|
389 |
+
document.getElementById('sourceTextReview').innerHTML = annotatedSource;
|
390 |
+
|
391 |
+
// عرض النص الهدف كما هو مع تقسيم الجمل
|
392 |
+
document.getElementById('targetTextReview').innerHTML = buildTargetText(targetText);
|
393 |
+
|
394 |
+
// بناء الشرح التفصيلي للاختلافات
|
395 |
+
const explanationHTML = generateExplanation(results);
|
396 |
+
document.getElementById('explanationText').innerHTML = explanationHTML;
|
397 |
+
|
398 |
+
// إزالة مؤشر التحليل
|
399 |
+
progressDiv.remove();
|
400 |
+
|
401 |
+
// عرض ملخص الاختلافات إن وُجدت
|
402 |
+
const minorCount = results.filter(r => r.type === 'MINOR').length;
|
403 |
+
const partialCount = results.filter(r => r.type === 'PARTIAL').length;
|
404 |
+
const missingCount = results.filter(r => r.type === 'MISSING').length;
|
405 |
+
if(minorCount || partialCount || missingCount) {
|
406 |
+
const summaryDiv = document.createElement('div');
|
407 |
+
summaryDiv.className = "p-4 rounded-xl bg-yellow-50 text-gray-800";
|
408 |
+
let summaryText = '<div class="font-bold mb-2">ملخص الاختلافات:</div><ul class="list-disc ml-4 space-y-1">';
|
409 |
+
if(minorCount) summaryText += `<li>اختلاف بسيط: ${minorCount}</li>`;
|
410 |
+
if(partialCount) summaryText += `<li>جمل ناقصة: ${partialCount}</li>`;
|
411 |
+
if(missingCount) summaryText += `<li>جمل مفقودة: ${missingCount}</li>`;
|
412 |
+
summaryText += '</ul>';
|
413 |
+
summaryDiv.innerHTML = summaryText;
|
414 |
+
document.getElementById('errorsList').appendChild(summaryDiv);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
415 |
}
|
416 |
});
|
417 |
</script>
|