def test_parse_amdpar_interp_heading(self): text = "ii. The heading for 35(b) blah blah is revised." xml = etree.fromstring(u'<AMDPAR>%s</AMDPAR>' % text) instructions, _ = amdparser.parse_amdpar(xml, ['1111', 'Interpretations']) with XMLBuilder('EREGS_INSTRUCTIONS') as ctx: ctx.PUT(label='1111-Interpretations-35-(b)[title]') self.assertEqual(etree.tostring(instructions), ctx.xml_str)
def assert_instruction_conversion(instruction_text, initial_label): """We have several tests that require creating an AMDPAR with the provided instruction_text, parsing, and comparing it to a built set of XML.""" amdpar = etree.fromstring('<AMDPAR>{0}</AMDPAR>'.format(instruction_text)) with XMLBuilder('EREGS_INSTRUCTIONS') as expected: yield expected instructions, _ = amdparser.parse_amdpar(amdpar, initial_label) assert etree.tounicode(instructions) == expected.xml_str
def test_parse_amdpar_add_field(self): text = "Adding introductory text to paragraph (c)" xml = etree.fromstring('<AMDPAR>%s</AMDPAR>' % text) instructions, _ = amdparser.parse_amdpar(xml, ['1111', None, '12']) with XMLBuilder('EREGS_INSTRUCTIONS') as ctx: ctx.PUT(label='1111-?-12-c[text]') self.assertEqual(etree.tostring(instructions), ctx.xml_str)
def test_parse_amdpar_interp_entries(self): text = "Entries for 12(c)(3)(ix)(A) and (B) are added." xml = etree.fromstring('<AMDPAR>%s</AMDPAR>' % text) instructions, _ = amdparser.parse_amdpar(xml, ['1111', 'Interpretations']) with XMLBuilder('EREGS_INSTRUCTIONS') as ctx: ctx.POST(label='1111-Interpretations-12-(c)(3)(ix)(A)') ctx.POST(label='1111-Interpretations-12-(c)(3)(ix)(B)') self.assertEqual(etree.tostring(instructions), ctx.xml_str)
def test_parse_amdpar_and_and(self): text = "12(a) 'Titles and Paragraphs' and paragraph 3 are added" xml = etree.fromstring('<AMDPAR>%s</AMDPAR>' % text) instructions, _ = amdparser.parse_amdpar(xml, ['1111', 'Interpretations']) with XMLBuilder('EREGS_INSTRUCTIONS') as ctx: ctx.POST(label='1111-Interpretations-12-(a)') ctx.POST(label='1111-Interpretations-12-(a)-3') self.assertEqual(etree.tostring(instructions), ctx.xml_str)
def test_parse_amdpar_and_in_tags(self): text = "Under <E>Appendix A - Some phrase and another</E>, paragraph " text += "3 is added" xml = etree.fromstring('<AMDPAR>%s</AMDPAR>' % text) instructions, _ = amdparser.parse_amdpar(xml, ['1111', 'Interpretations']) with XMLBuilder('EREGS_INSTRUCTIONS') as ctx: ctx.POST(label='1111-Interpretations-A-()-3') self.assertEqual(etree.tostring(instructions), ctx.xml_str)
def test_parse_amdpar_subject_group(self): xml = etree.fromstring( '<AMDPAR>8. Section 479.90a is added to ' '[subject-group(Exemptions Relating to Transfers of Firearms)] ' 'to read as follows.</AMDPAR>') instructions, _ = amdparser.parse_amdpar(xml, []) with XMLBuilder('EREGS_INSTRUCTIONS') as ctx: ctx.POST(label='479-Subjgrp:ERtToF-90a') self.assertEqual(etree.tostring(instructions), ctx.xml_str)
def test_parse_amdpar_interp_redesignated(self): text = "Paragraph 1 under 51(b) is redesignated as paragraph 2 " text += "under subheading 51(b)(1) and revised" xml = etree.fromstring(u'<AMDPAR>%s</AMDPAR>' % text) instructions, _ = amdparser.parse_amdpar(xml, ['1111', 'Interpretations']) with XMLBuilder('EREGS_INSTRUCTIONS') as ctx: ctx.DELETE(label='1111-Interpretations-51-(b)-1') ctx.POST(label='1111-Interpretations-51-(b)(1)-2') self.assertEqual(etree.tostring(instructions), ctx.xml_str)
def test_parse_amdpar_interp_context(self): text = "b. 35(b)(1) Some title and paragraphs 1, 2, and 3 are added." xml = etree.fromstring(u'<AMDPAR>%s</AMDPAR>' % text) instructions, _ = amdparser.parse_amdpar(xml, ['1111', 'Interpretations']) with XMLBuilder('EREGS_INSTRUCTIONS') as ctx: ctx.POST(label='1111-Interpretations-35-(b)(1)') ctx.POST(label='1111-Interpretations-35-(b)(1)-1') ctx.POST(label='1111-Interpretations-35-(b)(1)-2') ctx.POST(label='1111-Interpretations-35-(b)(1)-3') self.assertEqual(etree.tostring(instructions), ctx.xml_str)
def test_parse_amdpar_interp_phrase(self): text = u"In Supplement I to part 999, under" text += u'<E T="03">Section 999.3—Header,</E>' text += u"under" text += u'<E T="03">3(b) Subheader,</E>' text += u"new paragraph 1.iv is added:" xml = etree.fromstring(u'<AMDPAR>%s</AMDPAR>' % text) instructions, _ = amdparser.parse_amdpar(xml, ['1111']) with XMLBuilder('EREGS_INSTRUCTIONS') as ctx: ctx.POST(label='999-Interpretations-3-(b)-1-iv') self.assertEqual(etree.tostring(instructions), ctx.xml_str)
def test_parse_amdpar_newly_redesignated(self): text = "Paragraphs 3.ii, 3.iii, 4 and newly redesignated paragraph " text += "10 are revised." xml = etree.fromstring('<AMDPAR>%s</AMDPAR>' % text) with XMLBuilder('EREGS_INSTRUCTIONS') as ctx: ctx.PUT(label='1111-Interpretations-2-(a)-3-ii') ctx.PUT(label='1111-Interpretations-2-(a)-3-iii') ctx.PUT(label='1111-Interpretations-2-(a)-4') ctx.PUT(label='1111-Interpretations-2-(a)-10') instructions, _ = amdparser.parse_amdpar( xml, ['1111', 'Interpretations', '2', '(a)']) self.assertEqual(etree.tostring(instructions), ctx.xml_str)
def test_parse_amdpar_verbs_ands(self): text = "Under 45(a)(1) Title, paragraphs 1 and 2 are removed, and " text += "45(a)(1)(i) Deeper Title and paragraphs 1 and 2 are added" xml = etree.fromstring('<AMDPAR>%s</AMDPAR>' % text) instructions, _ = amdparser.parse_amdpar(xml, ['1111', 'Interpretations']) with XMLBuilder('EREGS_INSTRUCTIONS') as ctx: ctx.DELETE(label='1111-Interpretations-45-(a)(1)-1') ctx.DELETE(label='1111-Interpretations-45-(a)(1)-2') ctx.POST(label='1111-Interpretations-45-(a)(1)(i)') ctx.POST(label='1111-Interpretations-45-(a)(1)(i)-1') ctx.POST(label='1111-Interpretations-45-(a)(1)(i)-2') self.assertEqual(etree.tostring(instructions), ctx.xml_str)
def test_parse_amdpar_definition(self): """We should correctly deduce which paragraphs are being updated, even when they are identified by definition alone""" text = ("Section 478.11 is amended by adding a definition for the " u"term “Nonimmigrant visa” in alphabetical order to read as " "follows:") xml = etree.fromstring('<AMDPAR>%s</AMDPAR>' % text) instructions, _ = amdparser.parse_amdpar(xml, []) with XMLBuilder('EREGS_INSTRUCTIONS') as ctx: ctx.POST(label='478-?-11-p{}'.format( hash_for_paragraph("Nonimmigrant visa"))) self.assertEqual(etree.tostring(instructions), ctx.xml_str)
def test_parse_amdpar_moved_then_modified(self): text = "Under Paragraph 22(a), paragraph 1 is revised, paragraph " text += "2 is redesignated as paragraph 3 and revised, and new " text += "paragraph 2 is added." xml = etree.fromstring('<AMDPAR>%s</AMDPAR>' % text) instructions, _ = amdparser.parse_amdpar(xml, ['1111', 'Interpretations']) with XMLBuilder('EREGS_INSTRUCTIONS') as ctx: ctx.PUT(label='1111-Interpretations-22-(a)-1') ctx.DELETE(label='1111-Interpretations-22-(a)-2') ctx.POST(label='1111-Interpretations-22-(a)-3') ctx.POST(label='1111-Interpretations-22-(a)-2') self.assertEqual(etree.tostring(instructions), ctx.xml_str)
def test_parse_amdpar_definition(self): """We should correctly deduce which paragraphs are being updated, even when they are identified by definition alone""" text = ("Section 478.11 is amended by adding a definition for the " u"term “Nonimmigrant visa” in alphabetical order to read as " "follows:") xml = etree.fromstring('<AMDPAR>%s</AMDPAR>' % text) instructions, _ = amdparser.parse_amdpar(xml, []) with XMLBuilder('EREGS_INSTRUCTIONS') as ctx: ctx.POST(label='478-?-11-p{}'.format(hash_for_paragraph( "Nonimmigrant visa"))) self.assertEqual(etree.tostring(instructions), ctx.xml_str)
def transform(self, xml): has_part = xml.xpath('//*[AMDPAR and @PART]') context = ['0'] if has_part: context = [has_part[0].get('PART')] elif xml.xpath('//AMDPAR'): logger.warning('Could not find any PART designators.') for amdparent in xml.xpath(self.AMDPARENT_XPATH): # Always start with only the CFR part context = [amdparent.get('PART') or context[0]] for amdpar in amdparent.xpath('.//AMDPAR'): instructions, context = parse_amdpar(amdpar, context) amdpar.append(instructions) instructions.set( 'final_context', '-'.join('?' if l is None else l for l in context))
def preprocess_amdpars(xml): """Modify the AMDPAR tag to contain an <EREGS_INSTRUCTIONS> element. This element contains an interpretation of the AMDPAR, as viewed as a sequence of actions for how to modify the CFR. Do _not_ modify any existing EREGS_INSTRUCTIONS (they've been manually created)""" has_part = xml.xpath('//*[AMDPAR and @PART]') context = ['0'] if has_part: context = [has_part[0].get('PART')] elif xml.xpath('//AMDPAR'): logger.warning('Could not find any PART designators.') for amdparent in xml.xpath(_AMDPARENT_XPATH): # Always start with only the CFR part context = [amdparent.get('PART') or context[0]] for amdpar in amdparent.xpath('.//AMDPAR'): instructions, context = parse_amdpar(amdpar, context) amdpar.append(instructions) instructions.set('final_context', uncertain_label(context))