Skip to content

Commit 9ef3764

Browse files
Run v2v3 converter internally when v2 XML is submitted. Fixes ietf-tools#3305. Commit ready for merge.
- Legacy-Id: 19242
1 parent 73e0120 commit 9ef3764

3 files changed

Lines changed: 117 additions & 58 deletions

File tree

ietf/submit/forms.py

Lines changed: 44 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import sys
1212
import tempfile
1313
import xml2rfc
14+
from contextlib import ExitStack
1415

1516
from email.utils import formataddr
1617
from unidecode import unidecode
@@ -164,26 +165,41 @@ def format_messages(where, e, log):
164165
if self.cleaned_data.get('xml'):
165166
#if not self.cleaned_data.get('txt'):
166167
xml_file = self.cleaned_data.get('xml')
167-
name, ext = os.path.splitext(os.path.basename(xml_file.name))
168-
tfh, tfn = tempfile.mkstemp(prefix=name+'-', suffix='.xml')
169168
file_name = {}
170169
xml2rfc.log.write_out = io.StringIO() # open(os.devnull, "w")
171170
xml2rfc.log.write_err = io.StringIO() # open(os.devnull, "w")
172-
try:
171+
tfn = None
172+
with ExitStack() as stack:
173+
@stack.callback
174+
def cleanup(): # called when context exited, even in case of exception
175+
if tfn is not None:
176+
os.unlink(tfn)
177+
173178
# We need to write the xml file to disk in order to hand it
174179
# over to the xml parser. XXX FIXME: investigate updating
175180
# xml2rfc to be able to work with file handles to in-memory
176181
# files.
177-
with io.open(tfn, 'wb+') as tf:
182+
name, ext = os.path.splitext(os.path.basename(xml_file.name))
183+
with tempfile.NamedTemporaryFile(prefix=name+'-',
184+
suffix='.xml',
185+
mode='wb+',
186+
delete=False) as tf:
187+
tfn = tf.name
178188
for chunk in xml_file.chunks():
179189
tf.write(chunk)
180190
os.environ["XML_LIBRARY"] = settings.XML_LIBRARY
191+
192+
parser = xml2rfc.XmlRfcParser(str(tfn), quiet=True)
181193
# --- Parse the xml ---
182194
try:
183-
parser = xml2rfc.XmlRfcParser(str(tfn), quiet=True)
184-
self.xmltree = parser.parse(remove_comments=False, quiet=True)
195+
self.xmltree = parser.parse(remove_comments=False)
196+
# If we have v2, run it through v2v3. Keep track of the submitted version, though.
185197
self.xmlroot = self.xmltree.getroot()
186198
self.xml_version = self.xmlroot.get('version', '2')
199+
if self.xml_version == '2':
200+
v2v3 = xml2rfc.V2v3XmlWriter(self.xmltree)
201+
self.xmltree.tree = v2v3.convert2to3()
202+
self.xmlroot = self.xmltree.getroot() # update to the new root
187203

