# -*- coding: UTF-8 -*-


# Expression
# By: Frederic Peters <fpeters@entrouvert.com>
#     Emmanuel Raviart <eraviart@entrouvert.com>
#
# Copyright (C) 2004 Entr'ouvert, Frederic Peters & Emmanuel Raviart
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


"""XForms Module"""

import libxml2

import expression.core.elements as elements
import expression.core.environs as environs
import expression.core.html as html
import expression.core.locations as locations
import expression.core.logs as logs
import expression.core.namespaces as namespaces
import expression.core.stations as stations
import expression.core.widgets as widgets
import expression.core.xmlschemas as xmlschemas


class Element(elements.Element):
    pass


class WidgetElement(widgets.WidgetMixin, Element):
    def generateXmlContext(self, context, layout):
        return False

    def generateHtmlContextControlAlert(self, context, layout):
        modelContext = self.getModelContext(context)
        alert = self.getAlert(context, modelContext)
        if alert is None:
            return False
        return alert.generateHtmlContextMessage(context, layout)

    def generateHtmlContextControlHelp(self, context, layout):
        help = self.help
        if help is None:
            return False
        return help.generateHtmlContextMessage(context, layout)

    def generateHtmlContextControlHint(self, context, layout):
        hint = self.hint
        if hint is None:
            return False
        return hint.generateHtmlContextMessage(context, layout)

    def generateHtmlContextControlLabel(self, context, layout):
        label = self.label
        if label is None:
            return False
        return label.generateHtmlContextMessage(context, layout)

    def getAlert(self, context, modelContext):
        alertNodes = self.evaluateXpath("xforms:alert")
        if not alertNodes:
            return None
        alertNode = alertNodes[0]
        return self.newElement(alertNode)

    def getFieldName(self, context, modelContext):
        return self.getParent().getFieldName(context, modelContext)

    def getHelp(self):
        helpNodes = self.evaluateXpath("xforms:help")
        if not helpNodes:
            return None
        helpNode = helpNodes[0]
        return self.newElement(helpNode)

    def getHint(self):
        hintNodes = self.evaluateXpath("xforms:hint")
        if not hintNodes:
            return None
        hintNode = hintNodes[0]
        return self.newElement(hintNode)

    def getComputedId(self, context, modelContext):
        return self.getParent().getComputedId(context, modelContext)

    def getInstanceData(self, context, modelContext):
        instanceElement = modelContext.getInstanceElement()
        valueNodes = instanceElement.evaluateXpath(self.getInstanceDataXpath(
            context, modelContext))
        if not valueNodes:
            return None
        return instanceElement.newElement(valueNodes[0])
        
    def getInstanceDataNode(self, context, modelContext):
        valueNodes = self.getInstanceDataNodes(context, modelContext)
        if not valueNodes:
            return None
        return valueNodes[0]

    def getInstanceDataNodes(self, context, modelContext):
        instance = modelContext.getInstance()
        valueNodes = instance.evaluateXpath(self.getInstanceDataXpath(context, modelContext))
        return valueNodes

    def getInstanceDataType(self, context, modelContext):
        # FIXME: What to use for getTypeAtXpath first argument and
        # TypeContext second argument? Is it really modelContext?
        schemasContext = modelContext.schemas
        if schemasContext is None:
            return xmlschemas.TypeContext(
                xmlschemas.String(), modelContext, previous = modelContext)
        instanceElementType = modelContext.getInstanceElementType()
        if instanceElementType is None:
            return xmlschemas.TypeContext(
                xmlschemas.String(), modelContext, previous = modelContext)
        instanceDataType = instanceElementType.getTypeAtXpath(
            modelContext, self.getInstanceDataXpath(context, modelContext))
        if instanceDataType is None:
            return xmlschemas.TypeContext(
                xmlschemas.String(), modelContext, previous = modelContext)
        return instanceDataType

    def getInstanceDataXpath(self, context, modelContext):
        bindIdNodes = self.evaluateXpath("@bind")
        if bindIdNodes:
            bindId = bindIdNodes[0].content
            bind = modelContext.getBindById(bindId)
            if bind is not None:
                return "*/%s" % bind.nodeset
        modelIdNodes = self.evaluateXpath("@model")
        if modelIdNodes:
            xpath = ""
        elif self.getParent():
            xpath = self.getParent().getChildInstanceDataXpath(context, modelContext)
        else:
            xpath = ""
        nodesetNodes = self.evaluateXpath("@nodeset")
        if nodesetNodes:
            ref = nodesetNodes[0].content
            # Quick & dirty hack, so that XForms expressions like:
            #   <xforms:repeat nodeset="yep:andcriteria | yep:falsecriteria">
            #    <xform:input>...</xforms:input>
            #   </xforms:repeat>
            # give the following XPath:
            #   "(yep:andcriteria | yep:falsecriteria)[0]"
            #   "(yep:andcriteria | yep:falsecriteria)[1]"
            # instead of:
            #   "yep:andcriteria | yep:falsecriteria[0]"
            #   "yep:andcriteria | yep:falsecriteria[1]"
            if "|" in ref:
                ref = "(%s)" % ref
        else:
            refNodes = self.evaluateXpath("@ref")
            if refNodes:
                ref = refNodes[0].content
            else:
                ref = ""
        if xpath and ref:
            xpath = "%s/%s" % (xpath, ref)
        else:
            xpath = "%s%s" % (xpath, ref)
        return xpath

    def getLabel(self):
        labelNodes = self.evaluateXpath("xforms:label")
        if not labelNodes:
            return None
        labelNode = labelNodes[0]
        return self.newElement(labelNode)

    def getRealNode(self, context, modelContext):
        """The real node is either the node of this element or the one refered
        by the single-node binding attributes."""

        # Works only for Single-node binding attributes. Don't use getRealNode
        # for elements with node-set binding attributes.
        instanceDataXpath = None
        bindIdNodes = self.evaluateXpath("@bind")
        if bindIdNodes:
            bindId = bindIdNodes[0].content
            bind = modelContext.getBindById(bindId)
            if bind is not None:
                instanceDataXpath = "*/%s" % bind.nodeset
        refNodes = self.evaluateXpath("@ref")
        if refNodes:
            ref = refNodes[0].content
            modelIdNodes = self.evaluateXpath("@model")
            if modelIdNodes:
                instanceDataXpath = ""
            elif self.getParent():
                instanceDataXpath = self.getParent().getChildInstanceDataXpath(
                    context, modelContext)
            else:
                instanceDataXpath = ""
            if instanceDataXpath:
                instanceDataXpath = "%s/%s" % (instanceDataXpath, ref)
            else:
                instanceDataXpath = ref
        if instanceDataXpath is None:
            return self.node
        instance = modelContext.getInstance()
        instanceDataNodes = instance.evaluateXpath(instanceDataXpath)
        if not instanceDataNodes:
            return self.node
        return instanceDataNodes[0]

    def newContext(self, specimen, *attributes, **keywords):
        return WidgetElementContext(self, specimen, *attributes, **keywords)

    def submitContext(self, context):
        pass

    help = property(getHelp)
    hint = property(getHint)
    label = property(getLabel)


class WidgetElementContext(html.WidgetElementContext):
    pass


class Control(WidgetElement):
    """XForms Control."""

    def generateXmlContext(self, context, layout):
        # Generate a HTML form either when formCreationNeeded is True or when
        # it is not specified (when a XForms control is encountered it is by
        # default inside a form).
        if context.formCreationNeeded is None or context.formCreationNeeded:
            form = html.form(
                action = locations.cleanUpUrl(context.getHttpPostUri(), "sessionToken"),
                enctype = "multipart/form-data", method = "post")
            oldFormCreationNeeded = context.formCreationNeeded
            context.formCreationNeeded = False
            context.inForm = True
            filled = self.generateHtmlContextControl(context, form)
            context.formCreationNeeded = oldFormCreationNeeded
            context.inForm = False
            if filled:
                if not environs.getVar("canUseCookie") or environs.getVar("testCookieSupport"):
                    session = environs.getVar("session")
                    if session is not None and session.publishToken:
                        form.append(html.input(
                            name = "sessionToken", type = "hidden", value = session.token))
                layout.append(form)
            return filled
        else:
            return self.generateHtmlContextControl(context, layout)

    def generateHtmlContextControl(self, context, layout):
        inForm = context.inForm
        modelContext = self.getModelContext(context)
        relevant = self.isRelevant(context, modelContext)
        if not inForm and not relevant:
            return False
        filled = False
        if relevant:
            groupingElementClass = self.htmlControlGroupingElementClass
            groupingElement = groupingElementClass(class_ = self.getHtmlControlClass(context))
            if self.generateHtmlContextControlLabel(context, groupingElement):
                filled = True
            groupingElementFilled = False
            if inForm:
                if self.generateHtmlContextControlHelp(context, groupingElement):
                    groupingElementFilled = True
                if self.generateHtmlContextControlHint(context, groupingElement):
                    groupingElementFilled = True
                if self.isReadOnly(context, modelContext):
                    if self.generateHtmlContextControlCoreReadOnly(context, groupingElement):
                        groupingElementFilled = True
                elif self.generateHtmlContextControlCoreForm(context, groupingElement):
                    groupingElementFilled = True
                if self.generateHtmlContextControlAlert(context, groupingElement):
                    groupingElementFilled = True
            elif self.generateHtmlContextControlCoreStatic(context, groupingElement):
                groupingElementFilled = True
            if groupingElementFilled:
                layout.append(groupingElement)
                filled = True
        # do not show irrelevant field values in html source, and no need to submit them
        #elif inForm:
        #    if self.generateHtmlContextControlCoreHidden(context, layout):
        #        filled = True
        return filled

    def generateHtmlContextControlCoreForm(self, context, layout):
        modelContext = self.getModelContext(context)
        layout.append(html.input(
            id = self.getComputedId(context, modelContext),
            name = self.getFieldName(context, modelContext), type = "text",
            value = self.getFieldValue(context)))
        return  True

    def generateHtmlContextControlCoreHidden(self, context, layout):
        modelContext = self.getModelContext(context)
        layout.append(html.input(
            name = self.getFieldName(context, modelContext), type = "hidden",
            value = self.getFieldValue(context)))
        return True

    def generateHtmlContextControlCoreReadOnly(self, context, layout):
        filled = False
        # no need to submit read-only field values -> no need for a hidden input field
        #if self.generateHtmlContextControlCoreHidden(context, layout):
        #    filled = True
        if self.generateHtmlContextControlCoreStatic(context, layout):
            filled = True
        return filled

    def generateHtmlContextControlCoreStatic(self, context, layout):
        filled = False
        htmlValueNodes = self.getHtmlValueNodes(context)
        if htmlValueNodes:
            for htmlValueNode in htmlValueNodes:
                layout.append(htmlValueNode)
            filled = True
        return filled

    def generatePlainTextContext(self, context):
        return self.generatePlainTextContextControl(context)

    def generatePlainTextContextControl(self, context):
        modelContext = self.getModelContext(context)
        if not self.isRelevant(context, modelContext):
            return u""
        label = self.label
        if label is None:
            labelText = u""
        else:
            labelText = label.generatePlainTextContext(context)
        coreText = self.generatePlainTextContextControlCore(context)
        if labelText and coreText:
            return u"%s %s" % (labelText, coreText)
        else:
            return u"%s%s" % (labelText, coreText)

    def generatePlainTextContextControlCore(self, context):
        modelContext = self.getModelContext(context)
        valueNode = self.getInstanceDataNode(context, modelContext)
        if valueNode is None:
            return u""
        valueType = self.getInstanceDataType(context, modelContext)
        return valueType.convertValueNodeToPlainText(valueNode)

    def getAlert(self, context, modelContext):
        alerts = modelContext.alerts
        if alerts is not None:
            computedId = self.getComputedId(context, modelContext)
            if computedId in alerts:
                return AutomaticalAlert(alerts[computedId])
        return WidgetElement.getAlert(self, context, modelContext)

    def getAppearance(self):
        nodes = self.evaluateXpath("@appearance")
        if nodes:
            return nodes[0].content
        else:
            return None

    def getBind(self, context, modelContext):
        bindNodes = self.evaluateXpath("@bind")
        if bindNodes:
            bindId = bindNodes[0].content
            bind = modelContext.getBindById(bindId)
        else:
            refNodes = self.evaluateXpath("@nodeset | @ref")
            if not refNodes:
                return WidgetElement.getBind(self, context, modelContext)
            bind = modelContext.getBindByInstanceDataXpath(
                self.getInstanceDataXpath(context, modelContext))
        return bind

    def getComputedId(self, context, modelContext):
        # FIXME: The try is a quick & dirty hack for the first group label of a description.
        try:
            instanceDataXpath = self.getRootInstanceDataXpath(context, modelContext)
        except AttributeError:
            logs.debug("getComputedId(%s, %s, %s): getInstanceDataXpath failed" % (
                self, context, modelContext))
            return ""
        return instanceDataXpath.replace(":", "_")

    def getFieldName(self, context, modelContext):
        # FIXME: The try is a quick & dirty hack for the first group label of a description.
        try:
            instanceDataXpath = self.getRootInstanceDataXpath(context, modelContext)
        except AttributeError:
            logs.debug("getFieldName(%s, %s, %s): getInstanceDataXpath failed" % (
                self, context, modelContext))
            return ""
        return instanceDataXpath.replace(":", "_")

    def getFieldValue(self, context):
        modelContext = self.getModelContext(context)
        valueNode = self.getInstanceDataNode(context, modelContext)
        if valueNode is None:
            fieldValue = ""
        else:
            valueType = self.getInstanceDataType(context, modelContext)
            fieldValue = valueType.convertValueNodeToHtmlAttributeValue(
                valueNode)
        return fieldValue

    def getHtmlControlClass(self, context):
        modelContext = self.getModelContext(context)
        computedId = self.getComputedId(context, modelContext)
        type = None
        valueType = self.getInstanceDataType(context, modelContext)
        if isinstance(valueType, xmlschemas.TypeContext):
            type = "xsd-%s" % valueType.prototype.__class__.__name__.lower()
        controlClass = "%s-%s" % (
            namespaces.getName(self.node.ns().content),
            self.node.name
        )
        specimenClass = "%s-%s" % (
            namespaces.getName(context.specimen.node.ns().content),
            context.specimen.node.name
        )
        ref = self.node.prop("ref")
        if ref:
            ref = ref.replace(":", "-").replace("/", "_")
        customClass = self.node.prop("class")
        required = None
        if self.isRequired(context, modelContext):
            required = "requiredField"
        classes = [
            controlClass,
            specimenClass,
            computedId,
            ref,
            type,
            customClass,
            required
        ]
        return " ".join([c for c in classes if c])

    def getHtmlControlGroupingElementClass(self):
        return html.div

    def getHtmlValueNodes(self, context):
        modelContext = self.getModelContext(context)
        valueNode = self.getInstanceDataNode(context, modelContext)
        if valueNode is None:
            return []
        valueType = self.getInstanceDataType(context, modelContext)
        if valueType is None:
            return []
        return valueType.convertValueNodeToHtmlNodes(valueNode)

    def getModelId(self):
        modelIdNodes = self.evaluateXpath("@model")
        if modelIdNodes:
            return modelIdNodes[0].content
        return WidgetElement.getModelId(self)

    def isReadOnly(self, context, modelContext):
        bind = self.getBind(context, modelContext)
        if bind is None:
            return False
        return bind.isReadOnly(
            context, modelContext, self.getInstanceDataXpath(context, modelContext))

    def isRelevant(self, context, modelContext):
        bind = self.getBind(context, modelContext)
        if bind is None:
            return True
        return bind.isRelevant(
            context, modelContext, self.getInstanceDataXpath(context, modelContext))

    def isRequired(self, context, modelContext):
        bind = self.getBind(context, modelContext)
        if bind is None:
            return False
        return bind.isRequired(
            context, modelContext, self.getInstanceDataXpath(context, modelContext))

    def setFieldValue(self, context, modelContext, fieldValue):
        valueNode = self.getInstanceDataNode(context, modelContext)
        # FIXME
        # assert valueNode is not None
        # FIXME: Handle None fieldValue properly.
        if valueNode is not None and fieldValue is not None:
            # Note: Don't use setContent, because for some nodes it tries to
            # convert entities references.
            # valueNode.setContent(fieldValue)
            if valueNode.children is not None:
                childNode = valueNode.children
                childNode.unlinkNode()
                childNode.freeNodeList()
            valueNode.addChild(valueNode.doc.newDocText(fieldValue))

