# Path: pyxform/constants.py
# TYPE = "type"
# TITLE = "title"
# NAME = "name"
# ID_STRING = "id_string"
# SMS_KEYWORD = "sms_keyword"
# SMS_FIELD = "sms_field"
# SMS_OPTION = "sms_option"
# SMS_SEPARATOR = "sms_separator"
# SMS_ALLOW_MEDIA = "sms_allow_media"
# SMS_DATE_FORMAT = "sms_date_format"
# SMS_DATETIME_FORMAT = "sms_datetime_format"
# SMS_RESPONSE = "sms_response"
# COMPACT_PREFIX = "prefix"
# COMPACT_DELIMITER = "delimiter"
# COMPACT_TAG = "compact_tag"
# VERSION = "version"
# PUBLIC_KEY = "public_key"
# SUBMISSION_URL = "submission_url"
# AUTO_SEND = "auto_send"
# AUTO_DELETE = "auto_delete"
# DEFAULT_LANGUAGE_KEY = "default_language"
# DEFAULT_LANGUAGE_VALUE = "default"
# LABEL = "label"
# HINT = "hint"
# STYLE = "style"
# ATTRIBUTE = "attribute"
# ALLOW_CHOICE_DUPLICATES = "allow_choice_duplicates"
# BIND = (
#     "bind"  # TODO: What should I do with the nested types? (readonly and relevant) # noqa
# )
# MEDIA = "media"
# CONTROL = "control"
# APPEARANCE = "appearance"
# LOOP = "loop"
# COLUMNS = "columns"
# REPEAT = "repeat"
# GROUP = "group"
# CHILDREN = "children"
# SELECT_ONE = "select one"
# SELECT_ALL_THAT_APPLY = "select all that apply"
# RANK = "rank"
# CHOICES = "choices"
# LIST_NAME = "list name"
# CASCADING_SELECT = "cascading_select"
# TABLE_LIST = "table-list"  # hyphenated because it goes in appearance, and convention for appearance column is dashes # noqa
# FIELD_LIST = "field-list"
# SURVEY = "survey"
# SETTINGS = "settings"
# EXTERNAL_CHOICES = "external_choices"
# OSM = "osm"
# OSM_TYPE = "binary"
# NAMESPACES = "namespaces"
# SUPPORTED_SHEET_NAMES = [
#     SURVEY,
#     CHOICES,
#     SETTINGS,
#     EXTERNAL_CHOICES,
#     OSM,
# ]
# XLS_EXTENSIONS = [".xls"]
# XLSX_EXTENSIONS = [".xlsx", ".xlsm"]
# SUPPORTED_FILE_EXTENSIONS = XLS_EXTENSIONS + XLSX_EXTENSIONS
# LOCATION_PRIORITY = "location-priority"
# LOCATION_MIN_INTERVAL = "location-min-interval"
# LOCATION_MAX_AGE = "location-max-age"
# TRACK_CHANGES = "track-changes"
# IDENTIFY_USER = "identify-user"
# TRACK_CHANGES_REASONS = "track-changes-reasons"
# EXTERNAL_INSTANCES = ["calculate", "constraint", "readonly", "required", "relevant"]
# CURRENT_XFORMS_VERSION = "1.0.0"
# DEPRECATED_DEVICE_ID_METADATA_FIELDS = ["subscriberid", "simserial"]
# AUDIO_QUALITY_VOICE_ONLY = "voice-only"
# AUDIO_QUALITY_LOW = "low"
# AUDIO_QUALITY_NORMAL = "normal"
# AUDIO_QUALITY_EXTERNAL = "external"
# EXTERNAL_CHOICES_ITEMSET_REF_LABEL = "label"
# EXTERNAL_CHOICES_ITEMSET_REF_VALUE = "name"
# ROW_FORMAT_STRING: str = "[row : %s]"
# 
# Path: pyxform/errors.py
# class PyXFormError(Exception):
#     """Common base class for pyxform exceptions."""
# 
#     pass


