We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Draft submission with empty strings in date element fails with generic "Meta-Data errors found!" message
### Describe the issue

I tried submitting an xml draft, hard fail due to "Meta-Data error found".   Very odd since it passes the submission checks.

There is a closed issue that sounds similar, but apparently not fixed.

I can send the xml, but this doesn't allow that file type.

<img width="1009" alt="Image" src="https://github.com/ietf-tools/datatracker/assets/6423658/fb3e7616-ea04-46e8-a6d2-0d359888f7b7">



### Code of Conduct

- [X] I agree to follow the [IETF's Code of Conduct](https://github.com/ietf-tools/.github/blob/main/CODE_OF_CONDUCT.md)

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `ietf/utils/xmldraft.py`
Content:
```
1 # Copyright The IETF Trust 2022, All Rights Reserved
2 # -*- coding: utf-8 -*-
3 import datetime
4 import io
5 import re
6 import xml2rfc
7 
8 import debug  # pyflakes: ignore
9 
10 from contextlib import ExitStack
11 from xml2rfc.util.date import augment_date, extract_date
12 from ietf.utils.timezone import date_today
13 
14 from .draft import Draft
15 
16 
17 class XMLDraft(Draft):
18     """Draft from XML source
19 
20     Not all methods from the superclass are implemented yet.
21     """
22     def __init__(self, xml_file):
23         """Initialize XMLDraft instance
24 
25         :parameter xml_file: path to file containing XML source
26         """
27         super().__init__()
28         # cast xml_file to str so, e.g., this will work with a Path
29         self.xmltree, self.xml_version = self.parse_xml(str(xml_file))
30         self.xmlroot = self.xmltree.getroot()
31         self.filename, self.revision = self._parse_docname()
32 
33     @staticmethod
34     def parse_xml(filename):
35         """Parse XML draft
36 
37         Converts to xml2rfc v3 schema, then returns the root of the v3 tree and the original
38         xml version.
39         """
40         orig_write_out = xml2rfc.log.write_out
41         orig_write_err = xml2rfc.log.write_err
42         parser_out = io.StringIO()
43         parser_err = io.StringIO()
44 
45         with ExitStack() as stack:
46             @stack.callback
47             def cleanup():  # called when context exited, even if there's an exception
48                 xml2rfc.log.write_out = orig_write_out
49                 xml2rfc.log.write_err = orig_write_err
50 
51             xml2rfc.log.write_out = parser_out
52             xml2rfc.log.write_err = parser_err
53 
54             parser = xml2rfc.XmlRfcParser(filename, quiet=True)
55             try:
56                 tree = parser.parse()
57             except Exception as e:
58                 raise XMLParseError(parser_out.getvalue(), parser_err.getvalue()) from e
59 
60             xml_version = tree.getroot().get('version', '2')
61             if xml_version == '2':
62                 v2v3 = xml2rfc.V2v3XmlWriter(tree)
63                 tree.tree = v2v3.convert2to3()
64         return tree, xml_version
65 
66     def _document_name(self, ref):
67         """Get document name from reference."""
68         series = ["rfc", "bcp", "fyi", "std"]
69         # handle xinclude first
70         # FIXME: this assumes the xinclude is a bibxml href; if it isn't, there can
71         # still be false negatives. it would be better to expand the xinclude and parse
72         # its seriesInfo.
73         if ref.tag.endswith("}include"):
74             name = re.search(
75                 rf"reference\.({'|'.join(series).upper()})\.(\d{{4}})\.xml",
76                 ref.attrib["href"],
77             )
78             if name:
79                 return f"{name.group(1)}{int(name.group(2))}".lower()
80             name = re.search(
81                 r"reference\.I-D\.(?:draft-)?(.*)\.xml", ref.attrib["href"]
82             )
83             if name:
84                 return f"draft-{name.group(1)}"
85             # can't extract the name, give up
86             return ""
87 
88         # check the anchor next
89         anchor = ref.get("anchor").lower()  # always give back lowercase
90         label = anchor.rstrip("0123456789")  # remove trailing digits
91         if label in series:
92             number = int(anchor[len(label) :])
93             return f"{label}{number}"
94 
95         # if we couldn't find a match so far, try the seriesInfo
96         series_query = " or ".join(f"@name='{x.upper()}'" for x in series)
97         for info in ref.xpath(
98             f"./seriesInfo[{series_query} or @name='Internet-Draft']"
99         ):
100             if not info.attrib["value"]:
101                 continue
102             if info.attrib["name"] == "Internet-Draft":
103                 return info.attrib["value"]
104             else:
105                 return f'{info.attrib["name"].lower()}{info.attrib["value"]}'
106         return ""
107 
108     def _reference_section_type(self, section_name):
109         """Determine reference type from name of references section"""
110         if section_name:
111             section_name = section_name.lower()
112             if 'normative' in section_name:
113                 return self.REF_TYPE_NORMATIVE
114             elif 'informative' in section_name:
115                 return self.REF_TYPE_INFORMATIVE
116         return self.REF_TYPE_UNKNOWN
117 
118     def _reference_section_name(self, section_elt):
119         section_name = section_elt.findtext('name')
120         if section_name is None and 'title' in section_elt.keys():
121             section_name = section_elt.get('title')  # fall back to title if we have it
122         return section_name
123 
124     def _parse_docname(self):
125         docname = self.xmlroot.attrib.get('docName')
126         revmatch = re.match(
127             r'^(?P<filename>.+?)(?:-(?P<rev>[0-9][0-9]))?$',
128             docname,
129 
130         )
131         if revmatch is None:
132             raise ValueError('Unable to parse docName')
133         # If a group had no match it is None
134         return revmatch.group('filename'), revmatch.group('rev')
135 
136     def get_title(self):
137         return self.xmlroot.findtext('front/title').strip()
138 
139     def get_creation_date(self):
140         date_elt = self.xmlroot.find("front/date")
141         if date_elt is not None:
142             # ths mimics handling of date elements in the xml2rfc text/html writers
143             today = date_today()
144             year, month, day = extract_date(date_elt, today)
145             year, month, day = augment_date(year, month, day, today)
146             if day is None:
147                 # Must choose a day for a datetime.date. Per RFC 7991 sect 2.17, we use
148                 # today's date if it is consistent with the rest of the date. Otherwise,
149                 # arbitrariy (and consistent with the text parser) assume the 15th.
150                 if year == today.year and month == today.month:
151                     day = today.day
152                 else:
153                     day = 15
154             return datetime.date(year, month, day)
155         return None
156     
157     # todo fix the implementation of XMLDraft.get_abstract()
158     #
159     # This code was pulled from ietf.submit.forms where it existed for some time.
160     # It does not work, at least with modern xml2rfc. This assumes that the abstract
161     # is simply text in the front/abstract node, but the XML schema wraps the actual
162     # abstract text in <t> elements (and allows <dl>, <ol>, and <ul> as well). As a
163     # result, this method normally returns an empty string, which is later replaced by
164     # the abstract parsed from the rendered text. For now, I a commenting this out
165     # and making it explicit that the abstract always comes from the text format.
166     #
167     # def get_abstract(self):
168     #     """Extract the abstract"""
169     #     abstract = self.xmlroot.findtext('front/abstract')
170     #     return abstract.strip() if abstract else ''
171 
172     def get_author_list(self):
173         """Get detailed author list
174 
175         Returns a list of dicts with the following keys:
176             name, first_name, middle_initial, last_name,
177             name_suffix, email, country, affiliation
178         Values will be None if not available
179         """
180         result = []
181         empty_author = {
182             k: None for k in [
183                 'name', 'first_name', 'middle_initial', 'last_name',
184                 'name_suffix', 'email', 'country', 'affiliation',
185             ]
186         }
187 
188         for author in self.xmlroot.findall('front/author'):
189             info = {
190                 'name': author.attrib.get('fullname'),
191                 'email': author.findtext('address/email'),
192                 'affiliation': author.findtext('organization'),
193             }
194             elem = author.find('address/postal/country')
195             if elem is not None:
196                 ascii_country = elem.get('ascii', None)
197                 info['country'] = ascii_country if ascii_country else elem.text
198             for item in info:
199                 if info[item]:
200                     info[item] = info[item].strip()
201             result.append(empty_author | info)  # merge, preferring info
202         return result
203 
204     def get_refs(self):
205         """Extract references from the draft"""
206         refs = {}
207         # accept nested <references> sections
208         for section in self.xmlroot.findall("back//references"):
209             ref_type = self._reference_section_type(
210                 self._reference_section_name(section)
211             )
212             for ref in (
213                 section.findall("./reference")
214                 + section.findall("./referencegroup")
215                 + section.findall(
216                     "./xi:include", {"xi": "http://www.w3.org/2001/XInclude"}
217                 )
218             ):
219                 name = self._document_name(ref)
220                 if name:
221                     refs[name] = ref_type
222         return refs
223 
224 
225 class XMLParseError(Exception):
226     """An error occurred while parsing"""
227     def __init__(self, out: str, err: str, *args):
228         super().__init__(*args)
229         self._out = out
230         self._err = err
231 
232     def parser_msgs(self):
233         return self._out.splitlines() + self._err.splitlines()
234
```
--- 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()

```

diff --git a/ietf/utils/xmldraft.py b/ietf/utils/xmldraft.py
--- a/ietf/utils/xmldraft.py
+++ b/ietf/utils/xmldraft.py
@@ -136,24 +136,27 @@
     def get_title(self):
         return self.xmlroot.findtext('front/title').strip()
 
+    @staticmethod
+    def parse_creation_date(date_elt):
+        if date_elt is None:
+            return None
+        today = date_today()
+        # ths mimics handling of date elements in the xml2rfc text/html writers
+        year, month, day = extract_date(date_elt, today)
+        year, month, day = augment_date(year, month, day, today)
+        if not day:
+            # Must choose a day for a datetime.date. Per RFC 7991 sect 2.17, we use
+            # today's date if it is consistent with the rest of the date. Otherwise,
+            # arbitrariy (and consistent with the text parser) assume the 15th.
+            if year == today.year and month == today.month:
+                day = today.day
+            else:
+                day = 15
+        return datetime.date(year, month, day)
+
     def get_creation_date(self):
-        date_elt = self.xmlroot.find("front/date")
-        if date_elt is not None:
-            # ths mimics handling of date elements in the xml2rfc text/html writers
-            today = date_today()
-            year, month, day = extract_date(date_elt, today)
-            year, month, day = augment_date(year, month, day, today)
-            if day is None:
-                # Must choose a day for a datetime.date. Per RFC 7991 sect 2.17, we use
-                # today's date if it is consistent with the rest of the date. Otherwise,
-                # arbitrariy (and consistent with the text parser) assume the 15th.
-                if year == today.year and month == today.month:
-                    day = today.day
-                else:
-                    day = 15
-            return datetime.date(year, month, day)
-        return None
-    
+        return self.parse_creation_date(self.xmlroot.find("front/date"))
+
     # todo fix the implementation of XMLDraft.get_abstract()
     #
     # This code was pulled from ietf.submit.forms where it existed for some time.

{"golden_diff": "diff --git a/ietf/utils/xmldraft.py b/ietf/utils/xmldraft.py\n--- a/ietf/utils/xmldraft.py\n+++ b/ietf/utils/xmldraft.py\n@@ -136,24 +136,27 @@\n     def get_title(self):\n         return self.xmlroot.findtext('front/title').strip()\n \n+    @staticmethod\n+    def parse_creation_date(date_elt):\n+        if date_elt is None:\n+            return None\n+        today = date_today()\n+        # ths mimics handling of date elements in the xml2rfc text/html writers\n+        year, month, day = extract_date(date_elt, today)\n+        year, month, day = augment_date(year, month, day, today)\n+        if not day:\n+            # Must choose a day for a datetime.date. Per RFC 7991 sect 2.17, we use\n+            # today's date if it is consistent with the rest of the date. Otherwise,\n+            # arbitrariy (and consistent with the text parser) assume the 15th.\n+            if year == today.year and month == today.month:\n+                day = today.day\n+            else:\n+                day = 15\n+        return datetime.date(year, month, day)\n+\n     def get_creation_date(self):\n-        date_elt = self.xmlroot.find(\"front/date\")\n-        if date_elt is not None:\n-            # ths mimics handling of date elements in the xml2rfc text/html writers\n-            today = date_today()\n-            year, month, day = extract_date(date_elt, today)\n-            year, month, day = augment_date(year, month, day, today)\n-            if day is None:\n-                # Must choose a day for a datetime.date. Per RFC 7991 sect 2.17, we use\n-                # today's date if it is consistent with the rest of the date. Otherwise,\n-                # arbitrariy (and consistent with the text parser) assume the 15th.\n-                if year == today.year and month == today.month:\n-                    day = today.day\n-                else:\n-                    day = 15\n-            return datetime.date(year, month, day)\n-        return None\n-    \n+        return self.parse_creation_date(self.xmlroot.find(\"front/date\"))\n+\n     # todo fix the implementation of XMLDraft.get_abstract()\n     #\n     # This code was pulled from ietf.submit.forms where it existed for some time.\n", "issue": "Draft submission with empty strings in date element fails with generic \"Meta-Data errors found!\" message\n### Describe the issue\n\nI tried submitting an xml draft, hard fail due to \"Meta-Data error found\".   Very odd since it passes the submission checks.\r\n\r\nThere is a closed issue that sounds similar, but apparently not fixed.\r\n\r\nI can send the xml, but this doesn't allow that file type.\r\n\r\n<img width=\"1009\" alt=\"Image\" src=\"https://github.com/ietf-tools/datatracker/assets/6423658/fb3e7616-ea04-46e8-a6d2-0d359888f7b7\">\r\n\r\n\n\n### Code of Conduct\n\n- [X] I agree to follow the [IETF's Code of Conduct](https://github.com/ietf-tools/.github/blob/main/CODE_OF_CONDUCT.md)\n", "before_files": [{"content": "# Copyright The IETF Trust 2022, All Rights Reserved\n# -*- coding: utf-8 -*-\nimport datetime\nimport io\nimport re\nimport xml2rfc\n\nimport debug  # pyflakes: ignore\n\nfrom contextlib import ExitStack\nfrom xml2rfc.util.date import augment_date, extract_date\nfrom ietf.utils.timezone import date_today\n\nfrom .draft import Draft\n\n\nclass XMLDraft(Draft):\n    \"\"\"Draft from XML source\n\n    Not all methods from the superclass are implemented yet.\n    \"\"\"\n    def __init__(self, xml_file):\n        \"\"\"Initialize XMLDraft instance\n\n        :parameter xml_file: path to file containing XML source\n        \"\"\"\n        super().__init__()\n        # cast xml_file to str so, e.g., this will work with a Path\n        self.xmltree, self.xml_version = self.parse_xml(str(xml_file))\n        self.xmlroot = self.xmltree.getroot()\n        self.filename, self.revision = self._parse_docname()\n\n    @staticmethod\n    def parse_xml(filename):\n        \"\"\"Parse XML draft\n\n        Converts to xml2rfc v3 schema, then returns the root of the v3 tree and the original\n        xml version.\n        \"\"\"\n        orig_write_out = xml2rfc.log.write_out\n        orig_write_err = xml2rfc.log.write_err\n        parser_out = io.StringIO()\n        parser_err = io.StringIO()\n\n        with ExitStack() as stack:\n            @stack.callback\n            def cleanup():  # called when context exited, even if there's an exception\n                xml2rfc.log.write_out = orig_write_out\n                xml2rfc.log.write_err = orig_write_err\n\n            xml2rfc.log.write_out = parser_out\n            xml2rfc.log.write_err = parser_err\n\n            parser = xml2rfc.XmlRfcParser(filename, quiet=True)\n            try:\n                tree = parser.parse()\n            except Exception as e:\n                raise XMLParseError(parser_out.getvalue(), parser_err.getvalue()) from e\n\n            xml_version = tree.getroot().get('version', '2')\n            if xml_version == '2':\n                v2v3 = xml2rfc.V2v3XmlWriter(tree)\n                tree.tree = v2v3.convert2to3()\n        return tree, xml_version\n\n    def _document_name(self, ref):\n        \"\"\"Get document name from reference.\"\"\"\n        series = [\"rfc\", \"bcp\", \"fyi\", \"std\"]\n        # handle xinclude first\n        # FIXME: this assumes the xinclude is a bibxml href; if it isn't, there can\n        # still be false negatives. it would be better to expand the xinclude and parse\n        # its seriesInfo.\n        if ref.tag.endswith(\"}include\"):\n            name = re.search(\n                rf\"reference\\.({'|'.join(series).upper()})\\.(\\d{{4}})\\.xml\",\n                ref.attrib[\"href\"],\n            )\n            if name:\n                return f\"{name.group(1)}{int(name.group(2))}\".lower()\n            name = re.search(\n                r\"reference\\.I-D\\.(?:draft-)?(.*)\\.xml\", ref.attrib[\"href\"]\n            )\n            if name:\n                return f\"draft-{name.group(1)}\"\n            # can't extract the name, give up\n            return \"\"\n\n        # check the anchor next\n        anchor = ref.get(\"anchor\").lower()  # always give back lowercase\n        label = anchor.rstrip(\"0123456789\")  # remove trailing digits\n        if label in series:\n            number = int(anchor[len(label) :])\n            return f\"{label}{number}\"\n\n        # if we couldn't find a match so far, try the seriesInfo\n        series_query = \" or \".join(f\"@name='{x.upper()}'\" for x in series)\n        for info in ref.xpath(\n            f\"./seriesInfo[{series_query} or @name='Internet-Draft']\"\n        ):\n            if not info.attrib[\"value\"]:\n                continue\n            if info.attrib[\"name\"] == \"Internet-Draft\":\n                return info.attrib[\"value\"]\n            else:\n                return f'{info.attrib[\"name\"].lower()}{info.attrib[\"value\"]}'\n        return \"\"\n\n    def _reference_section_type(self, section_name):\n        \"\"\"Determine reference type from name of references section\"\"\"\n        if section_name:\n            section_name = section_name.lower()\n            if 'normative' in section_name:\n                return self.REF_TYPE_NORMATIVE\n            elif 'informative' in section_name:\n                return self.REF_TYPE_INFORMATIVE\n        return self.REF_TYPE_UNKNOWN\n\n    def _reference_section_name(self, section_elt):\n        section_name = section_elt.findtext('name')\n        if section_name is None and 'title' in section_elt.keys():\n            section_name = section_elt.get('title')  # fall back to title if we have it\n        return section_name\n\n    def _parse_docname(self):\n        docname = self.xmlroot.attrib.get('docName')\n        revmatch = re.match(\n            r'^(?P<filename>.+?)(?:-(?P<rev>[0-9][0-9]))?$',\n            docname,\n\n        )\n        if revmatch is None:\n            raise ValueError('Unable to parse docName')\n        # If a group had no match it is None\n        return revmatch.group('filename'), revmatch.group('rev')\n\n    def get_title(self):\n        return self.xmlroot.findtext('front/title').strip()\n\n    def get_creation_date(self):\n        date_elt = self.xmlroot.find(\"front/date\")\n        if date_elt is not None:\n            # ths mimics handling of date elements in the xml2rfc text/html writers\n            today = date_today()\n            year, month, day = extract_date(date_elt, today)\n            year, month, day = augment_date(year, month, day, today)\n            if day is None:\n                # Must choose a day for a datetime.date. Per RFC 7991 sect 2.17, we use\n                # today's date if it is consistent with the rest of the date. Otherwise,\n                # arbitrariy (and consistent with the text parser) assume the 15th.\n                if year == today.year and month == today.month:\n                    day = today.day\n                else:\n                    day = 15\n            return datetime.date(year, month, day)\n        return None\n    \n    # todo fix the implementation of XMLDraft.get_abstract()\n    #\n    # This code was pulled from ietf.submit.forms where it existed for some time.\n    # It does not work, at least with modern xml2rfc. This assumes that the abstract\n    # is simply text in the front/abstract node, but the XML schema wraps the actual\n    # abstract text in <t> elements (and allows <dl>, <ol>, and <ul> as well). As a\n    # result, this method normally returns an empty string, which is later replaced by\n    # the abstract parsed from the rendered text. For now, I a commenting this out\n    # and making it explicit that the abstract always comes from the text format.\n    #\n    # def get_abstract(self):\n    #     \"\"\"Extract the abstract\"\"\"\n    #     abstract = self.xmlroot.findtext('front/abstract')\n    #     return abstract.strip() if abstract else ''\n\n    def get_author_list(self):\n        \"\"\"Get detailed author list\n\n        Returns a list of dicts with the following keys:\n            name, first_name, middle_initial, last_name,\n            name_suffix, email, country, affiliation\n        Values will be None if not available\n        \"\"\"\n        result = []\n        empty_author = {\n            k: None for k in [\n                'name', 'first_name', 'middle_initial', 'last_name',\n                'name_suffix', 'email', 'country', 'affiliation',\n            ]\n        }\n\n        for author in self.xmlroot.findall('front/author'):\n            info = {\n                'name': author.attrib.get('fullname'),\n                'email': author.findtext('address/email'),\n                'affiliation': author.findtext('organization'),\n            }\n            elem = author.find('address/postal/country')\n            if elem is not None:\n                ascii_country = elem.get('ascii', None)\n                info['country'] = ascii_country if ascii_country else elem.text\n            for item in info:\n                if info[item]:\n                    info[item] = info[item].strip()\n            result.append(empty_author | info)  # merge, preferring info\n        return result\n\n    def get_refs(self):\n        \"\"\"Extract references from the draft\"\"\"\n        refs = {}\n        # accept nested <references> sections\n        for section in self.xmlroot.findall(\"back//references\"):\n            ref_type = self._reference_section_type(\n                self._reference_section_name(section)\n            )\n            for ref in (\n                section.findall(\"./reference\")\n                + section.findall(\"./referencegroup\")\n                + section.findall(\n                    \"./xi:include\", {\"xi\": \"http://www.w3.org/2001/XInclude\"}\n                )\n            ):\n                name = self._document_name(ref)\n                if name:\n                    refs[name] = ref_type\n        return refs\n\n\nclass XMLParseError(Exception):\n    \"\"\"An error occurred while parsing\"\"\"\n    def __init__(self, out: str, err: str, *args):\n        super().__init__(*args)\n        self._out = out\n        self._err = err\n\n    def parser_msgs(self):\n        return self._out.splitlines() + self._err.splitlines()\n", "path": "ietf/utils/xmldraft.py"}], "after_files": [{"content": "# Copyright The IETF Trust 2022, All Rights Reserved\n# -*- coding: utf-8 -*-\nimport datetime\nimport io\nimport re\nimport xml2rfc\n\nimport debug  # pyflakes: ignore\n\nfrom contextlib import ExitStack\nfrom xml2rfc.util.date import augment_date, extract_date\nfrom ietf.utils.timezone import date_today\n\nfrom .draft import Draft\n\n\nclass XMLDraft(Draft):\n    \"\"\"Draft from XML source\n\n    Not all methods from the superclass are implemented yet.\n    \"\"\"\n    def __init__(self, xml_file):\n        \"\"\"Initialize XMLDraft instance\n\n        :parameter xml_file: path to file containing XML source\n        \"\"\"\n        super().__init__()\n        # cast xml_file to str so, e.g., this will work with a Path\n        self.xmltree, self.xml_version = self.parse_xml(str(xml_file))\n        self.xmlroot = self.xmltree.getroot()\n        self.filename, self.revision = self._parse_docname()\n\n    @staticmethod\n    def parse_xml(filename):\n        \"\"\"Parse XML draft\n\n        Converts to xml2rfc v3 schema, then returns the root of the v3 tree and the original\n        xml version.\n        \"\"\"\n        orig_write_out = xml2rfc.log.write_out\n        orig_write_err = xml2rfc.log.write_err\n        parser_out = io.StringIO()\n        parser_err = io.StringIO()\n\n        with ExitStack() as stack:\n            @stack.callback\n            def cleanup():  # called when context exited, even if there's an exception\n                xml2rfc.log.write_out = orig_write_out\n                xml2rfc.log.write_err = orig_write_err\n\n            xml2rfc.log.write_out = parser_out\n            xml2rfc.log.write_err = parser_err\n\n            parser = xml2rfc.XmlRfcParser(filename, quiet=True)\n            try:\n                tree = parser.parse()\n            except Exception as e:\n                raise XMLParseError(parser_out.getvalue(), parser_err.getvalue()) from e\n\n            xml_version = tree.getroot().get('version', '2')\n            if xml_version == '2':\n                v2v3 = xml2rfc.V2v3XmlWriter(tree)\n                tree.tree = v2v3.convert2to3()\n        return tree, xml_version\n\n    def _document_name(self, ref):\n        \"\"\"Get document name from reference.\"\"\"\n        series = [\"rfc\", \"bcp\", \"fyi\", \"std\"]\n        # handle xinclude first\n        # FIXME: this assumes the xinclude is a bibxml href; if it isn't, there can\n        # still be false negatives. it would be better to expand the xinclude and parse\n        # its seriesInfo.\n        if ref.tag.endswith(\"}include\"):\n            name = re.search(\n                rf\"reference\\.({'|'.join(series).upper()})\\.(\\d{{4}})\\.xml\",\n                ref.attrib[\"href\"],\n            )\n            if name:\n                return f\"{name.group(1)}{int(name.group(2))}\".lower()\n            name = re.search(\n                r\"reference\\.I-D\\.(?:draft-)?(.*)\\.xml\", ref.attrib[\"href\"]\n            )\n            if name:\n                return f\"draft-{name.group(1)}\"\n            # can't extract the name, give up\n            return \"\"\n\n        # check the anchor next\n        anchor = ref.get(\"anchor\").lower()  # always give back lowercase\n        label = anchor.rstrip(\"0123456789\")  # remove trailing digits\n        if label in series:\n            number = int(anchor[len(label) :])\n            return f\"{label}{number}\"\n\n        # if we couldn't find a match so far, try the seriesInfo\n        series_query = \" or \".join(f\"@name='{x.upper()}'\" for x in series)\n        for info in ref.xpath(\n            f\"./seriesInfo[{series_query} or @name='Internet-Draft']\"\n        ):\n            if not info.attrib[\"value\"]:\n                continue\n            if info.attrib[\"name\"] == \"Internet-Draft\":\n                return info.attrib[\"value\"]\n            else:\n                return f'{info.attrib[\"name\"].lower()}{info.attrib[\"value\"]}'\n        return \"\"\n\n    def _reference_section_type(self, section_name):\n        \"\"\"Determine reference type from name of references section\"\"\"\n        if section_name:\n            section_name = section_name.lower()\n            if 'normative' in section_name:\n                return self.REF_TYPE_NORMATIVE\n            elif 'informative' in section_name:\n                return self.REF_TYPE_INFORMATIVE\n        return self.REF_TYPE_UNKNOWN\n\n    def _reference_section_name(self, section_elt):\n        section_name = section_elt.findtext('name')\n        if section_name is None and 'title' in section_elt.keys():\n            section_name = section_elt.get('title')  # fall back to title if we have it\n        return section_name\n\n    def _parse_docname(self):\n        docname = self.xmlroot.attrib.get('docName')\n        revmatch = re.match(\n            r'^(?P<filename>.+?)(?:-(?P<rev>[0-9][0-9]))?$',\n            docname,\n\n        )\n        if revmatch is None:\n            raise ValueError('Unable to parse docName')\n        # If a group had no match it is None\n        return revmatch.group('filename'), revmatch.group('rev')\n\n    def get_title(self):\n        return self.xmlroot.findtext('front/title').strip()\n\n    @staticmethod\n    def parse_creation_date(date_elt):\n        if date_elt is None:\n            return None\n        today = date_today()\n        # ths mimics handling of date elements in the xml2rfc text/html writers\n        year, month, day = extract_date(date_elt, today)\n        year, month, day = augment_date(year, month, day, today)\n        if not day:\n            # Must choose a day for a datetime.date. Per RFC 7991 sect 2.17, we use\n            # today's date if it is consistent with the rest of the date. Otherwise,\n            # arbitrariy (and consistent with the text parser) assume the 15th.\n            if year == today.year and month == today.month:\n                day = today.day\n            else:\n                day = 15\n        return datetime.date(year, month, day)\n\n    def get_creation_date(self):\n        return self.parse_creation_date(self.xmlroot.find(\"front/date\"))\n\n    # todo fix the implementation of XMLDraft.get_abstract()\n    #\n    # This code was pulled from ietf.submit.forms where it existed for some time.\n    # It does not work, at least with modern xml2rfc. This assumes that the abstract\n    # is simply text in the front/abstract node, but the XML schema wraps the actual\n    # abstract text in <t> elements (and allows <dl>, <ol>, and <ul> as well). As a\n    # result, this method normally returns an empty string, which is later replaced by\n    # the abstract parsed from the rendered text. For now, I a commenting this out\n    # and making it explicit that the abstract always comes from the text format.\n    #\n    # def get_abstract(self):\n    #     \"\"\"Extract the abstract\"\"\"\n    #     abstract = self.xmlroot.findtext('front/abstract')\n    #     return abstract.strip() if abstract else ''\n\n    def get_author_list(self):\n        \"\"\"Get detailed author list\n\n        Returns a list of dicts with the following keys:\n            name, first_name, middle_initial, last_name,\n            name_suffix, email, country, affiliation\n        Values will be None if not available\n        \"\"\"\n        result = []\n        empty_author = {\n            k: None for k in [\n                'name', 'first_name', 'middle_initial', 'last_name',\n                'name_suffix', 'email', 'country', 'affiliation',\n            ]\n        }\n\n        for author in self.xmlroot.findall('front/author'):\n            info = {\n                'name': author.attrib.get('fullname'),\n                'email': author.findtext('address/email'),\n                'affiliation': author.findtext('organization'),\n            }\n            elem = author.find('address/postal/country')\n            if elem is not None:\n                ascii_country = elem.get('ascii', None)\n                info['country'] = ascii_country if ascii_country else elem.text\n            for item in info:\n                if info[item]:\n                    info[item] = info[item].strip()\n            result.append(empty_author | info)  # merge, preferring info\n        return result\n\n    def get_refs(self):\n        \"\"\"Extract references from the draft\"\"\"\n        refs = {}\n        # accept nested <references> sections\n        for section in self.xmlroot.findall(\"back//references\"):\n            ref_type = self._reference_section_type(\n                self._reference_section_name(section)\n            )\n            for ref in (\n                section.findall(\"./reference\")\n                + section.findall(\"./referencegroup\")\n                + section.findall(\n                    \"./xi:include\", {\"xi\": \"http://www.w3.org/2001/XInclude\"}\n                )\n            ):\n                name = self._document_name(ref)\n                if name:\n                    refs[name] = ref_type\n        return refs\n\n\nclass XMLParseError(Exception):\n    \"\"\"An error occurred while parsing\"\"\"\n    def __init__(self, out: str, err: str, *args):\n        super().__init__(*args)\n        self._out = out\n        self._err = err\n\n    def parser_msgs(self):\n        return self._out.splitlines() + self._err.splitlines()\n", "path": "ietf/utils/xmldraft.py"}]}