These are running notes as I study how OLX is generated and parsed. Eventually, I want to clean this up and turn it into an official OLX Specification.
Relevant code
repo: openedx/XBlock
xblock/core.py
class Blocklike(...): @classmethod def parse_xml(cls, node, runtime, keys): """ Use `node` to construct a new block. Arguments: node (:class:`~xml.etree.ElementTree.Element`): The xml node to parse into an xblock. runtime (:class:`.Runtime`): The runtime to use while parsing. keys (:class:`.ScopeIds`): The keys identifying where this block will store its data. """ block = runtime.construct_xblock_from_class(cls, keys) # The base implementation: child nodes become child blocks. # Or fields, if they belong to the right namespace. for child in node: if child.tag is etree.Comment: continue qname = etree.QName(child) tag = qname.localname namespace = qname.namespace if namespace == XML_NAMESPACES["option"]: cls._set_field_if_present(block, tag, child.text, child.attrib) else: block.runtime.add_node_as_child(block, child) # Attributes become fields. for name, value in list(node.items()): # lxml has no iteritems cls._set_field_if_present(block, name, value, {}) # Text content becomes "content", if such a field exists. if "content" in block.fields and block.fields["content"].scope == Scope.content: text = node.text if text: text = text.strip() if text: block.content = text return block ... def add_xml_to_node(self, node): """ For exporting, set data on `node` from ourselves. """ # pylint: disable=E1101 # Set node.tag based on our class name. node.tag = self.xml_element_name() node.set('xblock-family', self.entry_point) # Set node attributes based on our fields. for field_name, field in list(self.fields.items()): if field_name in ('children', 'parent', 'content'): continue if field.is_set_on(self) or field.force_export: self._add_field(node, field_name, field) # A content field becomes text content. text = self.xml_text_content() if text is not None: node.text = text def xml_element_name(self): """ What XML element name should be used for this block? """ return self.scope_ids.block_type def xml_text_content(self): """ What is the text content for this block's XML node? """ if 'content' in self.fields and self.content: # pylint: disable=unsupported-membership-test return self.content else: return None def _add_field(self, node, field_name, field): """ Add xml representation of field to node. Depending on settings, it either stores the value of field as an xml attribute or creates a separate child node. """ value = field.to_string(field.read_from(self)) text_value = "" if value is None else value # Is the field type supposed to serialize the fact that the value is None to XML? save_none_as_xml_attr = field.none_to_xml and value is None field_attrs = {"none": "true"} if save_none_as_xml_attr else {} if save_none_as_xml_attr or field.xml_node: # Field will be output to XML as an separate element. tag = etree.QName(XML_NAMESPACES["option"], field_name) elem = etree.SubElement(node, tag, field_attrs) if field.xml_node: # Only set the value if forced via xml_node; # in all other cases, the value is None. # Avoids an unnecessary XML end tag. elem.text = text_value else: # Field will be output to XML as an attribute on the node. node.set(field_name, text_value) ... class XBlock(Blocklike, ...): ...