# -*- coding: utf-8 -*-
# $Id: XML.py 1634 2013-04-12 15:36:36Z amelung $
#
# Copyright (c) 2007-2011 Otto-von-Guericke-Universität Magdeburg
#
# This file is part of ECSpooler.
################################################################################
# Changelog #
################################################################################
#
# 16.06.2010, chbauman:
# Fixed typo, removed unnecessary imports.
# Added comments to functions.
# Ensuring that the model solution imports the correct DTD in _xPathCheck.
# More detailed comment on the testSequence.
# 12.07.2010, chbauman:
# Additional InputField 'verbose' to enable and disable verbose output, i.e. XML fragments.
# Corrected spelling
import logging
# local imports
# This is the name we use to store the dtd on the server:
# These variables indicate how we name our xml files internally:
# Wrapper template for xQuery tests:
'''(: Include both xml files: :)
let %s := doc("modelSolution.xml")
let %s := doc("submission.xml")
(: return-part sequence automatically inserted by the XML backend :)
return (%s)
# input schema
"wellFormed",
),
'dtd',
"If empty no validation will be performed.",
),
"modelSolution",
"model solution for this task. If no XML file is specified here, it will be " +
"ignored.",
),
"xpath",
"automatically tested with.",
),
"verbose",
"to the student.",
),
))
# testSchema
'default',
),
))
"""
A backend for checking XML programs by comparing
student and model solution on given test data.
"""
# Indicates whether the student's submission has already been written to a file.
submissionModule = None
# String used to store the test stage outcomes:
"""
This constructor is needed to reset the logging environment.
"""
"""
Overriding Backend._process_execute
@see: Backend._process_execute(self, job)
"""
try:
except Exception, e:
return result
"""
This function is used to delegate the processing tasks.
It is responsible for clearing old feedbacks.
"""
# Clear global variables:
self.submissionModule = None
# Do some error handling:
msg = 'No test specification selected.'
# We ensure that we defined our schema correctly:
assert inputFields, 'No InputField found.'
assert len(inputFields) == 4, 'Did not find exactly 3 InputFields, as expected. (Found %s)' % len(inputFields)
assert repeatFields, 'No RepeatField found.'
assert len(repeatFields) == 1, 'Did not find exactly 1 RepeatField, as expected. (Found %s)' % len(repeatFields)
# Store the result and report it if we have a return value:
if result:
return result
if result:
return result
if result:
return result
# Look at self.feedback: If it is non-empty tests were run. If not, none were run.
else:
"""
Uses _writeModule to write an XML file, replacing all DOCTYPE declarations using SYSTEM that
are not inside of comments.
@return: Dictionary with keys module and file
"""
# We have to ensure to replace all DOCTYPE declarations using SYSTEM that are not inside
# of comments:
correctedXML = re.sub('SYSTEM\s+".*">', 'SYSTEM "%s%s">' % (dtdName, self.dtdFileSuffix), nonCommentedXML)
"""
This function checks whether the submission is well-formed, if the InputField 'wellFormed'
is checked (because it is a boolean field and rendered as a check box).
@return: If job['wellFormed'] is checked and the submission is well-formed, nothing will be
returned, but a message will be appended to self.feedback.
If job['wellFormed'] is checked and the submission is not well-formed, a false BackendResult
will be returned.
If job['wellFormed'] is not checked, nothing will be returned.
"""
if wellFormedCheck:
"Tests require valid 'submission' (%s)" % submission
try:
# Write the module:
if not self.submissionModule:
self.submissionModule = self._writeXML('submission', submission, self.srcFileSuffix, job.getId(), dtdName)
# Perform well-formed test:
except Exception, e:
msg = 'Internal error during well-formed-check: %s: %s' % \
else:
# This test was passed - append a success indicator to the global feedback:
else:
"""
This function checks whether the submission can be validated against a given DTD.
@return: If job['dtd'] is not empty and the submission can successfully be validated against
the DTD, nothing will be returned, but a message will be appended to self.feedback.
If job['dtd'] is not empty and the submission cannot successfully be validated against the
DTD, a false BackendResult will be returned.
If job['dtd'] is empty, nothing will be returned.
"""
# Check if the InputField for the DTD is non-empty. If it is, proceed, otherwise return.
"Tests require valid 'submission' (%s)" % submission
try:
# Write the submission:
if not self.submissionModule:
self.submissionModule = self._writeXML('submission', submission, self.srcFileSuffix, job.getId(), dtdName)
# Perform well-formed test:
except Exception, e:
msg = 'Internal error during dtd-check: %s: %s' % \
else:
# This test was passed - append a success indicator to the global feedback:
else:
"""
This function checks whether the submission returns the same node sets requested by a XPath
expression, as a model solution.
@return: If both job['modelSolution'] and job['xpath'] are not empty and all stated XPath
expressions return the same node sets in both files, nothing will be returned, but a message
will be appended to self.feedback.
If both job['modelSolution'] and job['xpath'] are not empty and a XPath expression does not
return the same node sets in both files, a false BackendResult will be returned.
If either job['modelSolution'] or job['xpath'] is empty, nothing will be returned.
"""
"Tests require valid 'submission' (%s)" % submission
# Perform test only if modelSolution and xpath are non-empty:
try:
# Get the testData:
msg = 'No test data defined.'
# Create the test sequence:
# This sequence compares all node-sets resulting from querying the defined XPath
# statements on both XML files indicating the test number. This number will later be
# used to determine which, if any, test failed, so that the user gets an appropriate
# feedback.
if job['verbose']:
testSequence = ', '.join(['%s = %s, ";;%s;;", %s, ";;", %s, "||"' % (test.replace('${DOC}', modelSelector), test.replace('${DOC}', submissionSelector), testData.index(test), test.replace('${DOC}', modelSelector), test.replace('${DOC}', submissionSelector)) for test in testData])
else:
testSequence = ', '.join(['%s = %s, ";;%s;;", "||"' % (test.replace('${DOC}', modelSelector), test.replace('${DOC}', submissionSelector), testData.index(test)) for test in testData])
# Write the XQuery file and the model solution, as well as the submission:
xqlWrapperModule = self._writeModule('xqlWrapper', xqlWrapperTemplate % testSequence, self.xqlFileSuffix, job.getId())
modelSolution = self._writeXML('modelSolution', job['modelSolution'], self.srcFileSuffix, job.getId(), dtdName)
# Eventually write the submission:
if not self.submissionModule:
self.submissionModule = self._writeXML('submission', submission, self.srcFileSuffix, job.getId(), dtdName)
# Perform XPath tests:
except Exception, e:
msg = 'Internal error during xpath-check: %s: %s' % \
# Ohoh, we found a failed test
# Get the test number
# Handle result message differently when in verbose mode
if(job['verbose']):
# Get the first test's output
# Split the output according to the split item
# Create feedback using expected and actual values
result = "Your submission failed. A XPath expression evaluated against a sample solution and compared to your submission yielded a different result.\nTest case was: %s\nExpected:\n%s\n\nBut was:\n%s" % (testData[falsePosition].replace('${DOC}', ''), testOutputArtifacts[2], testOutputArtifacts[3])
else:
result = "Your submission failed. A XPath expression evaluated against a sample solution and compared to your submission yielded a different result.\nTest case was:\n\n%s" % testData[falsePosition].replace('${DOC}', '')
else:
# This test was passed - append a success indicator to the global feedback:
else: