#! /usr/bin/env python

import pyslet.xml20081126.structures as xml
import pyslet.xsdatatypes20041028 as xsi
import pyslet.html40_19991224 as html
import pyslet.imsqtiv2p1 as qtiv2
import pyslet.imsmdv1p2p1 as imsmd

import core
import common

import string
import itertools
from types import StringTypes


class Item(
        common.QTICommentContainer,
        core.SectionItemMixin,
        core.ObjectMixin):

    """The Item is the smallest unit data structure that can be exchanged using
    the QTI specification. Each Item consists of five distinct parts, namely:
    objectives - the materials used to describe the objectives with respect to
    each view; rubric - the materials used to define the context of the
    Item and available for each view; presentation - the instructions describing
    the nature of the question to be asked; resprocessing - the
    instructions to be followed when analyzing the responses to create a
    corresponding score and feedback; itemfeedback - the materials to be
    presented as feedback to the entered response::

    <!ELEMENT item (qticomment?
            duration?
            itemmetadata?
            objectives*
            itemcontrol*
            itemprecondition*
            itempostcondition*
            (itemrubric | rubric)*
            presentation?
            resprocessing*
            itemproc_extension?
            itemfeedback*
            reference?)>
    <!ATTLIST item  maxattempts CDATA  #IMPLIED
            label CDATA  #IMPLIED
            ident CDATA  #REQUIRED
            title CDATA  #IMPLIED
            xml:lang    CDATA  #IMPLIED >"""
    XMLNAME = 'item'
    XMLATTR_maxattempts = 'maxattempts'
    XMLATTR_label = 'label'
    XMLATTR_ident = 'ident'
    XMLATTR_title = 'title'
    XMLCONTENT = xml.ElementContent

    def __init__(self, parent):
        common.QTICommentContainer.__init__(self, parent)
        self.maxattempts = None
        self.label = None
        self.ident = None
        self.title = None
        self.Duration = None
        self.ItemMetadata = None
        self.Objectives = []
        self.ItemControl = []
        self.ItemPreCondition = []
        self.ItemPostCondition = []
        self.Rubric = []				#: includes ItemRubric
        self.Presentation = None
        self.ResProcessing = []
        self.ItemProcExtension = None
        self.ItemFeedback = []
        self.QTIReference = None

    def GetChildren(self):
        for child in common.QTICommentContainer.GetChildren(self):
            yield child
        if self.Duration:
            yield self.Duration
        if self.ItemMetadata:
            yield self.ItemMetadata
        for child in itertools.chain(
                self.Objectives,
                self.ItemControl,
                self.ItemPreCondition,
                self.ItemPostCondition,
                self.Rubric):
            yield child
        if self.Presentation:
            yield self.Presentation
        for child in self.ResProcessing:
            yield child
        if self.ItemProcExtension:
            yield self.ItemProcExtension
        for child in self.ItemFeedback:
            yield child
        if self.QTIReference:
            yield self.QTIReference

    def MigrateV2(self, output):
        """Converts this item to QTI v2

        For details, see :py:meth:`pyslet.qtiv1.QuesTestInterop.MigrateV2`."""
        # First thing we do is initialize any fixups
        for rp in self.ResProcessing:
            rp._interactionFixup = {}
        doc = qtiv2.core.QTIDocument(root=qtiv2.items.AssessmentItem)
        item = doc.root
        lom = imsmd.LOM(None)
        log = []
        ident = qtiv2.MakeValidNCName(self.ident)
        if self.ident != ident:
            log.append(
                "Warning: illegal NCName for ident: %s, replaced with: %s" %
                (self.ident, ident))
        item.identifier = ident
        title = self.title
        # may be specified in the metadata
        if self.ItemMetadata:
            mdTitles = self.ItemMetadata.metadata.get('title', ())
        else:
            mdTitles = ()
        if title:
            item.title = title
        elif mdTitles:
            item.title = mdTitles[0][0]
        else:
            item.title = ident
        if self.maxattempts is not None:
            log.append(
                "Warning: maxattempts can not be controlled at item level, ignored: maxattempts='" +
                self.maxattempts +
                "'")
        if self.label:
            item.label = self.label
        lang = self.ResolveLang()
        item.SetLang(lang)
        general = lom.LOMGeneral()
        id = general.LOMIdentifier()
        id.SetValue(self.ident)
        if title:
            lomTitle = general.ChildElement(imsmd.LOMTitle)
            lomTitle = lomTitle.ChildElement(lomTitle.LangStringClass)
            lomTitle.SetValue(title)
            if lang:
                lomTitle.SetLang(lang)
        if mdTitles:
            if title:
                # If we already have a title, then we have to add qmd_title as description metadata
                # you may think qmd_title is a better choice than the title attribute
                # but qmd_title is an extension so the title attribute takes
                # precedence
                i = 0
            else:
                lomTitle = general.ChildElement(imsmd.LOMTitle)
                lomTitle = lomTitle.ChildElement(lomTitle.LangStringClass)
                lomTitle.SetValue(mdTitles[0][0])
                lang = mdTitles[0][1].ResolveLang()
                if lang:
                    lomTitle.SetLang(lang)
                i = 1
            for mdTitle in mdTitles[i:]:
                description = general.ChildElement(general.DescriptionClass)
                lomTitle = description.ChildElement(
                    description.LangStringClass)
                lomTitle.SetValue(mdTitle[0])
                mdLang = mdTitle[1].ResolveLang()
                if mdLang:
                    lomTitle.SetLang(mdLang)
        if self.QTIComment:
            # A comment on an item is added as a description to the metadata
            description = general.ChildElement(general.DescriptionClass)
            description.ChildElement(description.LangStringClass).SetValue(
                self.QTIComment.GetValue())
        if self.Duration:
            log.append(
                "Warning: duration is currently outside the scope of version 2: ignored " +
                self.Duration.GetValue())
        if self.ItemMetadata:
            self.ItemMetadata.MigrateV2(doc, lom, log)
        for objective in self.Objectives:
            if objective.view != core.View.All:
                objective.MigrateV2(item, log)
            else:
                objective.LRMMigrateObjectives(lom, log)
        if self.ItemControl:
            log.append(
                "Warning: itemcontrol is currently outside the scope of version 2")
        for rubric in self.Rubric:
            rubric.MigrateV2(item, log)
        if self.Presentation:
            self.Presentation.MigrateV2(item, log)
        if self.ResProcessing:
            if len(self.ResProcessing) > 1:
                log.append(
                    "Warning: multiople <resprocessing> not supported, ignoring all but the last")
            self.ResProcessing[-1].MigrateV2(item, log)
        for feedback in self.ItemFeedback:
            feedback.MigrateV2(item, log)
        item.SortDeclarations()
        output.append((doc, lom, log))
        # print doc.root


