def page_processor(self, page, row): question = structures.ElementObject() # Check if an instruction or a question. Instrument fields not added # if a row's data_type is 'instruction' if row['data_type'] == 'instruction': question['type'] = 'text' question['options'] = { 'text': localized_string_object(self.localization, row['text']), } else: question['type'] = 'question' question['options'] = structures.QuestionObject( fieldId=self.reader.get_name(row['fieldid']), text=localized_string_object(self.localization, row['text']), help=localized_string_object(self.localization, row['help']), ) # Build field object field = structures.FieldObject( id=self.reader.get_name(row['fieldid']), description=row['text'], type=self._field_type, ) # Process question and field self.question_and_field_processor(page, row, question, field) # Store instrument question field self._storage['i'] = field # Add the new question to form page page.add_element(question)
def page_processor(self, page, row): section_header = row['section_header'] if section_header: header = structures.ElementObject() header['type'] = 'header' header['options'] = { 'text': localized_string_object(self.localization, section_header) } else: header = None # Check if a calc, and if so, remove, b/c not a form field/question if row['field_type'] == 'calc': question = None else: question = structures.ElementObject(type='question') field_name = self.reader.get_name(row['variable_field_name']) if section_header: field_name = '%s_%s' % ( field_name, self.reader.get_name(section_header), ) question['options'] = structures.QuestionObject( fieldId=field_name, text=localized_string_object(self.localization, row['field_label']), help=localized_string_object(self.localization, row['field_note']), ) # Now that we have a header only OR question and maybe a header, we # either add the header by itself as a section header, or we add a new # question containing a possible sub-header. If no header or question, # then we have a calculation, which is handled when instrument fields # are constructed in the qestion_processor function. if header and not question: # Add non-questions to form (e.g., headers) page.add_element(header) # Process existing ques or process and add new ques to the form if question: add_question_and_store_fields = self.question_and_field_processor( page, row, question) # This block MUST COME AFTER the question processor to prevent # adding questions and fields that caused errors, but allow the # conversion to proceed to the next line. if add_question_and_store_fields: # Add the new question to form page page.add_element(question) # Store instrument question field if self._field and self._field['id']: self._storage['i'] = self._field
def process_yesno(): if side_effects: question.set_widget(get_widget(type='radioGroup')) question.add_enumeration( structures.DescriptorObject( id="yes", text=localized_string_object(self.localization, "Yes"), )) question.add_enumeration( structures.DescriptorObject( id="no", text=localized_string_object(self.localization, "No"), )) type_object = structures.TypeObject(base='enumeration', ) type_object.add_enumeration('yes', description='Yes') type_object.add_enumeration('no', description='No') return type_object
def question_and_field_processor(self, page, row, question, field): """ Processes questions and field types. Question object are fully configured, and the field type is generated for the instrument definition fields corresponding to the question. """ # Use question object in question['options'] question_obj = question['options'] # Process choices before questions, so choices are available to # question processing if row['enumeration_type'] in ( 'enumeration', 'enumerationSet', ): # data_type might be a JSON string of a dict which contains # 'Choices' or 'choices', an array of single key dicts. try: data_type = json.loads(row['data_type']) choices = (data_type.get('Choices', False) or data_type.get('choices', False) or None) except: error = RedcapFormatError( "Unable to parse \"data_type\" field:", "Expected valid JSON formatted text") raise error # Sort the array on key order not array order if choices: field['type'] = structures.TypeObject( base=row['enumeration_type'], ) choices = [{ self.reader.get_name(k): v } for c in choices for k, v in c.items()] choices.sort() for choice in choices: field['type'].add_enumeration(choice.keys()[0]) name, description = choice.items()[0] question_obj.add_enumeration( structures.DescriptorObject( id=self.reader.get_name(name), text=localized_string_object( self.localization, description), )) question_obj.set_widget( structures.WidgetConfigurationObject( type='checkGroup' if row['enumeration_type'] == 'enumerationSet' else 'radioGroup')) else: field['type'] = row['data_type']
def process_truefalse(): if side_effects: question.set_widget(get_widget(type='radioGroup')) question.add_enumeration( structures.DescriptorObject( id="true", text=localized_string_object(self.localization, "True"), )) question.add_enumeration( structures.DescriptorObject( id="false", text=localized_string_object(self.localization, "False"), )) type_object = structures.TypeObject(base='enumeration', ) type_object.add_enumeration('true', description='True') type_object.add_enumeration('false', description='False') return type_object
def get_choices_form(self, row): """ Returns array of DescriptorObject Expecting: choices_or_calculations to be pipe separated list of (comma delimited) tuples: internal, external """ return [ structures.DescriptorObject( id=self.reader.get_name(x.strip().split(',')[0]), text=localized_string_object( self.localization, ','.join(x.strip().split(',')[1:]).strip()), ) for x in row['choices_or_calculations'].split('|') ]
def question_field_processor(self, question_data, question): """ Processe questions and fields """ question_type = question_data['QuestionType'] question_text = localized_string_object( self.localization, self.clean_question(question_data['QuestionText']) ) if question_type == 'DB': # Question is only display text question['type'] = 'text' question['options'] = {'text': question_text} else: # Question is an interactive form element question['type'] = 'question' question['options'] = structures.QuestionObject( fieldId=question_data['DataExportTag'].lower(), text=localized_string_object( self.localization, self.clean_question( question_data['QuestionText'] ) ), ) # Choices are generated, where "choices" is an array of # tuples: (id, choice) self._choices = question_data.get('Choices', []) order = question_data.get('ChoiceOrder', []) if self._choices: if isinstance(self._choices, dict): if not order: keys = self._choices.keys() if all([k.isdigit() for k in keys]): keys = [int(k) for k in keys] order = sorted(keys) self._choices = [(x, self._choices[str(x)]) for x in order] elif isinstance(self._choices, list): self._choices = [i for i in enumerate(self._choices)] else: error = ConversionValueError( "Choices are not formatted correctly. Got choices:", str(self._choices) ) error.wrap("With question data:", str(question)) raise error self._choices = [ (str(i).lower(), c['Display']) for i, c in self._choices ] # Process question object and field type object question_obj = question['options'] field_type = structures.TypeObject(base='enumeration', ) for _id, choice in self._choices: question_obj.add_enumeration( structures.DescriptorObject( id=_id, text=localized_string_object( self.localization, choice ), ) ) field_type.add_enumeration(str(_id)) else: field_type = 'text' # Consruct field for instrument definition field = structures.FieldObject( id=question_data['DataExportTag'].lower(), description=question_data['QuestionDescription'], type=field_type, required=False, identifiable=False, ) self._field_storage.append(field)
def question_and_field_processor(self, page, row, question): """ Processes questions. Return True if a question should be added as a new element to the page, and if a form field should be added to the instrument definition. New matrix questions and regular questions are added to both definitions. However, existing matrix questions are not added. A pointer reference is store to allow for: 1) modifying existing matrix questions 2) storing the instrument field by the parent function scope """ add_question_and_store_field = True # Use question object in question['options'] question_obj = question['options'] # If row involves branching logic, add to question if row['branching_logic']: question_obj.add_event( structures.EventObject( trigger=self.convert_trigger(row['branching_logic']), action='disable', )) # Check for a matrix question matrix_group_name = self.reader.get_name( row.get('matrix_group_name', '')) if matrix_group_name: # Question is a matrix question # # assessment[m][r][c] # m = matrix_group_name # r = variable_field_name # c = field_type # Ranme for code clarity matrix = question_obj if self._current_matrix_group_name != matrix_group_name: # New matrix question self._current_matrix_group_name = matrix_group_name # Start a new matrix question field for the form matrix['fieldId'] = matrix_group_name # Create a new matrix question field for the instrument field = structures.FieldObject() field['id'] = self.reader.get_name(row['matrix_group_name']) field['description'] = row.get('section_header', '') field['type'] = structures.TypeObject(base='matrix', ) field_type = field['type'] # Process matrix # Append the only column(to instrument). # Use the field_type (checkbox or radiobutton) as the id. field_type.add_column( structures.ColumnObject( id=self.reader.get_name(row['field_type']), description=row['field_type'], type=self.get_type(matrix, row, side_effects=False), required=bool(row['required_field']), identifiable=bool(row['identifier']), )) # add the column to the form matrix.add_question( structures.QuestionObject( fieldId=self.reader.get_name(row['field_type']), text=localized_string_object(self.localization, row['field_label']), enumerations=self.get_choices_form(row), )) # Append the first row (to instrument). field_type.add_row( structures.RowObject( id=self.reader.get_name(row['variable_field_name']), description=row['field_label'], required=bool(row['required_field']), )) # add the row to the form. matrix.add_row( structures.DescriptorObject( id=self.reader.get_name(row['variable_field_name']), text=localized_string_object(self.localization, row['field_label']), )) # Assign pointers to matrix construction objects, so # matrix questions have modifiable instrument and form # definitions if next question is an existing matrix # question self._matrix = matrix self._field = field self._field_type = field_type else: # Current matrix question # Modify existing matrix question for instrument definition self._field_type.add_row( structures.RowObject( id=self.reader.get_name(row['variable_field_name']), description=row['field_label'], required=bool(row['required_field']), )) # Field already exists, so prevent adding it to form self._field = structures.FieldObject() # Modify existing matrix question for form definition self._matrix.add_row( structures.DescriptorObject( id=self.reader.get_name(row['variable_field_name']), text=localized_string_object(self.localization, row['field_label']), )) self._field = None add_question_and_store_field = False else: # A non-matrix, regular question self._current_matrix_group_name = None self._matrix = None self._field_type = None # Make instrument field field = structures.FieldObject() field_type = self.get_type(question_obj, row) if field_type: field_name = self.reader.get_name(row['variable_field_name']) if row['section_header']: field_name = '%s_%s' % ( field_name, self.reader.get_name(row['section_header']), ) field['id'] = field_name field['description'] = row['field_label'] field['type'] = field_type field['required'] = bool(row['required_field']) field['identifiable'] = bool(row['identifier']) self._field = field return add_question_and_store_field