##     def setValueNode(self, context, modelContext, valueNode):
##         nodes = environs.getVar("currentActionHandler").evaluateXpath(
##             self.getInstanceDataXpath(context, modelContext))
##         if nodes:
##             node = nodes[0]
##             node.replaceNode(valueNode)
##             node.freeNode()
##         else:
##             self.node.addChild(valueNode)

    def submitContext(self, context):
        modelContext = self.getModelContext(context)
        if self.isRelevant(context, modelContext) and not self.isReadOnly(context, modelContext):
            submission = environs.getVar("submission")
            fieldName = self.getFieldName(context, modelContext)
            value = submission.getField(fieldName)
            valueType = self.getInstanceDataType(context, modelContext)
            if valueType is None:
                logs.info("""Missing type for value "%s" in field "%s".""" % (value, fieldName))
            try:
                content = valueType.convertSubmissionValueToContent(value)
            except ValueError:
                computedId = self.getComputedId(context, modelContext)
                modelContext.setErrorLabel(computedId, "emptyValue", N_("Unrecognized Value: %s") % value)
            else:
                context.specimen.setContentAtXpath(
                    self.getInstanceDataXpath(context, modelContext), content)
                if not content and self.isRequired(context, modelContext):
                    computedId = self.getComputedId(context, modelContext)
                    modelContext.setErrorLabel(computedId, "emptyValue", N_("Missing Value"))

    htmlControlGroupingElementClass = property(
        getHtmlControlGroupingElementClass)
    modelId = property(getModelId)


class Message(WidgetElement):
    translatable = True
    def generateHtmlContextMessage(self, context, layout):
        span = html.span(class_ = self.getHtmlMessageClass())
        if not self.generateHtmlContextMessageChildren(context, span):
            return False
        layout.append(span)
        return True    

    def generateHtmlContextMessageChildren(self, context, layout):
        filled = False
        modelContext = self.getModelContext(context)
        realNode = self.getRealNode(context, modelContext)
        childNode = realNode.children
        while childNode is not None:
            if childNode.type in ("cdata", "text"):
                content = childNode.content
                layout.append(content)
                filled = True
                childNode = childNode.next
                continue
            if childNode.type != "element":
                childNode = childNode.next
                continue
            element = self.newElement(childNode)
            if element.generateXmlContext(context, layout):
                filled = True
            childNode = childNode.next
        return filled

    def getHtmlMessageClass(self):
        return self.node and self.node.prop("class") or self.htmlMessageClass


class Alert(Message):
    htmlMessageClass = "xforms-alert"


class AutomaticalAlert(Alert):
    message = None

    def __init__(self, message):
        # Dont' call inherited.
        if message is not None:
            self.message = message

    def generateHtmlContextMessageChildren(self, context, layout):
        # Dont' call inherited.
        filled = False
        if self.message:
            layout.append(_(self.message))
            filled = True
        return filled


class Bind(Element):
    def buildXformsModelContext(self, context):
        nodeset = self.nodeset
        if context.bindsByNodeset is None:
            context.bindsByNodeset = {}
        context.bindsByNodeset[nodeset] = self
        id = self.id
        if id is not None:
            if context.bindsById is None:
                context.bindsById = {}
            context.bindsById[id] = self

    def getNodeset(self):
        sourceNodes = self.evaluateXpath("@nodeset")
        if sourceNodes:
            return sourceNodes[0].content
        else:
            return None

    def isReadOnly(self, context, modelContext, instanceDataXpath):
        sourceNodes = self.evaluateXpath("@readonly")
        if sourceNodes:
            formula = sourceNodes[0].content
            instance = modelContext.getInstance()
            # Note: we can't use instanceData as base for evaluateXpath,
            # because it can be None. So we use instance, but we should change
            # the paths in formula so that they are relative to instance
            # instead of instanceData (dirty hack needed because of a stupid
            # XForms design decision). FIXME: TODO
            return bool(instance.evaluateXpath(formula))
        else:
            return False

    def isRelevant(self, context, modelContext, instanceDataXpath):
        sourceNodes = self.evaluateXpath("@relevant")
        if sourceNodes:
            formula = sourceNodes[0].content
            instance = modelContext.getInstance()
            # Note: we can't use instanceData as base for evaluateXpath,
            # because it can be None. So we use instance, but we should change
            # the paths in formula so that they are relative to instance
            # instead of instanceData (dirty hack needed because of a stupid
            # XForms design decision). FIXME: TODO
            return bool(instance.evaluateXpath(formula))
        else:
            return True

    def isRequired(self, context, modelContext, instanceDataXpath):
        sourceNodes = self.evaluateXpath("@required")
        if sourceNodes:
            formula = sourceNodes[0].content
            instance = modelContext.getInstance()
            # Note: we can't use instanceData as base for evaluateXpath,
            # because it can be None. So we use instance, but we should change
            # the paths in formula so that they are relative to instance
            # instead of instanceData (dirty hack needed because of a stupid
            # XForms design decision). FIXME: TODO
            return bool(instance.evaluateXpath(formula))
        else:
            return False

    def getTypeName(self):
        sourceNodes = self.evaluateXpath("@type")
        if sourceNodes:
            return sourceNodes[0].content
        else:
            return None

    nodeset = property(getNodeset)