class ItemMetadata(common.MetadataContainerMixin, core.QTIElement):

    """The itemmetadata element contains all of the QTI-specific meta-data to be
    applied to the Item. This meta-data can consist of either entries defined
    using an external vocabulary or the individually named entries::

    <!ELEMENT itemmetadata (
            qtimetadata*
            qmd_computerscored?
            qmd_feedbackpermitted?
            qmd_hintspermitted?
            qmd_itemtype?
            qmd_levelofdifficulty?
            qmd_maximumscore?
            qmd_renderingtype*
            qmd_responsetype*
            qmd_scoringpermitted?
            qmd_solutionspermitted?
            qmd_status?
            qmd_timedependence?
            qmd_timelimit?
            qmd_toolvendor?
            qmd_topic?
            qmd_weighting?
            qmd_material*
            qmd_typeofsolution?
            )>

    This element contains more structure than is in common use, at the moment we
    represent this structure directly and automaticaly conform output to it,
    adding extension elements at the end.  In the future we might be more
    generous and allow input *and* output of elements in any sequence and
    provide separate methods for conforming these elements."""
    XMLNAME = 'itemmetadata'
    XMLCONTENT = xml.ElementContent

    def __init__(self, parent):
        core.QTIElement.__init__(self, parent)
        common.MetadataContainerMixin.__init__(self)
        self.QTIMetadata = []
        self.QMDComputerScored = None
        self.QMDFeedbackPermitted = None
        self.QMDHintsPermitted = None
        self.QMDItemType = None
        self.QMDLevelOfDifficulty = None
        self.QMDMaximumScore = None
        self.QMDRenderingType = []
        self.QMDResponseType = []
        self.QMDScoringPermitted = None
        self.QMDSolutionsPermitted = None
        self.QMDStatus = None
        self.QMDTimeDependence = None
        self.QMDTimeLimit = None
        self.QMDToolVendor = None
        self.QMDTopic = None
        self.QMDWeighting = None
        self.QMDMaterial = []
        self.QMDTypeOfSolution = None
        # Extensions in common use....
        self.QMDAuthor = []
        self.QMDDescription = []
        self.QMDDomain = []
        self.QMDKeywords = []
        self.QMDOrganization = []
        self.QMDTitle = None

    def GetChildren(self):
        for child in self.QTIMetadata:
            yield child
        if self.QMDComputerScored:
            yield self.QMDComputerScored
        if self.QMDFeedbackPermitted:
            yield self.QMDFeedbackPermitted
        if self.QMDHintsPermitted:
            yield self.QMDHintsPermitted
        if self.QMDItemType:
            yield self.QMDItemType
        if self.QMDLevelOfDifficulty:
            yield self.QMDLevelOfDifficulty
        if self.QMDMaximumScore:
            yield self.QMDMaximumScore
        for child in itertools.chain(
                self.QMDRenderingType,
                self.QMDResponseType):
            yield child
        if self.QMDScoringPermitted:
            yield self.QMDScoringPermitted
        if self.QMDSolutionsPermitted:
            yield self.QMDSolutionsPermitted
        if self.QMDStatus:
            yield self.QMDStatus
        if self.QMDTimeDependence:
            yield self.QMDTimeDependence
        if self.QMDTimeLimit:
            yield self.QMDTimeLimit
        if self.QMDToolVendor:
            yield self.QMDToolVendor
        if self.QMDTopic:
            yield self.QMDTopic
        if self.QMDWeighting:
            yield self.QMDWeighting
        for child in self.QMDMaterial:
            yield child
        if self.QMDTypeOfSolution:
            yield self.QMDTypeOfSolution
        for child in itertools.chain(
                self.QMDAuthor,
                self.QMDDescription,
                self.QMDDomain,
                self.QMDKeywords,
                self.QMDOrganization):
            yield child
        if self.QMDTitle:
            yield self.QMDTitle
        for child in core.QTIElement.GetChildren(self):
            yield child

    def LRMMigrateLevelOfDifficulty(self, lom, log):
        difficulty = self.metadata.get('levelofdifficulty', ())
        for value, definition in difficulty:
            # IMS Definition says: The options are: "Pre-school", "School" or
            # "HE/FE", # "Vocational" and "Professional Development" so we bind
            # this value to the "Context" in LOM if one of the QTI or LOM
            # defined terms have been used, otherwise, we bind to Difficulty, as
            # this seems to be more common usage.
            context, lomFlag = QMDLevelOfDifficulty.LOMContextMap.get(
                value.lower(), (None, False))
            educational = lom.ChildElement(imsmd.LOMEducational)
            if context is None:
                # add value as difficulty
                lomFlag = value.lower(
                ) in QMDLevelOfDifficulty.LOMDifficultyMap
                d = educational.ChildElement(imsmd.LOMDifficulty)
                if lomFlag:
                    d.LRMSource.LangString.SetValue(imsmd.LOM_SOURCE)
                else:
                    d.LRMSource.LangString.SetValue(imsmd.LOM_UNKNOWNSOURCE)
                d.LRMSource.LangString.SetLang("x-none")
                d.LRMValue.LangString.SetValue(value)
                d.LRMValue.LangString.SetLang("x-none")
            else:
                # add value as educational context
                c = educational.ChildElement(imsmd.LOMContext)
                if lomFlag:
                    c.LRMSource.LangString.SetValue(imsmd.LOM_SOURCE)
                else:
                    c.LRMSource.LangString.SetValue(imsmd.LOM_UNKNOWNSOURCE)
                c.LRMSource.LangString.SetLang("x-none")
                c.LRMValue.LangString.SetValue(context)
                c.LRMValue.LangString.SetLang("x-none")

    def LRMMigrateStatus(self, lom, log):
        status = self.metadata.get('status', ())
        for value, definition in status:
            s = lom.ChildElement(
                imsmd.LOMLifecycle).ChildElement(imsmd.LOMStatus)
            value = value.lower()
            source = QMDStatus.SourceMap.get(value, imsmd.LOM_UNKNOWNSOURCE)
            s.LRMSource.LangString.SetValue(source)
            s.LRMSource.LangString.SetLang("x-none")
            s.LRMValue.LangString.SetValue(value)
            s.LRMValue.LangString.SetLang("x-none")

    def LRMMigrateTopic(self, lom, log):
        topics = self.metadata.get('topic', ())
        for value, definition in topics:
            lang = definition.ResolveLang()
            value = value.strip()
            description = lom.ChildElement(
                imsmd.LOMEducational).ChildElement(imsmd.Description)
            description.AddString(lang, value)

    def LRMMigrateContributor(self, field_name, lomRole, lom, log):
        contributors = self.metadata.get(field_name, ())
        if contributors:
            if imsmd.vobject is None:
                log.append(
                    'Warning: qmd_%s support disabled (vobject not installed)' %
                    field_name)
            else:
                for value, definition in contributors:
                    lifecycle = lom.ChildElement(imsmd.LOMLifecycle)
                    contributor = lifecycle.ChildElement(imsmd.LOMContribute)
                    role = contributor.LOMRole
                    role.LRMSource.LangString.SetValue(imsmd.LOM_SOURCE)
                    role.LRMSource.LangString.SetLang("x-none")
                    role.LRMValue.LangString.SetValue(lomRole)
                    role.LRMValue.LangString.SetLang("x-none")
                    names = value.strip().split(',')
                    for name in names:
                        if not name.strip():
                            continue
                        vcard = imsmd.vobject.vCard()
                        vcard.add('n')
                        vcard.n.value = imsmd.vobject.vcard.Name(
                            family=name, given='')
                        vcard.add('fn')
                        vcard.fn.value = name.strip()
                        contributor.ChildElement(
                            imsmd.LOMCEntity).LOMVCard.SetValue(vcard)

    def LRMMigrateDescription(self, lom, log):
        descriptions = self.metadata.get('description', ())
        for value, definition in descriptions:
            lang = definition.ResolveLang()
            genDescription = lom.ChildElement(
                imsmd.LOMGeneral).ChildElement(imsmd.Description)
            genDescription = genDescription.ChildElement(
                genDescription.LangStringClass)
            genDescription.SetValue(value)
            if lang:
                genDescription.SetLang(lang)

    def LRMMigrateDomain(self, lom, log):
        domains = self.metadata.get('domain', ())
        warn = False
        for value, definition in domains:
            lang = definition.ResolveLang()
            kwValue = value.strip()
            if kwValue:
                kwContainer = lom.ChildElement(
                    imsmd.LOMGeneral).ChildElement(imsmd.LOMKeyword)
                kwContainer = kwContainer.ChildElement(
                    kwContainer.LangStringClass)
                kwContainer.SetValue(kwValue)
                # set the language of the kw
                if lang:
                    kwContainer.SetLang(lang)
                if not warn:
                    log.append(
                        "Warning: qmd_domain extension field will be added as LOM keyword")
                    warn = True

    def LRMMigrateKeywords(self, lom, log):
        keywords = self.metadata.get('keywords', ())
        for value, definition in keywords:
            lang = definition.ResolveLang()
            values = string.split(value, ',')
            for kwValue in values:
                v = kwValue.strip()
                if v:
                    kwContainer = lom.ChildElement(
                        imsmd.LOMGeneral).ChildElement(imsmd.LOMKeyword)
                    kwContainer = kwContainer.ChildElement(
                        kwContainer.LangStringClass)
                    kwContainer.SetValue(v)
                    # set the language of the kw
                    if lang:
                        kwContainer.SetLang(lang)

    def LRMMigrateOrganization(self, lom, log):
        organizations = self.metadata.get('organization', ())
        if organizations:
            if imsmd.vobject is None:
                log.append(
                    'Warning: qmd_organization support disabled (vobject not installed)')
            else:
                for value, definition in organizations:
                    lifecycle = lom.ChildElement(imsmd.LOMLifecycle)
                    contributor = lifecycle.ChildElement(imsmd.LOMContribute)
                    role = contributor.LOMRole
                    role.LRMSource.LangString.SetValue(imsmd.LOM_SOURCE)
                    role.LRMSource.LangString.SetLang("x-none")
                    role.LRMValue.LangString.SetValue("unknown")
                    role.LRMValue.LangString.SetLang("x-none")
                    name = value.strip()
                    vcard = imsmd.vobject.vCard()
                    vcard.add('n')
                    vcard.n.value = imsmd.vobject.vcard.Name(
                        family=name, given='')
                    vcard.add('fn')
                    vcard.fn.value = name
                    vcard.add('org')
                    vcard.org.value = [name]
                    contributor.ChildElement(
                        imsmd.LOMCEntity).LOMVCard.SetValue(vcard)

    def MigrateV2(self, doc, lom, log):
        item = doc.root
        itemtypes = self.metadata.get('itemtype', ())
        for itemtype, itemtypeDef in itemtypes:
            log.append(
                "Warning: qmd_itemtype now replaced by qtiMetadata.interactionType in manifest, ignoring %s" %
                itemtype)
        self.LRMMigrateLevelOfDifficulty(lom, log)
        self.LRMMigrateStatus(lom, log)
        vendors = self.metadata.get('toolvendor', ())
        for value, definition in vendors:
            item.metadata.ChildElement(qtiv2.md.ToolVendor).SetValue(value)
        self.LRMMigrateTopic(lom, log)
        self.LRMMigrateContributor('author', 'author', lom, log)
        self.LRMMigrateContributor('creator', 'initiator', lom, log)
        self.LRMMigrateContributor('owner', 'publisher', lom, log)
        self.LRMMigrateDescription(lom, log)
        self.LRMMigrateDomain(lom, log)
        self.LRMMigrateKeywords(lom, log)
        self.LRMMigrateOrganization(lom, log)


class QMDMetadataElement(core.QTIElement):

    """Abstract class to represent old-style qmd_ tags"""

    def ContentChanged(self):
        self.DeclareMetadata(self.GetXMLName(), self.GetValue(), self)


class QMDComputerScored(QMDMetadataElement):

    """Whether or not the Item can be computer scored."""
    XMLNAME = 'qmd_computerscored'


class QMDFeedbackPermitted(QMDMetadataElement):

    """Indicates whether or not feedback is available within the Item."""
    XMLNAME = 'qmd_feedbackpermitted'


class QMDHintsPermitted(QMDMetadataElement):

    """Indicates whether or not hints are available within the Item."""
    XMLNAME = 'qmd_hintspermitted'


class QMDItemType(QMDMetadataElement):

    """The type of Item available."""
    XMLNAME = 'qmd_itemtype'


class QMDLevelOfDifficulty(QMDMetadataElement):

    """The educational level for which the Item is intended::

    <!ELEMENT qmd_levelofdifficulty (#PCDATA)>"""
    XMLNAME = 'qmd_levelofdifficulty'

    LOMDifficultyMap = {
        "very easy": True,
        "easy": True,
        "medium": True,
        "difficult": True,
        "very difficult": True
    }
    """A mapping from difficulty values to the LOM difficulties.  This value is
	supposed to be an educational level, e.g., "pre-school" but in practice it
	is often used for values more akin to the LOM concept of difficulty."""

    LOMContextMap = {
        # value is outside LOM defined vocab
        "pre-school": ("pre-school", False),
        "school": ("school", True),
        "he/fe": ("higher education", True),
        # value is outside LOM defined vocab
        "vocational": ("vocational", False),
        "professional development": ("training", True)
    }
    """A mapping from difficulty values to the LOM contexts.  This mapping
	returns a tuple of the context value and a boolean indicating whether or not
	this is one from the LOM standard or not."""


class QMDMaterial(QMDMetadataElement):

    """The type of material content used within the Item."""
    XMLNAME = 'qmd_material'


class QMDMaximumScore(QMDMetadataElement):

    """The maximum score possible from the Item."""
    XMLNAME = 'qmd_maximumscore'


class QMDRenderingType(QMDMetadataElement):

    """The type of rendering used within the Item."""
    XMLNAME = 'qmd_renderingtype'


class QMDResponseType(QMDMetadataElement):

    """The class of response expected for the Item."""
    XMLNAME = 'qmd_responsetype'


class QMDScoringPermitted(QMDMetadataElement):

    """Indicates whether or not scoring is available within the Item."""
    XMLNAME = 'qmd_scoringpermitted'


class QMDSolutionsPermitted(QMDMetadataElement):

    """Indicates whether or not solutions are available within the Item."""
    XMLNAME = 'qmd_solutionspermitted'


