diff --git a/cms/djangoapps/contentstore/views/tests/test_import_export.py b/cms/djangoapps/contentstore/views/tests/test_import_export.py index 831b9a1e311a3da7d5b914e9e4db761d1e4ce60b..81ee02b1243799210d462eb2ab0cae650c044b29 100644 --- a/cms/djangoapps/contentstore/views/tests/test_import_export.py +++ b/cms/djangoapps/contentstore/views/tests/test_import_export.py @@ -2,7 +2,6 @@ Unit tests for course import and export """ - import copy import json import logging @@ -55,6 +54,7 @@ class ImportEntranceExamTestCase(CourseTestCase, MilestonesTestCaseMixin): """ Unit tests for importing a course with entrance exam """ + def setUp(self): super(ImportEntranceExamTestCase, self).setUp() self.url = reverse_course_url('import_handler', self.course.id) @@ -533,6 +533,7 @@ class ExportTestCase(CourseTestCase): """ Tests for export_handler. """ + def setUp(self): """ Sets up the test course. @@ -788,9 +789,9 @@ class ExportTestCase(CourseTestCase): @patch('contentstore.views.import_export._latest_task_status') @patch('user_tasks.models.UserTaskArtifact.objects.get') def test_export_status_handler_other( - self, - mock_get_user_task_artifact, - mock_latest_task_status, + self, + mock_get_user_task_artifact, + mock_latest_task_status, ): """ Verify that the export status handler generates the correct export path @@ -808,9 +809,9 @@ class ExportTestCase(CourseTestCase): @patch('contentstore.views.import_export._latest_task_status') @patch('user_tasks.models.UserTaskArtifact.objects.get') def test_export_status_handler_s3( - self, - mock_get_user_task_artifact, - mock_latest_task_status, + self, + mock_get_user_task_artifact, + mock_latest_task_status, ): """ Verify that the export status handler generates the correct export path @@ -828,9 +829,9 @@ class ExportTestCase(CourseTestCase): @patch('contentstore.views.import_export._latest_task_status') @patch('user_tasks.models.UserTaskArtifact.objects.get') def test_export_status_handler_filesystem( - self, - mock_get_user_task_artifact, - mock_latest_task_status, + self, + mock_get_user_task_artifact, + mock_latest_task_status, ): """ Verify that the export status handler generates the correct export path @@ -849,6 +850,7 @@ class TestLibraryImportExport(CourseTestCase): """ Tests for importing content libraries from XML and exporting them to XML. """ + def setUp(self): super(TestLibraryImportExport, self).setUp() self.export_dir = tempfile.mkdtemp() @@ -906,6 +908,7 @@ class TestCourseExportImport(LibraryTestCase): """ Tests for importing after exporting the course containing content libraries from XML. """ + def setUp(self): super(TestCourseExportImport, self).setUp() self.export_dir = tempfile.mkdtemp() @@ -1022,18 +1025,20 @@ class TestCourseExportImport(LibraryTestCase): ) +@ddt.ddt @override_settings(CONTENTSTORE=TEST_DATA_CONTENTSTORE) class TestCourseExportImportProblem(CourseTestCase): """ Tests for importing after exporting the course containing problem with pre tags from XML. """ + def setUp(self): super(TestCourseExportImportProblem, self).setUp() self.export_dir = tempfile.mkdtemp() self.source_course = CourseFactory.create(default_store=ModuleStoreEnum.Type.split) self.addCleanup(shutil.rmtree, self.export_dir, ignore_errors=True) - def _setup_source_course_with_problem_content(self, publish_item=False): + def _setup_source_course_with_problem_content(self, data, publish_item=False): """ Sets up course with problem content. """ @@ -1058,8 +1063,7 @@ class TestCourseExportImportProblem(CourseTestCase): category='problem', display_name='Test Problem', publish_item=publish_item, - data='<problem><pre><code>x=10 print("hello \n")</code></pre>' - '<multiplechoiceresponse></multiplechoiceresponse></problem>', + data=data, ) def get_problem_content(self, block_location): @@ -1071,21 +1075,38 @@ class TestCourseExportImportProblem(CourseTestCase): return self.get_problem_content(self.store.get_item(block_location).children[0]) - def assert_problem_definition(self, course_location): + def assert_problem_definition(self, course_location, expected_problem_content): """ Asserts that problems' data is as expected with pre-tag content maintained. """ - expected_problem_content = '<problem>\n <pre>\n <code>x=10 print("hello \n")</code>\n </pre>\n ' \ - '<multiplechoiceresponse/>\n</problem>\n' problem_content = self.get_problem_content(course_location) self.assertEqual(expected_problem_content, problem_content) - def test_problem_content_on_course_export_import(self): + @ddt.data( + [ + '<problem><pre><code>x=10 print("hello \n")</code></pre>' + '<pre><div><pre><code>x=10 print("hello \n")</code></pre></div></pre>' + '<multiplechoiceresponse></multiplechoiceresponse></problem>', + + '<problem>\n <pre>\n <code>x=10 print("hello \n")</code>\n </pre>\n <pre>\n <div>\n <pre>\n ' + ' <code>x=10 print("hello \n")</code>\n </pre>\n </div>\n </pre>\n ' + '<multiplechoiceresponse/>\n</problem>\n' + ], + [ + '<problem><pre><code>x=10 print("hello \n")</code></pre>' + '<multiplechoiceresponse></multiplechoiceresponse></problem>', + + '<problem>\n <pre>\n <code>x=10 print("hello \n")</code>\n </pre>\n ' + '<multiplechoiceresponse/>\n</problem>\n' + ] + ) + @ddt.unpack + def test_problem_content_on_course_export_import(self, problem_data, expected_problem_content): """ Verify that problem content in destination matches expected problem content, specifically concerned with pre tag data with problem. """ - self._setup_source_course_with_problem_content() + self._setup_source_course_with_problem_content(problem_data) dest_course = CourseFactory.create(default_store=ModuleStoreEnum.Type.split) @@ -1109,4 +1130,4 @@ class TestCourseExportImportProblem(CourseTestCase): create_if_not_present=True, ) - self.assert_problem_definition(dest_course.location) + self.assert_problem_definition(dest_course.location, expected_problem_content) diff --git a/common/lib/xmodule/xmodule/raw_module.py b/common/lib/xmodule/xmodule/raw_module.py index 4d802453577c6bdf5a5299db8b32f356ace79c12..9f5b3c9b56372ae3592407dca5d48ef244348e55 100644 --- a/common/lib/xmodule/xmodule/raw_module.py +++ b/common/lib/xmodule/xmodule/raw_module.py @@ -1,5 +1,3 @@ - - import logging import re @@ -12,7 +10,7 @@ from .exceptions import SerializationError log = logging.getLogger(__name__) -PRE_TAG_REGEX = re.compile(r'<pre>[\s\S]*?</pre>') +PRE_TAG_REGEX = re.compile(r'<pre\b[^>]*>(?:(?=([^<]+))\1|<(?!pre\b[^>]*>))*?</pre>') class RawMixin(object): @@ -25,12 +23,21 @@ class RawMixin(object): @classmethod def definition_from_xml(cls, xml_object, system): - pre_tag_data = [etree.tostring(pre_tag_info) for pre_tag_info in xml_object.findall('.//pre')] - data = etree.tostring(xml_object, pretty_print=True, encoding='unicode') - if pre_tag_data: - for index, pre_tag in enumerate(re.findall(PRE_TAG_REGEX, data)): - data = re.sub(pre_tag, pre_tag_data[index].decode(), data) - return {'data': data}, [] + try: + data = etree.tostring(xml_object, pretty_print=True, encoding='unicode') + pre_tag_data = [] + for pre_tag_info in xml_object.findall('.//pre'): + if len(pre_tag_info.findall('.//pre')) == 0: + pre_tag_data.append(etree.tostring(pre_tag_info)) + + if pre_tag_data: + matches = re.finditer(PRE_TAG_REGEX, data) + for match_num, match in enumerate(matches): + data = re.sub(match.group(), pre_tag_data[match_num].decode(), data) + etree.XML(data) # it just checks if generated string is valid xml + return {'data': data}, [] + except etree.XMLSyntaxError: + return {'data': etree.tostring(xml_object, pretty_print=True, encoding='unicode')}, [] def definition_to_xml(self, resource_fs): """