# 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
        return [k]
    return []


def xls_to_dict(path_or_file):
    """
    Return a Python dictionary with a key for each worksheet
    name. For each sheet there is a list of dictionaries, each
    dictionary corresponds to a single row in the worksheet. A
    dictionary has keys taken from the column headers and values
    equal to the cell value for that row and column.
    All the keys and leaf elements are unicode text.
    """
    try:
        if isinstance(path_or_file, str):
            workbook = xlrd.open_workbook(filename=path_or_file)
        else:
            workbook = xlrd.open_workbook(file_contents=path_or_file.read())
    except XLRDError as error:
        raise PyXFormError("Error reading .xls file: %s" % error)

    def xls_to_dict_normal_sheet(sheet):
        def iswhitespace(string):
            return isinstance(string, str) and len(string.strip()) == 0

        # Check for duplicate column headers
        column_header_list = list()
        for column in range(0, sheet.ncols):
            column_header = sheet.cell_value(0, column)
            if column_header in column_header_list:

# 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

        return [k]
    return []


def xls_to_dict(path_or_file):
    """
    Return a Python dictionary with a key for each worksheet
    name. For each sheet there is a list of dictionaries, each
    dictionary corresponds to a single row in the worksheet. A
    dictionary has keys taken from the column headers and values
    equal to the cell value for that row and column.
    All the keys and leaf elements are unicode text.
    """
    try:
        if isinstance(path_or_file, str):
            workbook = xlrd.open_workbook(filename=path_or_file)
        else:
            workbook = xlrd.open_workbook(file_contents=path_or_file.read())
    except XLRDError as error:
        raise PyXFormError("Error reading .xls file: %s" % error)

    def xls_to_dict_normal_sheet(sheet):
        def iswhitespace(string):
            return isinstance(string, str) and len(string.strip()) == 0

        # Check for duplicate column headers
        column_header_list = list()
        for column in range(0, sheet.ncols):
            column_header = sheet.cell_value(0, column)
            if column_header in column_header_list:

                raise PyXFormError("Duplicate column header: %s" % column_header)