Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
da296442
Commit
da296442
authored
Mar 27, 2015
by
Calen Pennington
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Extract a pure-XBlock version of XmlDescriptor
parent
62a90db1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
173 additions
and
77 deletions
+173
-77
common/lib/xmodule/xmodule/xml_module.py
+173
-77
No files found.
common/lib/xmodule/xmodule/xml_module.py
View file @
da296442
...
@@ -13,12 +13,16 @@ from xmodule.modulestore import EdxJSONEncoder
...
@@ -13,12 +13,16 @@ from xmodule.modulestore import EdxJSONEncoder
import
dogstats_wrapper
as
dog_stats_api
import
dogstats_wrapper
as
dog_stats_api
from
lxml.etree
import
(
# pylint: disable=no-name-in-module
Element
,
ElementTree
,
XMLParser
,
)
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
# assume all XML files are persisted as utf-8.
# assume all XML files are persisted as utf-8.
edx_xml_parser
=
etree
.
XMLParser
(
dtd_validation
=
False
,
load_dtd
=
False
,
EDX_XML_PARSER
=
XMLParser
(
dtd_validation
=
False
,
load_dtd
=
False
,
remove_comments
=
True
,
remove_blank_text
=
True
,
remove_comments
=
True
,
remove_blank_text
=
True
,
encoding
=
'utf-8'
)
encoding
=
'utf-8'
)
def
name_to_pathname
(
name
):
def
name_to_pathname
(
name
):
...
@@ -53,16 +57,6 @@ def is_pointer_tag(xml_obj):
...
@@ -53,16 +57,6 @@ def is_pointer_tag(xml_obj):
return
len
(
xml_obj
)
==
0
and
actual_attr
==
expected_attr
and
not
has_text
return
len
(
xml_obj
)
==
0
and
actual_attr
==
expected_attr
and
not
has_text
def
get_metadata_from_xml
(
xml_object
,
remove
=
True
):
meta
=
xml_object
.
find
(
'meta'
)
if
meta
is
None
:
return
''
dmdata
=
meta
.
text
if
remove
:
xml_object
.
remove
(
meta
)
return
dmdata
def
serialize_field
(
value
):
def
serialize_field
(
value
):
"""
"""
Return a string version of the value (where value is the JSON-formatted, internally stored value).
Return a string version of the value (where value is the JSON-formatted, internally stored value).
...
@@ -108,16 +102,30 @@ def deserialize_field(field, value):
...
@@ -108,16 +102,30 @@ def deserialize_field(field, value):
return
value
return
value
class
Xml
Descriptor
(
XModuleDescriptor
):
class
Xml
ParserMixin
(
object
):
"""
"""
Mixin class for standardized parsing of from xml
Class containing XML parsing functionality shared between XBlock and XModuleDescriptor.
"""
"""
# Extension to append to filename paths
filename_extension
=
'xml'
xml_attributes
=
Dict
(
help
=
"Map of unhandled xml attributes, used only for storage between import and export"
,
xml_attributes
=
Dict
(
help
=
"Map of unhandled xml attributes, used only for storage between import and export"
,
default
=
{},
scope
=
Scope
.
settings
)
default
=
{},
scope
=
Scope
.
settings
)
# Extension to append to filename paths
# VS[compat]. Backwards compatibility code that can go away after
filename_extension
=
'xml'
# importing 2012 courses.
# A set of metadata key conversions that we want to make
metadata_translations
=
{
'slug'
:
'url_name'
,
'name'
:
'display_name'
,
}
@classmethod
def
_translate
(
cls
,
key
):
"""
VS[compat]
"""
return
cls
.
metadata_translations
.
get
(
key
,
key
)
# The attributes will be removed from the definition xml passed
# The attributes will be removed from the definition xml passed
# to definition_from_xml, and from the xml returned by definition_to_xml
# to definition_from_xml, and from the xml returned by definition_to_xml
...
@@ -135,6 +143,19 @@ class XmlDescriptor(XModuleDescriptor):
...
@@ -135,6 +143,19 @@ class XmlDescriptor(XModuleDescriptor):
metadata_to_export_to_policy
=
(
'discussion_topics'
,
'checklists'
)
metadata_to_export_to_policy
=
(
'discussion_topics'
,
'checklists'
)
@staticmethod
def
_get_metadata_from_xml
(
xml_object
,
remove
=
True
):
"""
Extract the metadata from the XML.
"""
meta
=
xml_object
.
find
(
'meta'
)
if
meta
is
None
:
return
''
dmdata
=
meta
.
text
if
remove
:
xml_object
.
remove
(
meta
)
return
dmdata
@classmethod
@classmethod
def
definition_from_xml
(
cls
,
xml_object
,
system
):
def
definition_from_xml
(
cls
,
xml_object
,
system
):
"""
"""
...
@@ -163,16 +184,16 @@ class XmlDescriptor(XModuleDescriptor):
...
@@ -163,16 +184,16 @@ class XmlDescriptor(XModuleDescriptor):
Returns an lxml Element
Returns an lxml Element
"""
"""
return
etree
.
parse
(
file_object
,
parser
=
edx_xml_parser
)
.
getroot
()
return
etree
.
parse
(
file_object
,
parser
=
EDX_XML_PARSER
)
.
getroot
()
# pylint: disable=no-member
@classmethod
@classmethod
def
load_file
(
cls
,
filepath
,
fs
,
def_id
):
# pylint: disable=invalid-name
def
load_file
(
cls
,
filepath
,
fs
,
def_id
):
# pylint: disable=invalid-name
'''
"""
Open the specified file in fs, and call cls.file_to_xml on it,
Open the specified file in fs, and call cls.file_to_xml on it,
returning the lxml object.
returning the lxml object.
Add details and reraise on error.
Add details and reraise on error.
'''
"""
try
:
try
:
with
fs
.
open
(
filepath
)
as
xml_file
:
with
fs
.
open
(
filepath
)
as
xml_file
:
return
cls
.
file_to_xml
(
xml_file
)
return
cls
.
file_to_xml
(
xml_file
)
...
@@ -184,7 +205,7 @@ class XmlDescriptor(XModuleDescriptor):
...
@@ -184,7 +205,7 @@ class XmlDescriptor(XModuleDescriptor):
@classmethod
@classmethod
def
load_definition
(
cls
,
xml_object
,
system
,
def_id
,
id_generator
):
def
load_definition
(
cls
,
xml_object
,
system
,
def_id
,
id_generator
):
'''
"""
Load a descriptor definition from the specified xml_object.
Load a descriptor definition from the specified xml_object.
Subclasses should not need to override this except in special
Subclasses should not need to override this except in special
cases (e.g. html module)
cases (e.g. html module)
...
@@ -194,7 +215,7 @@ class XmlDescriptor(XModuleDescriptor):
...
@@ -194,7 +215,7 @@ class XmlDescriptor(XModuleDescriptor):
system: the modulestore system (aka, runtime) which accesses data and provides access to services
system: the modulestore system (aka, runtime) which accesses data and provides access to services
def_id: the definition id for the block--used to compute the usage id and asides ids
def_id: the definition id for the block--used to compute the usage id and asides ids
id_generator: used to generate the usage_id
id_generator: used to generate the usage_id
'''
"""
# VS[compat] -- the filename attr should go away once everything is
# VS[compat] -- the filename attr should go away once everything is
# converted. (note: make sure html files still work once this goes away)
# converted. (note: make sure html files still work once this goes away)
...
@@ -234,7 +255,7 @@ class XmlDescriptor(XModuleDescriptor):
...
@@ -234,7 +255,7 @@ class XmlDescriptor(XModuleDescriptor):
# Add the attributes from the pointer node
# Add the attributes from the pointer node
definition_xml
.
attrib
.
update
(
xml_object
.
attrib
)
definition_xml
.
attrib
.
update
(
xml_object
.
attrib
)
definition_metadata
=
get_metadata_from_xml
(
definition_xml
)
definition_metadata
=
cls
.
_
get_metadata_from_xml
(
definition_xml
)
cls
.
clean_metadata_from_xml
(
definition_xml
)
cls
.
clean_metadata_from_xml
(
definition_xml
)
definition
,
children
=
cls
.
definition_from_xml
(
definition_xml
,
system
)
definition
,
children
=
cls
.
definition_from_xml
(
definition_xml
,
system
)
if
definition_metadata
:
if
definition_metadata
:
...
@@ -289,42 +310,51 @@ class XmlDescriptor(XModuleDescriptor):
...
@@ -289,42 +310,51 @@ class XmlDescriptor(XModuleDescriptor):
metadata
[
attr
]
=
value
metadata
[
attr
]
=
value
@classmethod
@classmethod
def
from_xml
(
cls
,
xml_data
,
system
,
id_generator
):
def
parse_xml
(
cls
,
node
,
runtime
,
keys
,
id_generator
):
# pylint: disable=unused-argument
"""
"""
Creates an instance of this descriptor from the supplied xml_data.
Use `node` to construct a new block.
This may be overridden by subclasses
xml_data: A string of xml that will be translated into data and children for
Arguments:
this module
node (etree.Element): The xml node to parse into an xblock.
system: A DescriptorSystem for interacting with external resources
"""
runtime (:class:`.Runtime`): The runtime to use while parsing.
keys (:class:`.ScopeIds`): The keys identifying where this block
will store its data.
id_generator (:class:`.IdGenerator`): An object that will allow the
runtime to generate correct definition and usage ids for
children of this block.
Returns (XBlock): The newly parsed XBlock
xml_object
=
etree
.
fromstring
(
xml_data
)
"""
# VS[compat] -- just have the url_name lookup, once translation is done
# VS[compat] -- just have the url_name lookup, once translation is done
url_name
=
xml_object
.
get
(
'url_name'
,
xml_object
.
get
(
'slug'
))
url_name
=
node
.
get
(
'url_name'
,
node
.
get
(
'slug'
))
def_id
=
id_generator
.
create_definition
(
xml_object
.
tag
,
url_name
)
def_id
=
id_generator
.
create_definition
(
node
.
tag
,
url_name
)
usage_id
=
id_generator
.
create_usage
(
def_id
)
usage_id
=
id_generator
.
create_usage
(
def_id
)
# VS[compat] -- detect new-style each-in-a-file mode
# VS[compat] -- detect new-style each-in-a-file mode
if
is_pointer_tag
(
xml_object
):
if
is_pointer_tag
(
node
):
# new style:
# new style:
# read the actual definition file--named using url_name.replace(':','/')
# read the actual definition file--named using url_name.replace(':','/')
filepath
=
cls
.
_format_filepath
(
xml_object
.
tag
,
name_to_pathname
(
url_name
))
filepath
=
cls
.
_format_filepath
(
node
.
tag
,
name_to_pathname
(
url_name
))
definition_xml
=
cls
.
load_file
(
filepath
,
system
.
resources_fs
,
def_id
)
definition_xml
=
cls
.
load_file
(
filepath
,
runtime
.
resources_fs
,
def_id
)
system
.
parse_asides
(
definition_xml
,
def_id
,
usage_id
,
id_generator
)
runtime
.
parse_asides
(
definition_xml
,
def_id
,
usage_id
,
id_generator
)
else
:
else
:
filepath
=
None
filepath
=
None
definition_xml
=
xml_object
definition_xml
=
node
dog_stats_api
.
increment
(
dog_stats_api
.
increment
(
DEPRECATION_VSCOMPAT_EVENT
,
DEPRECATION_VSCOMPAT_EVENT
,
tags
=
[
"location:xmlparser_util_mixin_parse_xml"
]
tags
=
[
"location:xmlparser_util_mixin_parse_xml"
]
)
)
definition
,
children
=
cls
.
load_definition
(
definition_xml
,
system
,
def_id
,
id_generator
)
# note this removes metadata
# Note: removes metadata.
definition
,
children
=
cls
.
load_definition
(
definition_xml
,
runtime
,
def_id
,
id_generator
)
# VS[compat] -- make Ike's github preview links work in both old and
# VS[compat] -- make Ike's github preview links work in both old and
# new file layouts
# new file layouts
if
is_pointer_tag
(
xml_object
):
if
is_pointer_tag
(
node
):
# new style -- contents actually at filepath
# new style -- contents actually at filepath
definition
[
'filename'
]
=
[
filepath
,
filepath
]
definition
[
'filename'
]
=
[
filepath
,
filepath
]
...
@@ -341,7 +371,7 @@ class XmlDescriptor(XModuleDescriptor):
...
@@ -341,7 +371,7 @@ class XmlDescriptor(XModuleDescriptor):
metadata
[
'definition_metadata_err'
]
=
str
(
err
)
metadata
[
'definition_metadata_err'
]
=
str
(
err
)
# Set/override any metadata specified by policy
# Set/override any metadata specified by policy
cls
.
apply_policy
(
metadata
,
system
.
get_policy
(
usage_id
))
cls
.
apply_policy
(
metadata
,
runtime
.
get_policy
(
usage_id
))
field_data
=
{}
field_data
=
{}
field_data
.
update
(
metadata
)
field_data
.
update
(
metadata
)
...
@@ -352,10 +382,10 @@ class XmlDescriptor(XModuleDescriptor):
...
@@ -352,10 +382,10 @@ class XmlDescriptor(XModuleDescriptor):
kvs
=
InheritanceKeyValueStore
(
initial_values
=
field_data
)
kvs
=
InheritanceKeyValueStore
(
initial_values
=
field_data
)
field_data
=
KvsFieldData
(
kvs
)
field_data
=
KvsFieldData
(
kvs
)
return
system
.
construct_xblock_from_class
(
return
runtime
.
construct_xblock_from_class
(
cls
,
cls
,
# We're loading a descriptor, so student_id is meaningless
# We're loading a descriptor, so student_id is meaningless
ScopeIds
(
None
,
xml_object
.
tag
,
def_id
,
usage_id
),
ScopeIds
(
None
,
node
.
tag
,
def_id
,
usage_id
),
field_data
,
field_data
,
)
)
...
@@ -374,32 +404,17 @@ class XmlDescriptor(XModuleDescriptor):
...
@@ -374,32 +404,17 @@ class XmlDescriptor(XModuleDescriptor):
"""
"""
return
True
return
True
def
export_to_xml
(
self
,
resource_fs
):
def
add_xml_to_node
(
self
,
node
):
"""
"""
Returns an xml string representing this module, and all modules
For exporting, set data on `node` from ourselves.
underneath it. May also write required resources out to resource_fs
Assumes that modules have single parentage (that no module appears twice
in the same course), and that it is thus safe to nest modules as xml
children as appropriate.
The returned XML should be able to be parsed back into an identical
XModuleDescriptor using the from_xml method with the same system, org,
and course
resource_fs is a pyfilesystem object (from the fs package)
"""
"""
# Set up runtime.export_fs so that it's available through future
# uses of the pure xblock add_xml_to_node api
self
.
runtime
.
export_fs
=
resource_fs
# Get the definition
# Get the definition
xml_object
=
self
.
definition_to_xml
(
resource
_fs
)
xml_object
=
self
.
definition_to_xml
(
self
.
runtime
.
export
_fs
)
self
.
clean_metadata_from_xml
(
xml_object
)
self
.
clean_metadata_from_xml
(
xml_object
)
# Set the tag
so we get the file path right
# Set the tag
on both nodes so we get the file path right.
xml_object
.
tag
=
self
.
category
xml_object
.
tag
=
self
.
category
node
.
tag
=
self
.
category
# Add the non-inherited metadata
# Add the non-inherited metadata
for
attr
in
sorted
(
own_metadata
(
self
)):
for
attr
in
sorted
(
own_metadata
(
self
)):
...
@@ -422,24 +437,25 @@ class XmlDescriptor(XModuleDescriptor):
...
@@ -422,24 +437,25 @@ class XmlDescriptor(XModuleDescriptor):
# Write the definition to a file
# Write the definition to a file
url_path
=
name_to_pathname
(
self
.
url_name
)
url_path
=
name_to_pathname
(
self
.
url_name
)
filepath
=
self
.
_format_filepath
(
self
.
category
,
url_path
)
filepath
=
self
.
_format_filepath
(
self
.
category
,
url_path
)
resource_fs
.
makedir
(
os
.
path
.
dirname
(
filepath
),
recursive
=
True
,
allow_recreate
=
True
)
self
.
runtime
.
export_fs
.
makedir
(
os
.
path
.
dirname
(
filepath
),
recursive
=
True
,
allow_recreate
=
True
)
with
resource_fs
.
open
(
filepath
,
'w'
)
as
fileobj
:
with
self
.
runtime
.
export_fs
.
open
(
filepath
,
'w'
)
as
fileobj
:
fileobj
.
write
(
etree
.
tostring
(
xml_object
,
pretty_print
=
True
,
encoding
=
'utf-8'
))
ElementTree
(
xml_object
)
.
write
(
fileobj
,
pretty_print
=
True
,
encoding
=
'utf-8'
)
# And return just a pointer with the category and filename.
record_object
=
etree
.
Element
(
self
.
category
)
else
:
else
:
record_object
=
xml_object
# Write all attributes from xml_object onto node
node
.
clear
()
node
.
tag
=
xml_object
.
tag
node
.
text
=
xml_object
.
text
node
.
tail
=
xml_object
.
tail
node
.
attrib
=
xml_object
.
attrib
node
.
extend
(
xml_object
)
record_object
.
set
(
'url_name'
,
self
.
url_name
)
node
.
set
(
'url_name'
,
self
.
url_name
)
# Special case for course pointers:
# Special case for course pointers:
if
self
.
category
==
'course'
:
if
self
.
category
==
'course'
:
# add org and course attributes on the pointer tag
# add org and course attributes on the pointer tag
record_object
.
set
(
'org'
,
self
.
location
.
org
)
node
.
set
(
'org'
,
self
.
location
.
org
)
record_object
.
set
(
'course'
,
self
.
location
.
course
)
node
.
set
(
'course'
,
self
.
location
.
course
)
return
etree
.
tostring
(
record_object
,
pretty_print
=
True
,
encoding
=
'utf-8'
)
def
definition_to_xml
(
self
,
resource_fs
):
def
definition_to_xml
(
self
,
resource_fs
):
"""
"""
...
@@ -450,6 +466,86 @@ class XmlDescriptor(XModuleDescriptor):
...
@@ -450,6 +466,86 @@ class XmlDescriptor(XModuleDescriptor):
@property
@property
def
non_editable_metadata_fields
(
self
):
def
non_editable_metadata_fields
(
self
):
non_editable_fields
=
super
(
XmlDescriptor
,
self
)
.
non_editable_metadata_fields
"""
non_editable_fields
.
append
(
XmlDescriptor
.
xml_attributes
)
Return a list of all metadata fields that cannot be edited.
"""
non_editable_fields
=
super
(
XmlParserMixin
,
self
)
.
non_editable_metadata_fields
non_editable_fields
.
append
(
XmlParserMixin
.
xml_attributes
)
return
non_editable_fields
return
non_editable_fields
class
XmlDescriptor
(
XmlParserMixin
,
XModuleDescriptor
):
# pylint: disable=abstract-method
"""
Mixin class for standardized parsing of XModule xml.
"""
@classmethod
def
from_xml
(
cls
,
xml_data
,
system
,
id_generator
):
"""
Creates an instance of this descriptor from the supplied xml_data.
This may be overridden by subclasses.
Args:
xml_data (str): A string of xml that will be translated into data and children
for this module
system (:class:`.XMLParsingSystem):
id_generator (:class:`xblock.runtime.IdGenerator`): Used to generate the
usage_ids and definition_ids when loading this xml
"""
# Shim from from_xml to the parse_xml defined in XmlParserMixin.
# This only exists to satisfy subclasses that both:
# a) define from_xml themselves
# b) call super(..).from_xml(..)
return
super
(
XmlDescriptor
,
cls
)
.
parse_xml
(
etree
.
fromstring
(
xml_data
),
# pylint: disable=no-member
system
,
None
,
# This is ignored by XmlParserMixin
id_generator
,
)
@classmethod
def
parse_xml
(
cls
,
node
,
runtime
,
keys
,
id_generator
):
"""
Interpret the parsed XML in `node`, creating an XModuleDescriptor.
"""
if
cls
.
from_xml
!=
XmlDescriptor
.
from_xml
:
# Skip the parse_xml from XmlParserMixin to get the shim parse_xml
# from XModuleDescriptor, which actually calls `from_xml`.
return
super
(
XmlParserMixin
,
cls
)
.
parse_xml
(
node
,
runtime
,
keys
,
id_generator
)
# pylint: disable=bad-super-call
else
:
return
super
(
XmlDescriptor
,
cls
)
.
parse_xml
(
node
,
runtime
,
keys
,
id_generator
)
def
export_to_xml
(
self
,
resource_fs
):
# pylint: disable=unused-argument
"""
Returns an xml string representing this module, and all modules
underneath it. May also write required resources out to resource_fs.
Assumes that modules have single parentage (that no module appears twice
in the same course), and that it is thus safe to nest modules as xml
children as appropriate.
The returned XML should be able to be parsed back into an identical
XModuleDescriptor using the from_xml method with the same system, org,
and course
"""
# Shim from export_to_xml to the add_xml_to_node defined in XmlParserMixin.
# This only exists to satisfy subclasses that both:
# a) define export_to_xml themselves
# b) call super(..).export_to_xml(..)
node
=
Element
(
self
.
category
)
super
(
XmlDescriptor
,
self
)
.
add_xml_to_node
(
node
)
return
etree
.
tostring
(
node
)
# pylint: disable=no-member
def
add_xml_to_node
(
self
,
node
):
"""
Export this :class:`XModuleDescriptor` as XML, by setting attributes on the provided
`node`.
"""
if
self
.
export_to_xml
!=
XmlDescriptor
.
export_to_xml
:
# Skip the add_xml_to_node from XmlParserMixin to get the shim add_xml_to_node
# from XModuleDescriptor, which actually calls `export_to_xml`.
super
(
XmlParserMixin
,
self
)
.
add_xml_to_node
(
node
)
# pylint: disable=bad-super-call
else
:
super
(
XmlDescriptor
,
self
)
.
add_xml_to_node
(
node
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment