diff --git a/common/lib/capa/capa_problem.py b/common/lib/capa/capa_problem.py
index f790190..c63c13d 100644
--- a/common/lib/capa/capa_problem.py
+++ b/common/lib/capa/capa_problem.py
@@ -23,7 +23,6 @@ import scipy
 import struct
 
 from lxml import etree
-from lxml.etree import Element
 from xml.sax.saxutils import unescape
 
 from util import contextualize_text
@@ -36,6 +35,7 @@ import eia
 
 log = logging.getLogger(__name__)
 
+# dict of tagname, Response Class -- this should come from auto-registering
 response_types = {'numericalresponse': NumericalResponse,
                   'formularesponse': FormulaResponse,
                   'customresponse': CustomResponse,
@@ -47,20 +47,13 @@ response_types = {'numericalresponse': NumericalResponse,
                   'optionresponse': OptionResponse,
                   'symbolicresponse': SymbolicResponse,
                   }
+
 entry_types = ['textline', 'schematic', 'choicegroup', 'textbox', 'imageinput', 'optioninput']
-solution_types = ['solution']    # extra things displayed after "show answers" is pressed
-response_properties = ["responseparam", "answer"]    # these get captured as student responses
+solution_types = ['solution']    			# extra things displayed after "show answers" is pressed
+response_properties = ["responseparam", "answer"]    	# these get captured as student responses
 
-# How to convert from original XML to HTML
-# We should do this with xlst later
+# special problem tags which should be turned into innocuous HTML
 html_transforms = {'problem': {'tag': 'div'},
-                   "numericalresponse": {'tag': 'span'},
-                   "customresponse": {'tag': 'span'},
-                   "externalresponse": {'tag': 'span'},
-                   "schematicresponse": {'tag': 'span'},
-                   "formularesponse": {'tag': 'span'},
-                   "symbolicresponse": {'tag': 'span'},
-                   "multiplechoiceresponse": {'tag': 'span'},
                    "text": {'tag': 'span'},
                    "math": {'tag': 'span'},
                    }
@@ -74,18 +67,6 @@ global_context = {'random': random,
 
 # These should be removed from HTML output, including all subelements
 html_problem_semantics = ["responseparam", "answer", "script"]
-# These should be removed from HTML output, but keeping subelements
-html_skip = ["numericalresponse", "customresponse", "schematicresponse", "formularesponse", "text", "externalresponse", 'symbolicresponse']
-
-# removed in MC
-## These should be transformed
-#html_special_response = {"textline":inputtypes.textline.render,
-#                         "schematic":inputtypes.schematic.render,
-#                         "textbox":inputtypes.textbox.render,
-#                         "formulainput":inputtypes.jstextline.render,
-#                         "solution":inputtypes.solution.render,
-#                         }
-
 
 class LoncapaProblem(object):
     '''
@@ -142,7 +123,8 @@ class LoncapaProblem(object):
         self.context = self.extract_context(self.tree, seed=self.seed)
 
         # pre-parse the XML tree: modifies it to add ID's and perform some in-place transformations
-        # this also creates the list (self.responders) of Response instances for each question in the problem
+        # this also creates the dict (self.responders) of Response instances for each question in the problem.
+        # the dict has keys = xml subtree of Response, values = Response instance
         self.preprocess_problem(self.tree, correct_map=self.correct_map, answer_map=self.student_answers)
 
     def __unicode__(self):
@@ -166,7 +148,7 @@ class LoncapaProblem(object):
         used to give complex problems (eg programming questions) multiple points.
         '''
         maxscore = 0
-        for responder in self.responders:
+        for responder in self.responders.values():
             if hasattr(responder,'get_max_score'):
                 try:
                     maxscore += responder.get_max_score()
@@ -182,6 +164,10 @@ class LoncapaProblem(object):
         return maxscore
 
     def get_score(self):
+        '''
+        Compute score for this problem.  The score is the number of points awarded.
+        Returns an integer, from 0 to get_max_score().
+        '''
         correct = 0
         for key in self.correct_map:
             if self.correct_map[key] == u'correct':
@@ -206,7 +192,7 @@ class LoncapaProblem(object):
         self.student_answers = answers
         self.correct_map = dict()
         log.info('%s: in grade_answers, answers=%s' % (self,answers))
-        for responder in self.responders:
+        for responder in self.responders.values():
             results = responder.get_score(answers)        # call the responsetype instance to do the actual grading
             self.correct_map.update(results)
         return self.correct_map
@@ -218,24 +204,14 @@ class LoncapaProblem(object):
         (see capa_module)
         """
         answer_map = dict()
-        for responder in self.responders:
+        for responder in self.responders.values():
             results = responder.get_answers()
             answer_map.update(results)                # dict of (id,correct_answer)
 
-        # This should be handled in each responsetype, not here.
-        # example for the following: <textline size="5" correct_answer="saturated" />
-        for responder in self.responders:
-            for entry in responder.inputfields:
-                answer = entry.get('correct_answer')        # correct answer, when specified elsewhere, eg in a textline
-                if answer:
-                    answer_map[entry.get('id')] = contextualize_text(answer, self.context)
-
         # include solutions from <solution>...</solution> stanzas
-        # Tentative merge; we should figure out how we want to handle hints and solutions
         for entry in self.tree.xpath("//" + "|//".join(solution_types)):
             answer = etree.tostring(entry)
-            if answer:
-                answer_map[entry.get('id')] = answer
+            if answer: answer_map[entry.get('id')] = answer
 
         return answer_map
 
@@ -244,7 +220,7 @@ class LoncapaProblem(object):
         the dicts returned by grade_answers and get_question_answers. (Though
         get_question_answers may only return a subset of these."""
         answer_ids = []
-        for responder in self.responders:
+        for responder in self.responders.values():
             answer_ids.append(responder.get_answers().keys())
         return answer_ids
 
@@ -252,7 +228,7 @@ class LoncapaProblem(object):
         '''
         Main method called externally to get the HTML to be rendered for this capa Problem.
         '''
-        return contextualize_text(etree.tostring(self.extract_html(self.tree)[0]), self.context)
+        return contextualize_text(etree.tostring(self.extract_html(self.tree)), self.context)
 
     # ======= Private ========
     def extract_context(self, tree, seed=struct.unpack('i', os.urandom(4))[0]):  # private
@@ -264,12 +240,11 @@ class LoncapaProblem(object):
         Problem XML goes to Python execution context. Runs everything in script tags
         '''
         random.seed(self.seed)
-        context = {'global_context': global_context}    # save global context in here also
-        context.update(global_context)            # initialize context to have stuff in global_context
-        context['__builtins__'] = globals()['__builtins__']    # put globals there also
-        context['the_lcp'] = self                # pass instance of LoncapaProblem in
+        context = {'global_context': global_context}    	# save global context in here also
+        context.update(global_context)            		# initialize context to have stuff in global_context
+        context['__builtins__'] = globals()['__builtins__']    	# put globals there also
+        context['the_lcp'] = self                		# pass instance of LoncapaProblem in
 
-        #for script in tree.xpath('/problem/script'):
         for script in tree.findall('.//script'):
             stype = script.get('type')
             if stype:
@@ -288,16 +263,20 @@ class LoncapaProblem(object):
         return context
 
     def extract_html(self, problemtree):  # private
-        ''' Helper function for get_html. Recursively converts XML tree to HTML
+        '''
+        Main (private) function which converts Problem XML tree to HTML.
+        Calls itself recursively.
+
+        Returns Element tree of XHTML representation of problemtree.
+        Calls render_html of Response instances to render responses into XHTML.
+
+        Used by get_html.
         '''
         if problemtree.tag in html_problem_semantics:
             return
 
         problemid = problemtree.get('id')    # my ID
 
-        # used to be
-        # if problemtree.tag in html_special_response:
-
         if problemtree.tag in inputtypes.get_input_xml_tags():
             # status is currently the answer for the problem ID for the input element,
             # but it will turn into a dict containing both the answer and any associated message
@@ -334,31 +313,25 @@ class LoncapaProblem(object):
                                                    use='capa_input')
             return render_object.get_html()  # function(problemtree, value, status, msg) # render the special response (textline, schematic,...)
 
-        tree = Element(problemtree.tag)
+        if problemtree in self.responders:		# let each Response render itself
+            return self.responders[problemtree].render_html(self.extract_html)
+
+        tree = etree.Element(problemtree.tag)
         for item in problemtree:
-            subitems = self.extract_html(item)
-            if subitems is not None:
-                for subitem in subitems:
-                    tree.append(subitem)
-        for (key, value) in problemtree.items():
-            tree.set(key, value)
+            item_xhtml = self.extract_html(item)		# nothing special: recurse
+            if item_xhtml is not None:
+                    tree.append(item_xhtml)
+
+        if tree.tag in html_transforms:
+            tree.tag = html_transforms[problemtree.tag]['tag']
+        else:
+            for (key, value) in problemtree.items():	# copy attributes over if not innocufying
+                tree.set(key, value)
 
         tree.text = problemtree.text
         tree.tail = problemtree.tail
 
-        if problemtree.tag in html_transforms:
-            tree.tag = html_transforms[problemtree.tag]['tag']
-            # Reset attributes. Otherwise, we get metadata in HTML
-            # (e.g. answers)
-            # TODO: We should remove and not zero them.
-            # I'm not sure how to do that quickly with lxml
-            for k in tree.keys():
-                tree.set(k, "")
-
-        # TODO: Fix. This loses Element().tail
-        #if problemtree.tag in html_skip:
-        #    return tree
-        return [tree]
+        return tree
 
     def preprocess_problem(self, tree, correct_map=dict(), answer_map=dict()):  # private
         '''
@@ -370,7 +343,7 @@ class LoncapaProblem(object):
         Also create capa Response instances for each responsetype and save as self.responders
         '''
         response_id = 1
-	self.responders = []
+	self.responders = {}
         for response in tree.xpath('//' + "|//".join(response_types)):
             response_id_str = self.problem_id + "_" + str(response_id)
             response.attrib['id'] = response_id_str				# create and save ID for this response
@@ -389,7 +362,7 @@ class LoncapaProblem(object):
                 answer_id = answer_id + 1
 
             responder = response_types[response.tag](response, inputfields, self.context, self.system)	# instantiate capa Response
-	    self.responders.append(responder)				# save in list in self
+	    self.responders[response] = responder				# save in list in self
 
         # <solution>...</solution> may not be associated with any specific response; give IDs for those separately
         # TODO: We should make the namespaces consistent and unique (e.g. %s_problem_%i).
diff --git a/common/lib/capa/inputtypes.py b/common/lib/capa/inputtypes.py
index 3b25be3..10fbdb7 100644
--- a/common/lib/capa/inputtypes.py
+++ b/common/lib/capa/inputtypes.py
@@ -33,26 +33,17 @@ def get_input_xml_tags():
 
 class SimpleInput():# XModule
     ''' Type for simple inputs -- plain HTML with a form element
+
     State is a dictionary with optional keys: 
     * Value
     * ID
     * Status (answered, unanswered, unsubmitted)
     * Feedback (dictionary containing keys for hints, errors, or other 
       feedback from previous attempt)
+
     '''
 
     xml_tags = {} ## Maps tags to functions
-    
-    @classmethod
-    def get_xml_tags(c):
-        return c.xml_tags.keys()
-
-    @classmethod
-    def get_uses(c):
-        return ['capa_input', 'capa_transform']
-
-    def get_html(self):
-        return self.xml_tags[self.tag](self.xml, self.value, self.status, self.system.render_template, self.msg)
 
     def __init__(self, system, xml, item_id = None, track_url=None, state=None, use = 'capa_input'):
         self.xml = xml
@@ -83,49 +74,16 @@ class SimpleInput():# XModule
         if 'status' in state:
             self.status = state['status']
 
-## TODO
-# class SimpleTransform():
-#     ''' Type for simple XML to HTML transforms. Examples:
-#     * Math tags, which go from LON-CAPA-style m-tags to MathJAX
-#     '''
-#     xml_tags = {} ## Maps tags to functions
-    
-#     @classmethod
-#     def get_xml_tags(c):
-#         return c.xml_tags.keys()
-
-#     @classmethod
-#     def get_uses(c):
-#         return ['capa_transform']
-
-#     def get_html(self):
-#         return self.xml_tags[self.tag](self.xml, self.value, self.status, self.msg)
-
-#     def __init__(self, system, xml, item_id = None, track_url=None, state=None, use = 'capa_input'):
-#         self.xml = xml
-#         self.tag = xml.tag
-#         if not state:
-#             state = {}
-#         if item_id:
-#             self.id = item_id
-#         if xml.get('id'):
-#             self.id = xml.get('id')
-#         if 'id' in state:
-#             self.id = state['id']
-#         self.system = system
-
-#         self.value = ''
-#         if 'value' in state:
-#             self.value = state['value']
-
-#         self.msg = ''
-#         if 'feedback' in state and 'message' in state['feedback']:
-#             self.msg = state['feedback']['message']
-
-#         self.status = 'unanswered'
-#         if 'status' in state:
-#             self.status = state['status']
+    @classmethod
+    def get_xml_tags(c):
+        return c.xml_tags.keys()
 
+    @classmethod
+    def get_uses(c):
+        return ['capa_input', 'capa_transform']
+
+    def get_html(self):
+        return self.xml_tags[self.tag](self.xml, self.value, self.status, self.system.render_template, self.msg)
 
 def register_render_function(fn, names=None, cls=SimpleInput):
     if names is None:
@@ -136,9 +94,6 @@ def register_render_function(fn, names=None, cls=SimpleInput):
         return fn
     return wrapped
 
-
-
-
 #-----------------------------------------------------------------------------
 
 @register_render_function
@@ -201,16 +156,16 @@ def choicegroup(element, value, status, render_template, msg=''):
     return etree.XML(html)
 
 @register_render_function
-def textline(element, value, state, render_template, msg=""):
+def textline(element, value, status, render_template, msg=""):
     '''
     Simple text line input, with optional size specification.
     '''
     if element.get('math') or element.get('dojs'):		# 'dojs' flag is temporary, for backwards compatibility with 8.02x
-        return SimpleInput.xml_tags['textline_dynamath'](element,value,state,render_template,msg)
+        return SimpleInput.xml_tags['textline_dynamath'](element,value,status,render_template,msg)
     eid=element.get('id')
     count = int(eid.split('_')[-2])-1 # HACK
     size = element.get('size')
-    context = {'id':eid, 'value':value, 'state':state, 'count':count, 'size': size, 'msg': msg}
+    context = {'id':eid, 'value':value, 'state':status, 'count':count, 'size': size, 'msg': msg}
     html = render_template("textinput.html", context)
     return etree.XML(html)
 
diff --git a/common/lib/capa/responsetypes.py b/common/lib/capa/responsetypes.py
index 1c09493..bfd4281 100644
--- a/common/lib/capa/responsetypes.py
+++ b/common/lib/capa/responsetypes.py
@@ -63,7 +63,8 @@ class GenericResponse(object):
 
       - get_max_score       : if defined, this is called to obtain the maximum score possible for this question
       - setup_response      : find and note the answer input field IDs for the response; called by __init__
-       - __unicode__        : unicode representation of this Response
+      - render_html         : render this Response as HTML (must return XHTML compliant string)
+      - __unicode__         : unicode representation of this Response
 
     Each response type may also specify the following attributes:
 
@@ -114,9 +115,30 @@ class GenericResponse(object):
         if self.max_inputfields==1:
             self.answer_id = self.answer_ids[0]		# for convenience
 
+        self.default_answer_map = {}			# dict for default answer map (provided in input elements)
+        for entry in self.inputfields:
+            answer = entry.get('correct_answer')        
+            if answer:
+                self.default_answer_map[entry.get('id')] = contextualize_text(answer, self.context)
+
         if hasattr(self,'setup_response'):
             self.setup_response()
 
+    def render_html(self,renderer):
+        '''
+        Return XHTML Element tree representation of this Response.
+
+        Arguments:
+
+          - renderer : procedure which produces HTML given an ElementTree
+        '''
+        tree = etree.Element('span')			# render ourself as a <span> + our content
+        for item in self.xml:
+            item_xhtml = renderer(item)			# call provided procedure to do the rendering
+            if item_xhtml is not None: tree.append(item_xhtml)
+        tree.tail = self.xml.tail
+        return tree
+
     @abc.abstractmethod
     def get_score(self, student_answers):
         '''
@@ -132,7 +154,6 @@ class GenericResponse(object):
         '''
         pass
 
-    #not an abstract method because plenty of responses will not want to preprocess anything, and we should not require that they override this method.
     def setup_response(self):
         pass
 
@@ -485,17 +506,17 @@ def sympy_check2():
         '''
         Give correct answer expected for this response.
 
-        capa_problem handles correct_answers from entry objects like textline, and that
-        is what should be used when this response has multiple entry objects.
+        use default_answer_map from entry elements (eg textline),
+        when this response has multiple entry objects.
 
         but for simplicity, if an "expect" attribute was given by the content author
-        ie <customresponse expect="foo" ...> then return it now.
+        ie <customresponse expect="foo" ...> then that.
         '''
         if len(self.answer_ids)>1:
-            return {}
+            return self.default_answer_map
         if self.expect:
             return {self.answer_ids[0] : self.expect}
-        return {}
+        return self.default_answer_map
 
 #-----------------------------------------------------------------------------
 
@@ -797,9 +818,8 @@ class SchematicResponse(GenericResponse):
         return  zip(sorted(self.answer_ids), self.context['correct'])
 
     def get_answers(self):
-        # Since this is explicitly specified in the problem, this will 
-        # be handled by capa_problem
-        return {}
+        # use answers provided in input elements
+        return self.default_answer_map
 
 #-----------------------------------------------------------------------------