class Choices(WidgetElement):
    def generateHtmlContextControlCheckBoxesForm(
            self, context, layout, checkedValues):
        filled = False
        li = html.li(class_ = "xforms-choices")
        liFilled = False
        if self.generateHtmlContextControlLabel(context, li):
            liFilled = True
        if self.generateHtmlContextControlHelp(context, li):
            liFilled = True
        if self.generateHtmlContextControlHint(context, li):
            liFilled = True

        ulFilled = False
        ul = html.ul()
        itemOrChoicesNodes = self.evaluateXpath("xforms:item | xforms:choices")
        for itemOrChoicesNode in itemOrChoicesNodes:
            element = self.newElement(itemOrChoicesNode)
            if element.generateHtmlContextControlCheckBoxesForm(
                    context, ul, checkedValues):
                ulFilled = True
        if ulFilled:
            li.append(ul)
            liFilled = True

        if self.generateHtmlContextControlAlert(context, li):
            liFilled = True
        if liFilled:
            layout.append(li)
            filled = True
        return filled

    def generateHtmlContextControlCheckBoxesStatic(
            self, context, layout, checkedValues):
        filled = False
        li = html.li(class_ = "xforms-choices")
        liFilled = False
        if self.generateHtmlContextControlLabel(context, li):
            liFilled = True
        if self.generateHtmlContextControlHelp(context, li):
            liFilled = True
        if self.generateHtmlContextControlHint(context, li):
            liFilled = True

        ulFilled = False
        ul = html.ul()
        itemOrChoicesNodes = self.evaluateXpath("xforms:item | xforms:choices")
        for itemOrChoicesNode in itemOrChoicesNodes:
            element = self.newElement(itemOrChoicesNode)
            if element.generateHtmlContextControlCheckBoxesStatic(
                    context, ul, checkedValues):
                ulFilled = True
        if ulFilled:
            li.append(ul)
            liFilled = True

        if self.generateHtmlContextControlAlert(context, li):
            liFilled = True
        if liFilled:
            layout.append(li)
            filled = True
        return filled

    def generateHtmlContextControlOptionsForm(self, context, layout, value):
        filled = False
        optgroup = html.optgroup(class_ = "xforms-choices")
        itemOrChoicesNodes = self.evaluateXpath("xforms:item | xforms:choices")
        for itemOrChoicesNode in itemOrChoicesNodes:
            element = self.newElement(itemOrChoicesNode)
            if element.generateHtmlContextControlOptionsForm(
                    context, optgroup, value):
                filled = True
        if filled:
            self.label.generateHtmlContextMessageChildren(context, optgroup)
            layout.append(optgroup)
        return filled

    def generateHtmlContextControlOptionsStatic(self, context, layout, value):
        filled = False
        itemOrChoicesNodes = self.evaluateXpath("xforms:item | xforms:choices")
        for itemOrChoicesNode in itemOrChoicesNodes:
            element = self.newElement(itemOrChoicesNode)
            if element.generateHtmlContextControlOptionsStatic(
                    context, layout, value):
                filled = True
        return filled

    def generateHtmlContextControlRadiosForm(self, context, layout, value):
        filled = False
        li = html.li(class_ = "xforms-choices")
        liFilled = False
        if self.generateHtmlContextControlLabel(context, li):
            liFilled = True
        if self.generateHtmlContextControlHelp(context, li):
            liFilled = True
        if self.generateHtmlContextControlHint(context, li):
            liFilled = True

        ulFilled = False
        ul = html.ul()
        itemOrChoicesNodes = self.evaluateXpath("xforms:item | xforms:choices")
        for itemOrChoicesNode in itemOrChoicesNodes:
            element = self.newElement(itemOrChoicesNode)
            if element.generateHtmlContextControlRadiosForm(
                    context, ul, value):
                ulFilled = True
        if ulFilled:
            li.append(ul)
            liFilled = True

        if self.generateHtmlContextControlAlert(context, li):
            liFilled = True
        if liFilled:
            layout.append(li)
            filled = True
        return filled

    def generateHtmlContextControlRadiosStatic(self, context, layout, value):
        filled = False
        itemOrChoicesNodes = self.evaluateXpath("xforms:item | xforms:choices")
        for itemOrChoicesNode in itemOrChoicesNodes:
            element = self.newElement(itemOrChoicesNode)
            if element.generateHtmlContextControlRadiosStatic(
                    context, layout, value):
                filled = True
        return filled


class Group(Control):

    def generateHtmlContextControlCore(self, context, layout):
        filled = False
        childNode = self.node.children
        while childNode is not None:
            if childNode.type in ("cdata", "text"):
                layout.append(childNode.content)
                filled = True
                childNode = childNode.next
                continue
            if childNode.type != "element":
                childNode = childNode.next
                continue
            element = self.newElement(childNode)
            if element.generateXmlContext(context, layout):
                filled = True
            childNode = childNode.next
        return filled

    def generateHtmlContextControlCoreForm(self, context, layout):
        return self.generateHtmlContextControlCore(context, layout)

    def generateHtmlContextControlCoreHidden(self, context, layout):
        return self.generateHtmlContextControlCore(context, layout)

    def generateHtmlContextControlCoreReadOnly(self, context, layout):
        return self.generateHtmlContextControlCore(context, layout)

    def generateHtmlContextControlCoreStatic(self, context, layout):
        return self.generateHtmlContextControlCore(context, layout)

    def submitContext(self, context):
        childNode = self.node.children
        while childNode is not None:
            if childNode.type != "element":
                childNode = childNode.next
                continue
            element = self.newElement(childNode)
            elementContext = element.newContext(context.specimen, previous = context)
            elementContext.submit()
            childNode = childNode.next


class Help(Message):
    htmlMessageClass = "xforms-help"


class Hint(Message):
    htmlMessageClass = "xforms-hint"


class Input(Control):

    def generateHtmlContextControlCoreForm(self, context, layout):
        modelContext = self.getModelContext(context)
        fieldName = self.getFieldName(context, modelContext)
        id = self.getComputedId(context, modelContext)
        valueType = self.getInstanceDataType(context, modelContext)
        if isinstance(valueType.prototype, xmlschemas.DateTime):
            layout.append(html.input(
                id = id, maxlength = "16", name = fieldName,
                size = "16", type = "text",
                value = self.getFieldValue(context)))
            layout.append(html.img(
                id= "%s_trigger" % id,
                src = context.constructUri("/javascript/jscalendar/img.png"),
                style = "cursor: pointer;",
                title = _("Date Selector"),
                onload = """Calendar.setup({
                    inputField: "%s",
                    ifFormat: "%%d/%%m/%%Y %%H:%%M",
                    button: "%s_trigger",
                    singleClick: true,
                    showsTime: true
                    })
                    """ % (id, id),
                onmouseout="this.style.background=''",
                onmouseover = "this.style.background='';"))
        elif isinstance(valueType.prototype, xmlschemas.Date):
            layout.append(html.input(
                id = id, maxlength = "10", name = fieldName,
                size = "10", type = "text",
                value = self.getFieldValue(context)))
            layout.append(html.img(
                id= "%s_trigger" % id,
                src = context.constructUri("/javascript/jscalendar/img.png"),
                style = "cursor: pointer;",
                title = _("Date Selector"),
                onload = """Calendar.setup({
                    inputField: "%s",
                    ifFormat: "%%d/%%m/%%Y",
                    button: "%s_trigger",
                    singleClick: true
                    })
                    """ % (id, id),
                onmouseout="this.style.background=''",
                onmouseover = "this.style.background='';"))
        elif isinstance(valueType.prototype, xmlschemas.Boolean):
            input = html.input(id = id, name = fieldName, type = "checkbox", value = "1")
            if self.getFieldValue(context) in ("1", "true"):
                input.node.setProp("checked", "true")
            layout.append(input)
        else:
            layout.append(html.input(
                id = id, name = fieldName, type = "text",
                value = self.getFieldValue(context)))
        return True


