26. من القاموس إلى XML والعكس
نقترح هنا استكشاف الوحدة النمطية [xml2dict] التي تسمح بتحويل:
- سلسلة XML إلى قاموس:
- قاموس إلى سلسلة XML؛
قبل ظهور jSON، كانت استجابة خدمات الويب غالبًا بتنسيق XML (لغة ترميز eXtended). من ناحية أخرى، كان بروتوكول خدمات الويب هذه غالبًا SOAP (بروتوكول معالجة الكائنات البسيطة). SOAP هو بروتوكول يعتمد على بروتوكول الويب HTTP. حاليًّا (2020)، تكون خدمات الويب في الغالب من النوع REST (نقل الحالة التمثيلية). خدمات الويب التي درسناها لا تنتمي إلى أي من هذه الأنواع، لكنها بالتأكيد أقرب إلى REST منها إلى SOAP. ومع ذلك، أفضّل وصفها بأنها من النوع «الحر» أو «المجهول» لأنها لا تتوافق مع جميع قواعد REST.
سنوضح مدى سهولة تحويل بنى العميل/الخادم jSON الخاصة بنا إلى بنى العميل/الخادم XML. يكفي استخدام الوحدة النمطية [xmltodict].
نبدأ بتثبيتها في محطة عمل Python:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\packages>pip install xmltodict
Collecting xmltodict
Using cached xmltodict-0.12.0-py2.py3-none-any.whl (9.2 kB)
Installing collected packages: xmltodict
Successfully installed xmltodict-0.12.0
وبعد الانتهاء من ذلك، سندرس من خلال مثال ما يمكننا فعله باستخدام هذه الوحدة النمطية:

