def end_pending_group(self): """End a pending group. A pending group is a group on the pending stack. This is not a context group. This function is only called internally in response to receiving and dealing with an 'end group' type. If the pending group is nested in a repeat, then it is added to that repeat. Raises: OdkFormError: If the parsing rules are broken based on the current context. """ if self.pending_stack: last_pending = self.pending_stack.pop() if not isinstance(last_pending, OdkGroup): msg = "Found end group but no group in pending stack" raise OdkFormError(msg) last_pending.add_pending() if self.pending_stack: self.pending_stack[-1].add(last_pending) else: self.result.append(last_pending) else: msg = "Found end group but nothing pending stack." raise OdkFormError(msg)
def parse_select_type(row, choices, ext_choices): """Extract relevant information from a select_* ODK prompt. Build a dictionary that distills the main details of the row. The select type questions can have a token type of 'prompt' or 'table'. The prompt type is default, and table type is if the appearance of the question has either 'label' or 'list-nolabel'. Args: row (dict): A row as a dictionary. Keys and values are strings. choices (dict): A diction ary with list_names as keys. Represents the choices found in 'choices' tab. ext_choices (dict): A dictionary with list_names as keys. Represents choices found in 'external_choices' tab. Returns: A dictionary with the simple information about this prompt. Raises: OdkFormError: If the row is not select_[one|multiple](_external)? KeyError: If the select question's choice list is not found. """ simple_row = {"token_type": "prompt"} simple_type = "select_one" row_type = row["type"] list_name = row_type.split(maxsplit=1)[1] try: if row_type.startswith("select_one_external "): choice_list = ext_choices[list_name] elif row_type.startswith("select_multiple_external "): simple_type = "select_multiple" choice_list = ext_choices[list_name] elif row_type.startswith("select_one "): choice_list = choices[list_name] elif row_type.startswith("select_multiple "): simple_type = "select_multiple" choice_list = choices[list_name] else: raise OdkFormError() except KeyError: raise OdkFormError("List '{}' not found.".format(list_name)) simple_row["simple_type"] = simple_type simple_row["choice_list"] = choice_list appearance = row.get("appearance", "") if appearance in ("label", "list-nolabel"): simple_row["token_type"] = "table" return simple_row
def cli(): """Command line interface for package. Side Effects: Executes program. Command Syntax: python3 -m ppp <file> <options> Examples: # Creates a 'myFile.html' in English with component highlighting. python3 -m ppp myFile.xlsx -l 'English' -h > myFile.html """ prog_desc = 'Convert XLSForm to Paper version.' argeparser = ArgumentParser(description=prog_desc) parser = _add_arguments(copy(argeparser)) args = parser.parse_args() if args.highlight and args.format and args.format not in SUPPORTED_FORMATS: msg = 'Can only specify highlighting when using the following ' \ 'formats: \'html\', \'pdf\'.' raise OdkFormError(msg) try: run(files=list(args.xlsxfiles), languages=[l for l in args.language] if args.language else [None], format=args.format, debug=args.debug, highlight=args.highlight, template=args.template, style=args.style, outpath=args.outpath) except OdkException as err: err = 'An error occurred while attempting to convert \'{}\':\n{}'\ .format(args.xlsxfiles, err) print(err, file=stderr)
def end_repeat(self): """Finish a repeat in this questionniare. A repeat can be ended only if it is first on the pending stack. Raises: OdkFormError: If the parsing rules are broken based on the current context. """ if self.pending_stack: last_pending = self.pending_stack.pop() if isinstance(last_pending, OdkRepeat): self.result.append(last_pending) else: msg = "Found end repeat but no repeat in pending stack." raise OdkFormError(msg) else: msg = "Found end repeat but nothing in pending stack." raise OdkFormError(msg)
def add_table(self, prompt): """Add a table row to the questionnaire. The table can only be added if there is a group on the pending stack. Args: prompt (OdkPrompt): The prompt representing the table row. Raises: OdkFormError: If the parsing rules are broken based on the current context. """ if self.pending_stack: last_pending = self.pending_stack[-1] if not isinstance(last_pending, OdkGroup): msg = 'A table can only be in a group.' raise OdkFormError(msg) last_pending.add_table(prompt) else: msg = 'A table can only be in a group, no group found.' raise OdkFormError(msg)
def end_group(self): """Finish a group after seeing 'end group' type. The 'end group' type can finish a field-list group or a context group. This function handles the logic for finishing both types. Raises: OdkFormError: If the parsing rules are broken based on the current context. """ if self.group_stack: last_group = self.group_stack.pop() if isinstance(last_group, OdkGroup): self.end_pending_group() else: msg = "Begin/end group mismatch" raise OdkFormError(msg)
def add_repeat(self, repeat): """Add a repeat to the pending stack. The pending stack must first be empty because a repeat cannot be nested in a group or other repeat. Args: repeat (OdkRepeat): The repeat to deal with. Raises: OdkFormError: If the parsing rules are broken based on the current context. """ if not self.pending_stack: self.pending_stack.append(repeat) else: msg = "Unable to nest repeat inside a group or repeat." raise OdkFormError(msg)
def add_group(self, group): """Add a group to the pending stack. A group can be added to the pending stack as long as it is empty or the last pending stack item is a repeat. This is triggered by a 'begin group' row with a 'field-list' in the appearance. Args: group (OdkGroup): The group to add to the pending stack. Raises: OdkFormError: If the parsing rules are broken based on the current context. """ if self.pending_stack: last = self.pending_stack[-1] if isinstance(last, OdkGroup): msg = "Groups cannot be nested in each other." raise OdkFormError(msg) self.pending_stack.append(group) self.group_stack.append(group)
def parse_group_repeat(row): """Extract relevant information about a begin/end group/repeat. Args: row (dict): A row as a dictionary. Keys and values are strings. Returns: A dictionary with the simple information about this prompt. Raises: OdkFormError: If type is not begin/end group/repeat. """ row_type = row["type"] token_type = row_type appearance = row.get("appearance", "") good = ("begin group", "end group", "begin repeat", "end repeat") if row_type == "begin group" and "field-list" not in appearance: token_type = "context group" elif row_type not in good: raise OdkFormError() simple_row = {"token_type": token_type} return simple_row
def get_choices(wb, ws): """Extract choices from an XLSForm. Args: wb (Xlsform): A Xlsform object representing ODK form. ws (Worksheet): One of 'choices' or 'external_choices'. Returns: dict: A dictionary of choice list names with list of choices options for each list. Raises: OdkformError: Catches instances where list specified in the 'survey' worksheet, but the list does not appear in the designated 'choices' or 'external_choices' worksheet. """ formatted_choices = {} try: choices = wb[ws] header = [str(x) for x in choices[0]] if "list_name" not in header: msg = 'Column "list_name" not found in {} tab'.format(ws) raise OdkFormError(msg) for i, row in enumerate(choices): if i == 0: continue dict_row = {str(k): str(v) for k, v in zip(header, row)} list_name = dict_row["list_name"] if list_name in formatted_choices: formatted_choices[list_name].add(dict_row) elif list_name: # Not "else:" because possibly blank rows. odkchoices = OdkChoices(list_name) odkchoices.add(dict_row) formatted_choices[list_name] = odkchoices except (KeyError, IndexError): # Worksheet does not exist. pass return formatted_choices
def parse_group_repeat(row): """Extract relevant information about a begin/end group/repeat. Args: row (dict): A row as a dictionary. Keys and values are strings. Returns: A dictionary with the simple information about this prompt. Raises: OdkFormError: If type is not begin/end group/repeat. """ row_type = row['type'] token_type = row_type appearance = row.get('appearance', '') good = ('begin group', 'end group', 'begin repeat', 'end repeat') if row_type == 'begin group' and 'field-list' not in appearance: token_type = 'context group' elif row_type not in good: raise OdkFormError() simple_row = {'token_type': token_type} return simple_row