Skip to content

Commit db11209

Browse files
committed
feat: add pointer tags parsing implementation in XBlock's parse_xml method
1 parent 618c452 commit db11209

File tree

1 file changed

+78
-0
lines changed

1 file changed

+78
-0
lines changed

xblock/core.py

+78
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,85 @@
3232
("block", "http://code.edx.org/xblock/block"),
3333
])
3434

35+
XML_PARSER = etree.XMLParser(dtd_validation=False, load_dtd=False, remove_blank_text=True, encoding='utf-8')
36+
3537
# __all__ controls what classes end up in the docs.
3638
__all__ = ['XBlock', 'XBlockAside']
3739

3840
UNSET = object()
3941

4042

43+
def name_to_pathname(name):
44+
"""
45+
Convert a location name for use in a path: replace ':' with '/'.
46+
This allows users of the xml format to organize content into directories
47+
"""
48+
return name.replace(':', '/')
49+
50+
51+
def is_pointer_tag(xml_obj):
52+
"""
53+
Check if xml_obj is a pointer tag: <blah url_name="something" />.
54+
No children, one attribute named url_name, no text.
55+
56+
Special case for course roots: the pointer is
57+
<course url_name="something" org="myorg" course="course">
58+
59+
xml_obj: an etree Element
60+
61+
Returns a bool.
62+
"""
63+
if xml_obj.tag != "course":
64+
expected_attr = {'url_name'}
65+
else:
66+
expected_attr = {'url_name', 'course', 'org'}
67+
68+
actual_attr = set(xml_obj.attrib.keys())
69+
70+
has_text = xml_obj.text is not None and len(xml_obj.text.strip()) > 0
71+
72+
return len(xml_obj) == 0 and actual_attr == expected_attr and not has_text
73+
74+
75+
def load_definition_xml(node, runtime, def_id):
76+
"""
77+
Loads definition_xml stored in a dedicated file
78+
"""
79+
url_name = node.get('url_name')
80+
filepath = format_filepath(node.tag, name_to_pathname(url_name))
81+
definition_xml = load_file(filepath, runtime.resources_fs, def_id)
82+
return definition_xml, filepath
83+
84+
85+
def format_filepath(category, name):
86+
return f'{category}/{name}.xml'
87+
88+
89+
def load_file(filepath, fs, def_id): # pylint: disable=invalid-name
90+
"""
91+
Open the specified file in fs, and call cls.file_to_xml on it,
92+
returning the lxml object.
93+
94+
Add details and reraise on error.
95+
"""
96+
try:
97+
with fs.open(filepath) as xml_file:
98+
return file_to_xml(xml_file)
99+
except Exception as err: # lint-amnesty, pylint: disable=broad-except
100+
# Add info about where we are, but keep the traceback
101+
raise Exception(f'Unable to load file contents at path {filepath} for item {def_id}: {err}') from err
102+
103+
104+
def file_to_xml(file_object):
105+
"""
106+
Used when this module wants to parse a file object to xml
107+
that will be converted to the definition.
108+
109+
Returns an lxml Element
110+
"""
111+
return etree.parse(file_object, parser=XML_PARSER).getroot()
112+
113+
41114
class _AutoNamedFieldsMetaclass(type):
42115
"""
43116
Builds classes such that their Field attributes know their own names.
@@ -746,6 +819,11 @@ def parse_xml(cls, node, runtime, keys):
746819
keys (:class:`.ScopeIds`): The keys identifying where this block
747820
will store its data.
748821
"""
822+
if is_pointer_tag(node):
823+
# new style:
824+
# read the actual definition file--named using url_name.replace(':','/')
825+
node, _ = load_definition_xml(node, runtime, keys.def_id)
826+
749827
block = runtime.construct_xblock_from_class(cls, keys)
750828

751829
# The base implementation: child nodes become child blocks.

0 commit comments

Comments
 (0)