class QMDStatus(QMDMetadataElement):

    """The status of the Item."""
    XMLNAME = 'qmd_status'

    SourceMap = {
        'draft': imsmd.LOM_SOURCE,
        'final': imsmd.LOM_SOURCE,
        'revised': imsmd.LOM_SOURCE,
        'unavailable': imsmd.LOM_SOURCE,
        'experimental': core.QTI_SOURCE,
        'normal': core.QTI_SOURCE,
        'retired': core.QTI_SOURCE
    }
    """A mapping from status values to a metadata source, one of the following
	constants: :py:data:`~pyslet.imsmdv1p2p1.LOM_SOURCE`
	:py:data:`~pyslet.qtiv1.core.QTI_SOURCE`"""


class QMDTimeDependence(QMDMetadataElement):

    """Whether or not the responses are time dependent."""
    XMLNAME = 'qmd_timedependence'


class QMDTimeLimit(QMDMetadataElement):

    """The number of minutes to be permitted for the completion of the Item."""
    XMLNAME = 'qmd_timelimit'


class QMDToolVendor(QMDMetadataElement):

    """The name of the vendor of the tool creating the Item."""
    XMLNAME = 'qmd_toolvendor'


class QMDTopic(QMDMetadataElement):

    """The brief description of the topic covered by the Item."""
    XMLNAME = 'qmd_topic'


class QMDTypeOfSolution(QMDMetadataElement):

    """The type of solution available in the Item."""
    XMLNAME = 'qmd_typeofsolution'


class QMDWeighting(QMDMetadataElement):

    """The weighting to be applied to the scores allocated to this Item before
    aggregations with other scores."""
    XMLNAME = 'qmd_weighting'


class QMDAuthor(QMDMetadataElement):

    """Not defined by QTI but seems to be in common use."""
    XMLNAME = 'qmd_author'


class QMDDescription(QMDMetadataElement):

    """Not defined by QTI but seems to be in common use."""
    XMLNAME = 'qmd_description'


class QMDDomain(QMDMetadataElement):

    """Not defined by QTI but seems to be in common use."""
    XMLNAME = 'qmd_domain'


class QMDKeywords(QMDMetadataElement):

    """Not defined by QTI but seems to be in common use."""
    XMLNAME = 'qmd_keywords'


class QMDOrganization(QMDMetadataElement):

    """Not defined by QTI but seems to be in common use."""
    XMLNAME = 'qmd_organization'


class QMDTitle(QMDMetadataElement):

    """Not defined by QTI but seems to be in common use."""
    XMLNAME = 'qmd_title'


class ItemPreCondition(core.QTIElement):

    """The preconditions that control whether or not the Item is activated::

    <!ELEMENT itemprecondition (#PCDATA)>"""
    XMLNAME = 'itemprecondition'
    XMLCONTENT = xml.XMLMixedContent


class ItemPostCondition(core.QTIElement):

    """The postconditions that control whether or not the next Item may be
    activated::

    <!ELEMENT itempostcondition (#PCDATA)>"""
    XMLNAME = 'itempostcondition'
    XMLCONTENT = xml.XMLMixedContent


class ItemControl(common.QTICommentContainer):

    """The control switches that are used to enable or disable the display of
    hints, solutions and feedback within the Item::

    <!ELEMENT itemcontrol (qticomment?)>
    <!ATTLIST itemcontrol  feedbackswitch  (Yes | No )  'Yes'
            hintswitch  (Yes | No )  'Yes'
            solutionswitch  (Yes | No )  'Yes'
            view	(All | Administrator | AdminAuthority | Assessor | Author |
                            Candidate | InvigilatorProctor | Psychometrician | Scorer |
                            Tutor ) 'All' >
    """
    XMLNAME = 'itemcontrol'
    XMLATTR_feedbackswitch = (
        'feedbackSwitch', core.ParseYesNo, core.FormatYesNo)
    XMLATTR_hintswitch = ('hintSwitch', core.ParseYesNo, core.FormatYesNo)
    XMLATTR_solutionswitch = (
        'solutionSwitch', core.ParseYesNo, core.FormatYesNo)
    XMLCONTENT = xml.ElementContent

    def __init__(self, parent):
        common.QTICommentContainer.__init__(self, parent)
        self.feedbackSwitch = True
        self.hintSwitch = True
        self.solutionSwitch = True
        self.view = core.View.DEFAULT


class ItemRubric(common.Rubric):

    """The itemrubric element is used to contain contextual information that is
    important to the element e.g. it could contain standard data values that
    might or might not be useful for answering the question. Different sets of
    rubric can be defined for each of the possible views::

    <!ELEMENT itemrubric (material)>
    <!ATTLIST itemrubric
            view	(All | Administrator | AdminAuthority | Assessor | Author |
                            Candidate | InvigilatorProctor | Psychometrician | Scorer |
                            Tutor ) 'All' >

    We are generous with this element, extending the allowable content model
    to make it equivalent to <rubric> which is a superset.  <itemrubric> was
    deprecated in favour of <rubric> with QTI v1.2"""
    XMLNAME = 'itemrubric'
    XMLCONTENT = xml.ElementContent


class FlowContainer(common.QTICommentContainer, common.ContentMixin):

    """Abstract class used to represent elements that contain flow and related
    elements::

    <!ELEMENT XXXXXXXXXX (qticomment? , (material | flow | response_*)* )>"""

    def __init__(self, parent):
        common.QTICommentContainer.__init__(self, parent)
        common.ContentMixin.__init__(self)

    def GetChildren(self):
        return itertools.chain(
            common.QTICommentContainer.GetChildren(self),
            common.ContentMixin.GetContentChildren(self))

    def ContentMixin(self, childClass):
        if childClass in (common.Material, Flow) or issubclass(childClass, Response):
            return common.ContentMixin.ContentMixin(self, childClass)
        else:
            raise TypeError


class Presentation(FlowContainer, common.PositionMixin):

    """This element contains all of the instructions for the presentation of the
    question during an evaluation. This information includes the actual material
    to be presented. The labels for the possible responses are also identified
    and these are used by the response processing element defined elsewhere in
    the Item::

    <!ELEMENT presentation (qticomment? ,
            (flow |
                    (material |
                    response_lid |
                    response_xy |
                    response_str |
                    response_num |
                    response_grp |
                    response_extension)+
                    )
            )>
    <!ATTLIST presentation  label CDATA  #IMPLIED
            xml:lang CDATA  #IMPLIED
            y0 CDATA  #IMPLIED
            x0 CDATA  #IMPLIED
            width CDATA  #IMPLIED
            height CDATA  #IMPLIED >"""
    XMLNAME = 'presentation'
    XMLATTR_label = 'label'

    XMLCONTENT = xml.ElementContent

    def __init__(self, parent):
        FlowContainer.__init__(self, parent)
        common.PositionMixin.__init__(self)
        self.label = None

    def MigrateV2(self, v2Item, log):
        """Presentation maps to the main content in itemBody."""
        itemBody = v2Item.ChildElement(qtiv2.content.ItemBody)
        if self.GotPosition():
            log.append(
                "Warning: discarding absolute positioning information on presentation")
        if self.InlineChildren():
            p = itemBody.ChildElement(
                html.P, (qtiv2.core.IMSQTI_NAMESPACE, 'p'))
            if self.label is not None:
                # p.label=self.label
                p.SetAttribute('label', self.label)
            self.MigrateV2Content(p, html.InlineMixin, log)
        elif self.label is not None:
            # We must generate a div to hold the label, we can't rely on owning
            # the whole itemBody
            div = itemBody.ChildElement(
                html.Div, (qtiv2.core.IMSQTI_NAMESPACE, 'div'))
            div.SetAttribute('label', self.label)
            # Although div will take an inline directly we force blocking at
            # the top level
            self.MigrateV2Content(div, html.BlockMixin, log)
        else:
            # mixture or block children, force use of blocks
            self.MigrateV2Content(itemBody, html.BlockMixin, log)
        self.CleanHotspotImages(itemBody)

    def CleanHotspotImages(self, itemBody):
        """Removes spurious img tags which represent images used in hotspotInteractions.

        Unfortunately we have to do this because images needed in hotspot interactions
        are often clumsily placed outside the response/render constructs.  Rather than
        fiddle around at the time we simply migrate the lot, duplicating the images
        in the hotspotInteractions.  When the itemBody is complete we do a grand tidy
        up to remove spurious images."""
        hotspots = list(
            itemBody.FindChildrenDepthFirst(
                (qtiv2.interactions.HotspotInteraction,
                 qtiv2.interactions.SelectPointInteraction),
                False))
        images = list(itemBody.FindChildrenDepthFirst(html.Img, False))
        for hs in hotspots:
            for img in images:
                # migrated images/hotspots will always have absolute URIs
                if img.src and str(img.src) == str(hs.Object.data):
                    parent = img.parent
                    parent.DeleteChild(img)
                    if isinstance(parent, html.P) and len(list(parent.GetChildren())) == 0:
                        # It is always safe to remove a paragraph left empty by deleting an image
                        # The chances are the paragraph was created by us to
                        # house a matimage
                        parent.parent.DeleteChild(parent)

    def IsInline(self):
        return False


class Flow(FlowContainer, common.FlowMixin):

    """This element contains all of the instructions for the presentation with
    flow blocking of the question during a test. This information includes the
    actual material to be presented. The labels for the possible responses are
    also identified and these are used by the response processing element
    defined elsewhere in the Item::

    <!ELEMENT flow (qticomment? ,
            (flow |
            material |
            material_ref |
            response_lid |
            response_xy |
            response_str |
            response_num |
            response_grp |
            response_extension)+
            )>
    <!ATTLIST flow  class CDATA  'Block' >"""
    XMLNAME = 'flow'
    XMLATTR_class = 'flowClass'
    XMLCONTENT = xml.ElementContent

    def __init__(self, parent):
        FlowContainer.__init__(self, parent)
        self.flowClass = None

    def IsInline(self):
        """flow is always treated as a block if flowClass is specified, otherwise
        it is treated as a block unless it is an only child."""
        if self.flowClass is None:
            return self.InlineChildren()
        else:
            return False

    def MigrateV2Content(self, parent, childType, log):
        """flow typically maps to a div element.

        A flow with a specified class always becomes a div
        A flow with inline children generates a paragraph to hold them
        A flow with no class is ignored."""
        if self.flowClass is not None:
            if childType in (html.BlockMixin, html.FlowMixin):
                div = parent.ChildElement(
                    html.Div, (qtiv2.core.IMSQTI_NAMESPACE, 'div'))
                div.styleClass = self.flowClass
                FlowContainer.MigrateV2Content(self, div, html.FlowMixin, log)
            else:
                span = parent.ChildElement(
                    html.Span, (qtiv2.core.IMSQTI_NAMESPACE, 'span'))
                span.styleClass = self.flowClass
                FlowContainer.MigrateV2Content(
                    self, span, html.InlineMixin, log)
        else:
            FlowContainer.MigrateV2Content(self, parent, childType, log)