import csv
import datetime
import re
import openpyxl
import xlrd
from collections import OrderedDict
from functools import reduce
from io import StringIO
from zipfile import BadZipFile
from xlrd import XLRDError
from xlrd.xldate import XLDateAmbiguous
from pyxform import constants
from pyxform.errors import PyXFormError

        result = []
        for row in sheet.iter_rows(min_row=2):
            row_dict = OrderedDict()
            for column, key in enumerate(column_header_list):
                if key is None:
                    continue

                value = row[column].value
                if isinstance(value, str):
                    value = value.strip()

                if not is_empty(value):
                    row_dict[key] = xlsx_value_to_str(value)

            result.append(row_dict)

        column_header_list = [key for key in column_header_list if key is not None]
        return result, _list_to_dict_list(column_header_list)

    result = OrderedDict()
    for sheetname in workbook.sheetnames:
        sheet = workbook[sheetname]
        # Note that the sheet exists but do no further processing here.
        result[sheetname] = []
        # Do not process sheets that have nothing to do with XLSForm.
        if sheetname not in constants.SUPPORTED_SHEET_NAMES:
            if len(workbook.sheetnames) == 1:
                (
                    result[constants.SURVEY],

# Path: pyxform/constants.py
# TYPE = "type"
# TITLE = "title"
# NAME = "name"
# ID_STRING = "id_string"
# SMS_KEYWORD = "sms_keyword"
# SMS_FIELD = "sms_field"
# SMS_OPTION = "sms_option"
# SMS_SEPARATOR = "sms_separator"
# SMS_ALLOW_MEDIA = "sms_allow_media"
# SMS_DATE_FORMAT = "sms_date_format"
# SMS_DATETIME_FORMAT = "sms_datetime_format"
# SMS_RESPONSE = "sms_response"
# COMPACT_PREFIX = "prefix"
# COMPACT_DELIMITER = "delimiter"
# COMPACT_TAG = "compact_tag"
# VERSION = "version"
# PUBLIC_KEY = "public_key"
# SUBMISSION_URL = "submission_url"
# AUTO_SEND = "auto_send"
# AUTO_DELETE = "auto_delete"
# DEFAULT_LANGUAGE_KEY = "default_language"
# DEFAULT_LANGUAGE_VALUE = "default"
# LABEL = "label"
# HINT = "hint"
# STYLE = "style"
# ATTRIBUTE = "attribute"
# ALLOW_CHOICE_DUPLICATES = "allow_choice_duplicates"
# BIND = (
#     "bind"  # TODO: What should I do with the nested types? (readonly and relevant) # noqa
# )
# MEDIA = "media"
# CONTROL = "control"
# APPEARANCE = "appearance"
# LOOP = "loop"
# COLUMNS = "columns"
# REPEAT = "repeat"
# GROUP = "group"
# CHILDREN = "children"
# SELECT_ONE = "select one"
# SELECT_ALL_THAT_APPLY = "select all that apply"
# RANK = "rank"
# CHOICES = "choices"
# LIST_NAME = "list name"
# CASCADING_SELECT = "cascading_select"
# TABLE_LIST = "table-list"  # hyphenated because it goes in appearance, and convention for appearance column is dashes # noqa
# FIELD_LIST = "field-list"
# SURVEY = "survey"
# SETTINGS = "settings"
# EXTERNAL_CHOICES = "external_choices"
# OSM = "osm"
# OSM_TYPE = "binary"
# NAMESPACES = "namespaces"
# SUPPORTED_SHEET_NAMES = [
#     SURVEY,
#     CHOICES,
#     SETTINGS,
#     EXTERNAL_CHOICES,
#     OSM,
# ]
# XLS_EXTENSIONS = [".xls"]
# XLSX_EXTENSIONS = [".xlsx", ".xlsm"]
# SUPPORTED_FILE_EXTENSIONS = XLS_EXTENSIONS + XLSX_EXTENSIONS
# LOCATION_PRIORITY = "location-priority"
# LOCATION_MIN_INTERVAL = "location-min-interval"
# LOCATION_MAX_AGE = "location-max-age"
# TRACK_CHANGES = "track-changes"
# IDENTIFY_USER = "identify-user"
# TRACK_CHANGES_REASONS = "track-changes-reasons"
# EXTERNAL_INSTANCES = ["calculate", "constraint", "readonly", "required", "relevant"]
# CURRENT_XFORMS_VERSION = "1.0.0"
# DEPRECATED_DEVICE_ID_METADATA_FIELDS = ["subscriberid", "simserial"]
# AUDIO_QUALITY_VOICE_ONLY = "voice-only"
# AUDIO_QUALITY_LOW = "low"
# AUDIO_QUALITY_NORMAL = "normal"
# AUDIO_QUALITY_EXTERNAL = "external"
# EXTERNAL_CHOICES_ITEMSET_REF_LABEL = "label"
# EXTERNAL_CHOICES_ITEMSET_REF_VALUE = "name"
# ROW_FORMAT_STRING: str = "[row : %s]"
# 
# Path: pyxform/errors.py
# class PyXFormError(Exception):
#     """Common base class for pyxform exceptions."""
# 
#     pass


# Path: pyxform/xls2json_backends.py
import csv
import datetime
import re
import openpyxl
import xlrd
from collections import OrderedDict
from functools import reduce
from io import StringIO
from zipfile import BadZipFile
from xlrd import XLRDError
from xlrd.xldate import XLDateAmbiguous
from pyxform import constants
from pyxform.errors import PyXFormError


        result = []
        for row in sheet.iter_rows(min_row=2):
            row_dict = OrderedDict()
            for column, key in enumerate(column_header_list):
                if key is None:
                    continue

                value = row[column].value
                if isinstance(value, str):
                    value = value.strip()

                if not is_empty(value):
                    row_dict[key] = xlsx_value_to_str(value)

            result.append(row_dict)

        column_header_list = [key for key in column_header_list if key is not None]
        return result, _list_to_dict_list(column_header_list)

    result = OrderedDict()
    for sheetname in workbook.sheetnames:
        sheet = workbook[sheetname]
        # Note that the sheet exists but do no further processing here.
        result[sheetname] = []
        # Do not process sheets that have nothing to do with XLSForm.
        if sheetname not in constants.SUPPORTED_SHEET_NAMES:
            if len(workbook.sheetnames) == 1:
                (
                    result[constants.SURVEY],

                    result[f"{constants.SURVEY}_header"],