النص البرمجي [xml_01] هو كما يلي:
from collections import OrderedDict
import xmltodict
# xmltodict.parse للانتقال من XML إلى القاموس. يجب أن يكون للقاموس جذر
# القاموس الناتج من النوع OrderedDict
# xmltodict.unparse للانتقال من القاموس إلى XML
def ordereddict2dict(ordered_dictionary) -> dict:
…
def transform(message: str, dictionary: dict):
# السجلات
print(f"\n{message}-------")
print(f"dictionnaire={dictionary}")
# القاموس -> xml
xml1 = xmltodict.unparse(dictionary)
print(f"xml={xml1}")
# xml -> OrderedDict
ordereddict_dictionary1 = xmltodict.parse(xml1)
print(f"ordereddict_dictionary1={ordereddict_dictionary1}")
# OrderedDict -> قاموس
print(f"dict_dictionary1={ordereddict2dict(ordereddict_dictionary1)}")
# اختبار 1
transform("test 1", {"nom": "séléné"})
# اختبار 2
transform("test 2", {"famille": {"père": {"prénom": "andré"}, "mère": {"prénom": "angèle"}, "nom": "séléné"}})
# اختبار 3
transform("test 3", {"famille": {"nom": "séléné", "père": {"prénom": "andré"}, "mère": {"prénom": "angèle"},
"hobbies": ["chant", "footing"]}})
# اختبار 4
transform("test 4", {'réponse': {
'أخطاء': ['الطريقة GET مطلوبة مع المعلمات [marié, enfants, salaire] فقط'، 'المعلمة [marié] مفقودة'،
'المعلمة [enfants] مفقودة'، 'المعلمة [salaire] مفقودة']}})
# اختبار 5
transform("test 5", {'réponse': {
'النتيجة': {'id': 0, 'متزوج': 'نعم', 'أطفال': 2, 'الراتب': 50000, 'الضريبة': 1384, 'الخصم': 384, 'الزيادة': 0,
'التخفيض': 347, 'المعدل': 0.14}}})
# اختبار 6
transform("test 6", {"root": {'liste': ["un", "deux", "trois"]}})
# اختبار 7
transform("test 7", {"root": {'liste': [{"un": [10, 11]}, {"deux": [20, 21]}, {"trois": [30, 31]}]}})
- الأسطر 14-25: تستقبل الدالة [transform] نصًا مراد كتابته [message] وقاموسًا [dictionary]؛
- السطر 16: عرض الرسالة؛
- السطر 17: يتم عرض القاموس المستلم؛
- السطور 19-20: يتم تحويل هذا القاموس إلى سلسلة XML ويتم عرضها. الطريقة التي تقوم بذلك هي [xmltodict.unparse]؛
- السطران 21-23: يتم تحويل السلسلة XML السابقة إلى قاموس ويتم عرض هذا القاموس. الطريقة التي تقوم بذلك هي [xmltodict.parse]. هذه الطريقة لا تنتج قاموسًا من النوع [dict] بل من النوع [OrderedDict] (السطر 1)؛
- السطران 24-25: يتم تحويل النوع [OrderedDict] الذي تم الحصول عليه إلى النوع [dict] باستخدام الطريقة (التي لم تُكتب بعد) [ordereddict2dict]. تعمل هذه الطريقة بطريقة تكرارية. إذا كانت بعض قيم القاموس من النوع [OrderedDict, list]، يتم فحص قيم هذه المجموعات لمعرفة ما إذا كانت هي أيضًا من النوع [OrderedDict]. إذا كان الأمر كذلك، يتم تحويلها إلى النوع [dict]. وتجدر الإشارة إلى أن الطريقة [xmltodict.parse] لا تنتج أي قاموس من النوع [dict]؛
قبل دراسة الوظائف المفقودة، دعونا نلقي نظرة على النتائج لمعرفة ما الذي نبحث عنه:
يُنتج الاختبار 1 (السطران 28-29) النتائج التالية:
test 1-------
dictionnaire={'nom': 'séléné'}
xml=<?xml version="1.0" encoding="utf-8"?>
<nom>séléné</nom>
ordereddict_dictionary1=OrderedDict([('nom', 'séléné')])
dict_dictionary1={'nom': 'séléné'}
- السطر 2: القاموس الذي تم اختباره. تجدر الإشارة إلى نقطة مهمة: تتطلب الطريقة [xml2dict.unparse] أن يكون القاموس بالصيغة {‘المفتاح’: القيمة} حيث يمكن أن يكون [valeur] بعد ذلك قاموسًا أو قائمة أو نوعًا بسيطًا؛
- السطران 3-4: السلسلة XML المستخرجة من القاموس. يسبقها الرأس [<?xml version="1.0" encoding="utf-8"?>\n] الذي يمثل عادةً السطر الأول من ملف XML؛
- السطر 5: النوع [OrderedDict] الذي تم الحصول عليه بواسطة الطريقة [xml2dict.parse] التي تتلقى السلسلة السابقة XML كمعلمة؛
- السطر 6: القاموس من النوع [dict] الذي تم الحصول عليه بتطبيق الطريقة [ordereddict2dict] على النوع السابق. نجد هنا القاموس الأصلي الموجود في السطر 2؛
جميع الاختبارات الأخرى مبنية على نفس النمط وينبغي أن تساعدكم على فهم كيفية الانتقال من قاموس إلى سلسلة XML ثم من هذه السلسلة XML إلى القاموس الأصلي.
وتعطي الاختبارات الأخرى النتائج التالية:
test 2-------
dictionnaire={'famille': {'père': {'prénom': 'andré'}, 'mère': {'prénom': 'angèle'}, 'nom': 'séléné'}}
xml=<?xml version="1.0" encoding="utf-8"?>
<famille><père><prénom>andré</prénom></père><mère><prénom>angèle</prénom></mère><nom>séléné</nom></famille>
ordereddict_dictionary1=OrderedDict([('famille', OrderedDict([('père', OrderedDict([('prénom', 'andré')])), ('mère', OrderedDict([('prénom', 'angèle')])), ('nom', 'séléné')]))])
dict_dictionary1={'famille': {'père': {'prénom': 'andré'}, 'mère': {'prénom': 'angèle'}, 'nom': 'séléné'}}
test 3-------
dictionnaire={'famille': {'nom': 'séléné', 'père': {'prénom': 'andré'}, 'mère': {'prénom': 'angèle'}, 'hobbies': ['chant', 'footing']}}
xml=<?xml version="1.0" encoding="utf-8"?>
<famille><nom>séléné</nom><père><prénom>andré</prénom></père><mère><prénom>angèle</prénom></mère><hobbies>chant</hobbies><hobbies>footing</hobbies></famille>
ordereddict_dictionary1=OrderedDict([('famille', OrderedDict([('nom', 'séléné'), ('père', OrderedDict([('prénom', 'andré')])), ('mère', OrderedDict([('prénom', 'angèle')])), ('hobbies', ['chant', 'footing'])]))])
dict_dictionary1={'famille': {'nom': 'séléné', 'père': {'prénom': 'andré'}, 'mère': {'prénom': 'angèle'}, 'hobbies': ['chant', 'footing']}}
test 4-------
dictionnaire={'réponse': {'erreurs': ['Méthode GET requise avec les seuls paramètres [marié, enfants, salaire]', 'paramètre [marié] manquant', 'paramètre [enfants] manquant', 'paramètre [salaire] manquant']}}
xml=<?xml version="1.0" encoding="utf-8"?>
<réponse><erreurs>Méthode GET requise avec les seuls paramètres [marié, enfants, salaire]</erreurs><erreurs>paramètre [marié] manquant</erreurs><erreurs>paramètre [enfants] manquant</erreurs><erreurs>paramètre [salaire] manquant</erreurs></réponse>
ordereddict_dictionary1=OrderedDict([('réponse', OrderedDict([('erreurs', ['Méthode GET requise avec les seuls paramètres [marié, enfants, salaire]', 'paramètre [marié] manquant', 'paramètre [enfants] manquant', 'paramètre [salaire] manquant'])]))])
dict_dictionary1={'réponse': {'erreurs': ['Méthode GET requise avec les seuls paramètres [marié, enfants, salaire]', 'paramètre [marié] manquant', 'paramètre [enfants] manquant', 'paramètre [salaire] manquant']}}
test 5-------
dictionnaire={'réponse': {'result': {'id': 0, 'marié': 'oui', 'enfants': 2, 'salaire': 50000, 'impôt': 1384, 'décôte': 384, 'surcôte': 0, 'réduction': 347, 'taux': 0.14}}}
xml=<?xml version="1.0" encoding="utf-8"?>
<réponse><result><id>0</id><marié>oui</marié><enfants>2</enfants><salaire>50000</salaire><impôt>1384</impôt><décôte>384</décôte><surcôte>0</surcôte><réduction>347</réduction><taux>0.14</taux></result></réponse>
ordereddict_dictionary1=OrderedDict([('réponse', OrderedDict([('result', OrderedDict([('id', '0'), ('marié', 'oui'), ('enfants', '2'), ('salaire', '50000'), ('impôt', '1384'), ('décôte', '384'), ('surcôte', '0'), ('réduction', '347'), ('taux', '0.14')]))]))])
dict_dictionary1={'réponse': {'result': {'id': '0', 'marié': 'oui', 'enfants': '2', 'salaire': '50000', 'impôt': '1384', 'décôte': '384', 'surcôte': '0', 'réduction': '347', 'taux': '0.14'}}}
test 6-------
dictionnaire={'root': {'liste': ['un', 'deux', 'trois']}}
xml=<?xml version="1.0" encoding="utf-8"?>
<root><liste>un</liste><liste>deux</liste><liste>trois</liste></root>
ordereddict_dictionary1=OrderedDict([('root', OrderedDict([('liste', ['un', 'deux', 'trois'])]))])
dict_dictionary1={'root': {'liste': ['un', 'deux', 'trois']}}
test 7-------
dictionnaire={'root': {'liste': [{'un': [10, 11]}, {'deux': [20, 21]}, {'trois': [30, 31]}]}}
xml=<?xml version="1.0" encoding="utf-8"?>
<root><liste><un>10</un><un>11</un></liste><liste><deux>20</deux><deux>21</deux></liste><liste><trois>30</trois><trois>31</trois></liste></root>
ordereddict_dictionary1=OrderedDict([('root', OrderedDict([('liste', [OrderedDict([('un', ['10', '11'])]), OrderedDict([('deux', ['20', '21'])]), OrderedDict([('trois', ['30', '31'])])])]))])
dict_dictionary1={'root': {'liste': [{'un': ['10', '11']}, {'deux': ['20', '21']}, {'trois': ['30', '31']}]}}
Process finished with exit code 0
- تُظهر السطران 23 و27 نقطة مهمة:
- السطر 23: القيم المرتبطة بمفاتيح القاموس [result] هي أرقام؛
- السطر 26: القيم المرتبطة بمفاتيح القاموس [ordereddict_dictionary1] هي سلاسل أحرف. وهذا يمثل نقطة ضعف في المكتبة [xmltodict]. فطريقتها [parse] لا تنتج سوى سلاسل أحرف. ويمكن فهم ذلك بسهولة:
- السطر 25: السلسلة XML التي يُنتج منها القاموس. في هذه السلسلة، لا توجد أي إشارة إلى نوع البيانات المُغلفة في العلامات XML. تقوم [xmltodict.parse] بما هو منطقي تمامًا: فهي تترك كل شيء على شكل سلاسل أحرف في القاموس الناتج. توجد مكتبات أخرى مشابهة لـ [xmltodict] حيث يُشار إلى نوع البيانات المُغلفة في العلامات. يمكن أن نجد، على سبيل المثال، العلامة [<enfants type=’int’>2</enfants>]؛
- ونتيجة لذلك، عند استخدام قاموس تم إنتاجه بواسطة الوحدة النمطية [xmltodict]، يجب معرفة نوع البيانات التي تحتوي عليها حتى نتمكن من الانتقال من النوع «str» إلى النوع الفعلي للبيانات؛
لنركز الآن على الطريقة [ordereddict2dict] التي تحول النوع [OrderedDict] إلى النوع [dict]:
# xmltodict.parse للانتقال من XML إلى القاموس. يجب أن يكون للقاموس جذر
# القاموس الناتج من النوع OrderedDict
# xmltodict.unparse للانتقال من القاموس إلى XML
def check(value):
# إذا كانت القيمة من النوع OrderedDict، يتم تحويلها
if isinstance(value, OrderedDict):
value2 = ordereddict2dict(value)
# إذا كانت القيمة من النوع «قائمة»، يتم تحويلها
elif isinstance(value, list):
value2 = list2list(value)
else:
# نكون أمام نوع بسيط وليس مجموعة
value2 = value
# نُرجع القيمة الجديدة
return value2
def list2list(liste: list) -> list:
# القائمة الجديدة
newliste = []
# نستخرج عناصر القائمة المعلمة
for value in liste:
# نضيف القيمة إلى القائمة الجديدة
newliste.append(check(value))
# نُرجع القائمة الجديدة
return newliste
def ordereddict2dict(ordered_dictionary: OrderedDict) -> dict:
# OrderedDict -> القاموس بطريقة متكررة
newdict = {}
for key, value in ordered_dictionary.items():
# يتم تخزين القيمة في القاموس الجديد
newdict[key] = check(value)
# نُرجع القاموس
return newdict
- السطر 30: تتلقى الدالة [ordereddict2dict] نوعًا [OrderedDict] كمعلمة؛
- السطر 32: القاموس من النوع [dict] الذي ستقوم الدالة بإرجاعه في السطر 37؛
- السطر 33: يتم استكشاف جميع المجموعات (المفتاح، القيمة) في القاموس [ordered_dictionary]؛
- السطر 35: في القاموس الجديد، يتم الاحتفاظ بالمفتاح [key]، لكن القيمة المرتبطة به ليست [value] بل [check(value)]. تتولى الدالة [check(value)] مهمة البحث، في حالة كون [value] مجموعة، عن جميع العناصر من النوع [OrderedDict] وتحويلها إلى النوع [dict]؛
تم تعريف الأسلوب [check] في الأسطر 5-16:
- السطر 5: لا يُعرف نوع [value]، ولذلك لم يكن بالإمكان كتابة [value : type]؛
- السطور 7-8: إذا كان [value] من النوع [OrderedDict]، فإننا نستدعي بشكل متكرر الدالة [ordereddict2dict] التي قمنا بتعليقها للتو؛
- الأسطر 9-11: هناك حالة أخرى محتملة وهي أن تكون [value] قائمة. في هذه الحالة، في السطر 11، يتم استدعاء الدالة [list2list] الواردة في الأسطر 19-27؛
- الأسطر 12-14: الحالة الأخيرة هي أن [value] ليس مجموعة بل نوعًا بسيطًا. الدالة [check]، شأنها شأن الدالتين [ordereddict2dict] و [list2list]، هي دالة متكررة. ومن المعروف أنه في هذه الحالة يجب دائمًا توقع الحالة التي تتوقف فيها التكرار. والأسطر 12-14 تمثل هذه الحالة؛
- السطر 16: تنتج الدالة [check] — سواء تم استدعاؤها بشكل متكرر أم لا — قيمة [valeur2] التي يجب أن تحل محل المعلمة [value] في السطر 5؛
تستفيد الطريقة [list2list] المُعرَّفة في الأسطر 19-27 من قائمة تم تمريرها كمعلمة. وستقوم باستكشافها واستبدال أي قيمة من النوع [OrderedDict] موجودة فيها بنوع [dict].
- السطر 21: القائمة الجديدة التي ستنشئها الدالة؛
- الأسطر 23-25: يتم استكشاف جميع القيم من النوع [value] الموجودة في القائمة واستبدالها بالقيمة [check(value)]. قد تحتوي هذه القيمة [value] بدورها على عناصر من النوع [list] أو [OrderedDict]. وسيتم معالجتها بشكل صحيح بواسطة الدالة التكرارية [check]؛