class Response(core.QTIElement, common.ContentMixin):

    """Abstract class to act as a parent for all response_* elements."""

    def __init__(self, parent):
        core.QTIElement.__init__(self, parent)
        common.ContentMixin.__init__(self)


class ResponseExtension(Response):

    """This element supports proprietary alternatives to the current range of
    'response' elements::

    <!ELEMENT response_extension ANY>"""
    XMLNAME = "response_extension"
    XMLCONTENT = xml.XMLMixedContent


class ResponseThing(Response):

    """Abstract class for the main response_* elements::

    <!ELEMENT response_* ((material | material_ref)? ,
            (render_choice | render_hotspot | render_slider | render_fib | render_extension) ,
            (material | material_ref)?)>
    <!ATTLIST response_*
            rcardinality	(Single | Multiple | Ordered )  'Single'
    rtiming  		(Yes | No )  'No'
            ident 			CDATA  #REQUIRED >"""
    XMLATTR_ident = 'ident'
    XMLATTR_rcardinality = (
        'rCardinality',
        core.RCardinality.DecodeTitleValue,
        core.RCardinality.EncodeValue)
    XMLATTR_rtiming = ('rTiming', core.ParseYesNo, core.FormatYesNo)
    XMLATTR_ident = 'ident'
    XMLCONTENT = xml.ElementContent

    def __init__(self, parent):
        Response.__init__(self, parent)
        self.ident = None
        self.rCardinality = core.RCardinality.DEFAULT
        self.rTiming = False
        self.intro = []
        self.prompt = []
        self.inlinePrompt = True
        self.render = None
        self.outro = []
        self.footer = []
        self.inlineFooter = True

    def ContentMixin(self, childClass):
        if childClass in (common.Material, common.MaterialRef):
            child = childClass(self)
            if self.render:
                self.outro.append(child)
            else:
                self.intro.append(child)
            return child
        elif issubclass(childClass, Render):
            child = childClass(self)
            self.render = child
            return child
        else:
            raise TypeError

    def GetChildren(self):
        for child in self.intro:
            yield child
        if self.render:
            yield self.render
        for child in self.outro:
            yield child

    def ContentChanged(self):
        if isinstance(self.render, RenderFIB) and self.render.MixedModel():
            # use simplified prompt logic.
            self.prompt = self.intro
            self.inlintPrompt = True
            for child in self.prompt:
                if not child.IsInline():
                    self.inlinePrompt = False
            self.footer = self.outro
            self.inlineFooter = True
            for child in self.footer:
                if not child.IsInline():
                    self.inlineFooter = False
        elif self.render:
            # all the material up to the first response_label is the prompt
            self.prompt = []
            self.inlinePrompt = True
            renderChildren = self.render.GetLabelContent()
            for child in self.intro + renderChildren:
                # print child.__class__,child.xmlname
                if isinstance(child, ResponseLabel):
                    break
                self.prompt.append(child)
                if not child.IsInline():
                    self.inlinePrompt = False
            self.footer = []
            self.inlineFooter = True
            foundLabel = False
            for child in renderChildren + self.outro:
                if isinstance(child, ResponseLabel):
                    self.footer = []
                    self.inlineFooter = True
                    foundLabel = True
                    continue
                if foundLabel:
                    self.footer.append(child)
                    if not child.IsInline():
                        self.inlineFooter = False

    def InlineChildren(self):
        return self.inlinePrompt and (
            self.render is None or self.render.IsInline()) and self.inlineFooter

    def GetBaseType(self, interaction):
        """Returns the base type to use for the given interaction."""
        raise QTIUnimplementedError(
            "BaseType selection for %s" % self.__class__.__name__)

    def MigrateV2Content(self, parent, childType, log):
        if self.inlinePrompt:
            interactionPrompt = self.prompt
        else:
            if childType is html.InlineMixin:
                raise QTIError("Unexpected attempt to inline interaction")
            interactionPrompt = None
            if isinstance(self.render, RenderHotspot):
                div = parent.ChildElement(
                    html.Div, (qtiv2.core.IMSQTI_NAMESPACE, 'div'))
                common.ContentMixin.MigrateV2Content(
                    self, div, html.FlowMixin, log, self.prompt)
                # Now we need to find any images and pass them to render hotspot instead
                # which we do by reusing the interactionPrompt (currently we only find
                # the first image).
                interactionPrompt = list(
                    div.FindChildrenDepthFirst(html.Img, False))[0:1]
            else:
                common.ContentMixin.MigrateV2Content(
                    self, parent, childType, log, self.prompt)
        if self.render:
            interactionList = self.render.MigrateV2Interaction(
                parent, childType, interactionPrompt, log)
            item = parent.FindParent(qtiv2.items.AssessmentItem)
            if len(interactionList) > 1 and self.rCardinality == core.RCardinality.Single:
                log.append(
                    "Error: unable to migrate a response with Single cardinality to a single interaction: %s" %
                    self.ident)
                interactionList = []
                responseList = []
            else:
                baseIdentifier = qtiv2.core.ValidateIdentifier(self.ident)
                responseList = []
                if len(interactionList) > 1:
                    i = 0
                    for interaction in interactionList:
                        i = i + 1
                        while True:
                            rIdentifier = "%s_%02i" % (baseIdentifier, i)
                            if item is None or not item.IsDeclared(rIdentifier):
                                break
                        interaction.responseIdentifier = rIdentifier
                        responseList.append(rIdentifier)
                elif interactionList:
                    interaction = interactionList[0]
                    interaction.responseIdentifier = baseIdentifier
                    responseList = [interaction.responseIdentifier]
            if item:
                for i, r in zip(interactionList, responseList):
                    d = item.ChildElement(qtiv2.variables.ResponseDeclaration)
                    d.identifier = r
                    d.cardinality = core.MigrateV2Cardinality(
                        self.rCardinality)
                    d.baseType = self.GetBaseType(interactionList[0])
                    self.render.MigrateV2InteractionDefault(d, i)
                    item.RegisterDeclaration(d)
                if len(responseList) > 1:
                    d = item.ChildElement(qtiv2.variables.OutcomeDeclaration)
                    d.identifier = baseIdentifier
                    d.cardinality = core.MigrateV2Cardinality(
                        self.rCardinality)
                    d.baseType = self.GetBaseType(interactionList[0])
                    item.RegisterDeclaration(d)
                    # now we need to fix this outcome up with a value in
                    # response processing
                    selfItem = self.FindParent(Item)
                    if selfItem:
                        for rp in selfItem.ResProcessing:
                            rp._interactionFixup[baseIdentifier] = responseList
        # the footer is in no-man's land so we just back-fill
        common.ContentMixin.MigrateV2Content(
            self, parent, childType, log, self.footer)


class ResponseLId(ResponseThing):

    """The <response_lid> element contains the instructions for the presentation
    of questions whose response will be the logical label of the selected
    answer. The question can be rendered in a variety of ways depending on the
    way in which the material is to be presented to the participant::

    <!ELEMENT response_lid ((material | material_ref)? ,
            (render_choice | render_hotspot | render_slider | render_fib | render_extension) ,
            (material | material_ref)?)>
    <!ATTLIST response_lid
            rcardinality	(Single | Multiple | Ordered )  'Single'
            rtiming  		(Yes | No )  'No'
            ident 			CDATA  #REQUIRED >"""
    XMLNAME = 'response_lid'

    def GetBaseType(self, interaction):
        """We always return identifier for response_lid."""
        return qtiv2.variables.BaseType.identifier


class ResponseXY(ResponseThing):

    """The <response_xy> element contains the instructions for the presentation
    of questions whose response will be the 'x-y' co-ordinate of the selected
    answer. The question can be rendered in a variety of ways depending on the
    way in which the material is to be presented to the participant::

    <!ELEMENT response_xy ((material | material_ref)? ,
            (render_choice | render_hotspot | render_slider | render_fib | render_extension) ,
            (material | material_ref)?)>
    <!ATTLIST response_xy
            rcardinality	(Single | Multiple | Ordered )  'Single'
            rtiming  		(Yes | No )  'No'
            ident 			CDATA  #REQUIRED >
    """
    XMLNAME = 'response_xy'

    def GetBaseType(self, interaction):
        """For select point we return a point for response_xy."""
        if isinstance(interaction, qtiv2.interactions.SelectPointInteraction):
            return qtiv2.variables.BaseType.point
        else:
            return ResponseThing.GetBaseType(self)


class ResponseStr(ResponseThing):

    """The <response_str> element contains the instructions for the presentation
    of questions whose response will be the a string. The question can be
    rendered in a variety of ways depending on the way in which the material is
    to be presented to the participant::

    <!ELEMENT response_str ((material | material_ref)? ,
            (render_choice | render_hotspot | render_slider | render_fib | render_extension) ,
            (material | material_ref)?)>

    <!ATTLIST response_str
            rcardinality	(Single | Multiple | Ordered )  'Single'
            ident 			CDATA #REQUIRED
            rtiming  		(Yes | No )  'No' >"""
    XMLNAME = 'response_str'

    def GetBaseType(self, interaction):
        """We always return string for response_str."""
        return qtiv2.variables.BaseType.string


class ResponseNum(ResponseThing):

    """The <response_num> element contains the instructions for the presentation
    of questions whose response will be a number. The question can be rendered
    in a variety of ways depending on the way in which the material is to be
    presented to the participant::

    <!ELEMENT response_num ((material | material_ref)? ,
            (render_choice | render_hotspot | render_slider | render_fib | render_extension) ,
            (material | material_ref)?)>
    <!ATTLIST response_num
            numtype			(Integer | Decimal | Scientific )  'Integer'
            rcardinality	(Single | Multiple | Ordered )  'Single'
            ident CDATA  	#REQUIRED
            rtiming  		(Yes | No )  'No' >
    """
    XMLNAME = 'response_num'
    XMLATTR_numtype = (
        'numType', core.NumType.DecodeTitleValue, core.NumType.EncodeValue)
    XMLCONTENT = xml.ElementContent

    def __init__(self, parent):
        ResponseThing.__init__(self, parent)
        self.numType = core.NumType.Integer

    def GetBaseType(self, interaction):
        """Returns integer for numtype of "Integer", float otherwise."""
        if self.numType == core.NumType.Integer:
            return qtiv2.variables.BaseType.integer
        else:
            return qtiv2.variables.BaseType.float