class Instance(Element):
    _rootElement = None

    def buildXformsModelContext(self, context):
        if context.defaultInstance is None:
            context.defaultInstance = self
        id = self.id
        if id is not None:
            if context.instancesById is None:
                context.instancesById = {}
            context.instancesById[id] = self

    def getRootElement(self):
        if self._rootElement is None:
            rootNode = self.getRootNode()
            if rootNode is None:
                return None
            # There doesn't exist any URI path to access a rootElement => no uriPathFragment.
            self._rootElement = elements.newElement(rootNode, previous = self, owner = self)
        rootElement = self._rootElement
        return rootElement

    def getRootNode(self):
        rootNodes = self.evaluateXpath("*")
        if not rootNodes:
            return None
        return rootNodes[0]


class DummyInstance(object):
    """This Instance class is used to encapsulate an Expression element, so that it behaves like
    an XForms instance."""

    _rootElement = None

    def __init__(self, rootElement):
        self._rootElement = rootElement

    def buildXformsModelContext(self, context):
        if context.defaultInstance is None:
            context.defaultInstance = self
        id = self.id
        if id is not None:
            if context.instancesById is None:
                context.instancesById = {}
            context.instancesById[id] = self

    def evaluateXpath(self, xpath, contextNode = None):
        return self._rootElement.evaluateXpath(xpath, contextNode)

    def getRootElement(self):
        return self._rootElement

    def serialize(self):
        return self._rootElement.serialize()


class Item(WidgetElement):
    def generateHtmlContextControlCheckBoxesForm(
            self, context, layout, checkedValues):
        filled = False
        li = html.li(class_ = "xforms-item")
        liFilled = False
        if self.generateHtmlContextControlHelp(context, li):
            liFilled = True
        if self.generateHtmlContextControlHint(context, li):
            liFilled = True
        if self.value.generateHtmlContextValueCheckBoxForm(
                context, li, checkedValues):
            liFilled = True
        if self.generateHtmlContextControlAlert(context, li):
            liFilled = True
        if self.generateHtmlContextControlLabel(context, li):
            liFilled = True
        if liFilled:
            layout.append(li)
            filled = True
        return filled

    def generateHtmlContextControlCheckBoxesStatic(self, context, layout, checkedValues):
        modelContext = self.getModelContext(context)
        value = self.value
        if value.getRealNode(context, modelContext).content not in checkedValues:
            return False
        filled = False
        li = html.li(class_ = "xforms-item")
        liFilled = False
        if self.generateHtmlContextControlHelp(context, li):
            liFilled = True
        if self.generateHtmlContextControlHint(context, li):
            liFilled = True
        if value.generateHtmlContextValueCheckBoxStatic(context, li, checkedValues):
            liFilled = True
        if self.generateHtmlContextControlAlert(context, li):
            liFilled = True
        if self.generateHtmlContextControlLabel(context, li):
            liFilled = True
        if liFilled:
            layout.append(li)
            filled = True
        return filled

    def generateHtmlContextControlOptionsForm(self, context, layout, value):
        filled = False
        if self.value.generateHtmlContextValueOptionForm(
                context, layout, value):
            filled = True
        return filled

    def generateHtmlContextControlOptionsStatic(self, context, layout, value):
        filled = False
        if self.value.generateHtmlContextValueOptionStaticSimple(
            context, layout, value
        ):
            filled = True
        return filled

    def generateHtmlContextControlRadiosForm(self, context, layout, value):
        filled = False
        li = html.li(class_ = "xforms-item")
        liFilled = False
        if self.generateHtmlContextControlHelp(context, li):
            liFilled = True
        if self.generateHtmlContextControlHint(context, li):
            liFilled = True
        if self.value.generateHtmlContextValueRadioForm(context, li, value):
            liFilled = True
        if self.generateHtmlContextControlAlert(context, li):
            liFilled = True
        if self.generateHtmlContextControlLabel(context, li):
            liFilled = True
        if liFilled:
            layout.append(li)
            filled = True
        return filled

    def generateHtmlContextControlRadiosStatic(self, context, layout, value):
        modelContext = self.getModelContext(context)
        value = self.value
        if value.getRealNode(context, modelContext).content != value:
            return False
        filled = False
        if self.generateHtmlContextControlHelp(context, layout):
            filled = True
        if self.generateHtmlContextControlHint(context, layout):
            filled = True
        if value.generateHtmlContextValueRadioStatic(context, layout, value):
            filled = True
        if self.generateHtmlContextControlAlert(context, layout):
            filled = True
        if self.generateHtmlContextControlLabel(context, layout):
            filled = True
        return filled

    def getComputedId(self, context, modelContext):
        value = self.value.getRealNode(context, modelContext).content
        return "%s_%s" % (self.getParent().getComputedId(context, modelContext), value)

    def getValue(self):
        valueNodes = self.evaluateXpath("xforms:value")
        if not valueNodes:
            return None
        valueNode = valueNodes[0]
        return self.newElement(valueNode)

    value = property(getValue)


class Label(Message):
    htmlMessageClass = "xforms-label"

    def generateHtmlContextMessage(self, context, layout):
        label = html.label(class_ = self.getHtmlMessageClass())
        if context.inForm:
            modelContext = self.getModelContext(context)
            id = self.getComputedId(context, modelContext)
            if id:
                label.node.setProp("for", id)
        if not self.generateHtmlContextMessageChildren(context, label):
            return False
        layout.append(label)
        return True    


