Spaces:
Sleeping
Sleeping
""" | |
وحدة مقارنة الملفات | |
""" | |
import streamlit as st | |
import pandas as pd | |
import random | |
from datetime import datetime | |
import difflib | |
class FileComparisonApp: | |
""" | |
وحدة مقارنة الملفات للنظام | |
""" | |
def __init__(self): | |
""" | |
تهيئة وحدة مقارنة الملفات | |
""" | |
# تهيئة حالة الجلسة الخاصة بمقارنة الملفات إذا لم تكن موجودة | |
if 'comparison_history' not in st.session_state: | |
# إنشاء بيانات تجريبية لسجل المقارنات | |
st.session_state.comparison_history = self._generate_sample_history() | |
def run(self): | |
""" | |
تشغيل وحدة مقارنة الملفات | |
""" | |
st.markdown("<h2 class='module-title'>وحدة مقارنة الملفات</h2>", unsafe_allow_html=True) | |
# إنشاء تبويبات لمقارنة الملفات المختلفة | |
tabs = st.tabs(["مقارنة المستندات", "مقارنة جداول البيانات", "مقارنة النصوص", "سجل المقارنات"]) | |
with tabs[0]: | |
self._render_document_comparison() | |
with tabs[1]: | |
self._render_spreadsheet_comparison() | |
with tabs[2]: | |
self._render_text_comparison() | |
with tabs[3]: | |
self._render_comparison_history() | |
def _render_document_comparison(self): | |
""" | |
عرض واجهة مقارنة المستندات | |
""" | |
st.markdown("### مقارنة المستندات") | |
st.markdown("مقارنة مستندات PDF أو Word مع إظهار الاختلافات بشكل مرئي") | |
# رفع الملفات | |
col1, col2 = st.columns(2) | |
with col1: | |
st.markdown("#### المستند الأول") | |
file1 = st.file_uploader("اختر المستند الأول", type=["pdf", "docx"], key="doc_file1") | |
with col2: | |
st.markdown("#### المستند الثاني") | |
file2 = st.file_uploader("اختر المستند الثاني", type=["pdf", "docx"], key="doc_file2") | |
# خيارات المقارنة | |
st.markdown("#### خيارات المقارنة") | |
col1, col2, col3 = st.columns(3) | |
with col1: | |
st.checkbox("تجاهل التنسيق", value=False, key="ignore_formatting") | |
with col2: | |
st.checkbox("تجاهل الهوامش", value=True, key="ignore_headers_footers") | |
with col3: | |
st.checkbox("إظهار التغييرات الطفيفة", value=False, key="show_minor_changes") | |
# زر المقارنة | |
if st.button("مقارنة المستندات", key="compare_docs_btn"): | |
if file1 is not None and file2 is not None: | |
# محاكاة عملية المقارنة | |
with st.spinner("جاري مقارنة المستندات..."): | |
# محاكاة وقت المعالجة | |
import time | |
time.sleep(2) | |
st.success("تمت مقارنة المستندات بنجاح!", icon="✅") | |
# عرض ملخص المقارنة | |
st.markdown("#### ملخص المقارنة") | |
comparison_summary = { | |
"عدد الصفحات المتطابقة": random.randint(3, 8), | |
"عدد الصفحات المختلفة": random.randint(1, 5), | |
"إجمالي التغييرات": random.randint(10, 50), | |
"إضافات": random.randint(5, 20), | |
"حذف": random.randint(5, 20), | |
"تعديلات": random.randint(5, 20) | |
} | |
summary_df = pd.DataFrame({ | |
"المعيار": list(comparison_summary.keys()), | |
"القيمة": list(comparison_summary.values()) | |
}) | |
st.dataframe(summary_df, use_container_width=True, hide_index=True) | |
# عرض صورة توضيحية للمقارنة | |
st.markdown("#### معاينة المقارنة") | |
st.info("سيتم عرض معاينة المقارنة هنا مع تمييز الاختلافات بألوان مختلفة", icon="ℹ️") | |
# زر تنزيل تقرير المقارنة | |
st.download_button( | |
label="تنزيل تقرير المقارنة", | |
data=b"محتوى وهمي لتقرير المقارنة", | |
file_name="document_comparison_report.pdf", | |
mime="application/pdf", | |
key="download_doc_comparison" | |
) | |
# إضافة المقارنة إلى السجل | |
new_entry = { | |
'id': len(st.session_state.comparison_history) + 1, | |
'type': 'مستند', | |
'file1': file1.name, | |
'file2': file2.name, | |
'changes': comparison_summary["إجمالي التغييرات"], | |
'date': datetime.now().strftime("%Y-%m-%d %H:%M") | |
} | |
st.session_state.comparison_history.insert(0, new_entry) | |
else: | |
st.warning("يرجى رفع المستندين للمقارنة", icon="⚠️") | |
def _render_spreadsheet_comparison(self): | |
""" | |
عرض واجهة مقارنة جداول البيانات | |
""" | |
st.markdown("### مقارنة جداول البيانات") | |
st.markdown("مقارنة ملفات Excel أو CSV مع تحديد الاختلافات في البيانات") | |
# رفع الملفات | |
col1, col2 = st.columns(2) | |
with col1: | |
st.markdown("#### الجدول الأول") | |
sheet1 = st.file_uploader("اختر الجدول الأول", type=["xlsx", "csv"], key="sheet_file1") | |
with col2: | |
st.markdown("#### الجدول الثاني") | |
sheet2 = st.file_uploader("اختر الجدول الثاني", type=["xlsx", "csv"], key="sheet_file2") | |
# خيارات المقارنة | |
st.markdown("#### خيارات المقارنة") | |
col1, col2 = st.columns(2) | |
with col1: | |
st.checkbox("مقارنة الصيغ", value=True, key="compare_formulas") | |
st.checkbox("مقارنة التنسيق", value=False, key="compare_formatting") | |
with col2: | |
st.checkbox("تجاهل الأوراق المخفية", value=True, key="ignore_hidden_sheets") | |
st.checkbox("مقارنة حسب القيمة فقط", value=False, key="compare_by_value") | |
# زر المقارنة | |
if st.button("مقارنة الجداول", key="compare_sheets_btn"): | |
if sheet1 is not None and sheet2 is not None: | |
# محاكاة عملية المقارنة | |
with st.spinner("جاري مقارنة جداول البيانات..."): | |
# محاكاة وقت المعالجة | |
import time | |
time.sleep(2) | |
st.success("تمت مقارنة جداول البيانات بنجاح!", icon="✅") | |
# عرض ملخص المقارنة | |
st.markdown("#### ملخص المقارنة") | |
comparison_summary = { | |
"عدد الخلايا المتطابقة": random.randint(500, 2000), | |
"عدد الخلايا المختلفة": random.randint(50, 200), | |
"صفوف مضافة": random.randint(5, 20), | |
"صفوف محذوفة": random.randint(5, 20), | |
"أعمدة مضافة": random.randint(1, 5), | |
"أعمدة محذوفة": random.randint(1, 5) | |
} | |
summary_df = pd.DataFrame({ | |
"المعيار": list(comparison_summary.keys()), | |
"القيمة": list(comparison_summary.values()) | |
}) | |
st.dataframe(summary_df, use_container_width=True, hide_index=True) | |
# عرض جدول الاختلافات | |
st.markdown("#### الاختلافات الرئيسية") | |
# إنشاء بيانات تجريبية للاختلافات | |
diff_data = [] | |
for i in range(10): | |
diff_data.append({ | |
"الورقة": f"الورقة {random.randint(1, 3)}", | |
"الخلية": f"{chr(65 + random.randint(0, 5))}{random.randint(1, 20)}", | |
"القيمة الأولى": f"القيمة {i} (الأولى)", | |
"القيمة الثانية": f"القيمة {i} (الثانية)", | |
"نوع الاختلاف": random.choice(["تعديل", "إضافة", "حذف"]) | |
}) | |
diff_df = pd.DataFrame(diff_data) | |
st.dataframe(diff_df, use_container_width=True) | |
# زر تنزيل تقرير المقارنة | |
st.download_button( | |
label="تنزيل تقرير المقارنة", | |
data=b"محتوى وهمي لتقرير المقارنة", | |
file_name="spreadsheet_comparison_report.xlsx", | |
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", | |
key="download_sheet_comparison" | |
) | |
# إضافة المقارنة إلى السجل | |
new_entry = { | |
'id': len(st.session_state.comparison_history) + 1, | |
'type': 'جدول بيانات', | |
'file1': sheet1.name, | |
'file2': sheet2.name, | |
'changes': comparison_summary["عدد الخلايا المختلفة"], | |
'date': datetime.now().strftime("%Y-%m-%d %H:%M") | |
} | |
st.session_state.comparison_history.insert(0, new_entry) | |
else: | |
st.warning("يرجى رفع جدولي البيانات للمقارنة", icon="⚠️") | |
def _render_text_comparison(self): | |
""" | |
عرض واجهة مقارنة النصوص | |
""" | |
st.markdown("### مقارنة النصوص") | |
st.markdown("مقارنة نصوص مباشرة أو ملفات نصية مع إظهار الاختلافات") | |
# اختيار طريقة الإدخال | |
input_method = st.radio( | |
"طريقة الإدخال", | |
options=["إدخال مباشر", "رفع ملفات"], | |
horizontal=True, | |
key="text_input_method" | |
) | |
if input_method == "إدخال مباشر": | |
# إدخال النصوص مباشرة | |
col1, col2 = st.columns(2) | |
with col1: | |
st.markdown("#### النص الأول") | |
text1 = st.text_area("أدخل النص الأول", height=200, key="direct_text1") | |
with col2: | |
st.markdown("#### النص الثاني") | |
text2 = st.text_area("أدخل النص الثاني", height=200, key="direct_text2") | |
# زر المقارنة | |
if st.button("مقارنة النصوص", key="compare_direct_text_btn"): | |
if text1 and text2: | |
self._show_text_comparison_results(text1, text2) | |
else: | |
st.warning("يرجى إدخال النصين للمقارنة", icon="⚠️") | |
else: | |
# رفع ملفات نصية | |
col1, col2 = st.columns(2) | |
with col1: | |
st.markdown("#### الملف الأول") | |
text_file1 = st.file_uploader("اختر الملف الأول", type=["txt", "md", "json", "xml", "html", "css", "js", "py"], key="text_file1") | |
with col2: | |
st.markdown("#### الملف الثاني") | |
text_file2 = st.file_uploader("اختر الملف الثاني", type=["txt", "md", "json", "xml", "html", "css", "js", "py"], key="text_file2") | |
# زر المقارنة | |
if st.button("مقارنة الملفات النصية", key="compare_text_files_btn"): | |
if text_file1 is not None and text_file2 is not None: | |
# قراءة محتوى الملفات | |
text1 = text_file1.getvalue().decode("utf-8") | |
text2 = text_file2.getvalue().decode("utf-8") | |
self._show_text_comparison_results(text1, text2, text_file1.name, text_file2.name) | |
else: | |
st.warning("يرجى رفع الملفين النصيين للمقارنة", icon="⚠️") | |
def _show_text_comparison_results(self, text1, text2, file1_name=None, file2_name=None): | |
""" | |
عرض نتائج مقارنة النصوص | |
""" | |
# محاكاة عملية المقارنة | |
with st.spinner("جاري مقارنة النصوص..."): | |
# استخدام difflib للمقارنة | |
d = difflib.Differ() | |
diff = list(d.compare(text1.splitlines(), text2.splitlines())) | |
# عرض ملخص المقارنة | |
st.markdown("#### ملخص المقارنة") | |
# حساب الإحصائيات | |
added = len([line for line in diff if line.startswith('+ ')]) | |
removed = len([line for line in diff if line.startswith('- ')]) | |
changed = len([line for line in diff if line.startswith('? ')]) | |
unchanged = len([line for line in diff if line.startswith(' ')]) | |
comparison_summary = { | |
"الأسطر المتطابقة": unchanged, | |
"الأسطر المضافة": added, | |
"الأسطر المحذوفة": removed, | |
"الأسطر المتغيرة": changed, | |
"إجمالي الاختلافات": added + removed + changed | |
} | |
summary_df = pd.DataFrame({ | |
"المعيار": list(comparison_summary.keys()), | |
"القيمة": list(comparison_summary.values()) | |
}) | |
st.dataframe(summary_df, use_container_width=True, hide_index=True) | |
# عرض الاختلافات | |
st.markdown("#### عرض الاختلافات") | |
# تنسيق الاختلافات بألوان مختلفة | |
html_diff = [] | |
for line in diff: | |
if line.startswith('+ '): | |
html_diff.append(f'<div style="background-color: #d4edda; color: #155724; padding: 2px 5px; margin: 2px 0; border-radius: 3px;">{line}</div>') | |
elif line.startswith('- '): | |
html_diff.append(f'<div style="background-color: #f8d7da; color: #721c24; padding: 2px 5px; margin: 2px 0; border-radius: 3px;">{line}</div>') | |
elif line.startswith('? '): | |
html_diff.append(f'<div style="background-color: #fff3cd; color: #856404; padding: 2px 5px; margin: 2px 0; border-radius: 3px;">{line}</div>') | |
else: | |
html_diff.append(f'<div style="padding: 2px 5px; margin: 2px 0;">{line}</div>') | |
st.markdown(''.join(html_diff), unsafe_allow_html=True) | |
# زر تنزيل تقرير المقارنة | |
st.download_button( | |
label="تنزيل تقرير المقارنة", | |
data='\n'.join(diff), | |
file_name="text_comparison_report.txt", | |
mime="text/plain", | |
key="download_text_comparison" | |
) | |
# إضافة المقارنة إلى السجل | |
new_entry = { | |
'id': len(st.session_state.comparison_history) + 1, | |
'type': 'نص', | |
'file1': file1_name or "نص مباشر 1", | |
'file2': file2_name or "نص مباشر 2", | |
'changes': comparison_summary["إجمالي الاختلافات"], | |
'date': datetime.now().strftime("%Y-%m-%d %H:%M") | |
} | |
st.session_state.comparison_history.insert(0, new_entry) | |
def _render_comparison_history(self): | |
""" | |
عرض سجل المقارنات | |
""" | |
st.markdown("### سجل المقارنات") | |
st.markdown("عرض سجل المقارنات السابقة مع إمكانية البحث والتصفية") | |
# خيارات التصفية | |
col1, col2 = st.columns(2) | |
with col1: | |
filter_type = st.multiselect( | |
"نوع المقارنة", | |
options=["الكل", "مستند", "جدول بيانات", "نص"], | |
default=["الكل"] | |
) | |
with col2: | |
date_range = st.selectbox( | |
"النطاق الزمني", | |
options=["الكل", "اليوم", "الأسبوع الماضي", "الشهر الماضي"] | |
) | |
# تطبيق التصفية | |
filtered_history = st.session_state.comparison_history | |
if "الكل" not in filter_type: | |
filtered_history = [h for h in filtered_history if h['type'] in filter_type] | |
# تحويل البيانات إلى DataFrame | |
if filtered_history: | |
df = pd.DataFrame(filtered_history) | |
df = df[['date', 'type', 'file1', 'file2', 'changes']] | |
df.columns = ['التاريخ', 'النوع', 'الملف الأول', 'الملف الثاني', 'عدد الاختلافات'] | |
# عرض الجدول | |
st.dataframe(df, use_container_width=True) | |
else: | |
st.info("لا توجد مقارنات تطابق معايير التصفية", icon="ℹ️") | |
def _generate_sample_history(self): | |
""" | |
إنشاء بيانات تجريبية لسجل المقارنات | |
""" | |
comparison_types = ['مستند', 'جدول بيانات', 'نص'] | |
file_names = { | |
'مستند': [ | |
'مواصفات_المشروع_v1.pdf', 'مواصفات_المشروع_v2.pdf', | |
'العقد_النهائي.docx', 'العقد_المعدل.docx', | |
'تقرير_المشروع_2025.pdf', 'تقرير_المشروع_2025_مراجعة.pdf' | |
], | |
'جدول بيانات': [ | |
'جدول_الكميات_v1.xlsx', 'جدول_الكميات_v2.xlsx', | |
'تحليل_الأسعار_2025.xlsx', 'تحليل_الأسعار_2025_محدث.xlsx', | |
'الميزانية_التقديرية.xlsx', 'الميزانية_النهائية.xlsx' | |
], | |
'نص': [ | |
'ملاحظات_الاجتماع.txt', 'ملاحظات_الاجتماع_محدثة.txt', | |
'شروط_المناقصة.txt', 'شروط_المناقصة_معدلة.txt', | |
'config.json', 'config_new.json' | |
] | |
} | |
history = [] | |
for i in range(15): | |
comp_type = random.choice(comparison_types) | |
# اختيار ملفين من نفس النوع | |
file_index = random.randint(0, 2) * 2 | |
file1 = file_names[comp_type][file_index] | |
file2 = file_names[comp_type][file_index + 1] | |
# تحديد تاريخ عشوائي خلال الشهر الماضي | |
days_ago = random.randint(0, 30) | |
entry_date = (datetime.now() - pd.Timedelta(days=days_ago)).strftime("%Y-%m-%d %H:%M") | |
entry = { | |
'id': i + 1, | |
'type': comp_type, | |
'file1': file1, | |
'file2': file2, | |
'changes': random.randint(10, 100), | |
'date': entry_date | |
} | |
history.append(entry) | |
# ترتيب السجل حسب التاريخ (الأحدث أولاً) | |
history.sort(key=lambda x: x['date'], reverse=True) | |
return history | |