class ResponseGrp(core.QTIElement, common.ContentMixin):

    """The <response_grp> element contains the instructions for the presentation
    of questions whose response will be a group of logical identifiers. The
    question can be rendered in a variety of ways depending on the way in which
    the material is to be presented to the participant::

    <!ELEMENT response_grp ((material | material_ref)? ,
            (render_choice | render_hotspot | render_slider | render_fib | render_extension) ,
            (material | material_ref)?)>
    <!ATTLIST response_grp
            rcardinality	(Single | Multiple | Ordered )  'Single'
            ident			CDATA  #REQUIRED
            rtiming			(Yes | No )  'No' >"""
    XMLNAME = 'response_grp'
    XMLCONTENT = xml.ElementContent


class Render(core.QTIElement, common.ContentMixin):

    """Abstract class to act as a parent for all render_* elements."""

    def __init__(self, parent):
        core.QTIElement.__init__(self, parent)
        common.ContentMixin.__init__(self)


class RenderThing(Render):

    """Abstract base class for all render_* objects::

    <!ELEMENT render_* ((material | material_ref | response_label | flow_label)* , response_na?)>"""
    XMLCONTENT = xml.ElementContent

    def __init__(self, parent):
        Render.__init__(self, parent)
        self.ResponseNA = None

    def ContentMixin(self, childClass):
        if childClass in (common.Material, common.MaterialRef, ResponseLabel, FlowLabel):
            return common.ContentMixin.ContentMixin(self, childClass)
        else:
            raise TypeError

    def GetChildren(self):
        for child in self.GetContentChildren():
            yield child
        if self.ResponseNA:
            yield self.ResponseNA

    def GetLabelContent(self):
        """Returns a flat list of content items, stripping away flow_labels."""
        children = []
        for child in self.GetContentChildren():
            if isinstance(child, FlowLabel):
                children = children + child.GetLabelContent()
            else:
                children.append(child)
        return children

    def IsInline(self):
        for child in self.GetLabelContent():
            if not child.IsInline():
                return False
        return True

    def MigrateV2Interaction(self, parent, childType, prompt, log):
        raise QTIUnimplementedError(
            "%s x %s" %
            (self.parent.__class__.__name__, self.__class__.__name__))

    def MigrateV2InteractionDefault(self, parent, interaction):
        # Most interactions do not need default values.
        pass


class RenderChoice(RenderThing):

    """The <render_choice> element instructs the question-engine to render the
    question using a classical multiple-choice format. The number of possible
    responses is determined by the <response_label> elements contained. Both
    flowed and non-flowed formats are supported::

    <!ELEMENT render_choice ((material | material_ref | response_label | flow_label)* , response_na?)>
    <!ATTLIST render_choice
            shuffle     (Yes | No )  'No'
            minnumber	CDATA  #IMPLIED
            maxnumber	CDATA  #IMPLIED >"""
    XMLNAME = 'render_choice'
    XMLATTR_shuffle = ('shuffle', core.ParseYesNo, core.FormatYesNo)
    XMLATTR_minnumber = ('minNumber', xsi.DecodeInteger, xsi.EncodeInteger)
    XMLATTR_maxnumber = ('maxNumber', xsi.DecodeInteger, xsi.EncodeInteger)
    XMLCONTENT = xml.ElementContent

    def __init__(self, parent):
        RenderThing.__init__(self, parent)
        self.shuffle = False
        self.minNumber = None
        self.maxNumber = None

    def IsInline(self):
        """This always results in a block-like interaction."""
        return False

    def MigrateV2Interaction(self, parent, childType, prompt, log):
        """Migrates this content to v2 adding it to the parent content node."""
        interaction = None
        if isinstance(self.parent, ResponseLId):
            if childType is html.InlineMixin:
                raise QTIError(
                    "Unexpected attempt to put block interaction in inline context")
            if self.parent.rCardinality == core.RCardinality.Ordered:
                raise QTIUnimplementedError("OrderInteraction")
            else:
                interaction = parent.ChildElement(
                    qtiv2.interactions.ChoiceInteraction)
        else:
            raise QTIUnimplementedError(
                "%s x render_choice" % self.parent.__class__.__name__)
        if prompt:
            interactionPrompt = interaction.ChildElement(
                qtiv2.interactions.Prompt)
            for child in prompt:
                child.MigrateV2Content(
                    interactionPrompt, html.InlineMixin, log)
        if self.minNumber is not None:
            interaction.minChoices = self.minNumber
        if self.maxNumber is not None:
            interaction.maxChoices = self.maxNumber
        interaction.shuffle = self.shuffle
        for child in self.GetLabelContent():
            if isinstance(child, ResponseLabel):
                child.MigrateV2SimpleChoice(interaction, log)
        return [interaction]


class RenderHotspot(RenderThing):

    """The <render_hotspot> element instructs the question-engine to render the
    question using a classical image hot-spot format. The number of possible
    responses is determined by the <response_label> elements contained. Both
    flowed and non-flowed formats are supported::

    <!ELEMENT render_hotspot ((material | material_ref | response_label | flow_label)* , response_na?)>
    <!ATTLIST render_hotspot
            maxnumber	CDATA  #IMPLIED
            minnumber	CDATA  #IMPLIED
            showdraw    (Yes | No )  'No' >"""
    XMLNAME = 'render_hotspot'
    XMLATTR_maxnumber = ('maxNumber', xsi.DecodeInteger, xsi.EncodeInteger)
    XMLATTR_minnumber = ('minNumber', xsi.DecodeInteger, xsi.EncodeInteger)
    XMLATTR_showdraw = ('showDraw', core.ParseYesNo, core.FormatYesNo)
    XMLCONTENT = xml.ElementContent

    def __init__(self, parent):
        RenderThing.__init__(self, parent)
        self.maxNumber = None
        self.minNumber = None
        self.showDraw = False

    def IsInline(self):
        """This always results in a block-like interaction."""
        return False

    def MigrateV2Interaction(self, parent, childType, prompt, log):
        """Migrates this content to v2 adding it to the parent content node."""
        interaction = None
        if isinstance(self.parent, ResponseLId):
            if self.parent.rCardinality == core.RCardinality.Ordered:
                raise QTIUnimplementedError("GraphicOrderInteraction")
            else:
                interaction = parent.ChildElement(
                    qtiv2.interactions.HotspotInteraction)
        elif isinstance(self.parent, ResponseXY):
            if self.parent.rCardinality == core.RCardinality.Ordered:
                raise QTIUnimplementedError('response_xy x render_hotspot')
            else:
                interaction = parent.ChildElement(
                    qtiv2.interactions.SelectPointInteraction)
        else:
            raise QTIUnimplementedError(
                "%s x render_hotspot" % self.parent.__class__.__name__)
        if self.showDraw:
            log.append(
                'Warning: ignoring showdraw="Yes", what did you really want to happen?')
        if self.minNumber is not None:
            interaction.minChoices = self.minNumber
        if self.maxNumber is not None:
            interaction.maxChoices = self.maxNumber
        labels = list(self.FindChildrenDepthFirst(ResponseLabel, False))
        # prompt is either a single <img> tag we already migrated or.. a set of inline
        # objects that are still to be migrated (and which should contain the
        # hotspot image).
        img = None
        interactionPrompt = None
        hotspotImage = interaction.ChildElement(
            html.Object, (qtiv2.core.IMSQTI_NAMESPACE, 'object'))
        if prompt:
            if not isinstance(prompt[0], html.Img):
                interactionPrompt = interaction.ChildElement(
                    qtiv2.interactions.Prompt)
                for child in prompt:
                    child.MigrateV2Content(
                        interactionPrompt, html.InlineMixin, log)
                prompt = list(
                    interactionPrompt.FindChildrenDepthFirst(
                        html.Img,
                        False))[
                    0:1]
        if prompt:
            # now the prompt should be a list containing a single image to use
            # as the hotspot
            img = prompt[0]
            hotspotImage.data = img.src
            hotspotImage.height = img.height
            hotspotImage.width = img.width
            if img.src:
                # Annoyingly, Img throws away mime-type information from
                # matimage
                images = list(
                    self.parent.FindChildrenDepthFirst(
                        common.MatImage,
                        False))[
                    0:1]
                if images and images[0].uri:
                    # Check that this is the right image in case img was
                    # embedded in MatText
                    if str(images[0].ResolveURI(images[0].uri)) == str(img.ResolveURI(img.src)):
                        hotspotImage.type = images[0].imageType
            for child in labels:
                if isinstance(child, ResponseLabel):
                    child.MigrateV2HotspotChoice(interaction, log)
            return [interaction]
        else:
            # tricky, let's start by getting all the matimage elements in the
            # presentation
            images = []
            presentation = self.FindParent(Presentation)
            if presentation:
                images = list(
                    presentation.FindChildrenDepthFirst(
                        common.MatImage,
                        False))
            hsi = []
            if len(images) == 1:
                # Single image that must have gone AWOL
                hsi.append((images[0], labels))
            else:
                # multiple images are scanned for those at fixed positions in the presentation
                # which are hit by a hotspot (interpreted relative to the
                # presentation).
                for img in images:
                    hits = []
                    for child in labels:
                        if child.HotspotInImage(img):
                            hits.append(child)
                    if hits:
                        # So some of our hotspots hit this image
                        hsi.append((img, hits))
            if len(hsi) == 0:
                log.append(
                    "Error: omitting render_hotspot with no hotspot image")
                return []
            else:
                img, hits = hsi[0]
                hotspotImage.data = img.ResolveURI(img.uri)
                hotspotImage.type = img.imageType
                hotspotImage.height = html.LengthType(img.height)
                hotspotImage.width = html.LengthType(img.width)
                # it will get cleaned up later
                if len(hsi) > 0:
                    # Worst case: multiple images => multiple hotspot
                    # interactions
                    if self.maxNumber is not None:
                        log.append(
                            "Warning: multi-image hotspot maps to multiple interactions, maxChoices can no longer be enforced")
                    interaction.minChoices = None
                    for child in hits:
                        child.MigrateV2HotspotChoice(
                            interaction, log, img.x0, img.y0)
                    interactionList = [interaction]
                    for img, hits in hsi[1:]:
                        interaction = parent.ChildElement(
                            qtiv2.interactions.HotspotInteraction)
                        if self.maxNumber is not None:
                            interaction.maxChoices = self.maxNumber
                        hotspotImage = interaction.ChildElement(
                            html.Object, (qtiv2.core.IMSQTI_NAMESPACE, 'object'))
                        hotspotImage.data = img.ResolveURI(img.uri)
                        hotspotImage.type = img.imageType
                        hotspotImage.height = html.LengthType(img.height)
                        hotspotImage.width = html.LengthType(img.width)
                        for child in hits:
                            child.MigrateV2HotspotChoice(
                                interaction, log, img.x0, img.y0)
                        interactionList.append(interaction)
                    return interactionList
                else:
                    # Best case: single image that just went AWOL
                    for child in labels:
                        child.MigrateV2HotspotChoice(
                            interaction, log, img.x0, img.y0)
                    return [interaction]


