Files
edx-platform/xmodule/raw_block.py
Kyle McCormick c70bfe980a build!: Switch to openedx-core (renamed from openedx-learning) (#38011)
build!: Switch to openedx-core (renamed from openedx-learning)

Instead of installing openedx-learning==0.32.0, we install openedx-core==0.34.1.
We update various class names, function names, docstrings, and comments to
represent the rename:

* We say "openedx-core" when referring to the whole repo or PyPI project
  * or occasionally "Open edX Core" if we want it to look nice in the docs.
* We say "openedx_content" to refer to the Content API within openedx-core,
   which is actually the thing we have been calling "Learning Core" all along.
  * In snake-case code, it's `*_openedx_content_*`.
  * In camel-case code, it's `*OpenedXContent*`

For consistency's sake we avoid anything else like oex_core, OeXCore,
OpenEdXCore, OexContent, openedx-content, OpenEdxContent, etc.
There should be no more references to learning_core, learning-core, Learning Core,
Learning-Core, LC, openedx-learning, openedx_learning, etc.

BREAKING CHANGE: for openedx-learning/openedx-core developers:
You may need to uninstall openedx-learning and re-install openedx-core
from your venv. If running tutor, you may need to un-mount openedx-learning,
rename the directory to openedx-core, re-mount it, and re-build.
The code APIs themselves are fully backwards-compatible.

Part of: https://github.com/openedx/openedx-core/issues/470
2026-02-18 22:38:25 +00:00

107 lines
4.1 KiB
Python

"""Mixin classes for handling raw XML data in XBlocks."""
import logging
import re
from lxml import etree
from xblock.fields import Scope, String
from .exceptions import SerializationError
log = logging.getLogger(__name__)
PRE_TAG_REGEX = re.compile(r"<pre\b[^>]*>(?:(?=([^<]+))\1|<(?!pre\b[^>]*>))*?</pre>")
class RawMixin:
"""
Common code between RawDescriptor and XBlocks converted from XModules.
"""
resources_dir = None
data = String(help="XML data for the block", default="", scope=Scope.content)
@classmethod
def definition_from_xml(cls, xml_object, system): # pylint: disable=unused-argument
"""Convert XML node into a dictionary with 'data' key for XBlock."""
return {"data": etree.tostring(xml_object, pretty_print=True, encoding="unicode")}, []
def definition_to_xml(self, resource_fs): # pylint: disable=unused-argument
"""
Return an Element if we've kept the import OLX, or None otherwise.
"""
# If there's no self.data, it means that an XBlock/XModule originally
# existed for this data at the time of import/editing, but was later
# uninstalled. RawDescriptor therefore never got to preserve the
# original OLX that came in, and we have no idea how it should be
# serialized for export. It's possible that we could do some smarter
# fallback here and attempt to extract the data, but it's reasonable
# and simpler to just skip this node altogether.
if not self.data:
log.warning(
"Could not serialize %s: No XBlock installed for '%s' tag.",
self.location,
self.location.block_type,
)
return None
# Normal case: Just echo back the original OLX we saved.
try:
return etree.fromstring(self.data)
except etree.XMLSyntaxError as err:
# Can't recover here, so just add some info and
# re-raise
lines = self.data.split("\n")
line, offset = err.position
msg = (
f"Unable to create xml for block {self.location}. "
f"Context: '{lines[line - 1][offset - 40 : offset + 40]}'"
)
raise SerializationError(self.location, msg) from err
@classmethod
def parse_xml_new_runtime(cls, node, runtime, keys):
"""
Interpret the parsed XML in `node`, creating a new instance of this
module.
"""
# In the new/openedx_content-based runtime, XModule parsing (from
# XmlMixin) is disabled, so definition_from_xml will not be
# called, and instead the "normal" XBlock parse_xml will be used.
# However, it's not compatible with RawMixin, so we implement
# support here.
data_field_value = cls.definition_from_xml(node, None)[0]["data"]
for child in node.getchildren():
node.remove(child)
# Get attributes, if any, via normal parse_xml.
try:
block = super().parse_xml_new_runtime(node, runtime, keys)
except AttributeError:
block = super().parse_xml(node, runtime, keys)
block.data = data_field_value
return block
class EmptyDataRawMixin:
"""
Common code between EmptyDataRawDescriptor and XBlocks converted from XModules.
"""
resources_dir = None
data = String(default="", scope=Scope.content)
@classmethod
def definition_from_xml(cls, xml_object, system): # pylint: disable=unused-argument
"""Convert XML node to dictionary with 'data', handling empty nodes specially."""
if len(xml_object) == 0 and len(list(xml_object.items())) == 0:
return {"data": ""}, []
return {"data": etree.tostring(xml_object, pretty_print=True, encoding="unicode")}, []
def definition_to_xml(self, resource_fs): # pylint: disable=unused-argument
"""Return an XML Element from stored data, or an empty element if data is empty."""
if self.data:
return etree.fromstring(self.data)
return etree.Element(self.category)