class Model(WidgetElement):
    def buildXformsContext(self, context):
        modelContext = ModelContext(
            self, context, previous = context, uriPathFragment = "model")
        if context.xformsDefaultModel is None:
            context.xformsDefaultModel = modelContext
        id = self.id
        if id is not None:
            if context.xformsModelsById is None:
                context.xformsModelsById = {}
            context.xformsModelsById[id] = modelContext
        schemaLocations = self.schemaLocations
        for schemaLocation in schemaLocations:
            schemaAbsolutePath = self.convertRelativeLocationToAbsolutePath(schemaLocation)
            if schemaAbsolutePath is None:
                logs.debug('Missing schema file "%s".' % schemaLocation)
                continue
            schemaHolder = xmlschemas.getSchemaHolder(schemaAbsolutePath)
            if schemaHolder is None:
                logs.debug('Missing schema file "%s".' % schemaLocation)
                continue
            schema = schemaHolder.getRootElement()
            if environs.getVar("debug"):
                if schema.validateElement(context.specimen.node):
                    logs.debug("%s is valid against %s." % (context.specimen, schema))
                else:
                    logs.warning("%s is NOT valid against %s." % (context.specimen, schema))
            if modelContext.schemas is None:
                modelContext.schemas = xmlschemas.SchemasContext(
                    modelContext.specimen, previous = modelContext, uriPathFragment = "schemas")
            schemasContext = modelContext.schemas
            if schemasContext.schemas is None:
                schemasContext.schemas = []
            schemasContext.schemas.append(schema)
        childNode = self.node.children
        while childNode is not None:
            if childNode.type != "element":
                childNode = childNode.next
                continue
            element = self.newElement(childNode)
            element.buildXformsModelContext(modelContext)
            childNode = childNode.next

    def generateXmlContext(self, context, layout):
        self.buildXformsContext(context)
        return False

    def getInstanceNode(self, schemaNameWithNamespace):
        instanceNodes = self.evaluateXpath(
            "xforms:instance/%s" % schemaNameWithNamespace)
        if not instanceNodes:
            return None
        return instanceNodes[0]

    def getModelId(self):
        return self.id

    def getSchemaLocations(self):
        schemaLocationsNodes = self.evaluateXpath("@schema")
        if not schemaLocationsNodes:
            return []
        schemaLocations = schemaLocationsNodes[0].content
        return schemaLocations.split()

    def getSubmissionNode(self, id = None):
        if id is None:
            submissionNodes = self.evaluateXpath(
                "xforms:submission[@id = '%s']" % id.replace("'", "&apos;"))
        else:
            submissionNodes = self.evaluateXpath("xforms:submission")
        if not submissionNodes:
            return None
        return submissionNodes[0]

    modelId = property(getModelId)
    schemaLocations = property(getSchemaLocations)


class ModelContext(stations.AbstractContext):
    alerts = None
    bindsById = None
    bindsByNodeset = None
    defaultInstance = None
    embedder = None
    embedderContext = None
    errors = None
    instancesById = None
    schemas = None

    def getBindById(self, id):
        if self.bindsById is not None and id in self.bindsById:
            return self.bindsById[id]
        else:
            return None

    def getBindByInstanceDataXpath(self, instanceDataXpath):
        if self.bindsByNodeset is None \
                or not instanceDataXpath in self.bindsByNodeset:
            return None
        return self.bindsByNodeset[instanceDataXpath]

    def getInstance(self):
        id = None # FIXME
        if id is None:
            instance = self.defaultInstance
        else:
            instance = self.instancesById[id]
        return instance

    def getInstanceElement(self):
        instance = self.getInstance()
        if instance is None:
            return None
        return instance.getRootElement()

    def getInstanceElementType(self):
        instanceElement = self.getInstanceElement()
        instanceElementType = instanceElement.type
        if instanceElementType is not None:
            return instanceElementType
        if self.schemas is not None:
            return self.schemas.getValueType(instanceElement)
        return None

    def setErrorLabel(self, computedId, symbol, label, override = False):
        if self.errors is None:
            self.errors = {}
        if computedId not in self.errors or override:
            self.errors[computedId] = symbol
        if self.alerts is None:
            self.alerts = {}
        if computedId not in self.alerts or override:
            self.alerts[computedId] = label


class Output(Control):

    def generateHtmlContextControlCoreForm(self, context, layout):
        layout.append(html.span(self.getFieldValue(context)))
        return True

    def generateHtmlContextControlRequired(self, context, layout):
        return False

    def getHtmlControlGroupingElementClass(self):
        return html.span

    def isReadOnly(self, context, modelContext):
        return True

    def isRequired(self, context, modelContext):
        return False

    def submitContext(self, context):
        pass

    htmlControlGroupingElementClass = property(
        getHtmlControlGroupingElementClass)


class Range(Input):
    pass


class Repeat(Control):
    index = None

    def generateHtmlContextControlCoreForm(self, context, layout):
        modelContext = self.getModelContext(context)
        countName = "%s-count" % self.getFieldName(context, modelContext)
        count = 0
        valueNodes = self.getInstanceDataNodes(context, modelContext)
        count = max(0, count, len(valueNodes))
        
        layout.append(html.input(name = countName, type = "hidden", value = str(count)))

        ul = html.ul(class_ = "multi")
        layout.append(html.div(ul))

        filled = False
        for i in range(count):
            #~ if i >= len(valueNodes):
                #~ self.index = None
            #~ else:
                #~ self.index = i
            self.index = i
            li = html.li()
            ul.append(li)

            childNode = self.node.children
            while childNode is not None:
                if childNode.type in ("cdata", "text"):
                    li.append(childNode.content)
                    childNode = childNode.next
                    continue
                if childNode.type != "element":
                    childNode = childNode.next
                    continue
                element = self.newElement(childNode)
                filled |= element.generateXmlContext(context, li)
                childNode = childNode.next
        return filled

    def generateHtmlContextControlCoreHidden(self, context, layout):
        filled = False
        modelContext = self.getModelContext(context)
        countName = "%s-count" % self.getFieldName(context, modelContext)
        count = 0
        valueNodes = self.getInstanceDataNodes(context, modelContext)
        count = max(0, count, len(valueNodes))
        
        layout.append(html.input(name = countName, type = "hidden", value = str(count)))

        for i in range(len(valueNodes)):
            self.index = i
            childNode = self.node.children
            while childNode is not None:
                if childNode.type != "element":
                    childNode = childNode.next
                    continue
                element = self.newElement(childNode)
                if element.generateHtmlContextControlCoreHidden(context, layout):
                    filled = True
                childNode = childNode.next
        return filled

    def generateHtmlContextControlCoreStatic(self, context, layout):
        filled = False
        modelContext = self.getModelContext(context)
        valueNodes = self.getInstanceDataNodes(context, modelContext)
        if not valueNodes:
            return filled
        ul = html.ul(class_ = "multi")
        for i in range(len(valueNodes)):
            self.index = i
            li = html.li()
            ul.append(li)
            childNode = self.node.children
            while childNode is not None:
                if childNode.type in ("cdata", "text"):
                    li.append(childNode.content)
                    filled = True
                    childNode = childNode.next
                    continue
                if childNode.type != "element":
                    childNode = childNode.next
                    continue
                element = self.newElement(childNode)
                if element.generateXmlContext(context, li):
                    filled = True
                childNode = childNode.next
        if filled:
            layout.append(ul)
        return filled

    def getFieldValue(self, context):
        return None

    def getChildInstanceDataXpath(self, context, modelContext):
        return "%s[%d]" % (self.getInstanceDataXpath(context, modelContext), self.index + 1)

    def submitContext(self, context):
        modelContext = self.getModelContext(context)
        countName = "%s-count" % self.getFieldName(context, modelContext)
        count = 0
        submission = environs.getVar("submission")
        if submission.hasField(countName):
            try:
                count = int(submission.getField(countName))
            except (TypeError, ValueError):
                pass

        for i in range(count):
            self.index = i
            childNode = self.node.children
            while childNode is not None:
                if childNode.type != "element":
                    childNode = childNode.next
                    continue
                element = self.newElement(childNode)
                elementContext = element.newContext(context.specimen, previous = context)
                elementContext.submit()
                childNode = childNode.next


class Secret(Control):

    def generateHtmlContextControlCoreForm(self, context, layout):
        modelContext = self.getModelContext(context)
        layout.append(html.input(
            id = self.getComputedId(context, modelContext),
            name = self.getFieldName(context, modelContext), type = "password",
            value = self.getFieldValue(context)))
        return True

    def generateHtmlContextControlCoreStatic(self, context, layout):
        layout.append("*" * len(self.getFieldValue(context)))
        return True


