diff --git a/ietf/doc/utils.py b/ietf/doc/utils.py index d45d40158..3c83b16be 100644 --- a/ietf/doc/utils.py +++ b/ietf/doc/utils.py @@ -763,9 +763,11 @@ def rebuild_reference_relations(doc, filenames): errors = [] unfound = set() for ( ref, refType ) in refs.items(): - # As of Dec 2021, DocAlias has a unique constraint on the name field, so count > 1 should not occur - refdoc = DocAlias.objects.filter( name=ref ) + refdoc = DocAlias.objects.filter(name=ref) + if not refdoc and re.match(r"^draft-.*-\d{2}$", ref): + refdoc = DocAlias.objects.filter(name=ref[:-3]) count = refdoc.count() + # As of Dec 2021, DocAlias has a unique constraint on the name field, so count > 1 should not occur if count == 0: unfound.add( "%s" % ref ) continue diff --git a/ietf/utils/test_draft_with_references_v3.xml b/ietf/utils/test_draft_with_references_v3.xml index a04880d1d..ad9b2b280 100644 --- a/ietf/utils/test_draft_with_references_v3.xml +++ b/ietf/utils/test_draft_with_references_v3.xml @@ -1,6 +1,6 @@ - + Test Draft with References @@ -37,9 +37,25 @@ + + + Cloud Software + + + + + + + + Informative References + + + + + Status of network hosts @@ -51,6 +67,34 @@ + + + Key Consistency and Discovery + + Brave Software + + + The Tor Project + + + Mozilla + + + Cloudflare + + + + This document describes the key consistency and correctness + requirements of protocols such as Privacy Pass, Oblivious DoH, and + Oblivious HTTP for user privacy. It discusses several mechanisms and + proposals for enabling user privacy in varying threat models. In + concludes with discussion of open problems in this area. + + + + + + @@ -191,4 +235,4 @@ - \ No newline at end of file + diff --git a/ietf/utils/tests.py b/ietf/utils/tests.py index 2095b5fa4..69c16be6d 100644 --- a/ietf/utils/tests.py +++ b/ietf/utils/tests.py @@ -374,10 +374,17 @@ class XMLDraftTests(TestCase): draft.get_refs(), { 'rfc1': XMLDraft.REF_TYPE_NORMATIVE, + 'rfc2': XMLDraft.REF_TYPE_NORMATIVE, + 'draft-wood-key-consistency-03': XMLDraft.REF_TYPE_INFORMATIVE, 'rfc255': XMLDraft.REF_TYPE_INFORMATIVE, 'bcp6': XMLDraft.REF_TYPE_INFORMATIVE, + 'bcp14': XMLDraft.REF_TYPE_INFORMATIVE, 'rfc1207': XMLDraft.REF_TYPE_UNKNOWN, 'rfc4086': XMLDraft.REF_TYPE_NORMATIVE, + 'draft-ietf-teas-pcecc-use-cases-00': XMLDraft.REF_TYPE_INFORMATIVE, + 'draft-ietf-teas-pcecc-use-cases': XMLDraft.REF_TYPE_INFORMATIVE, + 'draft-ietf-sipcore-multiple-reasons-00': XMLDraft.REF_TYPE_INFORMATIVE, + 'draft-ietf-sipcore-multiple-reasons': XMLDraft.REF_TYPE_INFORMATIVE, } ) diff --git a/ietf/utils/xmldraft.py b/ietf/utils/xmldraft.py index 15bf745cc..7e8674ea7 100644 --- a/ietf/utils/xmldraft.py +++ b/ietf/utils/xmldraft.py @@ -60,17 +60,47 @@ class XMLDraft(Draft): tree.tree = v2v3.convert2to3() return tree, xml_version - def _document_name(self, anchor): - """Guess document name from reference anchor + def _document_name(self, ref): + """Get document name from reference.""" + series = ["rfc", "bcp", "fyi", "std"] + # handle xinclude first + # FIXME: this assumes the xinclude is a bibxml href; if it isn't, there can + # still be false negatives. it would be better to expand the xinclude and parse + # its seriesInfo. + if ref.tag.endswith("}include"): + name = re.search( + rf"reference\.({'|'.join(series).upper()})\.(\d{{4}})\.xml", + ref.attrib["href"], + ) + if name: + return f"{name.group(1)}{int(name.group(2))}".lower() + name = re.search( + r"reference\.I-D\.(?:draft-)?(.*)\.xml", ref.attrib["href"] + ) + if name: + return f"draft-{name.group(1)}" + # can't extract the name, give up + return "" - Looks for series numbers and removes leading 0s from the number. - """ - anchor = anchor.lower() # always give back lowercase - label = anchor.rstrip('0123456789') # remove trailing digits - if label in ['rfc', 'bcp', 'fyi', 'std']: - number = int(anchor[len(label):]) - return f'{label}{number}' - return anchor + # check the anchor next + anchor = ref.get("anchor").lower() # always give back lowercase + label = anchor.rstrip("0123456789") # remove trailing digits + if label in series: + number = int(anchor[len(label) :]) + return f"{label}{number}" + + # if we couldn't find a match so far, try the seriesInfo + series_query = " or ".join(f"@name='{x.upper()}'" for x in series) + for info in ref.xpath( + f"./seriesInfo[{series_query} or @name='Internet-Draft']" + ): + if not info.attrib["value"]: + continue + if info.attrib["name"] == "Internet-Draft": + return info.attrib["value"] + else: + return f'{info.attrib["name"].lower()}{info.attrib["value"]}' + return "" def _reference_section_type(self, section_name): """Determine reference type from name of references section""" @@ -154,10 +184,20 @@ class XMLDraft(Draft): """Extract references from the draft""" refs = {} # accept nested sections - for section in self.xmlroot.findall('back//references'): - ref_type = self._reference_section_type(self._reference_section_name(section)) - for ref in (section.findall('./reference') + section.findall('./referencegroup')): - refs[self._document_name(ref.get('anchor'))] = ref_type + for section in self.xmlroot.findall("back//references"): + ref_type = self._reference_section_type( + self._reference_section_name(section) + ) + for ref in ( + section.findall("./reference") + + section.findall("./referencegroup") + + section.findall( + "./xi:include", {"xi": "http://www.w3.org/2001/XInclude"} + ) + ): + name = self._document_name(ref) + if name: + refs[name] = ref_type return refs