From ca3932629457a441d2a8797a4d28aa42c1521dce Mon Sep 17 00:00:00 2001 From: Kesara Rathnayake Date: Thu, 21 Mar 2024 18:25:54 +1300 Subject: [PATCH] feat: Validate docName and seriesInfo value for I-D (#1116) * refactor: Remove redundant import * refactor: Remove an obsolete warning * feat: Validate docName and seriesInfo value for I-D Fixes #1115 --- test.py | 77 ++++++++++++++++++++++++++++++++++++++++- xml2rfc/writers/base.py | 41 ++++++++++++---------- 2 files changed, 99 insertions(+), 19 deletions(-) diff --git a/test.py b/test.py index f486424a..fe2a9199 100644 --- a/test.py +++ b/test.py @@ -10,7 +10,7 @@ from xml2rfc.boilerplate_rfc_7841 import boilerplate_rfc_status_of_memo from xml2rfc.walkpdf import xmldoc -from xml2rfc.writers.base import default_options +from xml2rfc.writers.base import default_options, BaseV3Writer, RfcWriterError from xml2rfc.writers.text import MAX_WIDTH try: @@ -697,6 +697,81 @@ def test_render_reference(self): self.assertEqual(len(lines), 2) self.assertIn(url, lines[1].text) +class BaseV3WriterTest(unittest.TestCase): + '''BaseV3Writer tests''' + + def setUp(self): + xml2rfc.log.quiet = True + path = 'tests/input/elements.xml' + self.parser = xml2rfc.XmlRfcParser(path, + quiet=True, + options=default_options, + **options_for_xmlrfcparser) + self.xmlrfc = self.parser.parse() + self.writer = BaseV3Writer(self.xmlrfc, quiet=True) + + def test_validate_draft_name(self): + # Valid documents + valid_docs = [] + valid_docs.append(lxml.etree.fromstring(''' + + + + + +''')) + valid_docs.append(lxml.etree.fromstring(''' + + + + +''')) + valid_docs.append(lxml.etree.fromstring(''' + + + +''')) + valid_docs.append(lxml.etree.fromstring(''' + + + + +''')) + for valid_doc in valid_docs: + self.writer.root = valid_doc + self.assertTrue(self.writer.validate_draft_name()) + + # Invalid document + invalid_doc = lxml.etree.fromstring(''' + + + + +''') + self.writer.root = invalid_doc + with self.assertRaises(RfcWriterError): + self.writer.validate_draft_name() + if __name__ == '__main__': unittest.main() diff --git a/xml2rfc/writers/base.py b/xml2rfc/writers/base.py index 85beea0c..771ef4ab 100644 --- a/xml2rfc/writers/base.py +++ b/xml2rfc/writers/base.py @@ -7,7 +7,6 @@ import copy import datetime import textwrap -import lxml import os import re import xml2rfc.log @@ -1057,7 +1056,7 @@ def write_section_rec(self, section, count_str="1.", appendix=False, p_count = 1 # Paragraph counter for element in section: # Check for a PI - if element.tag is lxml.etree.PI: + if element.tag is etree.PI: pidict = self.parse_pi(element) if pidict and "needLines" in pidict: self.needLines(pidict["needLines"]) @@ -1297,7 +1296,7 @@ def _build_index(self): if 'anchor' in ref.attrib: self._indexRef(ref_counter, title=title.text, anchor=ref.attrib["anchor"]) else: - raise RfcWriterError("Reference is missing an anchor: %s" % lxml.etree.tostring(ref)) + raise RfcWriterError("Reference is missing an anchor: %s" % etree.tostring(ref)) # Appendix sections back = self.r.find('back') @@ -1646,7 +1645,7 @@ def write_to_file(self, file): v3_rnc_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'data', 'v3.rnc') v3_rng_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'data', 'v3.rng') -v3_schema = lxml.etree.ElementTree(file=v3_rng_file) +v3_schema = etree.ElementTree(file=v3_rng_file) def get_element_tags(): tags = set() @@ -1733,7 +1732,7 @@ def __init__(self, xmlrfc, quiet=None, options=default_options, date=None): self.date = date if date is not None else datetime.date.today() self.v3_rnc_file = v3_rnc_file self.v3_rng_file = v3_rng_file - self.v3_rng = lxml.etree.RelaxNG(file=self.v3_rng_file) + self.v3_rng = etree.RelaxNG(file=self.v3_rng_file) self.v3_schema = v3_schema self.schema = v3_schema self.index_items = [] @@ -1780,21 +1779,21 @@ def get_relevant_pis(self, e): if e != None: # directly inside element for c in e.getchildren(): - if c.tag == lxml.etree.PI and c.target == xml2rfc.V3_PI_TARGET: + if c.tag == etree.PI and c.target == xml2rfc.V3_PI_TARGET: pis.append(c) # siblings before element for s in e.itersiblings(preceding=True): - if s.tag == lxml.etree.PI and s.target == xml2rfc.V3_PI_TARGET: + if s.tag == etree.PI and s.target == xml2rfc.V3_PI_TARGET: pis.append(s) # ancestor's earlier siblings for a in e.iterancestors(): for s in a.itersiblings(preceding=True): - if s.tag == lxml.etree.PI and s.target == xml2rfc.V3_PI_TARGET: + if s.tag == etree.PI and s.target == xml2rfc.V3_PI_TARGET: pis.append(s) # before root elements p = self.root.getprevious() while p != None: - if p.tag == lxml.etree.PI and p.target == xml2rfc.V3_PI_TARGET: + if p.tag == etree.PI and p.target == xml2rfc.V3_PI_TARGET: pis.append(p) p = p.getprevious() return pis @@ -2045,9 +2044,9 @@ def pretty_print_prep(self, e, p): ind = self.options.indent ## The actual printing is done in self.write() def indent(e, i): - if e.tag in (lxml.etree.CDATA, ): + if e.tag in (etree.CDATA, ): return - if e.tag in (lxml.etree.Comment, lxml.etree.PI, ): + if e.tag in (etree.Comment, etree.PI, ): if not e.tail: if e.getnext() != None: e.tail = '\n'+' '*i @@ -2128,13 +2127,6 @@ def validate(self, when='', warn=False): self.v3_rng.assertValid(tree) return True except Exception as e: - lxmlver = lxml.etree.LXML_VERSION[:3] - if lxmlver < (3, 8, 0): - self.warn(None, "The available version of the lxml library (%s) does not provide xpath " - "information as part of validation errors. Upgrade to version 3.8.0 or " - "higher for better error messages." % ('.'.join(str(v) for v in lxmlver), )) - # These warnings are occasionally incorrect -- disable this - # output for now: deadly = False if hasattr(e, 'error_log'): for error in e.error_log: @@ -2166,6 +2158,19 @@ def validate_before(self, e, p): if not self.validate('before'): self.note(None, "Schema validation failed for input document") + self.validate_draft_name() + + def validate_draft_name(self): + if not self.root.attrib.get('number', False): + docName = self.root.attrib.get('docName', None) + info = self.root.find('./front/seriesInfo[@name="Internet-Draft"]') + si_draft_name = info.get('value') if info != None else None + + if all([docName, si_draft_name]) and docName != si_draft_name: + self.die(self.root, 'docName and value in must match.') + + return True + def validate_after(self, e, p): # XXX: There is an issue with exponential increase in validation time # as a function of the number of attributes on the root element, on