188204
draftname = self.xmlroot.attrib.get('docName')
189205
if draftname is None:
@@ -225,12 +241,11 @@ def format_messages(where, e, log):
225241
# --- Prep the xml ---
226242
file_name['xml'] = os.path.join(settings.IDSUBMIT_STAGING_PATH, '%s-%s%s' % (self.filename, self.revision, ext))
227243
try:
228-
if self.xml_version == '3':
229-
prep = xml2rfc.PrepToolWriter(self.xmltree, quiet=True, liberal=True, keep_pis=[xml2rfc.V3_PI_TARGET])
230-
prep.options.accept_prepped = True
231-
self.xmltree.tree = prep.prep()
232-
if self.xmltree.tree == None:
233-
self.add_error('xml', "Error from xml2rfc (prep): %s" % prep.errors)
244+
prep = xml2rfc.PrepToolWriter(self.xmltree, quiet=True, liberal=True, keep_pis=[xml2rfc.V3_PI_TARGET])
245+
prep.options.accept_prepped = True
246+
self.xmltree.tree = prep.prep()
247+
if self.xmltree.tree == None:
248+
self.add_error('xml', "Error from xml2rfc (prep): %s" % prep.errors)
234249
except Exception as e:
235250
msgs = format_messages('prep', e, xml2rfc.log)
236251
self.add_error('xml', msgs)
@@ -239,15 +254,9 @@ def format_messages(where, e, log):
239254
if not ('txt' in self.cleaned_data and self.cleaned_data['txt']):
240255
file_name['txt'] = os.path.join(settings.IDSUBMIT_STAGING_PATH, '%s-%s.txt' % (self.filename, self.revision))
241256
try:
242-
if self.xml_version != '3':
243-
self.xmltree = parser.parse(remove_comments=True, quiet=True)
244-
self.xmlroot = self.xmltree.getroot()
245-
pagedwriter = xml2rfc.PaginatedTextRfcWriter(self.xmltree, quiet=True)
246-
pagedwriter.write(file_name['txt'])
247-
else:
248-
writer = xml2rfc.TextWriter(self.xmltree, quiet=True)
249-
writer.options.accept_prepped = True
250-
writer.write(file_name['txt'])
257+
writer = xml2rfc.TextWriter(self.xmltree, quiet=True)
258+
writer.options.accept_prepped = True
259+
writer.write(file_name['txt'])
251260
log.log("In %s: xml2rfc %s generated %s from %s (version %s)" %
252261
( os.path.dirname(file_name['xml']),
253262
xml2rfc.__version__,
@@ -260,51 +269,28 @@ def format_messages(where, e, log):
260269
self.add_error('xml', msgs)
261270

262271
# --- Convert to html ---
263-
if self.xml_version == '3':
264-
try:
265-
file_name['html'] = os.path.join(settings.IDSUBMIT_STAGING_PATH, '%s-%s.html' % (self.filename, self.revision))
266-
writer = xml2rfc.HtmlWriter(self.xmltree, quiet=True)
267-
writer.write(file_name['html'])
268-
self.file_types.append('.html')
269-
log.log("In %s: xml2rfc %s generated %s from %s (version %s)" %
270-
( os.path.dirname(file_name['xml']),
271-
xml2rfc.__version__,
272-
os.path.basename(file_name['html']),
273-
os.path.basename(file_name['xml']),
274-
self.xml_version))
275-
except Exception as e:
276-
msgs = format_messages('html', e, xml2rfc.log)
277-
self.add_error('xml', msgs)
272+
try:
273+
file_name['html'] = os.path.join(settings.IDSUBMIT_STAGING_PATH, '%s-%s.html' % (self.filename, self.revision))
274+
writer = xml2rfc.HtmlWriter(self.xmltree, quiet=True)
275+
writer.write(file_name['html'])
276+
self.file_types.append('.html')
277+
log.log("In %s: xml2rfc %s generated %s from %s (version %s)" %
278+
( os.path.dirname(file_name['xml']),
279+
xml2rfc.__version__,
280+
os.path.basename(file_name['html']),
281+
os.path.basename(file_name['xml']),
282+
self.xml_version))
283+
except Exception as e:
284+
msgs = format_messages('html', e, xml2rfc.log)
285+
self.add_error('xml', msgs)
278286

279-
if self.xml_version == '2':
280-
ok, errors = self.xmltree.validate()
281-
else:
282-
ok, errors = True, ''
283-
284-
if not ok:
285-
# Each error has properties:
286-
#
287-
# message: the message text
288-
# domain: the domain ID (see lxml.etree.ErrorDomains)
289-
# type: the message type ID (see lxml.etree.ErrorTypes)
290-
# level: the log level ID (see lxml.etree.ErrorLevels)
291-
# line: the line at which the message originated (if applicable)
292-
# column: the character column at which the message originated (if applicable)
293-
# filename: the name of the file in which the message originated (if applicable)
294-
self.add_error('xml',
295-
[ forms.ValidationError("One or more XML validation errors occurred when processing the XML file:") ] +
296-
[ forms.ValidationError("%s: Line %s: %s" % (xml_file.name, r.line, r.message), code="%s"%r.type) for r in errors ]
297-
)
298287
except Exception as e:
299288
try:
300289
msgs = format_messages('txt', e, xml2rfc.log)
301290
log.log('\n'.join(msgs))
302291
self.add_error('xml', msgs)
303292
except Exception:
304293
self.add_error('xml', "An exception occurred when trying to process the XML file: %s" % e)
305-
finally:
306-
os.close(tfh)
307-
os.unlink(tfn)
308294

309295
if self.cleaned_data.get('txt'):
310296
# try to parse it
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<?xml-stylesheet type="text/xsl" href="rfc2629.xslt" ?>
3+
<!DOCTYPE rfc SYSTEM "rfc2629.dtd" []>
4+
<?rfc toc="yes"?>
5+
<rfc category="info" docName="%(name)s" ipr="trust200902">
6+
<front>
7+
<title>%(title)s</title>
8+
<author fullname="%(author)s" initials="%(initials)s" surname="%(surname)s">
9+
<organization>Test Centre Inc.</organization>
10+
<address>
11+
<postal>
12+
<country>UK</country>
13+
</postal>
14+
<email>%(email)s</email>
15+
</address>
16+
</author>
17+
<date day="%(day)s" month="%(month)s" year="%(year)s"/>
18+
<workgroup>%(group)s</workgroup>
19+
<abstract>
20+
<t>
21+
This document describes how to test tests.
22+
</t>
23+
</abstract>
24+
</front>
25+
<middle>
26+
<section numbered="true" toc="default" title="Introduction">
27+
<t>
28+
This document describes a protocol for testing tests.
29+
</t>
30+
</section>
31+
<section anchor="JSON" numbered="true" toc="default" title="JSON example">
32+
<t>
33+
The JSON object should look like this:
34+
35+
{
36+
"test": 1234
37+
}
38+
</t>
39+
</section>
40+
<section anchor="Security" numbered="true" toc="default" title="Security Considerations">
41+
<t>
42+
There are none.
43+
</t>
44+
</section>
45+
<section anchor="IANA" numbered="true" toc="default" title="IANA Considerations">
46+
<t>
47+
No new registrations for IANA.
48+
</t>
49+
</section>
50+
</middle>
51+
<back/>
52+
</rfc>

ietf/submit/tests.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1244,6 +1244,27 @@ def test_submit_new_wg_with_dash(self):
12441244

12451245
self.assertEqual(Submission.objects.get(name=name).group.acronym, group.acronym)
12461246

1247+
def test_submit_new_wg_v2_country_only(self):
1248+
"""V2 drafts should accept addresses without street/city"""
1249+
# break early in case of missing configuration
1250+
self.assertTrue(os.path.exists(settings.IDSUBMIT_IDNITS_BINARY))
1251+
1252+
# submit
1253+
author = PersonFactory()
1254+
group = GroupFactory()
1255+
name = "draft-authorname-testing-tests"
1256+
r = self.create_and_post_submission(
1257+
name=name,
1258+
rev='00',
1259+
author=author,
1260+
group=group.acronym,
1261+
formats=('xml',),
1262+
base_filename='test_submission_v2_country_only'
1263+
)
1264+
self.assertEqual(r.status_code, 302)
1265+
submission = Submission.objects.get(name=name)
1266+
self.assertEqual(submission.xml_version, '2') # should reflect the submitted version
1267+
12471268
def test_submit_new_irtf(self):
12481269
group = Group.objects.create(acronym="saturnrg", name="Saturn", type_id="rg", state_id="active")
12491270

0 commit comments

Comments
 (0)