Skip to content

26. از فرهنگ لغت به XML و بالعکس

در اینجا هدف ما بررسی ماژول [xml2dict] است که امکان تبدیل را فراهم می‌کند:

  • یک رشته XML به یک دیکشنری:
  • یک دیکشنری به یک رشته XML؛

پیش از ظهور jSON، پاسخ‌های سرویس‌های وب اغلب در XML (زبان نشانه‌گذاری eXtended) بودند. علاوه بر این، پروتکلی که توسط این سرویس‌های وب استفاده می‌شد اغلب SOAP (پروتکل پردازش اشیاء ساده) بود. SOAP پروتکلی است مبتنی بر پروتکل وب HTTP. در حال حاضر (۲۰۲۰)، سرویس‌های وب معمولاً از نوع REST (انتقال حالت نمایشی) هستند. سرویس‌های وبی که ما مطالعه کرده‌ایم به هیچ‌کدام از این انواع تعلق ندارند، اما قطعاً به REST نزدیک‌تر از SOAP هستند. با این حال، من ترجیح می‌دهم آنها را به عنوان انواع «باز» یا «نامشخص» توصیف کنم، زیرا از تمام قوانین REST پیروی نمی‌کنند.

ما قصد داریم نشان دهیم تبدیل معماری‌های کلاینت/سرور jSON به معماری‌های کلاینت/سرور XML چقدر آسان است. تنها کاری که باید انجام دهید استفاده از ماژول [xmltodict] است.

ابتدا آن را در یک ترمینال پایتون نصب می‌کنیم:


(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

پس از انجام این کار، به سراغ یک مثال می‌رویم تا ببینیم با این ماژول چه کارهایی می‌توانیم انجام دهیم:

Image

اسکریپت [xml_01] به شرح زیر است:


from collections import OrderedDict

import xmltodict


# xmltodict.parse برای عبور از XML به فرهنگ لغت. فرهنگ لغت باید یک ریشه داشته باشد
# واژه‌نامهٔ حاصل از نوع QZXW2HTMLCT3JkZXJlZERpY3QX است.
#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 -> dict
    print(f"dict_dictionary1={ordereddict2dict(ordereddict_dictionary1)}")


# آزمون ۱
transform("test 1", {"nom": "séléné"})
# آزمون ۲
transform("test 2", {"famille": {"père": {"prénom": "andré"}, "mère": {"prénom": "angèle"}, "nom": "séléné"}})
# آزمون ۳
transform("test 3", {"famille": {"nom": "séléné", "père": {"prénom": "andré"}, "mère": {"prénom": "angèle"},
                                 "hobbies": ["chant", "footing"]}})
# آزمون ۴
transform("test 4", {'réponse': {
    'خطاها': 'روش GET تنها با پارامترهای QZXW2HTMLBW21hcmnDqSwgZW5mYW50cy، c2FsYWlyZV0ZQX مورد نیاز است'، 'پارامتر [marié] مفقود است'،
                ''پارامتر [enfants] مفقود است', 'پارامتر [salaire] مفقود است']}})
# آزمایش ۵
transform("test 5", {'réponse': {
    'result': {'id': 0, 'married': 'yes', 'children': 2, 'salary': 50000, 'tax': 1384, 'discount': 384, 'surcharge': 0,
               ''کاهش': 347, 'نرخ': 0.14}}})
# test 6
transform("test 6", {"root": {'liste': ["un", "deux", "trois"]}})
# آزمون ۷
transform("test 7", {"root": {'liste': [{"un": [10, 11]}, {"deux": [20, 21]}, {"trois": [30, 31]}]}})
  • خطوط ۱۴–۲۵: تابع [transform] یک رشته متنی برای نوشتن [message] و یک دیکشنری [dictionary] را می‌پذیرد؛
  • خط 16: پیام نمایش داده می‌شود؛
  • خط ۱۷: فرهنگ لغت دریافت‌شده نمایش داده می‌شود؛
  • خطوط ۱۹–۲۰: این واژه‌نامه به رشته XML تبدیل می‌شود که سپس نمایش داده می‌شود. روشی که این تبدیل را انجام می‌دهد [xmltodict.unparse] است؛
  • خطوط ۲۱–۲۳: رشته قبلی XML به یک فرهنگ لغت تبدیل شده و سپس نمایش داده می‌شود. متدی که این عملیات را انجام می‌دهد [xmltodict.parse] است. این متد یک فرهنگ لغت از نوع [dict] تولید نمی‌کند، بلکه از نوع [OrderedDict] (خط 1) تولید می‌کند؛
  • خطوط ۲۴–۲۵: نوع حاصل [OrderedDict] با استفاده از متد (هنوز ننوشته) [ordereddict2dict] به [dict] تبدیل می‌شود. این متد به‌صورت بازگشتی عمل می‌کند. اگر هر یک از مقادیر در دیکشنری از نوع [OrderedDict, list] باشد، مقادیر در این مجموعه‌ها بررسی می‌شوند تا مشخص شود که آیا آنها نیز از نوع [OrderedDict] هستند یا خیر. در صورت مثبت بودن، آنها به نوع [dict] تبدیل می‌شوند. شایان ذکر است که متد [xmltodict.parse] هیچ فرهنگ لغتی از نوع [dict] تولید نمی‌کند؛

پیش از بررسی توابع گمشده، بیایید به نتایج نگاه کنیم تا ببینیم چه چیزی مورد جستجو است:

آزمون ۱ (خطوط ۲۸–۲۹) نتایج زیر را تولید می‌کند:


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é'}
  • خط ۲: دیکشنری در حال آزمایش. مهم است بدانیم که متد [xml2dict.unparse] نیاز دارد دیکشنری به شکل {'key': value} باشد، جایی که [valeur] می‌تواند سپس یک دیکشنری، یک لیست یا یک نوع ساده باشد؛
  • خطوط ۳–۴: رشته XML مشتق‌شده از دیکشنری. این رشته با سربرگ [<?xml version="1.0" encoding="utf-8"?>\n] پیش‌روی آن قرار می‌گیرد که معمولاً خط اول یک فایل XML است؛
  • خط ۵: نوع [OrderedDict] که با روش [xml2dict.parse] به دست می‌آید، روشی که رشته قبلی XML را به‌عنوان پارامتر می‌گیرد؛
  • خط ۶: فرهنگ لغت از نوع [dict] که با اعمال روش [ordereddict2dict] بر نوع قبلی به دست آمده است. این فرهنگ لغت اصلی از خط ۲ است؛

تمام تست‌های دیگر به همین شکل ساختار یافته‌اند و به شما کمک می‌کنند تا نحوه تبدیل یک دیکشنری به یک رشته 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
  • خطوط ۲۳ و ۲۷ یک نکته مهم را برجسته می‌کنند:
    • خط ۲۳: مقادیر مرتبط با کلیدها در دیکشنری [result] اعداد هستند؛
    • خط ۲۶: مقادیر مرتبط با کلیدها در فرهنگ لغت [ordereddict_dictionary1] رشته‌های کاراکتری هستند. این یک نقطه‌ضعف در کتابخانه [xmltodict] است. متد [parse] آن تنها رشته‌های کاراکتری تولید می‌کند. این به راحتی قابل درک است:
      • خط ۲۵: رشته 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
  • خط ۳۰: تابع [ordereddict2dict] یک نوع [OrderedDict] را به عنوان پارامتر دریافت می‌کند؛
  • خط ۳۲: دیکشنری از نوع [dict]، که در خط ۳۷ توسط تابع بازگردانده خواهد شد؛
  • خط ۳۳: تمام زوج‌های (کلید، مقدار) در دیکشنری [ordered_dictionary] مورد پردازش قرار می‌گیرند؛
  • خط ۳۵: در دیکشنری جدید، کلید [key] حفظ می‌شود، اما مقدار مربوطه [value] نیست، بلکه [check(value)] است. تابع [check(value)] مسئول یافتن تمام عناصر از نوع [OrderedDict] در صورت مجموعه بودن [value] و تبدیل آن‌ها به نوع [dict] است؛

متد [check] در خطوط ۵ تا ۱۶ تعریف شده است:

  • خط ۵: نوع [value] نامشخص است، بنابراین [value : type] را نمی‌توان نوشت؛
  • خطوط ۷–۸: اگر [value] از نوع [OrderedDict] باشد، آنگاه تابع [ordereddict2dict]—که ما به تازگی آن را کامنت کرده‌ایم—به صورت بازگشتی فراخوانی می‌شود؛
  • خطوط ۹–۱۱: مورد احتمالی دیگر این است که [value] یک لیست باشد. در این صورت، در خط ۱۱، تابع [list2list] (خطوط ۱۹–۲۷) فراخوانی می‌شود؛
  • خطوط ۱۲–۱۴: مورد نهایی زمانی است که [value] یک مجموعه نیست بلکه یک نوع ساده است. تابع [check]، مانند توابع [ordereddict2dict] و [list2list]، بازگشتی است. می‌دانیم که در چنین مواردی، باید همیشه برای حالتی که بازگشتی متوقف می‌شود، پیش‌بینی کنیم. خطوط ۱۲–۱۴ این حالت را مدیریت می‌کنند؛
  • خط 16: تابع [checkچه به صورت بازگشتی فراخوانی شود و چه نشود، مقداری از نوع [valeur2] تولید می‌کند که باید جایگزین پارامتر [value] در خط 5 شود؛

متد [list2list که در خطوط ۱۹–۲۷ تعریف شده است، یک لیست را که به‌عنوان پارامتر ارسال می‌شود، پردازش می‌کند. این متد لیست را به‌صورت حلقه‌ای پردازش کرده و هر مقدار از نوع [OrderedDict] را که در آن یافت می‌شود، با مقداری از نوع [dict] جایگزین می‌کند.

  • خط ۲۱: لیست جدیدی که تابع ایجاد خواهد کرد؛
  • خطوط ۲۳–۲۵: تمام مقادیر از نوع [value] در لیست بررسی شده و با مقدار [check(value)] جایگزین می‌شوند. این مقدار [value] ممکن است خود شامل عناصری از نوع [list] یا [OrderedDict] باشد. این موارد توسط تابع بازگشتی [check] به درستی مدیریت می‌شوند؛