class Select(Control):

    def generateHtmlContextControlCoreForm(self, context, layout):
        """ Renders as an HTML checkbox list appended to layout.
        Returns whether something was appended to layout.
        """
        selectedValues = self.getSelectedValues(context)
        filled = False
        ul = html.ul()
        for element in self.getItemOrChoiceElements(context):
            filled |= element.generateHtmlContextControlCheckBoxesForm(context, ul, selectedValues)
        if filled:
            layout.append(ul)
        return filled

    def generateHtmlContextControlCoreStatic(self, context, layout):
        """ Renders as an HTML read-only list appended to layout.
        Returns whether something was appended to layout.
        """
        selectedValues = self.getSelectedValues(context)
        filled = False
        ul = html.ul()
        for element in self.getItemOrChoiceElements(context):
            filled |= element.generateHtmlContextControlCheckBoxesStatic(context, ul, selectedValues)
        if filled:
            layout.append(ul)
        return filled

    def getAvailableValues(self, context):
        """ Returns a list with all available values.
        """
        return [node.content for node in self.evaluateXpath(".//xforms:value")]

    def getSelectedValues(self, context):
        """ Returns the list of selected value strings.
        """
        node = self.getInstanceDataNode(context, self.getModelContext(context))
        return node and node.content.split(" ") or []

    def getItemOrChoiceElements(self, context):
        """ Returns a list of elements that provide methods:
         - generateHtmlContextControlCheckBoxesForm
         - generateHtmlContextControlCheckBoxesStatic
        """
        return [self.newElement(node) for node in self.evaluateXpath("xforms:item | xforms:choices")]

    def submitContext(self, context):
        """ Updates the context specimen with submitted data for this control.
        Sets an error if no data was submitted.
        """
        submission = environs.getVar("submission")
        modelContext = self.getModelContext(context)
        fieldName = self.getFieldName(context, modelContext)
        splitedValue = []
        if submission.hasField(fieldName):
            submissionValue = submission.getField(fieldName)
            for itemValue in self.getAvailableValues(context):
                if isinstance(submissionValue, list) and itemValue in submissionValue \
                    or itemValue == submissionValue:
                    splitedValue.append(itemValue)
        context.specimen.setContentAtXpath(
            self.getInstanceDataXpath(context, modelContext),
            " ".join(splitedValue)
        )
        if not splitedValue and self.isRequired(context, modelContext):
            computedId = self.getComputedId(context, modelContext)
            modelContext.setErrorLabel(computedId, "emptyValue", N_("Missing Value"))


class Select1(Control):

    def generateHtmlContextControlCoreForm(self, context, layout):
        """ Renders and appends to layout either as an HTML radio button list, or an HTML select control.
        Returns whether something was appended to layout.
        """
        modelContext = self.getModelContext(context)
        value = self.getSelectedValue(context)
        filled = False
        appearance = self.getAppearance()
        if appearance == "full":
            ul = html.ul()
            for element in self.getItemOrChoiceElements(context):
                filled |= element.generateHtmlContextControlRadiosForm(context, ul, value)
            if filled:
                layout.append(ul)
        else: # appearance == "minimal"
            select = html.select(
                id = self.getComputedId(context, modelContext),
                name = self.getFieldName(context, modelContext)
            )
            for element in self.getItemOrChoiceElements(context):
                filled |= element.generateHtmlContextControlOptionsForm(context, select, value)
            if filled:
                layout.append(select)
        return filled

    def generateHtmlContextControlCoreStatic(self, context, layout):
        """ Renders and appends to layout as a list.
        Returns whether something was appended to layout.
        """
        modelContext = self.getModelContext(context)
        value = self.getSelectedValue(context)
        filled = False
        appearance = self.getAppearance()
        if appearance == "full":
            for element in self.getItemOrChoiceElements(context):
                filled |= element.generateHtmlContextControlRadiosStatic(context, layout, value)
        else: # appearance == "minimal"
            for element in self.getItemOrChoiceElements(context):
                filled |= element.generateHtmlContextControlOptionsStatic(context, layout, value)
        return filled

    def getAvailableValues(self, context):
        """ Returns a list with all available values.
        """
        return [node.content for node in self.evaluateXpath(".//xforms:value")]

    def getItemOrChoiceElements(self, context):
        """ Returns a list of elements that provide methods:
         - generateHtmlContextControlRadiosForm
         - generateHtmlContextControlOptionsForm
         - generateHtmlContextControlRadiosStatic
         - generateHtmlContextControlOptionsStatic
        """
        return [self.newElement(node) for node in self.evaluateXpath("xforms:item | xforms:choices")]

    def getSelectedValue(self, context):
        """ Returns the selected value.
        """
        node = self.getInstanceDataNode(context, self.getModelContext(context))
        return node and node.content or None

    def submitContext(self, context):
        """ Updates the context specimen with submitted data for this control.
        Sets an error if the submitted value is not available.
        Sets an error if no data was submitted.
        """
        submission = environs.getVar("submission")
        modelContext = self.getModelContext(context)
        fieldName = self.getFieldName(context, modelContext)
        value = submission.getField(fieldName, default = "")
        context.specimen.setContentAtXpath(self.getInstanceDataXpath(context, modelContext), value)
        if value not in self.getAvailableValues(context):
            computedId = self.getComputedId(context, modelContext)
            modelContext.setErrorLabel(
                computedId, "wrongValue", N_("Wrong Value"))
        elif not value and self.isRequired(context, modelContext):
            computedId = self.getComputedId(context, modelContext)
            modelContext.setErrorLabel(
                computedId, "emptyValue", N_("Missing Value"))


class Submission(Element):
    def buildXformsModelContext(self, context):
        # FIXME: TODO
        pass


class Submit(Control):

    def generateHtmlContextControlCoreForm(self, context, layout):
        layout.append(html.input(
            class_ = "button", name = self.getSubmissionId(), type = "submit",
            value = self.label.node.content))
        return True

    def generateHtmlContextControlCoreStatic(self, context, layout):
        label = self.label
        if label is None:
            return False
        a = html.a(class_ = 'button', href = self.getSubmissionActionUri(
            context))
        if not label.generateHtmlContextMessageChildren(context, a):
            return False
        layout.append(a)
        return True

    def generateHtmlContextControlCoreHidden(self, context, layout):
        return False

    def generateHtmlContextControlLabel(self, context, layout):
        return False

    def getSubmissionActionUri(self, context):
        # FIXME: TODO
        return None
        submissionId = self.getSubmissionId()
        submissionNode = self.getModel(context).getSubmission(submissionId)
        if submissionNode is None:
            return None
        actionNodes = self.evaluateXpath("@action", submissionNode)
        if not actionNodes:
            return None
        return context.constructUri(actionNodes[0].content)

    def getSubmissionId(self):
        submissionIdNodes = self.evaluateXpath("@submission")
        if submissionIdNodes:
            submissionId = submissionIdNodes[0].content
        else:
            submissionId = None
        return submissionId

    def getFieldValue(self, context):
        return None


