prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Sorting causes problems with import
  ### Description / Beschreibung
  
  When using different `uri_prefix`es for, e.g. a domain import, the sorting by `uri` destroys the order in the file, and parent Attributes are imported *after* their children (with a different `uri_prefix` earlier in the alphabet). This is the problematic line:
  
  https://github.com/rdmorganiser/rdmo/blob/master/rdmo/core/xml.py#L52
  
  ### Expected behaviour / Erwartetes Verhalten
  
  I am not sure if we could get rid of the sorting, we could also sort by `path` (which would give the field some meaning in the xml again). Ideas? @triole @MyPyDavid 
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `rdmo/core/xml.py`
  Content:
  ```
  1 import logging
  2 import re
  3 
  4 import defusedxml.ElementTree as ET
  5 
  6 log = logging.getLogger(__name__)
  7 
  8 
  9 def read_xml_file(file_name):
  10     try:
  11         return ET.parse(file_name).getroot()
  12     except Exception as e:
  13         log.error('Xml parsing error: ' + str(e))
  14 
  15 
  16 def parse_xml_string(string):
  17     try:
  18         return ET.fromstring(string)
  19     except Exception as e:
  20         log.error('Xml parsing error: ' + str(e))
  21 
  22 
  23 def flat_xml_to_elements(treenode):
  24     elements = []
  25     ns_map = get_ns_map(treenode)
  26     uri_attrib = get_ns_tag('dc:uri', ns_map)
  27 
  28     for node in treenode:
  29 
  30         element = {
  31             'uri': get_uri(node, ns_map),
  32             'type': node.tag
  33         }
  34 
  35         for subnode in node:
  36             tag = strip_ns(subnode.tag, ns_map)
  37 
  38             if uri_attrib in subnode.attrib:
  39                 # this node has an uri!
  40                 element[tag] = subnode.attrib[uri_attrib]
  41             elif 'lang' in subnode.attrib:
  42                 # this node has the lang attribute!
  43                 element['%s_%s' % (tag, subnode.attrib['lang'])] = subnode.text
  44             elif list(subnode):
  45                 # this node is a list!
  46                 element[tag] = [subsubnode.attrib[uri_attrib] for subsubnode in subnode]
  47             else:
  48                 element[tag] = subnode.text
  49 
  50         elements.append(element)
  51 
  52     elements = sort_elements_by_key(elements, 'uri')
  53     return elements
  54 
  55 
  56 def get_ns_tag(tag, ns_map):
  57     tag_split = tag.split(':')
  58     try:
  59         return '{%s}%s' % (ns_map[tag_split[0]], tag_split[1])
  60     except KeyError:
  61         return None
  62 
  63 
  64 def get_ns_map(treenode):
  65     ns_map = {}
  66     treestring = ET.tostring(treenode, encoding='utf8', method='xml')
  67 
  68     for match in re.finditer(r'(xmlns:)(.*?)(=")(.*?)(")', str(treestring)):
  69         if match:
  70             ns_map[match.group(2)] = match.group(4)
  71 
  72     return ns_map
  73 
  74 
  75 def get_uri(treenode, ns_map):
  76     if treenode is not None:
  77         ns_tag = get_ns_tag('dc:uri', ns_map)
  78         if ns_tag is not None:
  79             return treenode.attrib.get(ns_tag)
  80 
  81 
  82 def strip_ns(tag, ns_map):
  83     for ns in ns_map.values():
  84         if tag.startswith('{%s}' % ns):
  85             return tag.replace('{%s}' % ns, '')
  86     return tag
  87 
  88 
  89 def filter_elements_by_type(elements, element_type):
  90     for element in elements:
  91         if element['type'] == element_type:
  92             yield element
  93 
  94 
  95 def sort_elements_by_key(dictlist, key, reverse=False):
  96     return sorted(dictlist, key=lambda k: k[key], reverse=reverse)
  97
  ```
  
  Path: `rdmo/core/constants.py`
  Content:
  ```
  1 from django.utils.translation import gettext_lazy as _
  2 
  3 VALUE_TYPE_TEXT = 'text'
  4 VALUE_TYPE_URL = 'url'
  5 VALUE_TYPE_INTEGER = 'integer'
  6 VALUE_TYPE_FLOAT = 'float'
  7 VALUE_TYPE_BOOLEAN = 'boolean'
  8 VALUE_TYPE_DATETIME = 'datetime'
  9 VALUE_TYPE_OPTIONS = 'option'
  10 VALUE_TYPE_EMAIL = 'email'
  11 VALUE_TYPE_PHONE = 'phone'
  12 VALUE_TYPE_FILE = 'file'
  13 VALUE_TYPE_CHOICES = (
  14     (VALUE_TYPE_TEXT, _('Text')),
  15     (VALUE_TYPE_URL, _('URL')),
  16     (VALUE_TYPE_INTEGER, _('Integer')),
  17     (VALUE_TYPE_FLOAT, _('Float')),
  18     (VALUE_TYPE_BOOLEAN, _('Boolean')),
  19     (VALUE_TYPE_DATETIME, _('Datetime')),
  20     (VALUE_TYPE_EMAIL, _('E-mail')),
  21     (VALUE_TYPE_PHONE, _('Phone')),
  22     (VALUE_TYPE_OPTIONS, _('Option')),
  23     (VALUE_TYPE_FILE, _('File'))
  24 )
  25 
  26 PERMISSIONS = {
  27     'condition': (
  28         'conditions.add_condition', 'conditions.change_condition', 'conditions.delete_condition'
  29     ),
  30     'attribute': (
  31         'domain.add_attribute', 'domain.change_attribute', 'domain.delete_attribute'
  32     ),
  33     'optionset': (
  34         'options.add_optionset', 'options.change_optionset', 'options.delete_optionset'
  35     ),
  36     'option': (
  37         'options.add_option', 'options.change_option', 'options.delete_option'
  38     ),
  39     'catalog': (
  40         'questions.add_catalog', 'questions.change_catalog', 'questions.delete_catalog'
  41     ),
  42     'section': (
  43         'questions.add_section', 'questions.change_section', 'questions.delete_section'
  44     ),
  45     'questionset': (
  46         'questions.add_questionset', 'questions.change_questionset', 'questions.delete_questionset'
  47     ),
  48     'question': (
  49         'questions.add_question', 'questions.change_question', 'questions.delete_question'
  50     ),
  51     'task': (
  52         'tasks.add_task', 'tasks.change_task', 'tasks.delete_task'
  53     ),
  54     'view': (
  55         'views.add_view', 'views.change_view', 'views.delete_view'
  56     )
  57 }
  58
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/rdmo/core/constants.py b/rdmo/core/constants.py
  --- a/rdmo/core/constants.py
  +++ b/rdmo/core/constants.py
  @@ -55,3 +55,12 @@
           'views.add_view', 'views.change_view', 'views.delete_view'
       )
   }
  +
  +IMPORT_SORT_ORDER = (
  +    'domain',
  +    'conditions',
  +    'options',
  +    'questions',
  +    'tasks',
  +    'views'
  +)
  diff --git a/rdmo/core/xml.py b/rdmo/core/xml.py
  --- a/rdmo/core/xml.py
  +++ b/rdmo/core/xml.py
  @@ -3,6 +3,9 @@
   
   import defusedxml.ElementTree as ET
   
  +from .constants import IMPORT_SORT_ORDER
  +
  +
   log = logging.getLogger(__name__)
   
   
  @@ -49,7 +52,7 @@
   
           elements.append(element)
   
  -    elements = sort_elements_by_key(elements, 'uri')
  +    elements = sorted(elements, key=sort_elements)
       return elements
   
   
  @@ -92,5 +95,13 @@
               yield element
   
   
  -def sort_elements_by_key(dictlist, key, reverse=False):
  -    return sorted(dictlist, key=lambda k: k[key], reverse=reverse)
  +def sort_elements(element):
  +    # remove the uri_prefix from the uri to create the key to be sorted by
  +    sort_key = element['uri'].replace(element['uri_prefix'], '')
  +
  +    # remove the app name from the sort_key and replace it by its import order
  +    for i, item in enumerate(IMPORT_SORT_ORDER):
  +        if sort_key.startswith(item):
  +            sort_key = sort_key.replace(item, str(i))
  +
  +    return sort_key
  
verification_info: {"golden_diff": "diff --git a/rdmo/core/constants.py b/rdmo/core/constants.py\n--- a/rdmo/core/constants.py\n+++ b/rdmo/core/constants.py\n@@ -55,3 +55,12 @@\n         'views.add_view', 'views.change_view', 'views.delete_view'\n     )\n }\n+\n+IMPORT_SORT_ORDER = (\n+    'domain',\n+    'conditions',\n+    'options',\n+    'questions',\n+    'tasks',\n+    'views'\n+)\ndiff --git a/rdmo/core/xml.py b/rdmo/core/xml.py\n--- a/rdmo/core/xml.py\n+++ b/rdmo/core/xml.py\n@@ -3,6 +3,9 @@\n \n import defusedxml.ElementTree as ET\n \n+from .constants import IMPORT_SORT_ORDER\n+\n+\n log = logging.getLogger(__name__)\n \n \n@@ -49,7 +52,7 @@\n \n         elements.append(element)\n \n-    elements = sort_elements_by_key(elements, 'uri')\n+    elements = sorted(elements, key=sort_elements)\n     return elements\n \n \n@@ -92,5 +95,13 @@\n             yield element\n \n \n-def sort_elements_by_key(dictlist, key, reverse=False):\n-    return sorted(dictlist, key=lambda k: k[key], reverse=reverse)\n+def sort_elements(element):\n+    # remove the uri_prefix from the uri to create the key to be sorted by\n+    sort_key = element['uri'].replace(element['uri_prefix'], '')\n+\n+    # remove the app name from the sort_key and replace it by its import order\n+    for i, item in enumerate(IMPORT_SORT_ORDER):\n+        if sort_key.startswith(item):\n+            sort_key = sort_key.replace(item, str(i))\n+\n+    return sort_key\n", "issue": "Sorting causes problems with import\n### Description / Beschreibung\r\n\r\nWhen using different `uri_prefix`es for, e.g. a domain import, the sorting by `uri` destroys the order in the file, and parent Attributes are imported *after* their children (with a different `uri_prefix` earlier in the alphabet). This is the problematic line:\r\n\r\nhttps://github.com/rdmorganiser/rdmo/blob/master/rdmo/core/xml.py#L52\r\n\r\n### Expected behaviour / Erwartetes Verhalten\r\n\r\nI am not sure if we could get rid of the sorting, we could also sort by `path` (which would give the field some meaning in the xml again). Ideas? @triole @MyPyDavid \r\n\n", "before_files": [{"content": "import logging\nimport re\n\nimport defusedxml.ElementTree as ET\n\nlog = logging.getLogger(__name__)\n\n\ndef read_xml_file(file_name):\n    try:\n        return ET.parse(file_name).getroot()\n    except Exception as e:\n        log.error('Xml parsing error: ' + str(e))\n\n\ndef parse_xml_string(string):\n    try:\n        return ET.fromstring(string)\n    except Exception as e:\n        log.error('Xml parsing error: ' + str(e))\n\n\ndef flat_xml_to_elements(treenode):\n    elements = []\n    ns_map = get_ns_map(treenode)\n    uri_attrib = get_ns_tag('dc:uri', ns_map)\n\n    for node in treenode:\n\n        element = {\n            'uri': get_uri(node, ns_map),\n            'type': node.tag\n        }\n\n        for subnode in node:\n            tag = strip_ns(subnode.tag, ns_map)\n\n            if uri_attrib in subnode.attrib:\n                # this node has an uri!\n                element[tag] = subnode.attrib[uri_attrib]\n            elif 'lang' in subnode.attrib:\n                # this node has the lang attribute!\n                element['%s_%s' % (tag, subnode.attrib['lang'])] = subnode.text\n            elif list(subnode):\n                # this node is a list!\n                element[tag] = [subsubnode.attrib[uri_attrib] for subsubnode in subnode]\n            else:\n                element[tag] = subnode.text\n\n        elements.append(element)\n\n    elements = sort_elements_by_key(elements, 'uri')\n    return elements\n\n\ndef get_ns_tag(tag, ns_map):\n    tag_split = tag.split(':')\n    try:\n        return '{%s}%s' % (ns_map[tag_split[0]], tag_split[1])\n    except KeyError:\n        return None\n\n\ndef get_ns_map(treenode):\n    ns_map = {}\n    treestring = ET.tostring(treenode, encoding='utf8', method='xml')\n\n    for match in re.finditer(r'(xmlns:)(.*?)(=\")(.*?)(\")', str(treestring)):\n        if match:\n            ns_map[match.group(2)] = match.group(4)\n\n    return ns_map\n\n\ndef get_uri(treenode, ns_map):\n    if treenode is not None:\n        ns_tag = get_ns_tag('dc:uri', ns_map)\n        if ns_tag is not None:\n            return treenode.attrib.get(ns_tag)\n\n\ndef strip_ns(tag, ns_map):\n    for ns in ns_map.values():\n        if tag.startswith('{%s}' % ns):\n            return tag.replace('{%s}' % ns, '')\n    return tag\n\n\ndef filter_elements_by_type(elements, element_type):\n    for element in elements:\n        if element['type'] == element_type:\n            yield element\n\n\ndef sort_elements_by_key(dictlist, key, reverse=False):\n    return sorted(dictlist, key=lambda k: k[key], reverse=reverse)\n", "path": "rdmo/core/xml.py"}, {"content": "from django.utils.translation import gettext_lazy as _\n\nVALUE_TYPE_TEXT = 'text'\nVALUE_TYPE_URL = 'url'\nVALUE_TYPE_INTEGER = 'integer'\nVALUE_TYPE_FLOAT = 'float'\nVALUE_TYPE_BOOLEAN = 'boolean'\nVALUE_TYPE_DATETIME = 'datetime'\nVALUE_TYPE_OPTIONS = 'option'\nVALUE_TYPE_EMAIL = 'email'\nVALUE_TYPE_PHONE = 'phone'\nVALUE_TYPE_FILE = 'file'\nVALUE_TYPE_CHOICES = (\n    (VALUE_TYPE_TEXT, _('Text')),\n    (VALUE_TYPE_URL, _('URL')),\n    (VALUE_TYPE_INTEGER, _('Integer')),\n    (VALUE_TYPE_FLOAT, _('Float')),\n    (VALUE_TYPE_BOOLEAN, _('Boolean')),\n    (VALUE_TYPE_DATETIME, _('Datetime')),\n    (VALUE_TYPE_EMAIL, _('E-mail')),\n    (VALUE_TYPE_PHONE, _('Phone')),\n    (VALUE_TYPE_OPTIONS, _('Option')),\n    (VALUE_TYPE_FILE, _('File'))\n)\n\nPERMISSIONS = {\n    'condition': (\n        'conditions.add_condition', 'conditions.change_condition', 'conditions.delete_condition'\n    ),\n    'attribute': (\n        'domain.add_attribute', 'domain.change_attribute', 'domain.delete_attribute'\n    ),\n    'optionset': (\n        'options.add_optionset', 'options.change_optionset', 'options.delete_optionset'\n    ),\n    'option': (\n        'options.add_option', 'options.change_option', 'options.delete_option'\n    ),\n    'catalog': (\n        'questions.add_catalog', 'questions.change_catalog', 'questions.delete_catalog'\n    ),\n    'section': (\n        'questions.add_section', 'questions.change_section', 'questions.delete_section'\n    ),\n    'questionset': (\n        'questions.add_questionset', 'questions.change_questionset', 'questions.delete_questionset'\n    ),\n    'question': (\n        'questions.add_question', 'questions.change_question', 'questions.delete_question'\n    ),\n    'task': (\n        'tasks.add_task', 'tasks.change_task', 'tasks.delete_task'\n    ),\n    'view': (\n        'views.add_view', 'views.change_view', 'views.delete_view'\n    )\n}\n", "path": "rdmo/core/constants.py"}], "after_files": [{"content": "import logging\nimport re\n\nimport defusedxml.ElementTree as ET\n\nfrom .constants import IMPORT_SORT_ORDER\n\n\nlog = logging.getLogger(__name__)\n\n\ndef read_xml_file(file_name):\n    try:\n        return ET.parse(file_name).getroot()\n    except Exception as e:\n        log.error('Xml parsing error: ' + str(e))\n\n\ndef parse_xml_string(string):\n    try:\n        return ET.fromstring(string)\n    except Exception as e:\n        log.error('Xml parsing error: ' + str(e))\n\n\ndef flat_xml_to_elements(treenode):\n    elements = []\n    ns_map = get_ns_map(treenode)\n    uri_attrib = get_ns_tag('dc:uri', ns_map)\n\n    for node in treenode:\n\n        element = {\n            'uri': get_uri(node, ns_map),\n            'type': node.tag\n        }\n\n        for subnode in node:\n            tag = strip_ns(subnode.tag, ns_map)\n\n            if uri_attrib in subnode.attrib:\n                # this node has an uri!\n                element[tag] = subnode.attrib[uri_attrib]\n            elif 'lang' in subnode.attrib:\n                # this node has the lang attribute!\n                element['%s_%s' % (tag, subnode.attrib['lang'])] = subnode.text\n            elif list(subnode):\n                # this node is a list!\n                element[tag] = [subsubnode.attrib[uri_attrib] for subsubnode in subnode]\n            else:\n                element[tag] = subnode.text\n\n        elements.append(element)\n\n    elements = sorted(elements, key=sort_elements)\n    return elements\n\n\ndef get_ns_tag(tag, ns_map):\n    tag_split = tag.split(':')\n    try:\n        return '{%s}%s' % (ns_map[tag_split[0]], tag_split[1])\n    except KeyError:\n        return None\n\n\ndef get_ns_map(treenode):\n    ns_map = {}\n    treestring = ET.tostring(treenode, encoding='utf8', method='xml')\n\n    for match in re.finditer(r'(xmlns:)(.*?)(=\")(.*?)(\")', str(treestring)):\n        if match:\n            ns_map[match.group(2)] = match.group(4)\n\n    return ns_map\n\n\ndef get_uri(treenode, ns_map):\n    if treenode is not None:\n        ns_tag = get_ns_tag('dc:uri', ns_map)\n        if ns_tag is not None:\n            return treenode.attrib.get(ns_tag)\n\n\ndef strip_ns(tag, ns_map):\n    for ns in ns_map.values():\n        if tag.startswith('{%s}' % ns):\n            return tag.replace('{%s}' % ns, '')\n    return tag\n\n\ndef filter_elements_by_type(elements, element_type):\n    for element in elements:\n        if element['type'] == element_type:\n            yield element\n\n\ndef sort_elements(element):\n    # remove the uri_prefix from the uri to create the key to be sorted by\n    sort_key = element['uri'].replace(element['uri_prefix'], '')\n\n    # remove the app name from the sort_key and replace it by its import order\n    for i, item in enumerate(IMPORT_SORT_ORDER):\n        if sort_key.startswith(item):\n            sort_key = sort_key.replace(item, str(i))\n\n    return sort_key\n", "path": "rdmo/core/xml.py"}, {"content": "from django.utils.translation import gettext_lazy as _\n\nVALUE_TYPE_TEXT = 'text'\nVALUE_TYPE_URL = 'url'\nVALUE_TYPE_INTEGER = 'integer'\nVALUE_TYPE_FLOAT = 'float'\nVALUE_TYPE_BOOLEAN = 'boolean'\nVALUE_TYPE_DATETIME = 'datetime'\nVALUE_TYPE_OPTIONS = 'option'\nVALUE_TYPE_EMAIL = 'email'\nVALUE_TYPE_PHONE = 'phone'\nVALUE_TYPE_FILE = 'file'\nVALUE_TYPE_CHOICES = (\n    (VALUE_TYPE_TEXT, _('Text')),\n    (VALUE_TYPE_URL, _('URL')),\n    (VALUE_TYPE_INTEGER, _('Integer')),\n    (VALUE_TYPE_FLOAT, _('Float')),\n    (VALUE_TYPE_BOOLEAN, _('Boolean')),\n    (VALUE_TYPE_DATETIME, _('Datetime')),\n    (VALUE_TYPE_EMAIL, _('E-mail')),\n    (VALUE_TYPE_PHONE, _('Phone')),\n    (VALUE_TYPE_OPTIONS, _('Option')),\n    (VALUE_TYPE_FILE, _('File'))\n)\n\nPERMISSIONS = {\n    'condition': (\n        'conditions.add_condition', 'conditions.change_condition', 'conditions.delete_condition'\n    ),\n    'attribute': (\n        'domain.add_attribute', 'domain.change_attribute', 'domain.delete_attribute'\n    ),\n    'optionset': (\n        'options.add_optionset', 'options.change_optionset', 'options.delete_optionset'\n    ),\n    'option': (\n        'options.add_option', 'options.change_option', 'options.delete_option'\n    ),\n    'catalog': (\n        'questions.add_catalog', 'questions.change_catalog', 'questions.delete_catalog'\n    ),\n    'section': (\n        'questions.add_section', 'questions.change_section', 'questions.delete_section'\n    ),\n    'questionset': (\n        'questions.add_questionset', 'questions.change_questionset', 'questions.delete_questionset'\n    ),\n    'question': (\n        'questions.add_question', 'questions.change_question', 'questions.delete_question'\n    ),\n    'task': (\n        'tasks.add_task', 'tasks.change_task', 'tasks.delete_task'\n    ),\n    'view': (\n        'views.add_view', 'views.change_view', 'views.delete_view'\n    )\n}\n\nIMPORT_SORT_ORDER = (\n    'domain',\n    'conditions',\n    'options',\n    'questions',\n    'tasks',\n    'views'\n)\n", "path": "rdmo/core/constants.py"}]}