class RenderFIB(RenderThing):

    """The <render_fib> element instructs the question-engine to render the
    question using a classical fill-in-blank format. The number of possible
    responses is determined by the <response_label> elements contained. Both
    flowed and non-flowed formats are supported::

    <!ELEMENT render_fib ((material | material_ref | response_label | flow_label)* , response_na?)>
    <!ATTLIST render_fib
            encoding	CDATA  'UTF_8'
            fibtype		(String | Integer | Decimal | Scientific )  'String'
            rows		CDATA  #IMPLIED
            maxchars	CDATA  #IMPLIED
            prompt		(Box | Dashline | Asterisk | Underline )  #IMPLIED
            columns		CDATA  #IMPLIED
            charset		CDATA  'ascii-us'
            maxnumber	CDATA  #IMPLIED
            minnumber	CDATA  #IMPLIED >"""
    XMLNAME = 'render_fib'
    XMLATTR_encoding = 'encoding'
    XMLATTR_fibtype = (
        'fibType', core.FIBType.DecodeTitleValue, core.FIBType.EncodeValue)
    XMLATTR_rows = ('rows', xsi.DecodeInteger, xsi.EncodeInteger)
    XMLATTR_maxchars = ('maxChars', xsi.DecodeInteger, xsi.EncodeInteger)
    XMLATTR_prompt = (
        'prompt',
        core.PromptType.DecodeTitleValue,
        core.PromptType.EncodeValue)
    XMLATTR_columns = ('columns', xsi.DecodeInteger, xsi.EncodeInteger)
    XMLATTR_charset = 'charset'
    XMLATTR_maxnumber = ('maxNumber', xsi.DecodeInteger, xsi.EncodeInteger)
    XMLATTR_minnumber = ('minNumber', xsi.DecodeInteger, xsi.EncodeInteger)
    XMLCONTENT = xml.ElementContent

    def __init__(self, parent):
        RenderThing.__init__(self, parent)
        self.encoding = 'UTF_8'
        self.fibType = core.FIBType.String
        self.rows = None
        self.maxChars = None
        self.prompt = None
        self.columns = None
        self.charset = 'ascii-us'
        self.minNumber = None
        self.maxNumber = None
        self.labels = []

    def ContentChanged(self):
        self.labels = list(self.FindChildrenDepthFirst(ResponseLabel, False))

    def MixedModel(self):
        """Indicates whether or not this FIB uses a mixed model or not.

        A mixed model means that render_fib is treated as a mixture of
        interaction and content elements.  In an unmixed model the render_fib is
        treated as a single block interaction with an optional prompt.

        If the render_fib contains content, followed by labels, then we treat
        it as a prompt + fib and return False

        If the render_fib contains a mixture of content and labels, then we
        return True

        If the render_fib contains no content at all we assume it needs to be
        mixed into the surrounding content and return True."""
        children = self.GetLabelContent()
        foundLabel = False
        foundContent = False
        for child in children:
            if isinstance(child, ResponseLabel):
                foundLabel = True
            elif foundLabel:
                # any content after the first label means mixed mode.
                return True
            else:
                foundContent = True
        return not foundContent

    def IsInline(self):
        if self.MixedModel():
            return RenderThing.IsInline(self)
        else:
            return False

    def InlineFIBLabel(self):
        if self.rows is None or self.rows == 1:
            return True
        else:
            return False

    def MigrateV2FIBLabel(self, label, parent, childType, log):
        if self.InlineFIBLabel() or childType is html.InlineMixin:
            interaction = parent.ChildElement(
                qtiv2.interactions.TextEntryInteraction)
        else:
            interaction = parent.ChildElement(
                qtiv2.interactions.ExtendedTextInteraction)
        if list(label.GetChildren()):
            log.append(
                "Warning: ignoring content in render_fib.response_label")

    def MigrateV2Interaction(self, parent, childType, prompt, log):
        if self.InlineFIBLabel() or childType is html.InlineMixin:
            interactionType = qtiv2.interactions.TextEntryInteraction
        else:
            interactionType = qtiv2.interactions.ExtendedTextInteraction
        interactionList = list(
            parent.FindChildrenDepthFirst(interactionType, False))
        iCount = len(interactionList)
        # now migrate this object
        RenderThing.MigrateV2Content(self, parent, childType, log)
        interactionList = list(
            parent.FindChildrenDepthFirst(interactionType, False))
        # ignore any pre-existing interactions of this type
        interactionList = interactionList[iCount:]
        if self.parent.rCardinality == core.RCardinality.Single and len(interactionList) > 1:
            log.append(
                "Warning: single response fib ignoring all but last <response_label>")
            for interaction in interactionList[:-1]:
                interaction.parent.DeleteChild(interaction)
            interactionList = interactionList[-1:]
        for interaction in interactionList:
            if self.maxChars is not None:
                interaction.expectedLength = self.maxChars
            elif self.rows is not None and self.columns is not None:
                interaction.expectedLength = self.rows * self.columns
            if interactionType is qtiv2.interactions.ExtendedTextInteraction:
                if self.rows is not None:
                    interaction.expectedLines = self.rows
        return interactionList


class RenderSlider(RenderThing):

    """The <render_slider> element instructs the question-engine to render the
    question using dynamic slider. The number of possible responses is
    determined by the <response_label> elements contained. Both flowed and
    non-flowed formats are supported::

    <!ELEMENT render_slider ((material | material_ref | response_label | flow_label)* , response_na?)>
    <!ATTLIST render_slider
            orientation		(Horizontal | Vertical )  'Horizontal'
            lowerbound		CDATA  #REQUIRED
            upperbound		CDATA  #REQUIRED
            step			CDATA  #IMPLIED
            startval		CDATA  #IMPLIED
            steplabel		(Yes | No )  'No'
            maxnumber		CDATA  #IMPLIED
            minnumber		CDATA  #IMPLIED >"""
    XMLNAME = 'render_slider'
    XMLATTR_orientation = (
        'orientation',
        core.Orientation.DecodeTitleValue,
        core.Orientation.EncodeValue)
    XMLATTR_lowerbound = ('lowerBound', xsi.DecodeInteger, xsi.EncodeInteger)
    XMLATTR_upperbound = ('upperBound', xsi.DecodeInteger, xsi.EncodeInteger)
    XMLATTR_step = ('step', xsi.DecodeInteger, xsi.EncodeInteger)
    XMLATTR_startval = ('startVal', xsi.DecodeInteger, xsi.EncodeInteger)
    XMLATTR_steplabel = ('stepLabel', core.ParseYesNo, core.FormatYesNo)
    XMLATTR_maxnumber = ('maxNumber', xsi.DecodeInteger, xsi.EncodeInteger)
    XMLATTR_minnumber = ('minNumber', xsi.DecodeInteger, xsi.EncodeInteger)
    XMLCONTENT = xml.ElementContent

    def __init__(self, parent):
        RenderThing.__init__(self, parent)
        self.orientation = core.Orientation.Horizontal
        self.lowerBound = None
        self.upperBound = None
        self.step = None
        self.startVal = None
        self.stepLabel = False
        self.minNumber = None
        self.maxNumber = None

    def IsInline(self):
        """This always results in a block-like interaction."""
        return False

    def MigrateV2Interaction(self, parent, childType, prompt, log):
        """Migrates this content to v2 adding it to the parent content node."""
        interaction = None
        labels = list(self.FindChildrenDepthFirst(ResponseLabel, False))
        if self.maxNumber is None:
            maxChoices = len(labels)
        else:
            maxChoices = self.maxNumber
        if isinstance(self.parent, ResponseLId):
            if self.parent.rCardinality == core.RCardinality.Single:
                log.append(
                    "Warning: choice-slider replaced with choiceInteraction.slider")
                interaction = parent.ChildElement(
                    qtiv2.interactions.ChoiceInteraction)
                interaction.styleClass = 'slider'
                interaction.minChoices = 1
                interaction.maxChoices = 1
            elif self.parent.rCardinality == core.RCardinality.Ordered:
                log.append(
                    "Error: ordered-slider replaced with orderInteraction.slider")
                raise QTIUnimplementedError("OrderInteraction")
            else:
                log.append(
                    "Error: multiple-slider replaced with choiceInteraction.slider")
                interaction = parent.ChildElement(
                    qtiv2.interactions.ChoiceInteraction)
                interaction.styleClass = 'slider'
                if self.minNumber is not None:
                    interaction.minChoices = self.minNumber
                else:
                    interaction.minChoices = maxChoices
                interaction.maxChoices = maxChoices
            interaction.shuffle = False
            for child in labels:
                child.MigrateV2SimpleChoice(interaction, log)
        elif isinstance(self.parent, ResponseNum):
            if self.parent.rCardinality == core.RCardinality.Single:
                interaction = parent.ChildElement(
                    qtiv2.interactions.SliderInteraction)
                interaction.lowerBound = float(self.lowerBound)
                interaction.upperBound = float(self.upperBound)
                if self.step is not None:
                    interaction.step = self.step
                if self.orientation is not None:
                    interaction.orientation = core.MigrateV2Orientation(
                        self.orientation)
                # startValues are handled below after the variable is declared
            else:
                raise QTIUnimplementedError(
                    "Multiple/Ordered SliderInteraction")
        else:
            raise QTIUnimplementedError(
                "%s x render_slider" % self.parent.__class__.__name__)
        if prompt:
            interactionPrompt = interaction.ChildElement(
                qtiv2.interactions.Prompt)
            for child in prompt:
                child.MigrateV2Content(
                    interactionPrompt, html.InlineMixin, log)
        return [interaction]

    def MigrateV2InteractionDefault(self, declaration, interaction):
        # Most interactions do not need default values.
        if isinstance(interaction, qtiv2.interactions.SliderInteraction) and self.startVal is not None:
            value = declaration.ChildElement(
                qtiv2.variables.DefaultValue).ChildElement(
                qtiv2.variables.ValueElement)
            if declaration.baseType == qtiv2.variables.BaseType.integer:
                value.SetValue(xsi.EncodeInteger(self.startVal))
            elif declaration.baseType == qtiv2.variables.BaseType.float:
                value.SetValue(xsi.EncodeFloat(self.startVal))
            else:
                # slider bound to something else?
                raise QTIError(
                    "Unexpected slider type for default: %s" %
                    qtiv2.variables.BaseType.EncodeValue(
                        declaration.baseType))