class TextArea(Control):

    def generateHtmlContextControlCoreForm(self, context, layout):
        modelContext = self.getModelContext(context)
        if self.getAppearance() == "full":
            attributes = {"cols": "80", "rows": "10"}
        else:
            attributes = {"cols": "60", "rows": "4"}
        layout.append(html.textarea(
            self.getFieldValue(context),
            id = self.getComputedId(context, modelContext),
            name = self.getFieldName(context, modelContext), **attributes))
        return True


class Trigger(Control):

    def generateHtmlContextControlCoreForm(self, context, layout):
        # Unlike Submit Control, a Trigger never submits the form even inside
        # a form.
        label = self.label
        if label is None:
            return False
        a = html.a(
            class_ = 'button', href = self.getLoadResourceUri(context))
        if not label.generateHtmlContextMessageChildren(context, a):
            return False
        layout.append(a)
        return True

    def generateHtmlContextControlCoreStatic(self, context, layout):
        label = self.label
        if label is None:
            return False
        a = html.a(
            class_ = 'button', href = self.getLoadResourceUri(context))
        if not label.generateHtmlContextMessageChildren(context, a):
            return False
        layout.append(a)
        return True

    def generateHtmlContextControlCoreHidden(self, context, layout):
        return False

    def generateHtmlContextControlLabel(self, context, layout):
        return False

    def getFieldValue(self, context):
        return None

    def getLoadResourceUri(self, context):
        loadNodes = self.evaluateXpath("xforms:load[@ev:event='DOMActivate']")
        if not loadNodes:
            return None
        loadNode = loadNodes[0]
        resourceNodes = self.evaluateXpath("@resource", loadNode)
        if not resourceNodes:
            refNodes = self.evaluateXpath("@ref", loadNode)
            if not refNodes:
                return None
            ref = refNodes[0].content
##             resourceNodes = self.getInstanceDataNodes(context, subXpath = ref)
            modelContext = self.getModelContext(context)
            instanceDataNode = self.getInstanceDataNode(context, modelContext)
            if instanceDataNode is None:
                return None
            instance = modelContext.getInstance()
            resourceNodes = instance.evaluateXpath(ref, contextNode = instanceDataNode)
            if not resourceNodes:
                return None
        resource = resourceNodes[0].content
        return context.constructUri(resource)


class Upload(Control):

    def generateHtmlContextControlCoreForm(self, context, layout):
        modelContext = self.getModelContext(context)
        fieldName = self.getFieldName(context, modelContext)
        computedId = self.getComputedId(context, modelContext)
        layout.append(html.input(
            id_ = computedId, name = fieldName, type = "file"))
        return True


class Value(WidgetElement):
    def generateHtmlContextValueCheckBoxForm(self, context, layout, checkedValues):
        modelContext = self.getModelContext(context)
        realNode = self.getRealNode(context, modelContext)
        itemValue = realNode.content
        if itemValue in checkedValues:
            attributes = {"checked": "checked"}
        else:
            attributes = {}
        layout.append(html.input(
            class_ = "xforms-value",
            id = self.getComputedId(context, modelContext),
            name = self.getFieldName(context, modelContext), type = "checkbox",
            value = itemValue, **attributes))
        return True

    def generateHtmlContextValueCheckBoxStatic(self, context, layout, checkedValues):
        modelContext = self.getModelContext(context)
        realNode = self.getRealNode(context, modelContext)
        itemValue = realNode.content
        if itemValue in checkedValues:
            class_ = "xforms-value-checkbox-checked"
        else:
            class_ = "xforms-value-checkbox-unchecked"
        layout.append(html.span(class_ = class_))
        return True

    def generateHtmlContextValueOptionForm(self, context, layout, value):
        modelContext = self.getModelContext(context)
        realNode = self.getRealNode(context, modelContext)
        itemValue = realNode.content
        if itemValue == value:
            attributes = {"selected": "selected"}
        else:
            attributes = {}
        option = html.option(
            class_ = "xforms-value", value = itemValue, **attributes)
        layout.append(option)
        self.getParent().label.generateHtmlContextMessageChildren(context, option)
        return True

    def generateHtmlContextValueOptionStatic(self, context, layout, value):
        modelContext = self.getModelContext(context)
        realNode = self.getRealNode(context, modelContext)
        itemValue = realNode.content
        if itemValue == value:
            class_ = "xforms-value-option-selected"
        else:
            class_ = "xforms-value-option-unselected"
        span = html.span(class_ = class_)
        layout.append(span)
        self.getParent().label.generateHtmlContextMessageChildren(context, span)
        return True

    def generateHtmlContextValueOptionStaticSimple(self, context, layout, value):
        modelContext = self.getModelContext(context)
        realNode = self.getRealNode(context, modelContext)
        itemValue = realNode.content
        if itemValue == value:
            span = html.span()
            layout.append(span)
            self.getParent().label.generateHtmlContextMessageChildren(context, span)
            return True
        return False

    def generateHtmlContextValueRadioForm(self, context, layout, value):
        modelContext = self.getModelContext(context)
        realNode = self.getRealNode(context, modelContext)
        itemValue = realNode.content
        if itemValue == value:
            attributes = {"checked": "checked"}
        else:
            attributes = {}
        layout.append(html.input(
            class_ = "xforms-value",
            id = self.getComputedId(context, modelContext),
            name = self.getFieldName(context, modelContext), type = "radio",
            value = itemValue, **attributes))
        return True

    def generateHtmlContextValueRadioStatic(self, context, layout, value):
        modelContext = self.getModelContext(context)
        realNode = self.getRealNode(context, modelContext)
        itemValue = realNode.content
        if itemValue == value:
            class_ = "xforms-value-radio-checked"
        else:
            class_ = "xforms-value-radio-unchecked"
        layout.append(html.span(class_ = class_))
        return True


elements.registerElement(namespaces.xforms.uri, "alert", Alert)
elements.registerElement(namespaces.xforms.uri, "bind", Bind)
elements.registerElement(namespaces.xforms.uri, "choices", Choices)
elements.registerElement(namespaces.xforms.uri, "group", Group)
elements.registerElement(namespaces.xforms.uri, "help", Help)
elements.registerElement(namespaces.xforms.uri, "hint", Hint)
elements.registerElement(namespaces.xforms.uri, "input", Input)
elements.registerElement(namespaces.xforms.uri, "instance", Instance)
elements.registerElement(namespaces.xforms.uri, "item", Item)
elements.registerElement(namespaces.xforms.uri, "label", Label)
elements.registerElement(namespaces.xforms.uri, "model", Model)
elements.registerElement(namespaces.xforms.uri, "output", Output)
elements.registerElement(namespaces.xforms.uri, "range", Range)
elements.registerElement(namespaces.xforms.uri, "repeat", Repeat)
elements.registerElement(namespaces.xforms.uri, "secret", Secret)
elements.registerElement(namespaces.xforms.uri, "select", Select)
elements.registerElement(namespaces.xforms.uri, "select1", Select1)
elements.registerElement(namespaces.xforms.uri, "submission", Submission)
elements.registerElement(namespaces.xforms.uri, "submit", Submit)
elements.registerElement(namespaces.xforms.uri, "textarea", TextArea)
elements.registerElement(namespaces.xforms.uri, "trigger", Trigger)
elements.registerElement(namespaces.xforms.uri, "upload", Upload)
elements.registerElement(namespaces.xforms.uri, "value", Value)