class RenderExtension(Render):

    """This element supports proprietary alternatives to the current range of
    'render' elements::

    <!ELEMENT render_extension ANY>"""
    XMLNAME = "render_extension"
    XMLCONTENT = xml.XMLMixedContent


class ResponseLabel(core.QTIElement, common.ContentMixin):

    """The <response_label> is used to define the possible response choices that
    are presented to the user. This information includes the material to be
    shown to the user and the logical label that is associated with that
    response. The label is used in the response processing. Flow and non-flow
    approaches are supported::

    <!ELEMENT response_label (#PCDATA | qticomment | material | material_ref | flow_mat)*>
    <!ATTLIST response_label  rshuffle     (Yes | No )  'Yes'
    rarea			(Ellipse | Rectangle | Bounded )  'Ellipse'
    rrange			(Exact | Range )  'Exact'
    labelrefid		CDATA  #IMPLIED
    ident CDATA		#REQUIRED
    match_group		CDATA  #IMPLIED
    match_max		CDATA  #IMPLIED >"""
    XMLNAME = 'response_label'
    XMLATTR_ident = 'ident'
    XMLATTR_rshuffle = ('rShuffle', core.ParseYesNo, core.FormatYesNo)
    XMLATTR_rarea = (
        'rArea', core.Area.DecodeTitleValue, core.Area.EncodeValue)
    XMLCONTENT = xml.XMLMixedContent

    def __init__(self, parent):
        core.QTIElement.__init__(self, parent)
        common.ContentMixin.__init__(self)
        self.ident = ''
        self.rShuffle = True
        self.rArea = None

    def ContentMixin(self, childClass):
        """Although we inherit from the ContentMixin class we don't allow it to
        capture content-children because this element has mixed content - though
        in practice it really should have either data or element content."""
        return None

    def IsInline(self):
        """Whether or not a response_label is inline depends on the context...

        render_choice: a choice is a block
        render_hotspot: at most a label on the image so treated as inline
        render_fib: always inline
        """
        render = self.FindParent(RenderThing)
        if isinstance(render, RenderChoice):
            return False
        elif isinstance(render, RenderHotspot):
            return True
        elif isinstance(render, RenderFIB):
            return render.InlineFIBLabel()
        else:
            return self.InlineChildren()

    def InlineChildren(self):
        for child in core.QTIElement.GetChildren(self):
            if type(child) in StringTypes:
                continue
            elif issubclass(child.__class__, common.ContentMixin):
                if child.IsInline():
                    continue
                return False
            else:
                # QTIComment most likely
                continue
        return True

    def MigrateV2Content(self, parent, childType, log):
        """Migrates this content to v2 adding it to the parent content node."""
        render = self.FindParent(RenderThing)
        if isinstance(render, RenderFIB):
            render.MigrateV2FIBLabel(self, parent, childType, log)

    def MigrateV2SimpleChoice(self, interaction, log):
        """Migrate this label into a v2 simpleChoice in interaction."""
        choice = interaction.ChildElement(qtiv2.interactions.SimpleChoice)
        choice.identifier = qtiv2.core.ValidateIdentifier(self.ident)
        if isinstance(interaction, qtiv2.interactions.ChoiceInteraction) and interaction.shuffle:
            choice.fixed = not self.rShuffle
        data = []
        gotElements = False
        for child in core.QTIElement.GetChildren(self):
            if type(child) in StringTypes:
                if len(child.strip()):
                    data.append(child)
            elif isinstance(child, common.QTIComment):
                continue
            else:
                gotElements = True
        if data and gotElements:
            log.append(
                'Warning: ignoring PCDATA in <response_label>, "%s"' %
                string.join(
                    data,
                    ' '))
        elif data:
            for d in data:
                choice.AddData(d)
        else:
            content = []
            for child in core.QTIElement.GetChildren(self):
                if isinstance(child, common.ContentMixin):
                    content.append(child)
            common.ContentMixin.MigrateV2Content(
                self, choice, html.FlowMixin, log, content)

    def MigrateV2HotspotChoice(self, interaction, log, xOffset=0, yOffset=0):
        """Migrate this label into a v2 hotspotChoice in interaction."""
        if isinstance(interaction, qtiv2.interactions.SelectPointInteraction):
            log.append(
                "Warning: ignoring response_label in selectPointInteraction (%s)" %
                self.ident)
            return
        choice = interaction.ChildElement(qtiv2.interactions.HotspotChoice)
        choice.identifier = qtiv2.core.ValidateIdentifier(self.ident)
        # Hard to believe I know, but we sift the content of the response_label
        # into string data (which is parsed for coordinates) and elements which
        # have their text extracted for the hotspot label.
        lang, labelData, valueData = self.ParseValue()
        choice.shape, choice.coords = core.MigrateV2AreaCoords(
            self.rArea, valueData, log)
        if xOffset or yOffset:
            qtiv2.core.OffsetShape(
                choice.shape, choice.coords, xOffset, yOffset)
        if lang is not None:
            choice.SetLang(lang)
        if labelData:
            choice.hotspotLabel = labelData

    def HotspotInImage(self, matImage):
        """Tests this hotspot to see if it overlaps with matImage.

        The coordinates in the response label are interpreted relative to
        a notional 'stage' on which the presentation takes place.  If the
        image does not have X0,Y0 coordinates then it is ignored and
        we return 0.
        """
        if matImage.x0 is None or matImage.y0 is None:
            return False
        if matImage.width is None or matImage.height is None:
            return False
        lang, label, value = self.ParseValue()
        shape, coords = core.MigrateV2AreaCoords(self.rArea, value, [])
        bounds = qtiv2.core.CalculateShapeBounds(shape, coords)
        if bounds[0] > matImage.x0 + matImage.width or bounds[2] < matImage.x0:
            return False
        if bounds[1] > matImage.y0 + matImage.height or bounds[3] < matImage.y0:
            return False
        return True

    def ParseValue(self):
        """Returns lang,label,coords parsed from the value."""
        valueData = []
        labelData = []
        lang = None
        for child in self.GetChildren():
            if type(child) in StringTypes:
                valueData.append(child)
            else:
                childLang, text = child.ExtractText()
                if lang is None and childLang is not None:
                    lang = childLang
                labelData.append(text)
        valueData = string.join(valueData, ' ')
        labelData = string.join(labelData, ' ')
        return lang, labelData, valueData


class FlowLabel(
        common.QTICommentContainer,
        common.ContentMixin,
        common.FlowMixin):

    """The <flow_label> element is the blocking/paragraph equivalent to the
    <response_label> element::

    <!ELEMENT flow_label	(qticomment? , (flow_label | response_label)+)>
    <!ATTLIST flow_label	class CDATA  'Block' >"""
    XMLNAME = 'flow_label'
    XMLCONTENT = xml.ElementContent

    def __init__(self, parent):
        common.QTICommentContainer.__init__(self, parent)
        common.ContentMixin.__init__(self)

    def ContentMixin(self, childClass):
        if childClass is FlowLabel or issubclass(childClass, ResponseLabel):
            return common.ContentMixin.ContentMixin(self, childClass)
        else:
            raise TypeError

    def GetChildren(self):
        return itertools.chain(
            common.QTICommentContainer.GetChildren(self),
            self.contentChildren)

    def GetLabelContent(self):
        children = []
        for child in self.contentChildren:
            if isinstance(child, FlowLabel):
                children = children + child.GetLabelContent()
            else:
                children.append(child)
        return children


class ResponseNA(core.QTIElement):

    """This element supports proprietary alternatives for defining the response
    to be created when a participant makes no response to an Item::

    <!ELEMENT response_na ANY>"""
    XMLNAME = 'response_na'
    XMLCONTENT = xml.XMLMixedContent


class ResProcessing(common.QTICommentContainer):

    """This is the element within which all of the instructions for the response
    processing are contained. This includes the scoring variables to contain the
    associated scores and the set of response condition tests that are to be
    applied to the received user response::

    <!ELEMENT resprocessing (qticomment? , outcomes , (respcondition | itemproc_extension)+)>
    <!ATTLIST resprocessing  scoremodel CDATA  #IMPLIED >"""
    XMLNAME = 'resprocessing'
    XMLATTR_scoremodel = 'scoreModel'
    XMLCONTENT = xml.ElementContent

    def __init__(self, parent):
        common.QTICommentContainer.__init__(self, parent)
        self.scoreModel = None
        self.Outcomes = Outcomes(self)
        self.ConditionMixin = []

    def GetChildren(self):
        for child in QTICommentContainer.GetChildren(self):
            yield child
        yield self.Outcomes
        for child in self.ConditionMixin:
            yield child

    def MigrateV2(self, v2Item, log):
        """Migrates v1 resprocessing to v2 ResponseProcessing."""
        rp = v2Item.ChildElement(qtiv2.processing.ResponseProcessing)
        for outcomeFixup in sorted(self._interactionFixup.keys()):
            setValue = rp.ChildElement(qtiv2.processing.SetOutcomeValue)
            setValue.identifier = outcomeFixup
            multi = setValue.ChildElement(qtiv2.expressions.Multiple)
            for rID in self._interactionFixup[outcomeFixup]:
                var = multi.ChildElement(qtiv2.expressions.Variable)
                var.identifier = rID
        self.Outcomes.MigrateV2(v2Item, log)
        cMode = True
        ruleContainer = rp
        for condition in self.ConditionMixin:
            cMode, ruleContainer = condition.MigrateV2Rule(
                cMode, ruleContainer, log)


class Outcomes(common.QTICommentContainer):

    """The <outcomes> element contains all of the variable declarations that are
    to be made available to the scoring algorithm. Each variable is declared
    using the <decvar> element apart from the default variable called 'SCORE'
    that is an integer and has a default value of zero (0)::

    <!ELEMENT outcomes (qticomment? , (decvar , interpretvar*)+)>

    The implementation of this element takes a liberty with the content model
    because, despite the formulation above, the link between variables and
    their interpretation is not related to the order of the elements within
    the outcomes element.  (An interpretation without a variable reference
    defaults to an interpretation of the default 'SCORE' outcome.)

    When we output this element we do the decvars first, followed by
    the interpretVars."""
    XMLNAME = 'outcomes'
    XMLCONTENT = xml.ElementContent

    def __init__(self, parent):
        common.QTICommentContainer.__init__(self, parent)
        self.DecVar = []
        self.InterpretVar = []

    def GetChildren(self):
        return itertools.chain(
            QTICommentContainer.GetChildren(self),
            self.DecVar,
            self.InterpretVar)

    def MigrateV2(self, v2Item, log):
        for d in self.DecVar:
            d.MigrateV2(v2Item, log)
        for i in self.InterpretVar:
            i.MigrateV2(v2Item, log)


class ConditionMixin:

    """Mixin class to identify condition elements::

    (respcondition | itemproc_extension)"""
    pass


class RespCondition(common.QTICommentContainer, ConditionMixin):

    """This element contains the actual test to be applied to the user responses
    to determine their correctness or otherwise. Each <respcondition> contains
    an actual test, the assignment of a value to the associate scoring variables
    and the identification of the feedback to be associated with the test::

    <!ELEMENT respcondition (qticomment? , conditionvar , setvar* , displayfeedback* , respcond_extension?)>
    <!ATTLIST respcondition
            continue  (Yes | No )  'No'
            title CDATA  #IMPLIED >"""
    XMLNAME = 'respcondition'
    XMLATTR_continue = ('continueFlag', core.ParseYesNo, core.FormatYesNo)
    XMLATTR_title = 'title'
    XMLCONTENT = xml.ElementContent

    def __init__(self, parent):
        common.QTICommentContainer.__init__(self, parent)
        self.continueFlag = False
        self.title = None
        self.ConditionVar = common.ConditionVar(self)
        self.SetVar = []
        self.DisplayFeedback = []
        self.RespCondExtension = None

    def GetChildren(self):
        for child in QTICommentContainer.GetChildren(self):
            yield child
        yield self.ConditionVar
        for child in itertools.chain(
                self.SetVar,
                self.DisplayFeedback):
            yield child
        if self.RespCondExtension:
            yield self.RespCondExtension

    def MigrateV2Rule(self, cMode, ruleContainer, log):
        """Converts a response condition into v2 response processing rules.

        This method contains some tricky logic to help implement the confusing
        'continue' attribute of response conditions.  The continue attribute
        is interpreted in the following way:

        True: regardless of whether or not the condition matches, carry on to
        evaluate the next expression.

        False: only evaluate the next expression if the condition fails.

        The incoming cMode tells us if the previous condition set continue mode
        (the default is False on the attribute but the algorithm starts with
        continue mode True as the first rule is always evaluated).

        The way the rules are implemented is best illustrated by example, where
        X(True) represents condition X with continue='Yes' etc::

                R1(True),R2(True|False) becomes...

                if R1.test:
                        R1.rules
                if R2.test:
                        R2.rules

                R1(False),R2(True) becomes...

                if R1.test:
                        R1.rules
                else:
                        if R2.test:
                                R2.rules

                R1(False),R2(False) becomes...

                if R1.test:
                        R1.rules
                elif R2.test:
                        R2.rules"""
        if self.continueFlag:
            if not cMode:
                ruleContainer = ruleContainer.ChildElement(
                    qtiv2.processing.ResponseElse)
            rc = ruleContainer.ChildElement(qtiv2.processing.ResponseCondition)
            rcIf = rc.ChildElement(qtiv2.processing.ResponseIf)
        else:
            if cMode:
                rc = ruleContainer.ChildElement(
                    qtiv2.processing.ResponseCondition)
                ruleContainer = rc
                rcIf = rc.ChildElement(qtiv2.processing.ResponseIf)
            else:
                rcIf = ruleContainer.ChildElement(
                    qtiv2.processing.ResponseElseIf)
        self.ConditionVar.MigrateV2Expression(rcIf, log)
        for rule in self.SetVar:
            rule.MigrateV2Rule(rcIf, log)
        for rule in self.DisplayFeedback:
            rule.MigrateV2Rule(rcIf, log)
        return self.continueFlag, ruleContainer


class RespCondExtension(core.QTIElement):

    """This element supports proprietary alternatives to the <respcondition> element::

    <!ELEMENT respcond_extension ANY>"""
    XMLNAME = "respcond_extension"
    XMLCONTENT = xml.XMLMixedContent


class ItemProcExtension(common.ContentMixin, core.QTIElement, ConditionMixin):

    """This element supports proprietary alternatives to the standard Item
    response processing algorithms. element::

    <!ELEMENT itemproc_extension ANY>"""
    XMLNAME = "itemproc_extension"
    XMLCONTENT = xml.XMLMixedContent

    def __init__(self, parent):
        core.QTIElement.__init__(self, parent)
        common.ContentMixin.__init__(self)

    def MigrateV2Rule(self, cMode, ruleContainer, log):
        """Converts an itemProcExtension into v2 response processing rules.

        We only support one type of extension at the moment, the
        humanrater element used as an illustration in the specification
        examples."""
        for child in self.GetChildren():
            if type(child) in StringTypes:
                # ignore data
                continue
            elif child.xmlname == 'humanraterdata':
                # humanraterdata extension, migrate content with appropriate
                # view
                v2Item = ruleContainer.FindParent(qtiv2.items.AssessmentItem)
                rubric = v2Item.ChildElement(
                    qtiv2.content.ItemBody).ChildElement(qtiv2.RubricBlock)
                rubric.view = qtiv2.core.View.scorer
                material = list(
                    child.FindChildrenDepthFirst(common.Material, False))
                self.MigrateV2Content(rubric, html.BlockMixin, log, material)
        return cMode, ruleContainer


class ItemFeedback(core.QTIElement, common.ContentMixin):

    """The container for the feedback that is to be presented as a result of the
    user's responses. The feedback can include hints and solutions and both of
    these can be revealed in a variety of different ways::

    <!ELEMENT itemfeedback ((flow_mat | material) | solution | hint)+>
    <!ATTLIST itemfeedback
            view	(All | Administrator | AdminAuthority | Assessor | Author |
                            Candidate | InvigilatorProctor | Psychometrician | Scorer |
                            Tutor ) 'All'
            ident CDATA  #REQUIRED
            title CDATA  #IMPLIED >"""
    XMLNAME = 'itemfeedback'
    XMLATTR_view = ('view', core.View.DecodeLowerValue, core.View.EncodeValue)
    XMLATTR_title = 'title'
    XMLATTR_ident = 'ident'

    XMLCONTENT = xml.ElementContent

    def __init__(self, parent):
        core.QTIElement.__init__(self, parent)
        self.view = core.View.DEFAULT
        common.ContentMixin.__init__(self)
        self.title = None
        self.ident = None

    def ContentMixin(self, childClass):
        if childClass in (common.FlowMat, common.Material, Solution, Hint):
            return common.ContentMixin.ContentMixin(self, childClass)
        else:
            raise TypeError

    def GetChildren(self):
        return itertools.chain(
            QTIElement.GetChildren(self),
            self.contentChildren)

    def MigrateV2(self, v2Item, log):
        feedback = v2Item.ChildElement(qtiv2.QTIModalFeedback)
        if not self.view in (core.View.All, core.View.Candidate):
            log.append("Warning: discarding view on feedback (%s)" %
                       core.View.EncodeValue(self.view))
        identifier = qtiv2.core.ValidateIdentifier(self.ident, 'FEEDBACK_')
        feedback.outcomeIdentifier = 'FEEDBACK'
        feedback.showHide = qtiv2.core.ShowHide.show
        feedback.identifier = identifier
        feedback.title = self.title
        common.ContentMixin.MigrateV2Content(
            self, feedback, html.FlowMixin, log)


class Solution(common.ContentMixin, common.QTICommentContainer):

    """The <solution> element contains the solution(s) that are to be revealed
    to the participant. When these solutions are revealed is outside the scope
    of the specification::

    <!ELEMENT solution (qticomment? , solutionmaterial+)>
    <!ATTLIST solution  feedbackstyle  (Complete | Incremental | Multilevel | Proprietary )  'Complete' >"""
    XMLNAME = 'solution'
    XMLATTR_feedbackstyle = (
        'feedbackStyle',
        core.FeedbackStyle.DecodeTitleValue,
        core.FeedbackStyle.EncodeValue)
    XMLCONTENT = xml.ElementContent

    def __init__(self, parent):
        common.QTICommentContainer.__init__(self, parent)
        common.ContentMixin.__init__(self)
        self.feedbackStyle = core.FeedbackStyle.DEFAULT

    def ContentMixin(self, childClass):
        if childClass is SolutionMaterial:
            return common.ContentMixin.ContentMixin(self, childClass)
        else:
            raise TypeError

    def GetChildren(self):
        return itertools.chain(
            common.QTICommentContainer.GetChildren(),
            common.ContentMixin.GetContentChildren())


class FeedbackMaterial(common.ContentMixin, core.QTIElement):

    """Abstract class for solutionmaterial and hintmaterial::

    <!ELEMENT * (material+ | flow_mat+)>"""
    XMLCONTENT = xml.ElementContent

    def __init__(self, parent):
        core.QTIElement.__init__(self, parent)
        common.ContentMixin.__init__(self)

    def ContentMixin(self, childClass):
        if childClass in (common.Material, common.FlowMat):
            return common.ContentMixin.ContentMixin(self, childClass)
        else:
            raise TypeError

    def GetChildren(self):
        return common.ContentMixin.GetContentChildren()


class SolutionMaterial(FeedbackMaterial):

    """This is the container for the materials to be presented to the
    participant as the recommended solution. The solution can be revealed in a
    variety of ways but this information is not contained within the material
    being displayed::

    <!ELEMENT solutionmaterial (material+ | flow_mat+)>"""
    XMLNAME = 'solutionmaterial'


class Hint(common.ContentMixin, common.QTICommentContainer):

    """The <hint> element contains the hint(s) that are to be revealed to the
    participant. When these hints are revealed is outside the scope of the
    specification::

    <!ELEMENT hint (qticomment? , hintmaterial+)>
    <!ATTLIST hint  feedbackstyle  (Complete | Incremental | Multilevel | Proprietary )  'Complete' >"""
    XMLNAME = 'hint'
    XMLATTR_feedbackstyle = (
        'feedbackStyle',
        core.FeedbackStyle.DecodeTitleValue,
        core.FeedbackStyle.EncodeValue)
    XMLCONTENT = xml.ElementContent

    def __init__(self, parent):
        common.QTICommentContainer.__init__(self, parent)
        common.ContentMixin.__init__(self)
        self.feedbackStyle = FeedbackStyle.DEFAULT

    def ContentMixin(self, childClass):
        if childClass is HintMaterial:
            return common.ContentMixin.ContentMixin(self, childClass)
        else:
            raise TypeError

    def GetChildren(self):
        return itertools.chain(
            common.QTICommentContainer.GetChildren(),
            common.ContentMixin.GetContentChildren())


class HintMaterial(FeedbackMaterial):

    """This is the container for the materials to be presented to the
    participant as a hint. The hint can be revealed in a variety of ways but
    this information is not contained within the material being displayed::

    <!ELEMENT hintmaterial (material+ | flow_mat+)>"""
    XMLNAME = 'hintmaterial'
