diff --git a/.circleci/config.yml b/.circleci/config.yml
index e8ef335ba..ac0fd2e02 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -21,6 +21,14 @@ jobs:
command: |
sudo apt-get install -y openjdk-8-jre-headless
sudo sed -i -e '/^assistive_technologies=/s/^/#/' /etc/java-*-openjdk/accessibility.properties
+ - run:
+ name: Test code is well formatted
+ command: |
+ if ! type /home/circleci/.local/bin/black > /dev/null; then
+ echo "Black is not supported in this python version :("
+ else
+ /home/circleci/.local/bin/black --target-version=py27 pyxform --check --quiet || (echo 'The source code could use a bit more black.' && exit 1)
+ fi
- run:
name: Run tests
command: |
diff --git a/.gitignore b/.gitignore
index c280cd289..8faf9b55f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,3 +24,4 @@ env
# ignore pypi manifest
MANIFEST
+pyxform/tests/test_output/
diff --git a/README.rst b/README.rst
index aa7ef4877..e99cdf527 100644
--- a/README.rst
+++ b/README.rst
@@ -13,6 +13,9 @@ pyxform v0.13.x
.. |codecov| image:: https://codecov.io/github/XLSForm/pyxform/branch/master/graph/badge.svg
:target: https://codecov.io/github/XLSForm/pyxform
+.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
+ :target: https://github.com/python/black
+
pyxform is a Python library that makes writing XForms for ODK Collect and enketo
easy by converting XLS(X) spreadsheets into XForms. It is used as a library in a number of tools including `the ODK online converter `_ and `Ona `_.
diff --git a/pre-commit.sh b/pre-commit.sh
index cf4f4cefe..915d99610 100755
--- a/pre-commit.sh
+++ b/pre-commit.sh
@@ -8,8 +8,8 @@ if [ -n "$FILES" ]; then
fi
if [ -n "$FILES" ]; then
- if black --target-version=py36 $FILES; then
- touch .commit
+ if black --target-version=py27 $FILES; then
+ touch .commit
fi
fi
diff --git a/pyxform/__init__.py b/pyxform/__init__.py
index caeebde99..3340937f1 100644
--- a/pyxform/__init__.py
+++ b/pyxform/__init__.py
@@ -1,19 +1,24 @@
+# -*- coding: utf-8 -*-
"""
pyxform is a Python library designed to make authoring XForms for ODK
Collect easy.
"""
-__version__ = '0.13.1'
+__version__ = "0.13.1"
-from pyxform.builder import (SurveyElementBuilder, create_survey, # noqa
- create_survey_element_from_dict,
- create_survey_from_path, create_survey_from_xls)
-from pyxform.instance import SurveyInstance # noqa
-from pyxform.question import (InputQuestion, MultipleChoiceQuestion, # noqa
- Question)
-from pyxform.question_type_dictionary import QUESTION_TYPE_DICT # noqa
-from pyxform.section import Section # noqa
-from pyxform.survey import Survey # noqa
-from pyxform.xls2json import SurveyReader as ExcelSurveyReader # noqa
+from pyxform.builder import (
+ SurveyElementBuilder,
+ create_survey,
+ create_survey_element_from_dict,
+ create_survey_from_path,
+ create_survey_from_xls,
+)
+from pyxform.instance import SurveyInstance
+from pyxform.question import InputQuestion, MultipleChoiceQuestion, Question
+from pyxform.question_type_dictionary import QUESTION_TYPE_DICT
+from pyxform.section import Section
+from pyxform.survey import Survey
+from pyxform.xls2json import SurveyReader as ExcelSurveyReader
# This is what gets imported when someone imports pyxform
+# flake8: noqa
diff --git a/pyxform/aliases.py b/pyxform/aliases.py
index 4dde626b0..34a1f5bc4 100644
--- a/pyxform/aliases.py
+++ b/pyxform/aliases.py
@@ -1,3 +1,8 @@
+# -*- coding: utf-8 -*-
+"""
+Aliases for elements which could mean the same element in XForm but is represented
+differently on the XLSForm.
+"""
from pyxform import constants
# Aliases:
@@ -7,118 +12,116 @@
# which is why self mapped keys are necessary.
control = {
- u"group": constants.GROUP,
- u"lgroup": constants.REPEAT,
- u"repeat": constants.REPEAT,
- u"loop": constants.LOOP,
- u"looped group": constants.REPEAT
+ "group": constants.GROUP,
+ "lgroup": constants.REPEAT,
+ "repeat": constants.REPEAT,
+ "loop": constants.LOOP,
+ "looped group": constants.REPEAT,
}
select = {
- u"add select one prompt using": constants.SELECT_ONE,
- u"add select multiple prompt using": constants.SELECT_ALL_THAT_APPLY,
- u"select all that apply from": constants.SELECT_ALL_THAT_APPLY,
- u"select one from": constants.SELECT_ONE,
- u"select1": constants.SELECT_ONE,
- u"select_one": constants.SELECT_ONE,
- u"select one": constants.SELECT_ONE,
- u"select_multiple": constants.SELECT_ALL_THAT_APPLY,
- u"select all that apply": constants.SELECT_ALL_THAT_APPLY,
- u"select_one_external": u"select one external",
- u"select_one_from_file": constants.SELECT_ONE,
- u"select_multiple_from_file": constants.SELECT_ALL_THAT_APPLY,
- u"select one from file": constants.SELECT_ONE,
- u"select multiple from file": constants.SELECT_ALL_THAT_APPLY,
- u"rank": constants.RANK,
+ "add select one prompt using": constants.SELECT_ONE,
+ "add select multiple prompt using": constants.SELECT_ALL_THAT_APPLY,
+ "select all that apply from": constants.SELECT_ALL_THAT_APPLY,
+ "select one from": constants.SELECT_ONE,
+ "select1": constants.SELECT_ONE,
+ "select_one": constants.SELECT_ONE,
+ "select one": constants.SELECT_ONE,
+ "select_multiple": constants.SELECT_ALL_THAT_APPLY,
+ "select all that apply": constants.SELECT_ALL_THAT_APPLY,
+ "select_one_external": "select one external",
+ "select_one_from_file": constants.SELECT_ONE,
+ "select_multiple_from_file": constants.SELECT_ALL_THAT_APPLY,
+ "select one from file": constants.SELECT_ONE,
+ "select multiple from file": constants.SELECT_ALL_THAT_APPLY,
+ "rank": constants.RANK,
}
cascading = {
- u'cascading select': constants.CASCADING_SELECT,
- u'cascading_select': constants.CASCADING_SELECT,
+ "cascading select": constants.CASCADING_SELECT,
+ "cascading_select": constants.CASCADING_SELECT,
}
settings_header = {
- u"form_title": constants.TITLE,
- u"set form title": constants.TITLE,
- u"form_id": constants.ID_STRING,
- u"sms_keyword": constants.SMS_KEYWORD,
- u"sms_separator": constants.SMS_SEPARATOR,
- u"sms_allow_media": constants.SMS_ALLOW_MEDIA,
- u"sms_date_format": constants.SMS_DATE_FORMAT,
- u"sms_datetime_format": constants.SMS_DATETIME_FORMAT,
-
- u"prefix": constants.COMPACT_PREFIX,
- u"delimiter": constants.COMPACT_DELIMITER,
-
- u"set form id": constants.ID_STRING,
- u"public_key": constants.PUBLIC_KEY,
- u"submission_url": constants.SUBMISSION_URL,
- u"auto_send": constants.AUTO_SEND,
- u"auto_delete": constants.AUTO_DELETE
+ "form_title": constants.TITLE,
+ "set form title": constants.TITLE,
+ "form_id": constants.ID_STRING,
+ "sms_keyword": constants.SMS_KEYWORD,
+ "sms_separator": constants.SMS_SEPARATOR,
+ "sms_allow_media": constants.SMS_ALLOW_MEDIA,
+ "sms_date_format": constants.SMS_DATE_FORMAT,
+ "sms_datetime_format": constants.SMS_DATETIME_FORMAT,
+ "prefix": constants.COMPACT_PREFIX,
+ "delimiter": constants.COMPACT_DELIMITER,
+ "set form id": constants.ID_STRING,
+ "public_key": constants.PUBLIC_KEY,
+ "submission_url": constants.SUBMISSION_URL,
+ "auto_send": constants.AUTO_SEND,
+ "auto_delete": constants.AUTO_DELETE,
}
# TODO: Check on bind prefix approach in json.
# Conversion dictionary from user friendly column names to meaningful values
survey_header = {
- u"Label": u"label",
- u"Name": u"name",
- u"SMS Field": constants.SMS_FIELD,
- u"SMS Option": constants.SMS_OPTION,
- u"SMS Sepatator": constants.SMS_SEPARATOR,
- u"SMS Allow Media": constants.SMS_ALLOW_MEDIA,
- u"SMS Date Format": constants.SMS_DATE_FORMAT,
- u"SMS DateTime Format": constants.SMS_DATETIME_FORMAT,
- u"SMS Response": constants.SMS_RESPONSE,
- u"compact_tag": u"instance::odk:tag", # used for compact representation
- u"Type": u"type",
- u"List_name": u"list_name",
+ "Label": "label",
+ "Name": "name",
+ "SMS Field": constants.SMS_FIELD,
+ "SMS Option": constants.SMS_OPTION,
+ "SMS Sepatator": constants.SMS_SEPARATOR,
+ "SMS Allow Media": constants.SMS_ALLOW_MEDIA,
+ "SMS Date Format": constants.SMS_DATE_FORMAT,
+ "SMS DateTime Format": constants.SMS_DATETIME_FORMAT,
+ "SMS Response": constants.SMS_RESPONSE,
+ "compact_tag": "instance::odk:tag", # used for compact representation
+ "Type": "type",
+ "List_name": "list_name",
# u"repeat_count": u"jr:count", duplicate key
- u"read_only": u"bind::readonly",
- u"readonly": u"bind::readonly",
- u"relevant": u"bind::relevant",
- u"caption": constants.LABEL,
- u"appearance": u"control::appearance", # TODO: this is also an issue
- u"relevance": u"bind::relevant",
- u"required": u"bind::required",
- u"constraint": u"bind::constraint",
- u"constraining message": u"bind::jr:constraintMsg",
- u"constraint message": u"bind::jr:constraintMsg",
- u"constraint_message": u"bind::jr:constraintMsg",
- u"calculation": u"bind::calculate",
- u"calculate": u"bind::calculate",
- u"command": constants.TYPE,
- u"tag": constants.NAME,
- u"value": constants.NAME,
- u"image": u"media::image",
- u"audio": u"media::audio",
- u"video": u"media::video",
- u"count": u"control::jr:count",
- u"repeat_count": u"control::jr:count",
- u"jr:count": u"control::jr:count",
- u"autoplay": u"control::autoplay",
- u"rows": u"control::rows",
+ "read_only": "bind::readonly",
+ "readonly": "bind::readonly",
+ "relevant": "bind::relevant",
+ "caption": constants.LABEL,
+ "appearance": "control::appearance", # TODO: this is also an issue
+ "relevance": "bind::relevant",
+ "required": "bind::required",
+ "constraint": "bind::constraint",
+ "constraining message": "bind::jr:constraintMsg",
+ "constraint message": "bind::jr:constraintMsg",
+ "constraint_message": "bind::jr:constraintMsg",
+ "calculation": "bind::calculate",
+ "calculate": "bind::calculate",
+ "command": constants.TYPE,
+ "tag": constants.NAME,
+ "value": constants.NAME,
+ "image": "media::image",
+ "audio": "media::audio",
+ "video": "media::video",
+ "count": "control::jr:count",
+ "repeat_count": "control::jr:count",
+ "jr:count": "control::jr:count",
+ "autoplay": "control::autoplay",
+ "rows": "control::rows",
# New elements that have to go into itext elements:
- u"noAppErrorString": u"bind::jr:noAppErrorString",
- u"no_app_error_string": u"bind::jr:noAppErrorString",
- u"requiredMsg": u"bind::jr:requiredMsg",
- u"required_message": u"bind::jr:requiredMsg",
- u"required message": u"bind::jr:requiredMsg",
- u"body": u"control",
- u"parameters": u"parameters",
+ "noAppErrorString": "bind::jr:noAppErrorString",
+ "no_app_error_string": "bind::jr:noAppErrorString",
+ "requiredMsg": "bind::jr:requiredMsg",
+ "required_message": "bind::jr:requiredMsg",
+ "required message": "bind::jr:requiredMsg",
+ "body": "control",
+ "parameters": "parameters",
}
list_header = {
- u"caption": constants.LABEL,
- u"list_name": constants.LIST_NAME,
- u"value": constants.NAME,
- u"image": u"media::image",
- u"audio": u"media::audio",
- u"video": u"media::video"
+ "caption": constants.LABEL,
+ "list_name": constants.LIST_NAME,
+ "value": constants.NAME,
+ "image": "media::image",
+ "audio": "media::audio",
+ "video": "media::video",
}
# Note that most of the type aliasing happens in all.xls
_type = {
- u"imei": u"deviceid",
- u"image": u"photo",
- u"add image prompt": u"photo",
- u"add photo prompt": u"photo",
- u"add audio prompt": u"audio",
- u"add video prompt": u"video",
- u"add file prompt": u"file"
+ "imei": "deviceid",
+ "image": "photo",
+ "add image prompt": "photo",
+ "add photo prompt": "photo",
+ "add audio prompt": "audio",
+ "add video prompt": "video",
+ "add file prompt": "file",
}
yes_no = {
"yes": True,
@@ -137,14 +140,12 @@
"false()": False,
}
label_optional_types = [
- u"deviceid",
- u"phonenumber",
- u"simserial",
- u"calculate",
- u"start",
- u"end",
- u"today"
+ "deviceid",
+ "phonenumber",
+ "simserial",
+ "calculate",
+ "start",
+ "end",
+ "today",
]
-osm = {
- u"osm": constants.OSM_TYPE
-}
+osm = {"osm": constants.OSM_TYPE}
diff --git a/pyxform/builder.py b/pyxform/builder.py
index eedb43224..844ef53b1 100644
--- a/pyxform/builder.py
+++ b/pyxform/builder.py
@@ -1,12 +1,22 @@
+# -*- coding: utf-8 -*-
+"""
+Survey builder functionality.
+"""
import copy
import os
from pyxform import file_utils, utils
from pyxform.errors import PyXFormError
from pyxform.external_instance import ExternalInstance
-from pyxform.question import (InputQuestion, MultipleChoiceQuestion,
- OsmUploadQuestion, Question, RangeQuestion,
- TriggerQuestion, UploadQuestion)
+from pyxform.question import (
+ InputQuestion,
+ MultipleChoiceQuestion,
+ OsmUploadQuestion,
+ Question,
+ RangeQuestion,
+ TriggerQuestion,
+ UploadQuestion,
+)
from pyxform.question_type_dictionary import QUESTION_TYPE_DICT
from pyxform.section import GroupedSection, RepeatingSection
from pyxform.survey import Survey
@@ -22,7 +32,7 @@ def copy_json_dict(json_dict):
items = None
if type(json_dict) is list:
- json_dict_copy = [None]*len(json_dict)
+ json_dict_copy = [None] * len(json_dict)
items = enumerate(json_dict)
elif type(json_dict) is dict:
json_dict_copy = {}
@@ -40,30 +50,28 @@ def copy_json_dict(json_dict):
class SurveyElementBuilder(object):
# we use this CLASSES dict to create questions from dictionaries
QUESTION_CLASSES = {
- u"": Question,
- u"input": InputQuestion,
- u"trigger": TriggerQuestion,
- u"select": MultipleChoiceQuestion,
- u"select1": MultipleChoiceQuestion,
- u"odk:rank": MultipleChoiceQuestion,
- u"upload": UploadQuestion,
- u"osm": OsmUploadQuestion,
- u'range': RangeQuestion,
- }
+ "": Question,
+ "input": InputQuestion,
+ "trigger": TriggerQuestion,
+ "select": MultipleChoiceQuestion,
+ "select1": MultipleChoiceQuestion,
+ "odk:rank": MultipleChoiceQuestion,
+ "upload": UploadQuestion,
+ "osm": OsmUploadQuestion,
+ "range": RangeQuestion,
+ }
SECTION_CLASSES = {
- u"group": GroupedSection,
- u"repeat": RepeatingSection,
- u"survey": Survey,
- }
+ "group": GroupedSection,
+ "repeat": RepeatingSection,
+ "survey": Survey,
+ }
def __init__(self, **kwargs):
# I don't know why we would need an explicit none option for
# select alls
self._add_none_option = False
- self.set_sections(
- kwargs.get(u"sections", {})
- )
+ self.set_sections(kwargs.get("sections", {}))
def set_sections(self, sections):
"""
@@ -80,57 +88,60 @@ def create_survey_element_from_dict(self, d):
call it because it corresponds directly with a json object)
to a survey object
"""
- if u"add_none_option" in d:
- self._add_none_option = d[u"add_none_option"]
- if d[u"type"] in self.SECTION_CLASSES:
+ if "add_none_option" in d:
+ self._add_none_option = d["add_none_option"]
+ if d["type"] in self.SECTION_CLASSES:
return self._create_section_from_dict(d)
- elif d[u"type"] == u"loop":
+ elif d["type"] == "loop":
return self._create_loop_from_dict(d)
- elif d[u"type"] == u"include":
- section_name = d[u"name"]
+ elif d["type"] == "include":
+ section_name = d["name"]
if section_name not in self._sections:
- raise PyXFormError("This section has not been included.",
- section_name, self._sections.keys())
+ raise PyXFormError(
+ "This section has not been included.",
+ section_name,
+ self._sections.keys(),
+ )
d = self._sections[section_name]
full_survey = self.create_survey_element_from_dict(d)
return full_survey.children
- elif d[u"type"] == u"xml-external":
+ elif d["type"] == "xml-external":
return ExternalInstance(**d)
else:
return self._create_question_from_dict(
- d, copy_json_dict(QUESTION_TYPE_DICT), self._add_none_option)
+ d, copy_json_dict(QUESTION_TYPE_DICT), self._add_none_option
+ )
@staticmethod
- def _create_question_from_dict(d, question_type_dictionary,
- add_none_option=False):
- question_type_str = d[u"type"]
+ def _create_question_from_dict(d, question_type_dictionary, add_none_option=False):
+ question_type_str = d["type"]
d_copy = d.copy()
# TODO: Keep add none option?
- if add_none_option \
- and question_type_str.startswith(u"select all that apply"):
- SurveyElementBuilder\
- ._add_none_option_to_select_all_that_apply(d_copy)
+ if add_none_option and question_type_str.startswith("select all that apply"):
+ SurveyElementBuilder._add_none_option_to_select_all_that_apply(d_copy)
# Handle or_other on select type questions
- or_other_str = u" or specify other"
+ or_other_str = " or specify other"
if question_type_str.endswith(or_other_str):
- question_type_str = \
- question_type_str[:len(question_type_str) - len(or_other_str)]
+ question_type_str = question_type_str[
+ : len(question_type_str) - len(or_other_str)
+ ]
d_copy["type"] = question_type_str
- SurveyElementBuilder\
- ._add_other_option_to_multiple_choice_question(d_copy)
+ SurveyElementBuilder._add_other_option_to_multiple_choice_question(d_copy)
return [
SurveyElementBuilder._create_question_from_dict(
- d_copy, question_type_dictionary, add_none_option),
- SurveyElementBuilder._create_specify_other_question_from_dict(
- d_copy)]
+ d_copy, question_type_dictionary, add_none_option
+ ),
+ SurveyElementBuilder._create_specify_other_question_from_dict(d_copy),
+ ]
question_class = SurveyElementBuilder._get_question_class(
- question_type_str, question_type_dictionary)
+ question_type_str, question_type_dictionary
+ )
# todo: clean up this spaghetti code
- d_copy[u"question_type_dictionary"] = question_type_dictionary
+ d_copy["question_type_dictionary"] = question_type_dictionary
if question_class:
return question_class(**d_copy)
@@ -140,34 +151,28 @@ def _create_question_from_dict(d, question_type_dictionary,
@staticmethod
def _add_other_option_to_multiple_choice_question(d):
# ideally, we'd just be pulling from children
- choice_list = d.get(u"choices", d.get(u"children", []))
+ choice_list = d.get("choices", d.get("children", []))
if len(choice_list) <= 0:
raise PyXFormError("There should be choices for this question.")
- other_choice = {
- u"name": u"other",
- u"label": u"Other",
- }
+ other_choice = {"name": "other", "label": "Other"}
if other_choice not in choice_list:
choice_list.append(other_choice)
@staticmethod
def _add_none_option_to_select_all_that_apply(d_copy):
- choice_list = d_copy.get(u"choices", d_copy.get(u"children", []))
+ choice_list = d_copy.get("choices", d_copy.get("children", []))
if len(choice_list) <= 0:
raise PyXFormError("There should be choices for this question.")
- none_choice = {
- u"name": u"none",
- u"label": u"None",
- }
+ none_choice = {"name": "none", "label": "None"}
if none_choice not in choice_list:
choice_list.append(none_choice)
- none_constraint = u"(.='none' or not(selected(., 'none')))"
- if u"bind" not in d_copy:
- d_copy[u"bind"] = {}
- if u"constraint" in d_copy[u"bind"]:
- d_copy[u"bind"][u"constraint"] += " and " + none_constraint
+ none_constraint = "(.='none' or not(selected(., 'none')))"
+ if "bind" not in d_copy:
+ d_copy["bind"] = {}
+ if "constraint" in d_copy["bind"]:
+ d_copy["bind"]["constraint"] += " and " + none_constraint
else:
- d_copy[u"bind"][u"constraint"] = none_constraint
+ d_copy["bind"]["constraint"] = none_constraint
@staticmethod
def _get_question_class(question_type_str, question_type_dictionary):
@@ -177,38 +182,36 @@ def _get_question_class(question_type_str, question_type_dictionary):
type_dictionary -> QUESTION_CLASSES
"""
question_type = question_type_dictionary.get(question_type_str, {})
- control_dict = question_type.get(u"control", {})
- control_tag = control_dict.get(u"tag", u"")
- if control_tag == u"upload" \
- and control_dict.get(u"mediatype") == "osm/*":
- control_tag = u"osm"
+ control_dict = question_type.get("control", {})
+ control_tag = control_dict.get("tag", "")
+ if control_tag == "upload" and control_dict.get("mediatype") == "osm/*":
+ control_tag = "osm"
return SurveyElementBuilder.QUESTION_CLASSES[control_tag]
@staticmethod
def _create_specify_other_question_from_dict(d):
kwargs = {
- u"type": u"text",
- u"name": u"%s_other" % d[u"name"],
- u"label": u"Specify other.",
- u"bind": {u"relevant": u"selected(../%s, 'other')" % d[u"name"]},
- }
+ "type": "text",
+ "name": "%s_other" % d["name"],
+ "label": "Specify other.",
+ "bind": {"relevant": "selected(../%s, 'other')" % d["name"]},
+ }
return InputQuestion(**kwargs)
def _create_section_from_dict(self, d):
d_copy = d.copy()
- children = d_copy.pop(u"children", [])
- section_class = self.SECTION_CLASSES[d_copy[u"type"]]
- if d[u'type'] == u'survey' and u'title' not in d:
- d_copy[u'title'] = d[u'name']
+ children = d_copy.pop("children", [])
+ section_class = self.SECTION_CLASSES[d_copy["type"]]
+ if d["type"] == "survey" and "title" not in d:
+ d_copy["title"] = d["name"]
result = section_class(**d_copy)
for child in children:
# Deep copying the child is a hacky solution to the or_other bug.
# I don't know why it works.
# And I hope it doesn't break something else.
# I think the good solution would be to rewrite this class.
- survey_element = self.create_survey_element_from_dict(
- copy.deepcopy(child))
+ survey_element = self.create_survey_element_from_dict(copy.deepcopy(child))
if survey_element:
result.add_children(survey_element)
@@ -220,8 +223,8 @@ def _create_loop_from_dict(self, d):
Returns a GroupedSection
"""
d_copy = d.copy()
- children = d_copy.pop(u"children", [])
- columns = d_copy.pop(u"columns", [])
+ children = d_copy.pop("children", [])
+ columns = d_copy.pop("columns", [])
result = GroupedSection(**d_copy)
# columns is a left over from when this was
@@ -229,17 +232,16 @@ def _create_loop_from_dict(self, d):
for column_dict in columns:
# If this is a none option for a select all that apply
# question then we should skip adding it to the result
- if column_dict[u"name"] == "none":
+ if column_dict["name"] == "none":
continue
column = GroupedSection(**column_dict)
for child in children:
- question_dict = self._name_and_label_substitutions(
- child, column_dict)
+ question_dict = self._name_and_label_substitutions(child, column_dict)
question = self.create_survey_element_from_dict(question_dict)
column.add_child(question)
result.add_child(column)
- if result.name != u"":
+ if result.name != "":
return result
# TODO: Verify that nothing breaks if this returns a list
@@ -249,12 +251,19 @@ def _name_and_label_substitutions(self, question_template, column_headers):
# if the label in column_headers has multiple languages setup a
# dictionary by language to do substitutions.
info_by_lang = {}
- if type(column_headers[u"label"]) == dict:
+ if type(column_headers["label"]) == dict:
info_by_lang = dict(
- [(lang, {u"name": column_headers[u"name"],
- u"label": column_headers[u"label"][lang]})
- for lang in column_headers[u"label"].keys()]
- )
+ [
+ (
+ lang,
+ {
+ "name": column_headers["name"],
+ "label": column_headers["label"][lang],
+ },
+ )
+ for lang in column_headers["label"].keys()
+ ]
+ )
result = question_template.copy()
for key in result.keys():
@@ -263,9 +272,8 @@ def _name_and_label_substitutions(self, question_template, column_headers):
elif type(result[key]) == dict:
result[key] = result[key].copy()
for key2 in result[key].keys():
- if type(column_headers[u"label"]) == dict:
- result[key][key2] %= info_by_lang.get(
- key2, column_headers)
+ if type(column_headers["label"]) == dict:
+ result[key][key2] %= info_by_lang.get(key2, column_headers)
else:
result[key][key2] %= column_headers
return result
@@ -301,12 +309,13 @@ def create_survey_from_xls(path_or_file):
def create_survey(
- name_of_main_section=None, sections=None,
- main_section=None,
- id_string=None,
- title=None,
- default_language=None,
- ):
+ name_of_main_section=None,
+ sections=None,
+ main_section=None,
+ id_string=None,
+ title=None,
+ default_language=None,
+):
"""
name_of_main_section -- a string key used to find the main section in the
sections dict if it is not supplied in the
@@ -324,9 +333,10 @@ def create_survey(
builder.set_sections(sections)
# assert name_of_main_section in sections, name_of_main_section
- if u"id_string" not in main_section:
- main_section[u"id_string"] = name_of_main_section \
- if id_string is None else name_of_main_section
+ if "id_string" not in main_section:
+ main_section["id_string"] = (
+ name_of_main_section if id_string is None else name_of_main_section
+ )
survey = builder.create_survey_element_from_dict(main_section)
# not sure where to do this without repeating ourselves,
@@ -360,9 +370,6 @@ def create_survey_from_path(path, include_directory=False):
else:
main_section_name, section = file_utils.load_file_to_dict(path)
sections = {main_section_name: section}
- pkg = {
- u'name_of_main_section': main_section_name,
- u'sections': sections
- }
+ pkg = {"name_of_main_section": main_section_name, "sections": sections}
return create_survey(**pkg)
diff --git a/pyxform/constants.py b/pyxform/constants.py
index 534d6ebfc..c240eebfb 100644
--- a/pyxform/constants.py
+++ b/pyxform/constants.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""
This file contains constants that correspond with the property names in the
json survey format. (@see json_form_schema.json) These names are to be shared
@@ -8,68 +9,72 @@
# TODO: Replace matching strings in the json2xforms code (builder.py,
# survey.py, survey_element.py, question.py) with these constants
-TYPE = u"type"
-TITLE = u"title"
-NAME = u"name"
-ID_STRING = u"id_string"
-SMS_KEYWORD = u"sms_keyword"
-SMS_FIELD = u"sms_field"
-SMS_OPTION = u"sms_option"
-SMS_SEPARATOR = u"sms_separator"
-SMS_ALLOW_MEDIA = u"sms_allow_media"
-SMS_DATE_FORMAT = u"sms_date_format"
-SMS_DATETIME_FORMAT = u"sms_datetime_format"
-SMS_RESPONSE = u"sms_response"
+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 representation (https://opendatakit.github.io/xforms-spec/#compact-record-representation-(for-sms))
-COMPACT_PREFIX = u"prefix"
-COMPACT_DELIMITER = u"delimiter"
-COMPACT_TAG = u"compact_tag"
+COMPACT_PREFIX = "prefix"
+COMPACT_DELIMITER = "delimiter"
+COMPACT_TAG = "compact_tag"
-VERSION = u"version"
-PUBLIC_KEY = u"public_key"
-SUBMISSION_URL = u"submission_url"
-AUTO_SEND = u"auto_send"
-AUTO_DELETE = u"auto_delete"
-DEFAULT_LANGUAGE = u"default_language"
-LABEL = u"label"
-HINT = u"hint"
-STYLE = u"style"
-ATTRIBUTE = u"attribute"
+VERSION = "version"
+PUBLIC_KEY = "public_key"
+SUBMISSION_URL = "submission_url"
+AUTO_SEND = "auto_send"
+AUTO_DELETE = "auto_delete"
+DEFAULT_LANGUAGE = "default_language"
+LABEL = "label"
+HINT = "hint"
+STYLE = "style"
+ATTRIBUTE = "attribute"
-BIND = u"bind" # TODO: What should I do with the nested types? (readonly and relevant) # noqa
-MEDIA = u"media"
-CONTROL = u"control"
-APPEARANCE = u"appearance"
+BIND = (
+ "bind"
+) # TODO: What should I do with the nested types? (readonly and relevant) # noqa
+MEDIA = "media"
+CONTROL = "control"
+APPEARANCE = "appearance"
-LOOP = u"loop"
-COLUMNS = u"columns"
+LOOP = "loop"
+COLUMNS = "columns"
-REPEAT = u"repeat"
-GROUP = u"group"
-CHILDREN = u"children"
+REPEAT = "repeat"
+GROUP = "group"
+CHILDREN = "children"
-SELECT_ONE = u"select one"
-SELECT_ALL_THAT_APPLY = u"select all that apply"
-RANK = u"rank"
-CHOICES = u"choices"
+SELECT_ONE = "select one"
+SELECT_ALL_THAT_APPLY = "select all that apply"
+RANK = "rank"
+CHOICES = "choices"
# XLS Specific constants
-LIST_NAME = u"list name"
-CASCADING_SELECT = u"cascading_select"
-TABLE_LIST = u"table-list" # hyphenated because it goes in appearance, and convention for appearance column is dashes # noqa
+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
# The following are the possible sheet names:
-SURVEY = u"survey"
-SETTINGS = u"settings"
+SURVEY = "survey"
+SETTINGS = "settings"
# These sheet names are for list sheets
-CHOICES_AND_COLUMNS = u"choices and columns"
-CASCADING_CHOICES = u"cascades"
+CHOICES_AND_COLUMNS = "choices and columns"
+CASCADING_CHOICES = "cascades"
-OSM = u"osm"
-OSM_TYPE = u"binary"
+OSM = "osm"
+OSM_TYPE = "binary"
-NAMESPACES = u"namespaces"
+NAMESPACES = "namespaces"
SUPPORTED_SHEET_NAMES = [
SURVEY,
@@ -80,8 +85,8 @@
SETTINGS,
OSM,
]
-SUPPORTED_FILE_EXTENSIONS = ['.xls', '.xlsx', '.xlsm']
+SUPPORTED_FILE_EXTENSIONS = [".xls", ".xlsx", ".xlsm"]
-LOCATION_PRIORITY = u"location-priority"
-LOCATION_MIN_INTERVAL = u"location-min-interval"
-LOCATION_MAX_AGE = u"location-max-age"
+LOCATION_PRIORITY = "location-priority"
+LOCATION_MIN_INTERVAL = "location-min-interval"
+LOCATION_MAX_AGE = "location-max-age"
diff --git a/pyxform/errors.py b/pyxform/errors.py
index 0d17c1271..606b50707 100644
--- a/pyxform/errors.py
+++ b/pyxform/errors.py
@@ -1,6 +1,16 @@
+# -*- coding: utf-8 -*-
+"""
+Common base classes for pyxform exceptions.
+"""
+
+
class PyXFormError(Exception):
+ """Common base class for pyxform exceptions."""
+
pass
class ValidationError(PyXFormError):
+ """Common base class for pyxform validation exceptions."""
+
pass
diff --git a/pyxform/external_instance.py b/pyxform/external_instance.py
index c2c54caff..1f0838037 100644
--- a/pyxform/external_instance.py
+++ b/pyxform/external_instance.py
@@ -1,9 +1,11 @@
+# -*- coding: utf-8 -*-
+"""
+ExternalInstance class module
+"""
from pyxform.survey_element import SurveyElement
-from pyxform.utils import node
class ExternalInstance(SurveyElement):
-
def xml_control(self):
"""
No-op since there is no associated form control to place under .
diff --git a/pyxform/file_utils.py b/pyxform/file_utils.py
index b52cefc78..e864559de 100644
--- a/pyxform/file_utils.py
+++ b/pyxform/file_utils.py
@@ -1,7 +1,11 @@
-import os
+# -*- coding: utf-8 -*-
+"""
+The pyxform file utility functions.
+"""
import glob
-from pyxform import utils
+import os
+from pyxform import utils
from pyxform.xls2json import SurveyReader
@@ -32,7 +36,8 @@ def collect_compatible_files_in_directory(directory):
create a giant dict out of all the spreadsheets and json forms
in the given directory
"""
- available_files = glob.glob(os.path.join(directory, "*.xls")) + \
- glob.glob(os.path.join(directory, "*.json"))
+ available_files = glob.glob(os.path.join(directory, "*.xls")) + glob.glob(
+ os.path.join(directory, "*.json")
+ )
return dict([load_file_to_dict(f) for f in available_files])
diff --git a/pyxform/instance.py b/pyxform/instance.py
index 73bfe8e43..962a842f8 100644
--- a/pyxform/instance.py
+++ b/pyxform/instance.py
@@ -1,3 +1,7 @@
+# -*- coding: utf-8 -*-
+"""
+SurveyInstance class module.
+"""
from pyxform.xform_instance_parser import parse_xform_instance
@@ -45,20 +49,15 @@ def answer(self, name=None, value=None):
def to_json_dict(self):
children = []
for k, v in self._answers.items():
- children.append({'node_name': k, 'value': v})
- return {
- 'node_name': self._name,
- 'id': self._id,
- 'children': children
- }
+ children.append({"node_name": k, "value": v})
+ return {"node_name": self._name, "id": self._id, "children": children}
def to_xml(self):
"""
A horrible way to do this, but it works (until we need the attributes
pumped out in order, etc)
"""
- open_str = """<%s id="%s">""" % (
- self._name, self._id)
+ open_str = """<%s id="%s">""" % (self._name, self._id)
close_str = """%s>""" % self._name
vals = ""
for k, v in self._answers.items():
@@ -77,6 +76,7 @@ def answers(self):
def import_from_xml(self, xml_string_or_filename):
import os.path
+
if os.path.isfile(xml_string_or_filename):
xml_str = open(xml_string_or_filename).read()
else:
@@ -90,4 +90,7 @@ def __unicode__(self):
placed_count = len(self._answers.keys())
answer_count = orphan_count + placed_count
return "" % (
- answer_count, placed_count, orphan_count)
+ answer_count,
+ placed_count,
+ orphan_count,
+ )
diff --git a/pyxform/instance_info.py b/pyxform/instance_info.py
index b2d339c06..c9cc990d8 100644
--- a/pyxform/instance_info.py
+++ b/pyxform/instance_info.py
@@ -1,3 +1,7 @@
+# -*- coding: utf-8 -*-
+"""
+InstanceInfo class module.
+"""
class InstanceInfo(object):
diff --git a/pyxform/question.py b/pyxform/question.py
index 4f8efd67b..8e083f512 100644
--- a/pyxform/question.py
+++ b/pyxform/question.py
@@ -1,3 +1,7 @@
+# -*- coding: utf-8 -*-
+"""
+XForm Survey element classes for different question types.
+"""
import os.path
from pyxform.errors import PyXFormError
@@ -18,12 +22,12 @@ def validate(self):
def xml_instance(self, **kwargs):
survey = self.get_root()
attributes = {}
- attributes.update(self.get(u"instance", {}))
+ attributes.update(self.get("instance", {}))
for key, value in attributes.items():
attributes[key] = survey.insert_xpaths(value, self)
- if self.get(u"default"):
- return node(self.name, unicode(self.get(u"default")), **attributes)
+ if self.get("default"):
+ return node(self.name, unicode(self.get("default")), **attributes)
return node(self.name, **attributes)
def xml_control(self):
@@ -69,26 +73,26 @@ def xml_control(self):
for key, value in control_dict.items():
control_dict[key] = survey.insert_xpaths(value, self)
control_dict["ref"] = self.get_xpath()
- return node(u"trigger", *self.xml_label_and_hint(), **control_dict)
+ return node("trigger", *self.xml_label_and_hint(), **control_dict)
class UploadQuestion(Question):
def _get_media_type(self):
- return self.control[u"mediatype"]
+ return self.control["mediatype"]
def xml_control(self):
control_dict = self.control
control_dict["ref"] = self.get_xpath()
control_dict["mediatype"] = self._get_media_type()
- return node(u"upload", *self.xml_label_and_hint(), **control_dict)
+ return node("upload", *self.xml_label_and_hint(), **control_dict)
class Option(SurveyElement):
def xml_value(self):
- return node(u"value", self.name)
+ return node("value", self.name)
def xml(self):
- item = node(u"item")
+ item = node("item")
self.xml_label()
item.appendChild(self.xml_label())
item.appendChild(self.xml_value())
@@ -106,8 +110,8 @@ def __init__(self, **kwargs):
# I'm going to try to stick to just choices.
# Aliases in the json format will make it more difficult
# to use going forward.
- choices = list(kwargs_copy.pop(u"choices", [])) + list(
- kwargs_copy.pop(u"children", [])
+ choices = list(kwargs_copy.pop("choices", [])) + list(
+ kwargs_copy.pop("children", [])
)
Question.__init__(self, **kwargs_copy)
for choice in choices:
@@ -126,7 +130,7 @@ def validate(self):
choice.validate()
def xml_control(self):
- assert self.bind[u"type"] in [u"select", u"select1", u"odk:rank"]
+ assert self.bind["type"] in ["select", "select1", "odk:rank"]
survey = self.get_root()
control_dict = self.control.copy()
# Resolve field references in attributes
@@ -164,9 +168,7 @@ def xml_control(self):
nodeset = (
nodeset
+ ", "
- + survey.insert_xpaths(
- params["seed"], self
- ).strip()
+ + survey.insert_xpaths(params["seed"], self).strip()
)
else:
nodeset = nodeset + ", " + params["seed"]
@@ -177,9 +179,7 @@ def xml_control(self):
node("value", ref="name"),
node("label", ref=itemset_label_ref),
]
- result.appendChild(
- node("itemset", *itemset_children, nodeset=nodeset)
- )
+ result.appendChild(node("itemset", *itemset_children, nodeset=nodeset))
else:
for n in [o.xml() for o in self.children]:
result.appendChild(n)
@@ -189,15 +189,13 @@ def xml_control(self):
class SelectOneQuestion(MultipleChoiceQuestion):
def __init__(self, **kwargs):
super(SelectOneQuestion, self).__init__(**kwargs)
- self._dict[self.TYPE] = u"select one"
+ self._dict[self.TYPE] = "select one"
class Tag(SurveyElement):
def __init__(self, **kwargs):
kwargs_copy = kwargs.copy()
- choices = kwargs_copy.pop(u"choices", []) + kwargs_copy.pop(
- u"children", []
- )
+ choices = kwargs_copy.pop("choices", []) + kwargs_copy.pop("children", [])
super(Tag, self).__init__(**kwargs_copy)
@@ -209,7 +207,7 @@ def __init__(self, **kwargs):
self.add_child(option)
def xml(self):
- result = node(u"tag", key=self.name)
+ result = node("tag", key=self.name)
self.xml_label()
result.appendChild(self.xml_label())
for choice in self.children:
@@ -224,7 +222,7 @@ def validate(self):
class OsmUploadQuestion(UploadQuestion):
def __init__(self, **kwargs):
kwargs_copy = kwargs.copy()
- tags = kwargs_copy.pop(u"tags", []) + kwargs_copy.pop(u"children", [])
+ tags = kwargs_copy.pop("tags", []) + kwargs_copy.pop("children", [])
super(OsmUploadQuestion, self).__init__(**kwargs_copy)
@@ -242,7 +240,7 @@ def xml_control(self):
control_dict = self.control
control_dict["ref"] = self.get_xpath()
control_dict["mediatype"] = self._get_media_type()
- result = node(u"upload", *self.xml_label_and_hint(), **control_dict)
+ result = node("upload", *self.xml_label_and_hint(), **control_dict)
for osm_tag in self.children:
result.appendChild(osm_tag.xml())
diff --git a/pyxform/question_type_dictionary.py b/pyxform/question_type_dictionary.py
index ec85370a7..64731caa6 100644
--- a/pyxform/question_type_dictionary.py
+++ b/pyxform/question_type_dictionary.py
@@ -1,857 +1,374 @@
+# -*- coding: utf-8 -*-
+"""
+XForm survey question type mapping dictionary module.
+"""
from pyxform.xls2json import QuestionTypesReader, print_pyobj_to_json
+
def generate_new_dict():
"""
This is just here incase there is ever any need to generate the question
type dictionary from all.xls again.
It shouldn't be called as part of any application.
"""
- path_to_question_types = "/home/nathan/aptana-workspace/pyxform"\
- "/pyxform/question_types/all.xls"
+ path_to_question_types = (
+ "/home/nathan/aptana-workspace/pyxform" "/pyxform/question_types/all.xls"
+ )
json_dict = QuestionTypesReader(path_to_question_types).to_json_dict()
- print_pyobj_to_json(json_dict, 'new_quesiton_type_dict.json')
-
+ print_pyobj_to_json(json_dict, "new_quesiton_type_dict.json")
-QUESTION_TYPE_DICT = \
- {
- "q picture": {
- "control": {
- "tag": "upload",
- "mediatype": "image/*"
- },
- "bind": {
- "type": "binary"
- }
- },
- "photo": {
- "control": {
- "tag": "upload",
- "mediatype": "image/*"
- },
- "bind": {
- "type": "binary"
- }
- },
- "add date time prompt": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "dateTime"
- }
- },
- "add audio prompt": {
- "control": {
- "tag": "upload",
- "mediatype": "audio/*"
- },
- "bind": {
- "type": "binary"
- }
- },
- "q date time": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "dateTime"
- }
- },
- "phonenumber": {
- "bind": {
- "jr:preload": "property",
- "type": "string",
- "jr:preloadParams": "phonenumber"
- }
- },
- "get start time": {
- "bind": {
- "jr:preload": "timestamp",
- "type": "dateTime",
- "jr:preloadParams": "start"
- }
- },
- "add select multiple prompt using": {
- "control": {
- "tag": "select"
- },
- "bind": {
- "type": "select"
- }
- },
- "add note prompt": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "readonly": "true()",
- "type": "string"
- }
- },
- "calculate": {
- "bind": {
- "type": "string"
- }
- },
- "acknowledge": {
- "control": {
- "tag": "trigger"
- },
- "bind": {
- "type": "string"
- }
- },
- "location": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "geopoint"
- }
- },
- "text": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "string"
- }
- },
- "select all that apply from": {
- "control": {
- "tag": "select"
- },
- "bind": {
- "type": "select"
- }
- },
- "simserial": {
- "bind": {
- "jr:preload": "property",
- "type": "string",
- "jr:preloadParams": "simserial"
- }
- },
- "string": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "string"
- }
- },
- "q string": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "string"
- }
- },
- "imei": {
- "bind": {
- "jr:preload": "property",
- "type": "string",
- "jr:preloadParams": "deviceid"
- }
- },
- "integer": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "int"
- }
- },
- "datetime": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "dateTime"
- }
- },
- "q note": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "readonly": "true()",
- "type": "string"
- }
- },
- "subscriber id": {
- "bind": {
- "jr:preload": "property",
- "type": "string",
- "jr:preloadParams": "subscriberid"
- }
- },
- "decimal": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "decimal"
- }
- },
- "dateTime": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "dateTime"
- }
- },
- "q audio": {
- "control": {
- "tag": "upload",
- "mediatype": "audio/*"
- },
- "bind": {
- "type": "binary"
- }
- },
- "q geopoint": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "geopoint"
- }
- },
- "q geoshape": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "geoshape"
- }
- },
- "q geotrace": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "geotrace"
- }
- },
- "q image": {
- "control": {
- "tag": "upload",
- "mediatype": "image/*"
- },
- "bind": {
- "type": "binary"
- }
- },
- "get today": {
- "bind": {
- "jr:preload": "date",
- "type": "date",
- "jr:preloadParams": "today"
- }
- },
- "video": {
- "control": {
- "tag": "upload",
- "mediatype": "video/*"
- },
- "bind": {
- "type": "binary"
- }
- },
- "q acknowledge": {
- "control": {
- "tag": "trigger"
- },
- "bind": {
- "type": "string"
- }
- },
- "add video prompt": {
- "control": {
- "tag": "upload",
- "mediatype": "video/*"
- },
- "bind": {
- "type": "binary"
- }
- },
- "number of days in last month": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "int",
- "constraint": "0 <= . and . <= 31"
- },
- "hint": "Enter a number 0-31."
- },
- "get sim id": {
- "bind": {
- "jr:preload": "property",
- "type": "string",
- "jr:preloadParams": "simserial"
- }
- },
- "q location": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "geopoint"
- }
- },
- "select one": {
- "control": {
- "tag": "select1"
- },
- "bind": {
- "type": "select1"
- }
- },
- "select one external": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "string"
- }
- },
- "add image prompt": {
- "control": {
- "tag": "upload",
- "mediatype": "image/*"
- },
- "bind": {
- "type": "binary"
- }
- },
- "select all that apply": {
- "control": {
- "tag": "select"
- },
- "bind": {
- "type": "select"
- }
- },
- "get end time": {
- "bind": {
- "jr:preload": "timestamp",
- "type": "dateTime",
- "jr:preloadParams": "end"
- }
- },
- "barcode": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "barcode"
- }
- },
- "q video": {
- "control": {
- "tag": "upload",
- "mediatype": "video/*"
- },
- "bind": {
- "type": "binary"
- }
- },
- "geopoint": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "geopoint"
- }
- },
- "geoshape": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "geoshape"
- }
- },
- "geotrace": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "geotrace"
- }
- },
- "select multiple from": {
- "control": {
- "tag": "select"
- },
- "bind": {
- "type": "select"
- }
- },
- "end time": {
- "bind": {
- "jr:preload": "timestamp",
- "type": "dateTime",
- "jr:preloadParams": "end"
- }
- },
- "device id": {
- "bind": {
- "jr:preload": "property",
- "type": "string",
- "jr:preloadParams": "deviceid"
- }
- },
- "subscriberid": {
- "bind": {
- "jr:preload": "property",
- "type": "string",
- "jr:preloadParams": "subscriberid"
- }
- },
- "q barcode": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "barcode"
- }
- },
- "q select": {
- "control": {
- "tag": "select"
- },
- "bind": {
- "type": "select"
- }
- },
- "select one using": {
- "control": {
- "tag": "select1"
- },
- "bind": {
- "type": "select1"
- }
- },
- "rank": {
- "control": {
- "tag": "odk:rank"
- },
- "bind": {
- "type": "odk:rank"
- }
- },
- "image": {
- "control": {
- "tag": "upload",
- "mediatype": "image/*"
- },
- "bind": {
- "type": "binary"
- }
- },
- "q int": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "int"
- }
- },
- "add text prompt": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "string"
- }
- },
- "add date prompt": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "date"
- }
- },
- "q calculate": {
- "bind": {
- "type": "string"
- }
- },
- "start": {
- "bind": {
- "jr:preload": "timestamp",
- "type": "dateTime",
- "jr:preloadParams": "start"
- }
- },
- "trigger": {
- "control": {
- "tag": "trigger"
- }
- },
- "add acknowledge prompt": {
- "control": {
- "tag": "trigger"
- },
- "bind": {
- "type": "string"
- }
- },
- "percentage": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "int",
- "constraint": "0 <= . and . <= 100"
- }
- },
- "get phone number": {
- "bind": {
- "jr:preload": "property",
- "type": "string",
- "jr:preloadParams": "phonenumber"
- }
- },
- "today": {
- "bind": {
- "jr:preload": "date",
- "type": "date",
- "jr:preloadParams": "today"
- }
- },
- "gps": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "geopoint"
- }
- },
- "q date": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "date"
- }
- },
- "sim id": {
- "bind": {
- "jr:preload": "property",
- "type": "string",
- "jr:preloadParams": "simserial"
- }
- },
- "add decimal prompt": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "decimal"
- }
- },
- "number of days in last six months": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "int",
- "constraint": "0 <= . and . <= 183"
- },
- "hint": "Enter a number 0-183."
- },
- "deviceid": {
- "bind": {
- "jr:preload": "property",
- "type": "string",
- "jr:preloadParams": "deviceid"
- }
- },
- "int": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "int"
- }
- },
- "add barcode prompt": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "barcode"
- }
- },
- "select multiple using": {
- "control": {
- "tag": "select"
- },
- "bind": {
- "type": "select"
- }
- },
- "q decimal": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "decimal"
- }
- },
- "end": {
- "bind": {
- "jr:preload": "timestamp",
- "type": "dateTime",
- "jr:preloadParams": "end"
- }
- },
- "add calculate prompt": {
- "bind": {
- "type": "string"
- }
- },
- "add dateTime prompt": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "dateTime"
- }
- },
- "note": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "readonly": "true()",
- "type": "string"
- }
- },
- "add location prompt": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "geopoint"
- }
- },
- "get subscriber id": {
- "bind": {
- "jr:preload": "property",
- "type": "string",
- "jr:preloadParams": "subscriberid"
- }
- },
- "phone number": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "string",
- "constraint": "regex(., '^\\d*$')"
- },
- "hint": "Enter numbers only."
- },
- "get device id": {
- "bind": {
- "jr:preload": "property",
- "type": "string",
- "jr:preloadParams": "deviceid"
- }
- },
- "add integer prompt": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "int"
- }
- },
- "q dateTime": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "dateTime"
- }
- },
- "date": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "date"
- }
- },
- "q select1": {
- "control": {
- "tag": "select1"
- },
- "bind": {
- "type": "select1"
- }
- },
- "start time": {
- "bind": {
- "jr:preload": "timestamp",
- "type": "dateTime",
- "jr:preloadParams": "start"
- }
- },
- "number of days in last year": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "int",
- "constraint": "0 <= . and . <= 365"
- },
- "hint": "Enter a number 0-365."
- },
- "date time": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "dateTime"
- }
- },
- "time": {
- "control": {
- "tag": "input"
- },
- "bind": {
- "type": "time"
- }
- },
- "audio": {
- "control": {
- "tag": "upload",
- "mediatype": "audio/*"
- },
- "bind": {
- "type": "binary"
- }
- },
- "add select one prompt using": {
- "control": {
- "tag": "select1"
- },
- "bind": {
- "type": "select1"
- }
- },
- "hidden": {
- "bind": {
- "type": "string"
- }
- },
- "uri:subscriberid": {
- "bind": {
- "jr:preload": "property",
- "type": "string",
- "jr:preloadParams": "uri:subscriberid"
- }
- },
- "uri:phonenumber": {
- "bind": {
- "jr:preload": "property",
- "type": "string",
- "jr:preloadParams": "uri:phonenumber"
- }
- },
- "uri:simserial": {
- "bind": {
- "jr:preload": "property",
- "type": "string",
- "jr:preloadParams": "uri:simserial"
- }
- },
- "uri:deviceid": {
- "bind": {
- "jr:preload": "property",
- "type": "string",
- "jr:preloadParams": "uri:deviceid"
- }
- },
- "username": {
- "bind": {
- "jr:preload": "property",
- "type": "string",
- "jr:preloadParams": "username"
- }
- },
- "uri:username": {
- "bind": {
- "jr:preload": "property",
- "type": "string",
- "jr:preloadParams": "uri:username"
- }
- },
- "email": {
- "bind": {
- "jr:preload": "property",
- "type": "string",
- "jr:preloadParams": "email"
- }
- },
- "uri:email": {
- "bind": {
- "jr:preload": "property",
- "type": "string",
- "jr:preloadParams": "uri:email"
- }
- },
- "osm": {
- "control": {
- "tag": "upload",
- "mediatype": "osm/*"
- },
- "bind": {
- "type": "binary"
- }
- },
- "file": {
- "control": {
- "tag": "upload",
- "mediatype": "application/*"
- },
- "bind": {
- "type": "binary"
- }
- },
- "add file prompt": {
- "control": {
- "tag": "upload",
- "mediatype": "application/*"
- },
- "bind": {
- "type": "binary"
- }
- },
- "range": {
- "control": {
- "tag": "range"
- },
- "bind": {
- "type": "int"
- }
- },
- "audit": {
- "bind": {
- "type": "binary"
- }
- },
- "xml-external": {
- # Only effect is to add an external instance.
+QUESTION_TYPE_DICT = {
+ "q picture": {
+ "control": {"tag": "upload", "mediatype": "image/*"},
+ "bind": {"type": "binary"},
+ },
+ "photo": {
+ "control": {"tag": "upload", "mediatype": "image/*"},
+ "bind": {"type": "binary"},
+ },
+ "add date time prompt": {"control": {"tag": "input"}, "bind": {"type": "dateTime"}},
+ "add audio prompt": {
+ "control": {"tag": "upload", "mediatype": "audio/*"},
+ "bind": {"type": "binary"},
+ },
+ "q date time": {"control": {"tag": "input"}, "bind": {"type": "dateTime"}},
+ "phonenumber": {
+ "bind": {
+ "jr:preload": "property",
+ "type": "string",
+ "jr:preloadParams": "phonenumber",
+ }
+ },
+ "get start time": {
+ "bind": {
+ "jr:preload": "timestamp",
+ "type": "dateTime",
+ "jr:preloadParams": "start",
+ }
+ },
+ "add select multiple prompt using": {
+ "control": {"tag": "select"},
+ "bind": {"type": "select"},
+ },
+ "add note prompt": {
+ "control": {"tag": "input"},
+ "bind": {"readonly": "true()", "type": "string"},
+ },
+ "calculate": {"bind": {"type": "string"}},
+ "acknowledge": {"control": {"tag": "trigger"}, "bind": {"type": "string"}},
+ "location": {"control": {"tag": "input"}, "bind": {"type": "geopoint"}},
+ "text": {"control": {"tag": "input"}, "bind": {"type": "string"}},
+ "select all that apply from": {
+ "control": {"tag": "select"},
+ "bind": {"type": "select"},
+ },
+ "simserial": {
+ "bind": {
+ "jr:preload": "property",
+ "type": "string",
+ "jr:preloadParams": "simserial",
+ }
+ },
+ "string": {"control": {"tag": "input"}, "bind": {"type": "string"}},
+ "q string": {"control": {"tag": "input"}, "bind": {"type": "string"}},
+ "imei": {
+ "bind": {
+ "jr:preload": "property",
+ "type": "string",
+ "jr:preloadParams": "deviceid",
+ }
+ },
+ "integer": {"control": {"tag": "input"}, "bind": {"type": "int"}},
+ "datetime": {"control": {"tag": "input"}, "bind": {"type": "dateTime"}},
+ "q note": {
+ "control": {"tag": "input"},
+ "bind": {"readonly": "true()", "type": "string"},
+ },
+ "subscriber id": {
+ "bind": {
+ "jr:preload": "property",
+ "type": "string",
+ "jr:preloadParams": "subscriberid",
+ }
+ },
+ "decimal": {"control": {"tag": "input"}, "bind": {"type": "decimal"}},
+ "dateTime": {"control": {"tag": "input"}, "bind": {"type": "dateTime"}},
+ "q audio": {
+ "control": {"tag": "upload", "mediatype": "audio/*"},
+ "bind": {"type": "binary"},
+ },
+ "q geopoint": {"control": {"tag": "input"}, "bind": {"type": "geopoint"}},
+ "q geoshape": {"control": {"tag": "input"}, "bind": {"type": "geoshape"}},
+ "q geotrace": {"control": {"tag": "input"}, "bind": {"type": "geotrace"}},
+ "q image": {
+ "control": {"tag": "upload", "mediatype": "image/*"},
+ "bind": {"type": "binary"},
+ },
+ "get today": {
+ "bind": {"jr:preload": "date", "type": "date", "jr:preloadParams": "today"}
+ },
+ "video": {
+ "control": {"tag": "upload", "mediatype": "video/*"},
+ "bind": {"type": "binary"},
+ },
+ "q acknowledge": {"control": {"tag": "trigger"}, "bind": {"type": "string"}},
+ "add video prompt": {
+ "control": {"tag": "upload", "mediatype": "video/*"},
+ "bind": {"type": "binary"},
+ },
+ "number of days in last month": {
+ "control": {"tag": "input"},
+ "bind": {"type": "int", "constraint": "0 <= . and . <= 31"},
+ "hint": "Enter a number 0-31.",
+ },
+ "get sim id": {
+ "bind": {
+ "jr:preload": "property",
+ "type": "string",
+ "jr:preloadParams": "simserial",
+ }
+ },
+ "q location": {"control": {"tag": "input"}, "bind": {"type": "geopoint"}},
+ "select one": {"control": {"tag": "select1"}, "bind": {"type": "select1"}},
+ "select one external": {"control": {"tag": "input"}, "bind": {"type": "string"}},
+ "add image prompt": {
+ "control": {"tag": "upload", "mediatype": "image/*"},
+ "bind": {"type": "binary"},
+ },
+ "select all that apply": {"control": {"tag": "select"}, "bind": {"type": "select"}},
+ "get end time": {
+ "bind": {
+ "jr:preload": "timestamp",
+ "type": "dateTime",
+ "jr:preloadParams": "end",
+ }
+ },
+ "barcode": {"control": {"tag": "input"}, "bind": {"type": "barcode"}},
+ "q video": {
+ "control": {"tag": "upload", "mediatype": "video/*"},
+ "bind": {"type": "binary"},
+ },
+ "geopoint": {"control": {"tag": "input"}, "bind": {"type": "geopoint"}},
+ "geoshape": {"control": {"tag": "input"}, "bind": {"type": "geoshape"}},
+ "geotrace": {"control": {"tag": "input"}, "bind": {"type": "geotrace"}},
+ "select multiple from": {"control": {"tag": "select"}, "bind": {"type": "select"}},
+ "end time": {
+ "bind": {
+ "jr:preload": "timestamp",
+ "type": "dateTime",
+ "jr:preloadParams": "end",
+ }
+ },
+ "device id": {
+ "bind": {
+ "jr:preload": "property",
+ "type": "string",
+ "jr:preloadParams": "deviceid",
+ }
+ },
+ "subscriberid": {
+ "bind": {
+ "jr:preload": "property",
+ "type": "string",
+ "jr:preloadParams": "subscriberid",
+ }
+ },
+ "q barcode": {"control": {"tag": "input"}, "bind": {"type": "barcode"}},
+ "q select": {"control": {"tag": "select"}, "bind": {"type": "select"}},
+ "select one using": {"control": {"tag": "select1"}, "bind": {"type": "select1"}},
+ "rank": {"control": {"tag": "odk:rank"}, "bind": {"type": "odk:rank"}},
+ "image": {
+ "control": {"tag": "upload", "mediatype": "image/*"},
+ "bind": {"type": "binary"},
+ },
+ "q int": {"control": {"tag": "input"}, "bind": {"type": "int"}},
+ "add text prompt": {"control": {"tag": "input"}, "bind": {"type": "string"}},
+ "add date prompt": {"control": {"tag": "input"}, "bind": {"type": "date"}},
+ "q calculate": {"bind": {"type": "string"}},
+ "start": {
+ "bind": {
+ "jr:preload": "timestamp",
+ "type": "dateTime",
+ "jr:preloadParams": "start",
+ }
+ },
+ "trigger": {"control": {"tag": "trigger"}},
+ "add acknowledge prompt": {
+ "control": {"tag": "trigger"},
+ "bind": {"type": "string"},
+ },
+ "percentage": {
+ "control": {"tag": "input"},
+ "bind": {"type": "int", "constraint": "0 <= . and . <= 100"},
+ },
+ "get phone number": {
+ "bind": {
+ "jr:preload": "property",
+ "type": "string",
+ "jr:preloadParams": "phonenumber",
+ }
+ },
+ "today": {
+ "bind": {"jr:preload": "date", "type": "date", "jr:preloadParams": "today"}
+ },
+ "gps": {"control": {"tag": "input"}, "bind": {"type": "geopoint"}},
+ "q date": {"control": {"tag": "input"}, "bind": {"type": "date"}},
+ "sim id": {
+ "bind": {
+ "jr:preload": "property",
+ "type": "string",
+ "jr:preloadParams": "simserial",
+ }
+ },
+ "add decimal prompt": {"control": {"tag": "input"}, "bind": {"type": "decimal"}},
+ "number of days in last six months": {
+ "control": {"tag": "input"},
+ "bind": {"type": "int", "constraint": "0 <= . and . <= 183"},
+ "hint": "Enter a number 0-183.",
+ },
+ "deviceid": {
+ "bind": {
+ "jr:preload": "property",
+ "type": "string",
+ "jr:preloadParams": "deviceid",
+ }
+ },
+ "int": {"control": {"tag": "input"}, "bind": {"type": "int"}},
+ "add barcode prompt": {"control": {"tag": "input"}, "bind": {"type": "barcode"}},
+ "select multiple using": {"control": {"tag": "select"}, "bind": {"type": "select"}},
+ "q decimal": {"control": {"tag": "input"}, "bind": {"type": "decimal"}},
+ "end": {
+ "bind": {
+ "jr:preload": "timestamp",
+ "type": "dateTime",
+ "jr:preloadParams": "end",
+ }
+ },
+ "add calculate prompt": {"bind": {"type": "string"}},
+ "add dateTime prompt": {"control": {"tag": "input"}, "bind": {"type": "dateTime"}},
+ "note": {
+ "control": {"tag": "input"},
+ "bind": {"readonly": "true()", "type": "string"},
+ },
+ "add location prompt": {"control": {"tag": "input"}, "bind": {"type": "geopoint"}},
+ "get subscriber id": {
+ "bind": {
+ "jr:preload": "property",
+ "type": "string",
+ "jr:preloadParams": "subscriberid",
+ }
+ },
+ "phone number": {
+ "control": {"tag": "input"},
+ "bind": {"type": "string", "constraint": "regex(., '^\\d*$')"},
+ "hint": "Enter numbers only.",
+ },
+ "get device id": {
+ "bind": {
+ "jr:preload": "property",
+ "type": "string",
+ "jr:preloadParams": "deviceid",
+ }
+ },
+ "add integer prompt": {"control": {"tag": "input"}, "bind": {"type": "int"}},
+ "q dateTime": {"control": {"tag": "input"}, "bind": {"type": "dateTime"}},
+ "date": {"control": {"tag": "input"}, "bind": {"type": "date"}},
+ "q select1": {"control": {"tag": "select1"}, "bind": {"type": "select1"}},
+ "start time": {
+ "bind": {
+ "jr:preload": "timestamp",
+ "type": "dateTime",
+ "jr:preloadParams": "start",
+ }
+ },
+ "number of days in last year": {
+ "control": {"tag": "input"},
+ "bind": {"type": "int", "constraint": "0 <= . and . <= 365"},
+ "hint": "Enter a number 0-365.",
+ },
+ "date time": {"control": {"tag": "input"}, "bind": {"type": "dateTime"}},
+ "time": {"control": {"tag": "input"}, "bind": {"type": "time"}},
+ "audio": {
+ "control": {"tag": "upload", "mediatype": "audio/*"},
+ "bind": {"type": "binary"},
+ },
+ "add select one prompt using": {
+ "control": {"tag": "select1"},
+ "bind": {"type": "select1"},
+ },
+ "hidden": {"bind": {"type": "string"}},
+ "uri:subscriberid": {
+ "bind": {
+ "jr:preload": "property",
+ "type": "string",
+ "jr:preloadParams": "uri:subscriberid",
+ }
+ },
+ "uri:phonenumber": {
+ "bind": {
+ "jr:preload": "property",
+ "type": "string",
+ "jr:preloadParams": "uri:phonenumber",
+ }
+ },
+ "uri:simserial": {
+ "bind": {
+ "jr:preload": "property",
+ "type": "string",
+ "jr:preloadParams": "uri:simserial",
+ }
+ },
+ "uri:deviceid": {
+ "bind": {
+ "jr:preload": "property",
+ "type": "string",
+ "jr:preloadParams": "uri:deviceid",
+ }
+ },
+ "username": {
+ "bind": {
+ "jr:preload": "property",
+ "type": "string",
+ "jr:preloadParams": "username",
+ }
+ },
+ "uri:username": {
+ "bind": {
+ "jr:preload": "property",
+ "type": "string",
+ "jr:preloadParams": "uri:username",
+ }
+ },
+ "email": {
+ "bind": {
+ "jr:preload": "property",
+ "type": "string",
+ "jr:preloadParams": "email",
+ }
+ },
+ "uri:email": {
+ "bind": {
+ "jr:preload": "property",
+ "type": "string",
+ "jr:preloadParams": "uri:email",
}
- }
+ },
+ "osm": {
+ "control": {"tag": "upload", "mediatype": "osm/*"},
+ "bind": {"type": "binary"},
+ },
+ "file": {
+ "control": {"tag": "upload", "mediatype": "application/*"},
+ "bind": {"type": "binary"},
+ },
+ "add file prompt": {
+ "control": {"tag": "upload", "mediatype": "application/*"},
+ "bind": {"type": "binary"},
+ },
+ "range": {"control": {"tag": "range"}, "bind": {"type": "int"}},
+ "audit": {"bind": {"type": "binary"}},
+ "xml-external": {
+ # Only effect is to add an external instance.
+ },
+}
diff --git a/pyxform/section.py b/pyxform/section.py
index 8e58b1bee..a8a93dc20 100644
--- a/pyxform/section.py
+++ b/pyxform/section.py
@@ -1,3 +1,7 @@
+# -*- coding: utf-8 -*-
+"""
+Section survey element module.
+"""
from pyxform.errors import PyXFormError
from pyxform.external_instance import ExternalInstance
from pyxform.question import SurveyElement
@@ -19,8 +23,8 @@ def _validate_uniqueness_of_element_names(self):
if any(element.name.lower() == s.lower() for s in element_slugs):
raise PyXFormError(
"There are more than one survey elements named '%s' "
- "(case-insensitive) in the section named '%s'." %
- (element.name.lower(), self.name)
+ "(case-insensitive) in the section named '%s'."
+ % (element.name.lower(), self.name)
)
element_slugs.append(element.name)
@@ -30,14 +34,14 @@ def xml_instance(self, **kwargs):
"""
attributes = {}
attributes.update(kwargs)
- attributes.update(self.get(u'instance', {}))
+ attributes.update(self.get("instance", {}))
survey = self.get_root()
# Resolve field references in attributes
for key, value in attributes.items():
attributes[key] = survey.insert_xpaths(value, self)
result = node(self.name, **attributes)
for child in self.children:
- if child.get(u"flat"):
+ if child.get("flat"):
for grandchild in child.xml_instance_array():
result.appendChild(grandchild)
elif isinstance(child, ExternalInstance):
@@ -51,7 +55,7 @@ def xml_instance_array(self):
This method is used for generating flat instances.
"""
for child in self.children:
- if child.get(u"flat"):
+ if child.get("flat"):
for grandchild in child.xml_instance_array():
yield grandchild
else:
@@ -88,19 +92,15 @@ def xml_control(self):
# Resolve field references in attributes
for key, value in control_dict.items():
control_dict[key] = survey.insert_xpaths(value, self)
- repeat_node = node(u"repeat", nodeset=self.get_xpath(), **control_dict)
+ repeat_node = node("repeat", nodeset=self.get_xpath(), **control_dict)
for n in Section.xml_control(self):
repeat_node.appendChild(n)
label = self.xml_label()
if label:
- return node(
- u"group", self.xml_label(), repeat_node,
- ref=self.get_xpath()
- )
- return node(u"group", repeat_node, ref=self.get_xpath(),
- **self.control)
+ return node("group", self.xml_label(), repeat_node, ref=self.get_xpath())
+ return node("group", repeat_node, ref=self.get_xpath(), **self.control)
# I'm anal about matching function signatures when overriding a function,
# but there's no reason for kwargs to be an argument
@@ -140,27 +140,26 @@ def xml_control(self):
for key, value in attributes.items():
attributes[key] = survey.insert_xpaths(value, self)
- if not self.get('flat'):
- attributes['ref'] = self.get_xpath()
+ if not self.get("flat"):
+ attributes["ref"] = self.get_xpath()
- if 'label' in self and len(self['label']) > 0:
+ if "label" in self and len(self["label"]) > 0:
children.append(self.xml_label())
for n in Section.xml_control(self):
children.append(n)
- if u"appearance" in control_dict:
- attributes['appearance'] = control_dict['appearance']
+ if "appearance" in control_dict:
+ attributes["appearance"] = control_dict["appearance"]
- if u"intent" in control_dict:
+ if "intent" in control_dict:
survey = self.get_root()
- attributes['intent'] = survey.insert_xpaths(control_dict['intent'],
- self)
+ attributes["intent"] = survey.insert_xpaths(control_dict["intent"], self)
- return node(u"group", *children, **attributes)
+ return node("group", *children, **attributes)
def to_json_dict(self):
# This is quite hacky, might want to think about a smart way
# to approach this problem.
result = super(GroupedSection, self).to_json_dict()
- result[u"type"] = u"group"
+ result["type"] = "group"
return result
diff --git a/pyxform/survey.py b/pyxform/survey.py
index bd9d9d944..2ccc68543 100644
--- a/pyxform/survey.py
+++ b/pyxform/survey.py
@@ -2,6 +2,8 @@
"""
Survey module with XForm Survey objects and utility functions.
"""
+from __future__ import print_function
+
import codecs
import os
import re
@@ -18,8 +20,14 @@
from pyxform.question import Question
from pyxform.section import Section
from pyxform.survey_element import SurveyElement
-from pyxform.utils import (NSMAP, PatchedText, basestring,
- get_languages_with_bad_tags, node, unicode)
+from pyxform.utils import (
+ NSMAP,
+ PatchedText,
+ basestring,
+ get_languages_with_bad_tags,
+ node,
+ unicode,
+)
from pyxform.validators import enketo_validate, odk_validate
try:
@@ -44,7 +52,7 @@ def is_parent_a_repeat(survey, xpath):
Returns the XPATH of the first repeat of the given xpath in the survey,
otherwise False will be returned.
"""
- parent_xpath = '/'.join(xpath.split('/')[:-1])
+ parent_xpath = "/".join(xpath.split("/")[:-1])
if not parent_xpath:
return False
@@ -70,22 +78,22 @@ def share_same_repeat_parent(survey, xpath, context_xpath):
context_parent = is_parent_a_repeat(survey, context_xpath)
xpath_parent = is_parent_a_repeat(survey, xpath)
if context_parent and xpath_parent and xpath_parent in context_parent:
- context_parts = context_xpath[len(xpath_parent) + 1:].split('/')
+ context_parts = context_xpath[len(xpath_parent) + 1 :].split("/")
parts = []
steps = 1
- remainder_xpath = xpath[len(xpath_parent):]
- xpath_parts = xpath[len(xpath_parent) + 1:].split('/')
+ remainder_xpath = xpath[len(xpath_parent) :]
+ xpath_parts = xpath[len(xpath_parent) + 1 :].split("/")
for index, item in enumerate(context_parts[:-1]):
try:
- if xpath[len(context_parent) + 1:].split('/')[index] != item:
+ if xpath[len(context_parent) + 1 :].split("/")[index] != item:
steps = len(context_parts[index:])
parts = xpath_parts[index:]
break
else:
- parts = remainder_xpath.split('/')[index + 2:]
+ parts = remainder_xpath.split("/")[index + 2 :]
except IndexError:
- steps = len(context_parts[index - 1:])
- parts = xpath_parts[index - 1:]
+ steps = len(context_parts[index - 1 :])
+ parts = xpath_parts[index - 1 :]
break
return (steps, "/" + "/".join(parts) if parts else remainder_xpath)
@@ -101,39 +109,37 @@ class Survey(Section):
FIELDS = Section.FIELDS.copy()
FIELDS.update(
{
- u"_xpath": dict,
- u"_created": datetime.now, # This can't be dumped to json
- u"title": unicode,
- u"id_string": unicode,
- u"sms_keyword": unicode,
- u"sms_separator": unicode,
- u"sms_allow_media": bool,
- u"sms_date_format": unicode,
- u"sms_datetime_format": unicode,
- u"sms_response": unicode,
-
+ "_xpath": dict,
+ "_created": datetime.now, # This can't be dumped to json
+ "title": unicode,
+ "id_string": unicode,
+ "sms_keyword": unicode,
+ "sms_separator": unicode,
+ "sms_allow_media": bool,
+ "sms_date_format": unicode,
+ "sms_datetime_format": unicode,
+ "sms_response": unicode,
constants.COMPACT_PREFIX: unicode,
constants.COMPACT_DELIMITER: unicode,
-
- u"file_name": unicode,
- u"default_language": unicode,
- u"_translations": dict,
- u"submission_url": unicode,
- u"auto_send": unicode,
- u"auto_delete": unicode,
- u"public_key": unicode,
- u"instance_xmlns": unicode,
- u"version": unicode,
- u"choices": dict,
- u"style": unicode,
- u"attribute": dict,
- u"namespaces": unicode,
+ "file_name": unicode,
+ "default_language": unicode,
+ "_translations": dict,
+ "submission_url": unicode,
+ "auto_send": unicode,
+ "auto_delete": unicode,
+ "public_key": unicode,
+ "instance_xmlns": unicode,
+ "version": unicode,
+ "choices": dict,
+ "style": unicode,
+ "attribute": dict,
+ "namespaces": unicode,
}
) # yapf: disable
def validate(self):
- if self.id_string in [None, 'None']:
- raise PyXFormError('Survey cannot have an empty id_string')
+ if self.id_string in [None, "None"]:
+ raise PyXFormError("Survey cannot have an empty id_string")
super(Survey, self).validate()
self._validate_uniqueness_of_section_names()
@@ -143,8 +149,8 @@ def _validate_uniqueness_of_section_names(self):
if isinstance(element, Section):
if element.name in section_names:
raise PyXFormError(
- "There are two sections with the name %s." %
- element.name)
+ "There are two sections with the name %s." % element.name
+ )
section_names.append(element.name)
def get_nsmap(self):
@@ -153,15 +159,21 @@ def get_nsmap(self):
if namespaces and isinstance(namespaces, basestring):
nslist = [
- ns.split('=') for ns in namespaces.split()
- if len(ns.split('=')) == 2 and ns.split('=')[0] != ''
+ ns.split("=")
+ for ns in namespaces.split()
+ if len(ns.split("=")) == 2 and ns.split("=")[0] != ""
]
- xmlns = u'xmlns:'
+ xmlns = "xmlns:"
nsmap = NSMAP.copy()
- nsmap.update(dict([
- (xmlns + k, v.replace('"', '').replace("'", ""))
- for k, v in nslist if xmlns + k not in nsmap
- ]))
+ nsmap.update(
+ dict(
+ [
+ (xmlns + k, v.replace('"', "").replace("'", ""))
+ for k, v in nslist
+ if xmlns + k not in nsmap
+ ]
+ )
+ )
return nsmap
return NSMAP
@@ -173,17 +185,16 @@ def xml(self):
self.validate()
self._setup_xpath_dictionary()
body_kwargs = {}
- if hasattr(self, constants.STYLE) and getattr(
- self, constants.STYLE):
- body_kwargs['class'] = getattr(
- self, constants.STYLE)
+ if hasattr(self, constants.STYLE) and getattr(self, constants.STYLE):
+ body_kwargs["class"] = getattr(self, constants.STYLE)
nsmap = self.get_nsmap()
return node(
- u"h:html",
- node(u"h:head", node(u"h:title", self.title), self.xml_model()),
- node(u"h:body", *self.xml_control(), **body_kwargs),
- **nsmap)
+ "h:html",
+ node("h:head", node("h:title", self.title), self.xml_model()),
+ node("h:body", *self.xml_control(), **body_kwargs),
+ **nsmap
+ )
@staticmethod
def _generate_static_instances(list_name, choice_list):
@@ -201,52 +212,45 @@ def _generate_static_instances(list_name, choice_list):
choice_element_list = []
# Add a unique id to the choice element in case there is itext
# it references
- itext_id = '-'.join(['static_instance', list_name, str(idx)])
+ itext_id = "-".join(["static_instance", list_name, str(idx)])
choice_element_list.append(node("itextId", itext_id))
for name, value in choice.items():
- if isinstance(value, basestring) and name != 'label':
+ if isinstance(value, basestring) and name != "label":
choice_element_list.append(node(name, unicode(value)))
instance_element_list.append(node("item", *choice_element_list))
return InstanceInfo(
- type=u"choice",
- context=u"survey",
+ type="choice",
+ context="survey",
name=list_name,
src=None,
instance=node(
- "instance",
- node("root", *instance_element_list),
- id=list_name
- )
+ "instance", node("root", *instance_element_list), id=list_name
+ ),
)
@staticmethod
def _get_dummy_instance():
"""Instance content required by ODK Validate for select inputs."""
- return node("root",
- node("item", node("name", "_"), node("label", "_")))
+ return node("root", node("item", node("name", "_"), node("label", "_")))
@staticmethod
def _generate_external_instances(element):
if isinstance(element, ExternalInstance):
- name = element[u"name"]
+ name = element["name"]
src = "jr://file/{}.xml".format(name)
return InstanceInfo(
- type=u"external",
+ type="external",
context="[type: {t}, name: {n}]".format(
- t=element[u"parent"][u"type"],
- n=element[u"parent"][u"name"]
+ t=element["parent"]["type"], n=element["parent"]["name"]
),
name=name,
src=src,
instance=node(
- "instance",
- Survey._get_dummy_instance(),
- id=name,
- src=src
- )
+ "instance", Survey._get_dummy_instance(), id=name, src=src
+ ),
)
return None
@@ -274,25 +278,27 @@ def _validate_external_instances(instances):
"Instance names must be unique within a form. "
"The name '{i}' was found {c} time(s), "
"under these contexts: {contexts}".format(
- i=element, c=len(copies), contexts=contexts))
+ i=element, c=len(copies), contexts=contexts
+ )
+ )
if errors:
raise ValidationError("\n".join(errors))
@staticmethod
def _generate_pulldata_instances(element):
- if 'calculate' in element['bind']:
- calculate = element['bind']['calculate']
- if calculate.startswith('pulldata('):
- pieces = calculate.split('"') \
- if '"' in calculate else calculate.split("'")
+ if "calculate" in element["bind"]:
+ calculate = element["bind"]["calculate"]
+ if calculate.startswith("pulldata("):
+ pieces = (
+ calculate.split('"') if '"' in calculate else calculate.split("'")
+ )
if len(pieces) > 1 and pieces[1]:
file_id = pieces[1]
uri = "jr://file-csv/{}.csv".format(file_id)
return InstanceInfo(
- type=u"pulldata",
+ type="pulldata",
context="[type: {t}, name: {n}]".format(
- t=element[u"parent"][u"type"],
- n=element[u"parent"][u"name"]
+ t=element["parent"]["type"], n=element["parent"]["name"]
),
name=file_id,
src=uri,
@@ -300,33 +306,31 @@ def _generate_pulldata_instances(element):
"instance",
Survey._get_dummy_instance(),
id=file_id,
- src=uri
- )
+ src=uri,
+ ),
)
return None
@staticmethod
def _generate_from_file_instances(element):
- itemset = element.get('itemset')
- if itemset and (itemset.endswith('.csv') or itemset.endswith('.xml')):
+ itemset = element.get("itemset")
+ if itemset and (itemset.endswith(".csv") or itemset.endswith(".xml")):
file_id, ext = os.path.splitext(itemset)
- uri = 'jr://%s/%s' % (
- 'file' if ext == '.xml' else "file-%s" % ext[1:], itemset)
+ uri = "jr://%s/%s" % (
+ "file" if ext == ".xml" else "file-%s" % ext[1:],
+ itemset,
+ )
return InstanceInfo(
- type=u"file",
+ type="file",
context="[type: {t}, name: {n}]".format(
- t=element[u"parent"][u"type"],
- n=element[u"parent"][u"name"]
+ t=element["parent"]["type"], n=element["parent"]["name"]
),
name=file_id,
src=uri,
instance=node(
- "instance",
- Survey._get_dummy_instance(),
- id=file_id,
- src=uri
- )
+ "instance", Survey._get_dummy_instance(), id=file_id, src=uri
+ ),
)
return None
@@ -375,8 +379,8 @@ def _generate_instances(self):
# Append last so the choice instance is excluded on a name clash.
for name, value in self.choices.items():
instances += [
- self._generate_static_instances(list_name=name,
- choice_list=value)]
+ self._generate_static_instances(list_name=name, choice_list=value)
+ ]
# Check that external instances have unique names.
if instances:
@@ -387,15 +391,20 @@ def _generate_instances(self):
for i in instances:
if i.name in seen.keys() and seen[i.name].src != i.src:
# Instance id exists with different src URI -> error.
- msg = "The same instance id will be generated for different " \
- "external instance source URIs. Please check the form." \
- " Instance name: '{i}', Existing type: '{e}', " \
- "Existing URI: '{iu}', Duplicate type: '{d}', " \
- "Duplicate URI: '{du}', Duplicate context: '{c}'."\
- .format(
- i=i.name, iu=seen[i.name].src, e=seen[i.name].type,
- d=i.type, du=i.src, c=i.context
- )
+ msg = (
+ "The same instance id will be generated for different "
+ "external instance source URIs. Please check the form."
+ " Instance name: '{i}', Existing type: '{e}', "
+ "Existing URI: '{iu}', Duplicate type: '{d}', "
+ "Duplicate URI: '{du}', Duplicate context: '{c}'.".format(
+ i=i.name,
+ iu=seen[i.name].src,
+ e=seen[i.name].type,
+ d=i.type,
+ du=i.src,
+ c=i.context,
+ )
+ )
raise PyXFormError(msg)
elif i.name in seen.keys() and seen[i.name].src == i.src:
# Instance id exists with same src URI -> ok, don't duplicate.
@@ -420,8 +429,7 @@ def xml_model(self):
model_children += list(self._generate_instances())
model_children += self.xml_bindings()
- if (self.submission_url or self.public_key or self.auto_send or
- self.auto_delete):
+ if self.submission_url or self.public_key or self.auto_send or self.auto_delete:
submission_attrs = dict()
if self.submission_url:
submission_attrs["action"] = self.submission_url
@@ -431,8 +439,9 @@ def xml_model(self):
submission_attrs["orx:auto-send"] = self.auto_send
if self.auto_delete:
submission_attrs["orx:auto-delete"] = self.auto_delete
- submission_node = node("submission", method="form-data-post",
- **submission_attrs)
+ submission_node = node(
+ "submission", method="form-data-post", **submission_attrs
+ )
model_children.insert(0, submission_node)
return node("model", *model_children)
@@ -444,20 +453,20 @@ def xml_instance(self, **kwargs):
for key, value in self.attribute.items():
result.setAttribute(unicode(key), value)
- result.setAttribute(u"id", self.id_string)
+ result.setAttribute("id", self.id_string)
# add instance xmlns attribute to the instance node
if self.instance_xmlns:
- result.setAttribute(u"xmlns", self.instance_xmlns)
+ result.setAttribute("xmlns", self.instance_xmlns)
if self.version:
- result.setAttribute(u"version", self.version)
+ result.setAttribute("version", self.version)
if self.prefix:
- result.setAttribute(u"odk:prefix", self.prefix)
+ result.setAttribute("odk:prefix", self.prefix)
if self.delimiter:
- result.setAttribute(u"odk:delimiter", self.delimiter)
+ result.setAttribute("odk:delimiter", self.delimiter)
return result
@@ -474,54 +483,60 @@ def _setup_translations(self):
set up the self._translations dict which will be referenced in the
setup media and itext functions
"""
+
def _setup_choice_translations(name, choice_value, itext_id):
for media_type_or_language, value in choice_value.items(): # noqa
if isinstance(value, dict):
for language, val in value.items():
self._add_to_nested_dict(
self._translations,
- [language, itext_id, media_type_or_language], val)
+ [language, itext_id, media_type_or_language],
+ val,
+ )
else:
- if name == 'media':
+ if name == "media":
self._add_to_nested_dict(
self._translations,
- [self.default_language, itext_id,
- media_type_or_language],
- value)
+ [self.default_language, itext_id, media_type_or_language],
+ value,
+ )
else:
self._add_to_nested_dict(
self._translations,
- [media_type_or_language, itext_id, 'long'], value)
+ [media_type_or_language, itext_id, "long"],
+ value,
+ )
self._translations = defaultdict(dict) # pylint: disable=W0201
for element in self.iter_descendants():
for d in element.get_translations(self.default_language):
- if 'guidance_hint' in d['path']:
- hint_path = d['path'].replace('guidance_hint', 'hint')
- self._translations[d['lang']][hint_path] = \
- self._translations[d['lang']].get(hint_path, {})
- self._translations[d['lang']][hint_path].update(
- {"guidance": d['text']})
+ if "guidance_hint" in d["path"]:
+ hint_path = d["path"].replace("guidance_hint", "hint")
+ self._translations[d["lang"]][hint_path] = self._translations[
+ d["lang"]
+ ].get(hint_path, {})
+ self._translations[d["lang"]][hint_path].update(
+ {"guidance": d["text"]}
+ )
else:
- self._translations[d['lang']][d['path']] = \
- self._translations[d['lang']].get(d['path'], {})
- self._translations[d['lang']][d['path']].update(
- {"long": d['text']})
+ self._translations[d["lang"]][d["path"]] = self._translations[
+ d["lang"]
+ ].get(d["path"], {})
+ self._translations[d["lang"]][d["path"]].update({"long": d["text"]})
# This code sets up translations for choices in filtered selects.
for list_name, choice_list in self.choices.items():
for idx, choice in zip(range(len(choice_list)), choice_list):
for name, choice_value in choice.items():
- itext_id = '-'.join(['static_instance', list_name,
- str(idx)])
+ itext_id = "-".join(["static_instance", list_name, str(idx)])
if isinstance(choice_value, dict):
- _setup_choice_translations(name, choice_value,
- itext_id)
- elif name == 'label':
+ _setup_choice_translations(name, choice_value, itext_id)
+ elif name == "label":
self._add_to_nested_dict(
self._translations,
- [self.default_language, itext_id, 'long'],
- choice_value)
+ [self.default_language, itext_id, "long"],
+ choice_value,
+ )
def _add_empty_translations(self):
"""
@@ -541,7 +556,7 @@ def _add_empty_translations(self):
self._translations[lang][path] = {}
for content_type in content_types:
if content_type not in self._translations[lang][path]:
- self._translations[lang][path][content_type] = u"-"
+ self._translations[lang][path][content_type] = "-"
def _setup_media(self):
"""
@@ -556,7 +571,7 @@ def _setup_media(self):
for survey_element in self.iter_descendants():
translation_key = survey_element.get_xpath() + ":label"
- media_dict = survey_element.get(u"media")
+ media_dict = survey_element.get("media")
# This is probably papering over a real problem, but anyway,
# in py3, sometimes if an item is on an xform with multiple
@@ -571,8 +586,7 @@ def _setup_media(self):
for media_type, possibly_localized_media in media_dict.items():
if media_type not in SurveyElement.SUPPORTED_MEDIA:
- raise PyXFormError(
- "Media type: " + media_type + " not supported")
+ raise PyXFormError("Media type: " + media_type + " not supported")
if isinstance(possibly_localized_media, dict):
# media is localized
@@ -580,9 +594,7 @@ def _setup_media(self):
else:
# media is not localized so create a localized version
# using the default language
- localized_media = {
- self.default_language: possibly_localized_media
- }
+ localized_media = {self.default_language: possibly_localized_media}
for language, media in localized_media.items():
@@ -597,8 +609,7 @@ def _setup_media(self):
if translation_key not in translations_language:
translations_language[translation_key] = {}
- translations_trans_key = \
- translations_language[translation_key]
+ translations_trans_key = translations_language[translation_key]
if media_type not in translations_trans_key:
translations_trans_key[media_type] = {}
@@ -615,8 +626,7 @@ def itext(self):
result = []
for lang, translation in self._translations.items():
if lang == self.default_language:
- result.append(
- node("translation", lang=lang, default=u"true()"))
+ result.append(node("translation", lang=lang, default="true()"))
else:
result.append(node("translation", lang=lang))
@@ -631,49 +641,53 @@ def itext(self):
# There is a odk/jr bug where hints can't have a value
# for the "form" attribute.
# This is my workaround.
- if label_type == u"hint":
- value, output_inserted = \
- self.insert_output_values(media_value)
+ if label_type == "hint":
+ value, output_inserted = self.insert_output_values(media_value)
if media_type == "guidance":
itext_nodes.append(
- node("value", value,
- form='guidance',
- toParseString=output_inserted)
+ node(
+ "value",
+ value,
+ form="guidance",
+ toParseString=output_inserted,
+ )
)
else:
itext_nodes.append(
- node("value", value,
- toParseString=output_inserted)
+ node("value", value, toParseString=output_inserted)
)
continue
if media_type == "long":
- value, output_inserted = \
- self.insert_output_values(media_value)
+ value, output_inserted = self.insert_output_values(media_value)
# I'm ignoring long types for now because I don't know
# how they are supposed to work.
itext_nodes.append(
node("value", value, toParseString=output_inserted)
)
elif media_type == "image":
- value, output_inserted = \
- self.insert_output_values(media_value)
+ value, output_inserted = self.insert_output_values(media_value)
itext_nodes.append(
- node("value", "jr://images/" + value,
- form=media_type,
- toParseString=output_inserted)
+ node(
+ "value",
+ "jr://images/" + value,
+ form=media_type,
+ toParseString=output_inserted,
+ )
)
else:
- value, output_inserted = \
- self.insert_output_values(media_value)
+ value, output_inserted = self.insert_output_values(media_value)
itext_nodes.append(
- node("value", "jr://" + media_type + "/" + value,
- form=media_type,
- toParseString=output_inserted))
+ node(
+ "value",
+ "jr://" + media_type + "/" + value,
+ form=media_type,
+ toParseString=output_inserted,
+ )
+ )
- result[-1].appendChild(
- node("text", *itext_nodes, id=label_name))
+ result[-1].appendChild(node("text", *itext_nodes, id=label_name))
return node("itext", *result)
@@ -693,12 +707,13 @@ def _to_pretty_xml(self):
# space to text
# TODO: check out pyxml
# http://ronrothman.com/public/leftbraned/xml-dom-minidom-toprettyxml-and-silly-whitespace/
- xml_with_linebreaks = self.xml().toprettyxml(indent=' ')
- text_re = re.compile(r'(>)\n\s*(\s[^<>\s].*?)\n\s*(\s)', re.DOTALL)
- output_re = re.compile(r'\n.*()\n(\s\s)*')
- pretty_xml = text_re.sub(lambda m: ''.join(m.group(1, 2, 3)),
- xml_with_linebreaks)
- inline_output = output_re.sub(r'\g<1>', pretty_xml)
+ xml_with_linebreaks = self.xml().toprettyxml(indent=" ")
+ text_re = re.compile(r"(>)\n\s*(\s[^<>\s].*?)\n\s*(\s)", re.DOTALL)
+ output_re = re.compile(r"\n.*()\n(\s\s)*")
+ pretty_xml = text_re.sub(
+ lambda m: "".join(m.group(1, 2, 3)), xml_with_linebreaks
+ )
+ inline_output = output_re.sub(r"\g<1>", pretty_xml)
return '\n' + inline_output
def __repr__(self):
@@ -722,26 +737,28 @@ def _var_repl_function(self, matchobj, context, use_current=False):
replace ${varname} with the xpath to varname.
"""
name = matchobj.group(1)
- intro = "There has been a problem trying to replace ${%s} with the "\
+ intro = (
+ "There has been a problem trying to replace ${%s} with the "
"XPath to the survey element named '%s'." % (name, name)
+ )
if name not in self._xpath:
- raise PyXFormError(
- intro + " There is no survey element with this name.")
+ raise PyXFormError(intro + " There is no survey element with this name.")
if self._xpath[name] is None:
- raise PyXFormError(intro + " There are multiple survey elements"
- " with this name.")
- if context and not (context['type'] == 'calculate' and
- 'indexed-repeat' in context['bind']['calculate']):
+ raise PyXFormError(
+ intro + " There are multiple survey elements" " with this name."
+ )
+ if context and not (
+ context["type"] == "calculate"
+ and "indexed-repeat" in context["bind"]["calculate"]
+ ):
xpath, context_xpath = self._xpath[name], context.get_xpath()
# share same root i.e repeat_a from /data/repeat_a/...
- if xpath.split('/')[2] == context_xpath.split('/')[2]:
+ if xpath.split("/")[2] == context_xpath.split("/")[2]:
# if context xpath and target xpath fall under the same
# repeat use relative xpath referencing.
- steps, ref_path = share_same_repeat_parent(self, xpath,
- context_xpath)
+ steps, ref_path = share_same_repeat_parent(self, xpath, context_xpath)
if steps:
- ref_path = ref_path \
- if ref_path.endswith(name) else "/%s" % name
+ ref_path = ref_path if ref_path.endswith(name) else "/%s" % name
prefix = " current()/" if use_current else " "
return prefix + "/".join([".."] * steps) + ref_path + " "
@@ -752,6 +769,7 @@ def insert_xpaths(self, text, context, use_current=False):
"""
Replace all instances of ${var} with the xpath to var.
"""
+
def _var_repl_function(matchobj):
return self._var_repl_function(matchobj, context, use_current)
@@ -764,8 +782,7 @@ def _var_repl_output_function(self, matchobj, context):
A regex substitution function that will replace
${varname} with an output element that has the xpath to varname.
"""
- return ('')
+ return ''
def insert_output_values(self, text, context=None):
"""
@@ -773,8 +790,10 @@ def insert_output_values(self, text, context=None):
Returns that and a boolean indicating if there were any ${variables}
present.
"""
+
def _var_repl_output_function(matchobj):
return self._var_repl_output_function(matchobj, context)
+
# There was a bug where escaping is completely turned off in labels
# where variable replacement is used.
# For exampke, `${name} < 3` causes an error but `< 3` does not.
@@ -788,16 +807,15 @@ def _var_repl_output_function(matchobj):
# need to make sure we have reason to replace
# since at this point < is <,
# the net effect < gets translated again to <
- if unicode(xml_text).find('{') != -1:
- result = re.sub(
- bracketed_tag, _var_repl_output_function,
- unicode(xml_text))
+ if unicode(xml_text).find("{") != -1:
+ result = re.sub(bracketed_tag, _var_repl_output_function, unicode(xml_text))
return result, not result == xml_text
return text, False
# pylint: disable=too-many-arguments
- def print_xform_to_file(self, path=None, validate=True, pretty_print=True,
- warnings=None, enketo=False):
+ def print_xform_to_file(
+ self, path=None, validate=True, pretty_print=True, warnings=None, enketo=False
+ ):
"""
Print the xForm to a file and optionally validate it as well by
throwing exceptions and adding warnings to the warnings array.
@@ -828,12 +846,13 @@ def print_xform_to_file(self, path=None, validate=True, pretty_print=True,
if bad_languages:
warnings.append(
"\tThe following language declarations do not contain "
- "valid machine-readable codes: " +
- ", ".join(bad_languages) + ". " +
- "Learn more: http://xlsform.org#multiple-language-support")
+ "valid machine-readable codes: "
+ + ", ".join(bad_languages)
+ + ". "
+ + "Learn more: http://xlsform.org#multiple-language-support"
+ )
- def to_xml(self, validate=True, pretty_print=True, warnings=None,
- enketo=False):
+ def to_xml(self, validate=True, pretty_print=True, warnings=None, enketo=False):
"""
Generates the XForm XML.
validate is True by default - pass the XForm XML through ODK Validator.
@@ -850,8 +869,12 @@ def to_xml(self, validate=True, pretty_print=True, warnings=None,
try:
# this will throw an exception if the xml is not valid
self.print_xform_to_file(
- path=tmp.name, validate=validate, pretty_print=pretty_print,
- warnings=warnings, enketo=enketo)
+ path=tmp.name,
+ validate=validate,
+ pretty_print=pretty_print,
+ warnings=warnings,
+ enketo=enketo,
+ )
finally:
if os.path.exists(tmp.name):
os.remove(tmp.name)
diff --git a/pyxform/survey_element.py b/pyxform/survey_element.py
index 6a07bfaca..0c5aa6569 100644
--- a/pyxform/survey_element.py
+++ b/pyxform/survey_element.py
@@ -1,12 +1,14 @@
-import re
-
+# -*- coding: utf-8 -*-
+"""
+Survey Element base class for all survey elements.
+"""
import json
+import re
from pyxform import constants
from pyxform.errors import PyXFormError
from pyxform.question_type_dictionary import QUESTION_TYPE_DICT
-from pyxform.utils import is_valid_xml_tag, node, unicode, \
- INVALID_XFORM_TAG_REGEXP
+from pyxform.utils import INVALID_XFORM_TAG_REGEXP, is_valid_xml_tag, node, unicode
from pyxform.xls2json import print_pyobj_to_json
try:
@@ -33,37 +35,37 @@ class SurveyElement(dict):
# the following are important keys for the underlying dict that
# describes this survey element
FIELDS = {
- u"name": unicode,
- constants.COMPACT_TAG: unicode, # used for compact (sms) representation
- u"sms_field": unicode,
- u"sms_option": unicode,
- u"label": unicode,
- u"hint": unicode,
- u"guidance_hint": unicode,
- u"default": unicode,
- u"type": unicode,
- u"appearance": unicode,
- u"parameters": unicode,
- u"intent": unicode,
- u"jr:count": unicode,
- u"bind": dict,
- u"instance": dict,
- u"control": dict,
- u"media": dict,
+ "name": unicode,
+ constants.COMPACT_TAG: unicode, # used for compact (sms) representation
+ "sms_field": unicode,
+ "sms_option": unicode,
+ "label": unicode,
+ "hint": unicode,
+ "guidance_hint": unicode,
+ "default": unicode,
+ "type": unicode,
+ "appearance": unicode,
+ "parameters": unicode,
+ "intent": unicode,
+ "jr:count": unicode,
+ "bind": dict,
+ "instance": dict,
+ "control": dict,
+ "media": dict,
# this node will also have a parent and children, like a tree!
- u"parent": lambda: None,
- u"children": list,
- u"itemset": unicode,
- u"choice_filter": unicode,
- u"query": unicode,
- u"autoplay": unicode,
- u"flat": lambda: False,
+ "parent": lambda: None,
+ "children": list,
+ "itemset": unicode,
+ "choice_filter": unicode,
+ "query": unicode,
+ "autoplay": unicode,
+ "flat": lambda: False,
}
def _default(self):
# TODO: need way to override question type dictionary
defaults = QUESTION_TYPE_DICT
- return defaults.get(self.get(u"type"), {})
+ return defaults.get(self.get("type"), {})
def __getattr__(self, key):
"""
@@ -97,8 +99,8 @@ def __init__(self, **kwargs):
# appearance tag. # This is because such elements are used to label the
# options for selects in a field-list and might want blank labels for
# themselves.
- if self.control.get(u"appearance") == u"label" and not self.label:
- self[u"label"] = u" "
+ if self.control.get("appearance") == "label" and not self.label:
+ self["label"] = " "
def _link_children(self):
for child in self.children:
@@ -127,24 +129,21 @@ def add_children(self, children):
"NO": "false()",
"false": "false()",
"False": "false()",
- "FALSE": "false()"
+ "FALSE": "false()",
}
# Supported media types for attaching to questions
- SUPPORTED_MEDIA = [
- "image",
- "audio",
- "video"
- ]
+ SUPPORTED_MEDIA = ["image", "audio", "video"]
def validate(self):
if not is_valid_xml_tag(self.name):
invalid_char = re.search(INVALID_XFORM_TAG_REGEXP, self.name)
- msg = "The name '{}' is an invalid XML tag, it contains an " \
- "invalid character '{}'. Names must begin with a letter, " \
- "colon, or underscore, subsequent characters can include " \
- "numbers, dashes, and periods".format(
- self.name, invalid_char.group(0))
+ msg = (
+ "The name '{}' is an invalid XML tag, it contains an "
+ "invalid character '{}'. Names must begin with a letter, "
+ "colon, or underscore, subsequent characters can include "
+ "numbers, dashes, and periods".format(self.name, invalid_char.group(0))
+ )
raise PyXFormError(msg)
# TODO: Make sure renaming this doesn't cause any problems
@@ -165,8 +164,7 @@ def iter_descendants(self):
def any_repeat(self, parent_xpath):
"""Return True if there ia any repeat in `parent_xpath`."""
for item in self.iter_descendants():
- if item.get_xpath() == parent_xpath and \
- item.type == constants.REPEAT:
+ if item.get_xpath() == parent_xpath and item.type == constants.REPEAT:
return True
return False
@@ -183,7 +181,7 @@ def get_lineage(self):
# For some reason the root element has a True flat property...
output = [result[0]]
for item in result[1:]:
- if not item.get(u"flat"):
+ if not item.get("flat"):
output.append(item)
return output
@@ -194,12 +192,12 @@ def get_xpath(self):
"""
Return the xpath of this survey element.
"""
- return u"/".join([u""] + [n.name for n in self.get_lineage()])
+ return "/".join([""] + [n.name for n in self.get_lineage()])
def get_abbreviated_xpath(self):
lineage = self.get_lineage()
if len(lineage) >= 2:
- return u"/".join([unicode(n.name) for n in lineage[1:]])
+ return "/".join([unicode(n.name) for n in lineage[1:]])
else:
return lineage[0].name
@@ -210,14 +208,14 @@ def to_json_dict(self):
"""
self.validate()
result = self.copy()
- to_delete = [u"parent", u"question_type_dictionary", u"_created"]
+ to_delete = ["parent", "question_type_dictionary", "_created"]
for key in to_delete:
if key in result:
del result[key]
- children = result.pop(u"children")
- result[u"children"] = []
+ children = result.pop("children")
+ result["children"] = []
for child in children:
- result[u"children"].append(child.to_json_dict())
+ result["children"].append(child.to_json_dict())
# remove any keys with empty values
for k, v in list(result.items()):
if not v:
@@ -234,8 +232,11 @@ def json_dump(self, path=""):
print_pyobj_to_json(self.to_json_dict(), path)
def __eq__(self, y):
- return hasattr(y, 'to_json_dict') and callable(y.to_json_dict) and \
- self.to_json_dict() == y.to_json_dict()
+ return (
+ hasattr(y, "to_json_dict")
+ and callable(y.to_json_dict)
+ and self.to_json_dict() == y.to_json_dict()
+ )
def _translation_path(self, display_element):
return self.get_xpath() + ":" + display_element
@@ -245,65 +246,71 @@ def get_translations(self, default_language):
Returns translations used by this element so they can be included in
the block. @see survey._setup_translations
"""
- bind_dict = self.get(u'bind')
+ bind_dict = self.get("bind")
if bind_dict and type(bind_dict) is dict:
- constraint_msg = bind_dict.get(u'jr:constraintMsg')
+ constraint_msg = bind_dict.get("jr:constraintMsg")
if type(constraint_msg) is dict:
for lang, text in constraint_msg.items():
yield {
- 'path': self._translation_path(u'jr:constraintMsg'),
- 'lang': lang,
- 'text': text
+ "path": self._translation_path("jr:constraintMsg"),
+ "lang": lang,
+ "text": text,
}
- required_msg = bind_dict.get(u'jr:requiredMsg')
+ required_msg = bind_dict.get("jr:requiredMsg")
if type(required_msg) is dict:
for lang, text in required_msg.items():
yield {
- 'path': self._translation_path(u'jr:requiredMsg'),
- 'lang': lang,
- 'text': text
+ "path": self._translation_path("jr:requiredMsg"),
+ "lang": lang,
+ "text": text,
}
- no_app_error_string = bind_dict.get(u'jr:noAppErrorString')
+ no_app_error_string = bind_dict.get("jr:noAppErrorString")
if type(no_app_error_string) is dict:
for lang, text in no_app_error_string.items():
yield {
- 'path': self._translation_path(u'jr:noAppErrorString'),
- 'lang': lang,
- 'text': text
+ "path": self._translation_path("jr:noAppErrorString"),
+ "lang": lang,
+ "text": text,
}
- for display_element in [u'label', u'hint', u'guidance_hint']:
+ for display_element in ["label", "hint", "guidance_hint"]:
label_or_hint = self[display_element]
- if display_element is u'label' \
- and self.needs_itext_ref() \
- and type(label_or_hint) is not dict \
- and label_or_hint:
+ if (
+ display_element == "label"
+ and self.needs_itext_ref()
+ and type(label_or_hint) is not dict
+ and label_or_hint
+ ):
label_or_hint = {default_language: label_or_hint}
# always use itext for guidance hints because that's
# how they're defined - https://opendatakit.github.io/xforms-spec/#languages
- if display_element is u'guidance_hint' \
- and not(isinstance(label_or_hint, dict)) \
- and len(label_or_hint) > 0:
+ if (
+ display_element == "guidance_hint"
+ and not (isinstance(label_or_hint, dict))
+ and len(label_or_hint) > 0
+ ):
label_or_hint = {default_language: label_or_hint}
# always use itext for hint if there's a guidance hint
- if display_element is u'hint' \
- and not(isinstance(label_or_hint, dict)) \
- and len(label_or_hint) > 0 \
- and "guidance_hint" in self.keys() \
- and len(self["guidance_hint"]) > 0:
+ if (
+ display_element == "hint"
+ and not (isinstance(label_or_hint, dict))
+ and len(label_or_hint) > 0
+ and "guidance_hint" in self.keys()
+ and len(self["guidance_hint"]) > 0
+ ):
label_or_hint = {default_language: label_or_hint}
if type(label_or_hint) is dict:
for lang, text in label_or_hint.items():
yield {
- 'display_element': display_element, # Not used
- 'path': self._translation_path(display_element),
- 'element': self, # Not used
- 'lang': lang,
- 'text': text,
+ "display_element": display_element, # Not used
+ "path": self._translation_path(display_element),
+ "element": self, # Not used
+ "lang": lang,
+ "text": text,
}
def get_media_keys(self):
@@ -311,35 +318,34 @@ def get_media_keys(self):
@deprected
I'm leaving this in just in case it has outside references.
"""
- return {
- u"media": u"%s:media" % self.get_xpath()
- }
+ return {"media": "%s:media" % self.get_xpath()}
def needs_itext_ref(self):
return type(self.label) is dict or (
- type(self.media) is dict and len(self.media) > 0)
+ type(self.media) is dict and len(self.media) > 0
+ )
# XML generating functions, these probably need to be moved around.
def xml_label(self):
if self.needs_itext_ref():
# If there is a dictionary label, or non-empty media dict,
# then we need to make a label with an itext ref
- ref = "jr:itext('%s')" % self._translation_path(u"label")
- return node(u"label", ref=ref)
+ ref = "jr:itext('%s')" % self._translation_path("label")
+ return node("label", ref=ref)
else:
survey = self.get_root()
- label, output_inserted = survey.insert_output_values(self.label,
- self)
- return node(u"label", label, toParseString=output_inserted)
+ label, output_inserted = survey.insert_output_values(self.label, self)
+ return node("label", label, toParseString=output_inserted)
def xml_hint(self):
if isinstance(self.hint, dict) or self.guidance_hint:
path = self._translation_path("hint")
- return node(u"hint", ref="jr:itext('%s')" % path)
+ return node("hint", ref="jr:itext('%s')" % path)
else:
hint, output_inserted = self.get_root().insert_output_values(
- self.hint, self)
- return node(u"hint", hint, toParseString=output_inserted)
+ self.hint, self
+ )
+ return node("hint", hint, toParseString=output_inserted)
def xml_label_and_hint(self):
"""
@@ -353,8 +359,7 @@ def xml_label_and_hint(self):
result.append(self.xml_hint())
if len(result) == 0 or self.guidance_hint and len(result) == 1:
- msg = "The survey element named '%s' " \
- "has no label or hint." % self.name
+ msg = "The survey element named '%s' " "has no label or hint." % self.name
raise PyXFormError(msg)
return result
@@ -365,7 +370,7 @@ def xml_binding(self):
"""
survey = self.get_root()
bind_dict = self.bind.copy()
- if self.get('flat'):
+ if self.get("flat"):
# Don't generate bind element for flat groups.
return None
if bind_dict:
@@ -374,17 +379,14 @@ def xml_binding(self):
# the xls2json side.
if hashable(v) and v in self.binding_conversions:
v = self.binding_conversions[v]
- if k == u'jr:constraintMsg' and type(v) is dict:
- v = "jr:itext('%s')" % self._translation_path(
- u'jr:constraintMsg')
- if k == u'jr:requiredMsg' and type(v) is dict:
- v = "jr:itext('%s')" % self._translation_path(
- u'jr:requiredMsg')
- if k == u'jr:noAppErrorString' and type(v) is dict:
- v = "jr:itext('%s')" % self._translation_path(
- u'jr:noAppErrorString')
+ if k == "jr:constraintMsg" and type(v) is dict:
+ v = "jr:itext('%s')" % self._translation_path("jr:constraintMsg")
+ if k == "jr:requiredMsg" and type(v) is dict:
+ v = "jr:itext('%s')" % self._translation_path("jr:requiredMsg")
+ if k == "jr:noAppErrorString" and type(v) is dict:
+ v = "jr:itext('%s')" % self._translation_path("jr:noAppErrorString")
bind_dict[k] = survey.insert_xpaths(v, context=self)
- return node(u"bind", nodeset=self.get_xpath(), **bind_dict)
+ return node("bind", nodeset=self.get_xpath(), **bind_dict)
return None
def xml_bindings(self):
diff --git a/pyxform/tests/__init__.py b/pyxform/tests/__init__.py
index 6cddaf9e7..e69de29bb 100644
--- a/pyxform/tests/__init__.py
+++ b/pyxform/tests/__init__.py
@@ -1,12 +0,0 @@
-# These tests were being run twice by nose.
-# commenting out the imports prevents this.
-# from j2x_question_tests import *
-# from j2x_test_creation import *
-# #from j2x_test_xform_build_preparation import *
-# from js2x_test_import_from_json import *
-# from j2x_test_instantiation import *
-# from json2xform_test import *
-# from group_test import *
-# from builder_tests import *
-# from xls2json_tests import *
-# from dump_and_load_tests import *
diff --git a/pyxform/tests/attributecolumnstest.py b/pyxform/tests/attributecolumnstest.py
index b1fa1f1ee..d7a41a97d 100644
--- a/pyxform/tests/attributecolumnstest.py
+++ b/pyxform/tests/attributecolumnstest.py
@@ -1,9 +1,12 @@
+# -*- coding: utf-8 -*-
"""
Some tests for the new (v0.9) spec is properly implemented.
"""
-import unittest2 as unittest
import codecs
import os
+
+import unittest2 as unittest
+
import pyxform
from pyxform.tests.utils import XFormTestCase
@@ -17,24 +20,23 @@ class AttributeColumnsTest(XFormTestCase):
def runTest(self):
filename = "attribute_columns_test.xlsx"
self.get_file_path(filename)
- expected_output_path = os.path.join(DIR, "test_expected_output",
- self.root_filename + ".xml")
+ expected_output_path = os.path.join(
+ DIR, "test_expected_output", self.root_filename + ".xml"
+ )
# Do the conversion:
warnings = []
json_survey = pyxform.xls2json.parse_file_to_json(
- self.path_to_excel_file, warnings=warnings)
+ self.path_to_excel_file, warnings=warnings
+ )
survey = pyxform.create_survey_element_from_dict(json_survey)
survey.print_xform_to_file(self.output_path, warnings=warnings)
# print warnings
# Compare with the expected output:
- with codecs.open(expected_output_path, 'rb', encoding="utf-8") \
- as expected_file:
- with codecs.open(self.output_path, 'rb', encoding="utf-8") \
- as actual_file:
- self.assertXFormEqual(
- expected_file.read(), actual_file.read())
+ with codecs.open(expected_output_path, "rb", encoding="utf-8") as expected_file:
+ with codecs.open(self.output_path, "rb", encoding="utf-8") as actual_file:
+ self.assertXFormEqual(expected_file.read(), actual_file.read())
-if __name__ == '__main__':
+if __name__ == "__main__":
unittest.main()
diff --git a/pyxform/tests/bug_tests.py b/pyxform/tests/bug_tests.py
index 7ac40b732..0cbd6d6f2 100644
--- a/pyxform/tests/bug_tests.py
+++ b/pyxform/tests/bug_tests.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""
Some tests for the new (v0.9) spec is properly implemented.
"""
@@ -11,8 +12,6 @@
from pyxform.tests.utils import XFormTestCase
from pyxform.utils import has_external_choices
from pyxform.xls2json import SurveyReader, parse_file_to_workbook_dict
-from pyxform.tests.utils import XFormTestCase
-from pyxform.errors import PyXFormError
from pyxform.xls2json_backends import xls_to_dict
DIR = os.path.dirname(__file__)
@@ -31,7 +30,8 @@ def runTest(self):
warnings = []
with self.assertRaises(Exception):
json_survey = pyxform.xls2json.parse_file_to_json(
- path_to_excel_file, warnings=warnings)
+ path_to_excel_file, warnings=warnings
+ )
survey = pyxform.create_survey_element_from_dict(json_survey)
survey.print_xform_to_file(output_path, warnings=warnings)
@@ -49,7 +49,8 @@ def runTest(self):
warnings = []
with self.assertRaises(Exception):
json_survey = pyxform.xls2json.parse_file_to_json(
- path_to_excel_file, warnings=warnings)
+ path_to_excel_file, warnings=warnings
+ )
survey = pyxform.create_survey_element_from_dict(json_survey)
survey.print_xform_to_file(output_path, warnings=warnings)
@@ -61,20 +62,20 @@ def runTest(self):
filename = "repeat_date_test.xls"
self.get_file_path(filename)
expected_output_path = os.path.join(
- DIR, "test_expected_output", self.root_filename + ".xml")
+ DIR, "test_expected_output", self.root_filename + ".xml"
+ )
# Do the conversion:
warnings = []
json_survey = pyxform.xls2json.parse_file_to_json(
- self.path_to_excel_file, warnings=warnings)
+ self.path_to_excel_file, warnings=warnings
+ )
survey = pyxform.create_survey_element_from_dict(json_survey)
survey.print_xform_to_file(self.output_path, warnings=warnings)
# print warnings
# Compare with the expected output:
- with codecs.open(
- expected_output_path, 'rb', encoding="utf-8") as expected_file:
- with codecs.open(
- self.output_path, 'rb', encoding="utf-8") as actual_file:
+ with codecs.open(expected_output_path, "rb", encoding="utf-8") as expected_file:
+ with codecs.open(self.output_path, "rb", encoding="utf-8") as actual_file:
self.assertXFormEqual(expected_file.read(), actual_file.read())
@@ -85,20 +86,20 @@ def runTest(self):
filename = "xml_escaping.xls"
self.get_file_path(filename)
expected_output_path = os.path.join(
- DIR, "test_expected_output", self.root_filename + ".xml")
+ DIR, "test_expected_output", self.root_filename + ".xml"
+ )
# Do the conversion:
warnings = []
json_survey = pyxform.xls2json.parse_file_to_json(
- self.path_to_excel_file, warnings=warnings)
+ self.path_to_excel_file, warnings=warnings
+ )
survey = pyxform.create_survey_element_from_dict(json_survey)
survey.print_xform_to_file(self.output_path, warnings=warnings)
# print warnings
# Compare with the expected output:
- with codecs.open(
- expected_output_path, 'rb', encoding="utf-8") as expected_file:
- with codecs.open(
- self.output_path, 'rb', encoding="utf-8") as actual_file:
+ with codecs.open(expected_output_path, "rb", encoding="utf-8") as expected_file:
+ with codecs.open(self.output_path, "rb", encoding="utf-8") as actual_file:
self.assertXFormEqual(expected_file.read(), actual_file.read())
@@ -112,19 +113,19 @@ def runTest(self):
root_filename, ext = os.path.splitext(filename)
output_path = os.path.join(DIR, "test_output", root_filename + ".xml")
expected_output_path = os.path.join(
- DIR, "test_expected_output", root_filename + ".xml")
+ DIR, "test_expected_output", root_filename + ".xml"
+ )
# Do the conversion:
warnings = []
json_survey = pyxform.xls2json.parse_file_to_json(
- path_to_excel_file, warnings=warnings)
+ path_to_excel_file, warnings=warnings
+ )
survey = pyxform.create_survey_element_from_dict(json_survey)
survey.print_xform_to_file(output_path, warnings=warnings)
# print warnings
# Compare with the expected output:
- with codecs.open(
- expected_output_path, 'rb', encoding="utf-8") as expected_file:
- with codecs.open(
- output_path, 'rb', encoding="utf-8") as actual_file:
+ with codecs.open(expected_output_path, "rb", encoding="utf-8") as expected_file:
+ with codecs.open(output_path, "rb", encoding="utf-8") as actual_file:
self.assertXFormEqual(expected_file.read(), actual_file.read())
@@ -140,7 +141,8 @@ def runTest(self):
# Do the conversion:
warnings = []
json_survey = pyxform.xls2json.parse_file_to_json(
- path_to_excel_file, warnings=warnings)
+ path_to_excel_file, warnings=warnings
+ )
survey = pyxform.create_survey_element_from_dict(json_survey)
survey.print_xform_to_file(output_path, warnings=warnings)
@@ -157,7 +159,8 @@ def runTest(self):
# Do the conversion:
warnings = []
json_survey = pyxform.xls2json.parse_file_to_json(
- path_to_excel_file, warnings=warnings)
+ path_to_excel_file, warnings=warnings
+ )
survey = pyxform.create_survey_element_from_dict(json_survey)
survey.print_xform_to_file(output_path, warnings=warnings)
@@ -175,7 +178,8 @@ def runTest(self):
warnings = []
with self.assertRaises(PyXFormError):
json_survey = pyxform.xls2json.parse_file_to_json(
- path_to_excel_file, warnings=warnings)
+ path_to_excel_file, warnings=warnings
+ )
survey = pyxform.create_survey_element_from_dict(json_survey)
survey.print_xform_to_file(output_path, warnings=warnings)
@@ -184,11 +188,10 @@ class EmptyStringOnRelevantColumnTest(unittest.TestCase):
def runTest(self):
filename = "ict_survey_fails.xls"
path_to_excel_file = os.path.join(DIR, "bug_example_xls", filename)
- workbook_dict = pyxform.xls2json.parse_file_to_workbook_dict(
- path_to_excel_file)
+ workbook_dict = pyxform.xls2json.parse_file_to_workbook_dict(path_to_excel_file)
with self.assertRaises(KeyError):
# bind:relevant should not be part of workbook_dict
- workbook_dict['survey'][0][u'bind: relevant'].strip()
+ workbook_dict["survey"][0]["bind: relevant"].strip()
class BadChoicesSheetHeaders(unittest.TestCase):
@@ -196,10 +199,8 @@ def runTest(self):
filename = "spaces_in_choices_header.xls"
path_to_excel_file = os.path.join(DIR, "bug_example_xls", filename)
warnings = []
- pyxform.xls2json.parse_file_to_json(path_to_excel_file,
- warnings=warnings)
- self.assertEquals(len(warnings), 3,
- "Found " + str(len(warnings)) + " warnings")
+ pyxform.xls2json.parse_file_to_json(path_to_excel_file, warnings=warnings)
+ self.assertEquals(len(warnings), 3, "Found " + str(len(warnings)) + " warnings")
class TestChoiceNameAsType(unittest.TestCase):
@@ -222,6 +223,7 @@ def test_blank_second_row(self):
class TestXLDateAmbigous(unittest.TestCase):
"""Test non standard sheet with exception is processed successfully."""
+
def test_xl_date_ambigous(self):
"""Test non standard sheet with exception is processed successfully."""
filename = "xl_date_ambiguous.xlsx"
@@ -235,20 +237,24 @@ class TestXLDateAmbigousWithException(unittest.TestCase):
"""Test non standard sheet date values to raise an exception.
This exception is raised if the date values exceed the
datemode value accepted by that workbook."""
+
def test_xl_date_ambigous_with_exception(self):
"""Test non standard sheet with exception is processed successfully."""
filename = "xl_date_ambiguous_v1.xlsx"
path_to_excel_file = os.path.join(DIR, "bug_example_xls", filename)
with self.assertRaises(PyXFormError) as e:
xls_to_dict(path_to_excel_file)
- msg = 'The xls file provided has an invalid date on the'\
- ' survey sheet, under the default column on row number 5'
+ msg = (
+ "The xls file provided has an invalid date on the"
+ " survey sheet, under the default column on row number 5"
+ )
self.assertEqual(msg, str(e.exception))
class TestSpreadSheetFilesWithMacrosAreAllowed(unittest.TestCase):
"""Test that spreadsheets with .xlsm extension are allowed"""
+
def test_xlsm_files_are_allowed(self):
filename = "excel_with_macros.xlsm"
path_to_excel_file = os.path.join(DIR, "bug_example_xls", filename)
diff --git a/pyxform/tests/builder_tests.py b/pyxform/tests/builder_tests.py
index 3b06148fb..0aaa6a31a 100644
--- a/pyxform/tests/builder_tests.py
+++ b/pyxform/tests/builder_tests.py
@@ -1,12 +1,17 @@
+# -*- coding: utf-8 -*-
+"""
+Test builder module functionality.
+"""
+import os
import re
import xml.etree.ElementTree as ETree
from unittest import TestCase
+
+from pyxform import InputQuestion, Survey
from pyxform.builder import SurveyElementBuilder, create_survey_from_xls
-from pyxform.xls2json import print_pyobj_to_json
-from pyxform import Survey, InputQuestion
from pyxform.errors import PyXFormError
from pyxform.tests import utils
-import os
+from pyxform.xls2json import print_pyobj_to_json
FIXTURE_FILETYPE = "xls"
@@ -14,49 +19,40 @@
class BuilderTests(TestCase):
maxDiff = None
-# Moving to spec tests
-# def test_new_widgets(self):
-# survey = utils.build_survey('widgets.xls')
-# path = utils.path_to_text_fixture('widgets.xml')
-# survey.to_xml
-# with open(path) as f:
-# expected = ETree.fromstring(survey.to_xml())
-# result = ETree.fromstring(f.read())
-# self.assertTrue(xml_compare(expected, result))
+ # Moving to spec tests
+ # def test_new_widgets(self):
+ # survey = utils.build_survey('widgets.xls')
+ # path = utils.path_to_text_fixture('widgets.xml')
+ # survey.to_xml
+ # with open(path) as f:
+ # expected = ETree.fromstring(survey.to_xml())
+ # result = ETree.fromstring(f.read())
+ # self.assertTrue(xml_compare(expected, result))
def test_unknown_question_type(self):
- survey = utils.build_survey('unknown_question_type.xls')
- self.assertRaises(
- PyXFormError,
- survey.to_xml
- )
+ survey = utils.build_survey("unknown_question_type.xls")
+ self.assertRaises(PyXFormError, survey.to_xml)
def test_uniqueness_of_section_names(self):
# Looking at the xls file, I think this test might be broken.
- survey = utils.build_survey('group_names_must_be_unique.xls')
- self.assertRaises(
- Exception,
- survey.to_xml
- )
+ survey = utils.build_survey("group_names_must_be_unique.xls")
+ self.assertRaises(Exception, survey.to_xml)
def setUp(self):
self.this_directory = os.path.dirname(__file__)
- survey_out = Survey(
- name=u"age",
- sms_keyword=u"age",
- type=u"survey"
- )
- question = InputQuestion(name=u"age")
- question.type = u"integer"
- question.label = u"How old are you?"
+ survey_out = Survey(name="age", sms_keyword="age", type="survey")
+ question = InputQuestion(name="age")
+ question.type = "integer"
+ question.label = "How old are you?"
survey_out.add_child(question)
self.survey_out_dict = survey_out.to_json_dict()
- print_pyobj_to_json(self.survey_out_dict,
- utils.path_to_text_fixture("how_old_are_you.json"))
+ print_pyobj_to_json(
+ self.survey_out_dict, utils.path_to_text_fixture("how_old_are_you.json")
+ )
def test_create_from_file_object(self):
- path = utils.path_to_text_fixture('yes_or_no_question.xls')
- with open(path, 'rb') as f:
+ path = utils.path_to_text_fixture("yes_or_no_question.xls")
+ with open(path, "rb") as f:
create_survey_from_xls(f)
def tearDown(self):
@@ -66,127 +62,104 @@ def tearDown(self):
def test_create_table_from_dict(self):
d = {
- u"type": u"loop",
- u"name": u"my_loop",
- u"label": {u"English": u"My Loop"},
- u"columns": [
- {
- u"name": u"col1",
- u"label": {u"English": u"column 1"},
- },
- {
- u"name": u"col2",
- u"label": {u"English": u"column 2"},
- },
+ "type": "loop",
+ "name": "my_loop",
+ "label": {"English": "My Loop"},
+ "columns": [
+ {"name": "col1", "label": {"English": "column 1"}},
+ {"name": "col2", "label": {"English": "column 2"}},
],
- u"children": [
+ "children": [
{
- u"type": u"integer",
- u"name": u"count",
- u"label": {
- u"English": u"How many are there in this group?"
- }
- },
- ]
+ "type": "integer",
+ "name": "count",
+ "label": {"English": "How many are there in this group?"},
+ }
+ ],
}
builder = SurveyElementBuilder()
g = builder.create_survey_element_from_dict(d)
expected_dict = {
- u'name': u'my_loop',
- u'label': {u'English': u'My Loop'},
- u'type': u'group',
- u'children': [
+ "name": "my_loop",
+ "label": {"English": "My Loop"},
+ "type": "group",
+ "children": [
{
- u'name': u'col1',
- u'label': {u'English': u'column 1'},
- u'type': u'group',
- u'children': [
+ "name": "col1",
+ "label": {"English": "column 1"},
+ "type": "group",
+ "children": [
{
- u'name': u'count',
- u'label': {
- u'English':
- u'How many are there in this group?'
- },
- u'type': u'integer'
+ "name": "count",
+ "label": {"English": "How many are there in this group?"},
+ "type": "integer",
}
- ]
+ ],
},
{
- u'name': u'col2',
- u'label': {u'English': u'column 2'},
- u'type': u'group',
- u'children': [
+ "name": "col2",
+ "label": {"English": "column 2"},
+ "type": "group",
+ "children": [
{
- u'name': u'count',
- u'label': {
- u'English':
- u'How many are there in this group?'
- },
- u'type': u'integer'
+ "name": "count",
+ "label": {"English": "How many are there in this group?"},
+ "type": "integer",
}
- ]
- }
- ]
+ ],
+ },
+ ],
}
self.assertEqual(g.to_json_dict(), expected_dict)
def test_specify_other(self):
- survey = utils.create_survey_from_fixture("specify_other",
- filetype=FIXTURE_FILETYPE)
+ survey = utils.create_survey_from_fixture(
+ "specify_other", filetype=FIXTURE_FILETYPE
+ )
expected_dict = {
- u'name': u'specify_other',
- u'type': u'survey',
- u'title': u'specify_other',
- u'default_language': u'default',
- u'id_string': u'specify_other',
- u'sms_keyword': u'specify_other',
- u'children': [
+ "name": "specify_other",
+ "type": "survey",
+ "title": "specify_other",
+ "default_language": "default",
+ "id_string": "specify_other",
+ "sms_keyword": "specify_other",
+ "children": [
{
- u'name': u'sex',
- u'label': {u'English': u'What sex are you?'},
- u'type': u'select one',
- u'children': [
+ "name": "sex",
+ "label": {"English": "What sex are you?"},
+ "type": "select one",
+ "children": [
# TODO Change to choices (there is stuff in the
# json2xform half that will need to change)
- {
- u'name': u'male',
- u'label': {u'English': u'Male'}
- },
- {
- u'name': u'female',
- u'label': {u'English': u'Female'}
- },
- {
- u'name': u'other',
- u'label': u'Other'
- }
- ]
+ {"name": "male", "label": {"English": "Male"}},
+ {"name": "female", "label": {"English": "Female"}},
+ {"name": "other", "label": "Other"},
+ ],
},
{
- u'name': u'sex_other',
- u'bind': {u'relevant': u"selected(../sex, 'other')"},
- u'label': u'Specify other.',
- u'type': u'text'},
+ "name": "sex_other",
+ "bind": {"relevant": "selected(../sex, 'other')"},
+ "label": "Specify other.",
+ "type": "text",
+ },
{
- u'children': [
+ "children": [
{
- u'bind': {
- 'calculate': "concat('uuid:', uuid())",
- 'readonly': 'true()'
+ "bind": {
+ "calculate": "concat('uuid:', uuid())",
+ "readonly": "true()",
},
- u'name': 'instanceID',
- u'type': 'calculate'
+ "name": "instanceID",
+ "type": "calculate",
}
],
- u'control': {
- 'bodyless': True
- },
- u'name': 'meta',
- u'type': u'group'
- }
- ]
+ "control": {"bodyless": True},
+ "name": "meta",
+ "type": "group",
+ },
+ ],
}
self.maxDiff = None
self.assertEqual(survey.to_json_dict(), expected_dict)
@@ -197,80 +170,68 @@ def test_select_one_question_with_identical_choice_name(self):
as the name of the select one get compiled.
"""
survey = utils.create_survey_from_fixture(
- "choice_name_same_as_select_name", filetype=FIXTURE_FILETYPE)
+ "choice_name_same_as_select_name", filetype=FIXTURE_FILETYPE
+ )
expected_dict = {
- u'name': u'choice_name_same_as_select_name',
- u'title': u'choice_name_same_as_select_name',
- u'sms_keyword': u'choice_name_same_as_select_name',
- u'default_language': u'default',
- u'id_string': u'choice_name_same_as_select_name',
- u'type': u'survey',
- u'children': [
+ "name": "choice_name_same_as_select_name",
+ "title": "choice_name_same_as_select_name",
+ "sms_keyword": "choice_name_same_as_select_name",
+ "default_language": "default",
+ "id_string": "choice_name_same_as_select_name",
+ "type": "survey",
+ "children": [
{
- u'children': [
+ "children": [{"name": "zone", "label": "Zone"}],
+ "type": "select one",
+ "name": "zone",
+ "label": "Zone",
+ },
+ {
+ "children": [
{
- u'name': u'zone',
- u'label': u'Zone'
+ "bind": {
+ "calculate": "concat('uuid:', uuid())",
+ "readonly": "true()",
+ },
+ "name": "instanceID",
+ "type": "calculate",
}
],
- u'type': u'select one',
- u'name': u'zone',
- u'label': u'Zone',
+ "control": {"bodyless": True},
+ "name": "meta",
+ "type": "group",
},
- {u'children': [
- {
- u'bind': {
- 'calculate': "concat('uuid:', uuid())",
- 'readonly': 'true()'
- },
- u'name': 'instanceID',
- u'type': 'calculate'
- }
- ],
- u'control': {
- 'bodyless': True
- },
- u'name': 'meta',
- u'type': u'group'
- }
- ]
+ ],
}
self.maxDiff = None
self.assertEqual(survey.to_json_dict(), expected_dict)
def test_loop(self):
- survey = utils.create_survey_from_fixture(
- "loop", filetype=FIXTURE_FILETYPE)
+ survey = utils.create_survey_from_fixture("loop", filetype=FIXTURE_FILETYPE)
expected_dict = {
- u'name': u'loop',
- u'id_string': u'loop',
- u'sms_keyword': u'loop',
- u'title': u'loop',
- u'type': u'survey',
- u'default_language': u'default',
- u'children': [
+ "name": "loop",
+ "id_string": "loop",
+ "sms_keyword": "loop",
+ "title": "loop",
+ "type": "survey",
+ "default_language": "default",
+ "children": [
{
- u'name': u'available_toilet_types',
- u'label': {
- u'english':
- u'What type of toilets are on the premises?'
- },
- u'type': u'select all that apply',
- u'children': [
+ "name": "available_toilet_types",
+ "label": {"english": "What type of toilets are on the premises?"},
+ "type": "select all that apply",
+ "children": [
{
- u'name': u'pit_latrine_with_slab',
- u'label': {u'english': u'Pit latrine with slab'}
+ "name": "pit_latrine_with_slab",
+ "label": {"english": "Pit latrine with slab"},
},
{
- u'name': u'open_pit_latrine',
- u'label': {
- u'english':
- u'Pit latrine without slab/open pit'
- }
+ "name": "open_pit_latrine",
+ "label": {"english": "Pit latrine without slab/open pit"},
},
{
- u'name': u'bucket_system',
- u'label': {u'english': u'Bucket system'}
+ "name": "bucket_system",
+ "label": {"english": "Bucket system"},
},
# Removing this because select alls shouldn't need
# an explicit none option
@@ -278,311 +239,295 @@ def test_loop(self):
# u'name': u'none',
# u'label': u'None',
# },
- {
- u'name': u'other',
- u'label': u'Other'
- },
- ]
+ {"name": "other", "label": "Other"},
+ ],
},
-
{
- u'name': u'available_toilet_types_other',
- u'bind': {
- u'relevant':
- u"selected(../available_toilet_types, 'other')"
+ "name": "available_toilet_types_other",
+ "bind": {
+ "relevant": "selected(../available_toilet_types, 'other')"
},
- u'label': u'Specify other.',
- u'type': u'text'
+ "label": "Specify other.",
+ "type": "text",
},
{
- u'name': u'loop_toilet_types',
- u'type': u'group',
- u'children': [
+ "name": "loop_toilet_types",
+ "type": "group",
+ "children": [
{
- u'name': u'pit_latrine_with_slab',
- u'label': {u'english': u'Pit latrine with slab'},
- u'type': u'group',
- u'children': [
+ "name": "pit_latrine_with_slab",
+ "label": {"english": "Pit latrine with slab"},
+ "type": "group",
+ "children": [
{
- u'name': u'number',
- u'label': {
- u'english':
- u'How many Pit latrine with slab are'
- u' on the premises?'
+ "name": "number",
+ "label": {
+ "english": "How many Pit latrine with slab are"
+ " on the premises?"
},
- u'type': u'integer'
- }]},
+ "type": "integer",
+ }
+ ],
+ },
{
- u'name': u'open_pit_latrine',
- u'label': {
- u'english':
- u'Pit latrine without slab/open pit'
- },
- u'type': u'group',
- u'children': [
+ "name": "open_pit_latrine",
+ "label": {"english": "Pit latrine without slab/open pit"},
+ "type": "group",
+ "children": [
{
- u'name': u'number',
- u'label': {
- u'english':
- u'How many Pit latrine without '
- u'slab/open pit are on the premises?'
+ "name": "number",
+ "label": {
+ "english": "How many Pit latrine without "
+ "slab/open pit are on the premises?"
},
- u'type': u'integer'
+ "type": "integer",
}
- ]
+ ],
},
{
- u'name': u'bucket_system',
- u'label': {u'english': u'Bucket system'},
- u'type': u'group',
- u'children': [
+ "name": "bucket_system",
+ "label": {"english": "Bucket system"},
+ "type": "group",
+ "children": [
{
- u'name': u'number',
- u'label': {
- u'english':
- u'How many Bucket system are on the'
- u' premises?'},
- u'type': u'integer'
+ "name": "number",
+ "label": {
+ "english": "How many Bucket system are on the"
+ " premises?"
+ },
+ "type": "integer",
}
- ]
- }]},
+ ],
+ },
+ ],
+ },
{
- u'children': [
+ "children": [
{
- u'bind': {
- 'calculate': "concat('uuid:', uuid())",
- 'readonly': 'true()'
+ "bind": {
+ "calculate": "concat('uuid:', uuid())",
+ "readonly": "true()",
},
- u'name': 'instanceID',
- u'type': 'calculate'
+ "name": "instanceID",
+ "type": "calculate",
}
],
- u'control': {
- 'bodyless': True
- },
- u'name': 'meta',
- u'type': u'group'
- }]}
+ "control": {"bodyless": True},
+ "name": "meta",
+ "type": "group",
+ },
+ ],
+ }
self.maxDiff = None
self.assertEqual(survey.to_json_dict(), expected_dict)
def test_sms_columns(self):
- survey = utils.create_survey_from_fixture(
- "sms_info", filetype=FIXTURE_FILETYPE)
+ survey = utils.create_survey_from_fixture("sms_info", filetype=FIXTURE_FILETYPE)
expected_dict = {
- u'children': [{
- u'children': [
- {
- u'label': u'How old are you?',
- u'name': u'age',
- u'sms_field': u'q1',
- u'type': u'integer'
- },
- {
- u'children': [
- {
- u'label': u'no',
- u'name': u'0',
- u'sms_option': u'n'
- },
- {
- u'label': u'yes',
- u'name': u'1',
- u'sms_option': u'y'
- }],
- u'label': u'Do you have any children?',
- u'name': u'has_children',
- u'sms_field': u'q2',
- u'type': u'select one'
- },
- {
- u'label': u"What's your birth day?",
- u'name': u'bday',
- u'sms_field': u'q3',
- u'type': u'date'
- },
- {
- u'label': u'What is your name?',
- u'name': u'name',
- u'sms_field': u'q4',
- u'type': u'text'
- }
- ],
- u'name': u'section1',
- u'sms_field': u'a',
- u'type': u'group'
- },
+ "children": [
{
- u'children': [
+ "children": [
{
- u'label': u'May I take your picture?',
- u'name': u'picture',
- u'type': u'photo'
+ "label": "How old are you?",
+ "name": "age",
+ "sms_field": "q1",
+ "type": "integer",
},
{
- u'label':
- u'Record your GPS coordinates.',
- u'name': u'gps',
- u'type': u'geopoint'
- }
+ "children": [
+ {"label": "no", "name": "0", "sms_option": "n"},
+ {"label": "yes", "name": "1", "sms_option": "y"},
+ ],
+ "label": "Do you have any children?",
+ "name": "has_children",
+ "sms_field": "q2",
+ "type": "select one",
+ },
+ {
+ "label": "What's your birth day?",
+ "name": "bday",
+ "sms_field": "q3",
+ "type": "date",
+ },
+ {
+ "label": "What is your name?",
+ "name": "name",
+ "sms_field": "q4",
+ "type": "text",
+ },
],
- u'name': u'medias',
- u'sms_field': u'c',
- u'type': u'group'
- }, {
- u'children': [
+ "name": "section1",
+ "sms_field": "a",
+ "type": "group",
+ },
+ {
+ "children": [
{
- u'children': [{
- u'label': u'Mozilla Firefox',
- u'name': u'firefox',
- u'sms_option': u'ff'
- },
+ "label": "May I take your picture?",
+ "name": "picture",
+ "type": "photo",
+ },
+ {
+ "label": "Record your GPS coordinates.",
+ "name": "gps",
+ "type": "geopoint",
+ },
+ ],
+ "name": "medias",
+ "sms_field": "c",
+ "type": "group",
+ },
+ {
+ "children": [
+ {
+ "children": [
{
- u'label': u'Google Chrome',
- u'name': u'chrome',
- u'sms_option': u'gc'
+ "label": "Mozilla Firefox",
+ "name": "firefox",
+ "sms_option": "ff",
},
{
- u'label':
- u'Internet Explorer',
- u'name': u'ie',
- u'sms_option': u'ie'
+ "label": "Google Chrome",
+ "name": "chrome",
+ "sms_option": "gc",
},
{
- u'label': u'Safari',
- u'name': u'safari',
- u'sms_option': u'saf'
- }
+ "label": "Internet Explorer",
+ "name": "ie",
+ "sms_option": "ie",
+ },
+ {
+ "label": "Safari",
+ "name": "safari",
+ "sms_option": "saf",
+ },
],
- u'label':
- u'What web browsers do you use?',
- u'name': u'web_browsers',
- u'sms_field': u'q5',
- u'type': u'select all that apply'
+ "label": "What web browsers do you use?",
+ "name": "web_browsers",
+ "sms_field": "q5",
+ "type": "select all that apply",
}
],
- u'name': u'browsers',
- u'sms_field': u'b',
- u'type': u'group'
- }, {
- u'children': [{
- u'label': u'Phone Number',
- u'name': u'phone',
- u'type': u'phonenumber'
- }, {
- u'label': u'Start DT',
- u'name': u'start',
- u'type': u'start'
- }, {
- u'label': u'End DT',
- u'name': u'end',
- u'type': u'end'
- }, {
- u'label': u'Send Day',
- u'name': u'today',
- u'type': u'today'
- }, {
- u'label': u'IMEI',
- u'name': u'imei',
- u'type': u'deviceid'
- }, {
- u'label': u'Hey!',
- u'name': u'nope',
- u'type': u'note'
- }],
- u'name': u'metadata',
- u'sms_field': u'meta',
- u'type': u'group'
+ "name": "browsers",
+ "sms_field": "b",
+ "type": "group",
},
{
- u'children': [{
- u'bind': {
- 'calculate': "concat('uuid:', uuid())",
- 'readonly': 'true()'
+ "children": [
+ {
+ "label": "Phone Number",
+ "name": "phone",
+ "type": "phonenumber",
},
- u'name': 'instanceID',
- u'type': 'calculate'
- }],
- u'control': {'bodyless': True},
- u'name': 'meta',
- u'type': u'group'
- }],
- u'default_language': u'default',
- u'id_string': u'sms_info_form',
- u'name': u'sms_info',
- u'sms_allow_media': u'TRUE',
- u'sms_date_format': u'%Y-%m-%d',
- u'sms_datetime_format': u'%Y-%m-%d-%H:%M',
- u'sms_keyword': u'inf',
- u'sms_separator': u'+',
- u'title': u'SMS Example',
- u'type': u'survey'
+ {"label": "Start DT", "name": "start", "type": "start"},
+ {"label": "End DT", "name": "end", "type": "end"},
+ {"label": "Send Day", "name": "today", "type": "today"},
+ {"label": "IMEI", "name": "imei", "type": "deviceid"},
+ {"label": "Hey!", "name": "nope", "type": "note"},
+ ],
+ "name": "metadata",
+ "sms_field": "meta",
+ "type": "group",
+ },
+ {
+ "children": [
+ {
+ "bind": {
+ "calculate": "concat('uuid:', uuid())",
+ "readonly": "true()",
+ },
+ "name": "instanceID",
+ "type": "calculate",
+ }
+ ],
+ "control": {"bodyless": True},
+ "name": "meta",
+ "type": "group",
+ },
+ ],
+ "default_language": "default",
+ "id_string": "sms_info_form",
+ "name": "sms_info",
+ "sms_allow_media": "TRUE",
+ "sms_date_format": "%Y-%m-%d",
+ "sms_datetime_format": "%Y-%m-%d-%H:%M",
+ "sms_keyword": "inf",
+ "sms_separator": "+",
+ "title": "SMS Example",
+ "type": "survey",
}
self.assertEqual(survey.to_json_dict(), expected_dict)
def test_style_column(self):
survey = utils.create_survey_from_fixture(
- "style_settings", filetype=FIXTURE_FILETYPE)
+ "style_settings", filetype=FIXTURE_FILETYPE
+ )
expected_dict = {
- u'children': [
+ "children": [
{
- u'label': {u'english': u'What is your name?'},
- u'name': u'your_name',
- u'type': u'text'
+ "label": {"english": "What is your name?"},
+ "name": "your_name",
+ "type": "text",
},
{
- u'label': {u'english': u'How many years old are you?'},
- u'name': u'your_age',
- u'type': u'integer'
+ "label": {"english": "How many years old are you?"},
+ "name": "your_age",
+ "type": "integer",
},
{
- u'children': [
+ "children": [
{
- u'bind': {
- 'calculate': "concat('uuid:', uuid())",
- 'readonly': 'true()'
+ "bind": {
+ "calculate": "concat('uuid:', uuid())",
+ "readonly": "true()",
},
- u'name': 'instanceID',
- u'type': 'calculate'
+ "name": "instanceID",
+ "type": "calculate",
}
],
- u'control': {'bodyless': True},
- u'name': 'meta',
- u'type': u'group'
- }
+ "control": {"bodyless": True},
+ "name": "meta",
+ "type": "group",
+ },
],
- u'default_language': u'default',
- u'id_string': u'new_id',
- u'name': u'style_settings',
- u'sms_keyword': u'new_id',
- u'style': u'ltr',
- u'title': u'My Survey',
- u'type': u'survey',
+ "default_language": "default",
+ "id_string": "new_id",
+ "name": "style_settings",
+ "sms_keyword": "new_id",
+ "style": "ltr",
+ "title": "My Survey",
+ "type": "survey",
}
self.assertEqual(survey.to_json_dict(), expected_dict)
- STRIP_NS_FROM_TAG_RE = re.compile(r'\{.+\}')
+ STRIP_NS_FROM_TAG_RE = re.compile(r"\{.+\}")
def test_style_not_added_to_body_if_not_present(self):
- survey = utils.create_survey_from_fixture(
- "settings", filetype=FIXTURE_FILETYPE)
+ survey = utils.create_survey_from_fixture("settings", filetype=FIXTURE_FILETYPE)
xml = survey.to_xml()
# find the body tag
- root_elm = ETree.fromstring(xml.encode('utf-8'))
- body_elms = list(filter(
- lambda e: self.STRIP_NS_FROM_TAG_RE.sub('', e.tag) == 'body',
- [c for c in root_elm.getchildren()]))
+ root_elm = ETree.fromstring(xml.encode("utf-8"))
+ body_elms = list(
+ filter(
+ lambda e: self.STRIP_NS_FROM_TAG_RE.sub("", e.tag) == "body",
+ [c for c in root_elm.getchildren()],
+ )
+ )
self.assertEqual(len(body_elms), 1)
- self.assertIsNone(body_elms[0].get('class'))
+ self.assertIsNone(body_elms[0].get("class"))
def test_style_added_to_body_if_present(self):
survey = utils.create_survey_from_fixture(
- "style_settings", filetype=FIXTURE_FILETYPE)
+ "style_settings", filetype=FIXTURE_FILETYPE
+ )
xml = survey.to_xml()
# find the body tag
- root_elm = ETree.fromstring(xml.encode('utf-8'))
- body_elms = list(filter(
- lambda e: self.STRIP_NS_FROM_TAG_RE.sub('', e.tag) == 'body',
- [c for c in root_elm.getchildren()]))
+ root_elm = ETree.fromstring(xml.encode("utf-8"))
+ body_elms = list(
+ filter(
+ lambda e: self.STRIP_NS_FROM_TAG_RE.sub("", e.tag) == "body",
+ [c for c in root_elm.getchildren()],
+ )
+ )
self.assertEqual(len(body_elms), 1)
- self.assertEqual(body_elms[0].get('class'), 'ltr')
+ self.assertEqual(body_elms[0].get("class"), "ltr")
diff --git a/pyxform/tests/dump_and_load_tests.py b/pyxform/tests/dump_and_load_tests.py
index ec0ceb7f2..c78d72ec2 100644
--- a/pyxform/tests/dump_and_load_tests.py
+++ b/pyxform/tests/dump_and_load_tests.py
@@ -1,11 +1,15 @@
+# -*- coding: utf-8 -*-
+"""
+Test multiple XLSForm can be generated successfully.
+"""
+import os
from unittest import TestCase
+
from pyxform.builder import create_survey_from_path
-import os
from pyxform.tests import utils
class DumpAndLoadTests(TestCase):
-
def setUp(self):
self.excel_files = [
"gps.xls",
@@ -20,24 +24,19 @@ def setUp(self):
# "include_json.xls",
"simple_loop.xls",
"yes_or_no_question.xls",
- ]
+ ]
self.surveys = {}
self.this_directory = os.path.dirname(__file__)
for filename in self.excel_files:
path = utils.path_to_text_fixture(filename)
- try:
- self.surveys[filename] = create_survey_from_path(path)
- except Exception as e:
- print("Error on : " + filename)
- raise e
+ self.surveys[filename] = create_survey_from_path(path)
def test_load_from_dump(self):
for filename, survey in self.surveys.items():
survey.json_dump()
path = survey.name + ".json"
survey_from_dump = create_survey_from_path(path)
- self.assertEqual(survey.to_json_dict(),
- survey_from_dump.to_json_dict())
+ self.assertEqual(survey.to_json_dict(), survey_from_dump.to_json_dict())
def tearDown(self):
for filename, survey in self.surveys.items():
diff --git a/pyxform/tests/file_utils_test.py b/pyxform/tests/file_utils_test.py
index 5d67ad141..ee21521b3 100644
--- a/pyxform/tests/file_utils_test.py
+++ b/pyxform/tests/file_utils_test.py
@@ -1,7 +1,11 @@
-from __future__ import print_function
+# -*- coding: utf-8 -*-
+"""
+Test xls2json_backends util functions.
+"""
from unittest import TestCase
-from pyxform.xls2json_backends import convert_file_to_csv_string
+
from pyxform.tests import utils
+from pyxform.xls2json_backends import convert_file_to_csv_string
class BackendUtilsTests(TestCase):
@@ -10,9 +14,4 @@ def test_xls_to_csv(self):
converted_xls = convert_file_to_csv_string(specify_other_xls)
specify_other_csv = utils.path_to_text_fixture("specify_other.csv")
converted_csv = convert_file_to_csv_string(specify_other_csv)
- print("csv:")
- print(converted_csv)
- print("xls:")
- print(converted_xls)
self.assertEqual(converted_csv, converted_xls)
-
diff --git a/pyxform/tests/group_test.py b/pyxform/tests/group_test.py
index 7c9ea86f3..c57d5f4d6 100644
--- a/pyxform/tests/group_test.py
+++ b/pyxform/tests/group_test.py
@@ -1,68 +1,65 @@
+# -*- coding: utf-8 -*-
"""
Testing simple cases for Xls2Json
"""
from unittest import TestCase
-from pyxform.xls2json import SurveyReader
+
from pyxform.builder import create_survey_element_from_dict
from pyxform.tests import utils
+from pyxform.xls2json import SurveyReader
class GroupTests(TestCase):
-
def test_json(self):
x = SurveyReader(utils.path_to_text_fixture("group.xls"))
x_results = x.to_json_dict()
expected_dict = {
- u'name': u'group',
- u'title': u'group',
- u'id_string': u'group',
- u'sms_keyword': u'group',
- u'default_language': u'default',
- u'type': u'survey',
- u'children': [
+ "name": "group",
+ "title": "group",
+ "id_string": "group",
+ "sms_keyword": "group",
+ "default_language": "default",
+ "type": "survey",
+ "children": [
{
- u'name': u'family_name',
- u'type': u'text',
- u'label': {u'English': u"What's your family name?"}
- },
+ "name": "family_name",
+ "type": "text",
+ "label": {"English": "What's your family name?"},
+ },
{
- u'name': u'father',
- u'type': u'group',
- u'label': {u'English': u'Father'},
- u'children': [
+ "name": "father",
+ "type": "group",
+ "label": {"English": "Father"},
+ "children": [
{
- u'name': u'phone_number',
- u'type': u'phone number',
- u'label': {
- u'English':
- u"What's your father's phone number?"}
- },
+ "name": "phone_number",
+ "type": "phone number",
+ "label": {"English": "What's your father's phone number?"},
+ },
{
- u'name': u'age',
- u'type': u'integer',
- u'label': {u'English': u'How old is your father?'}
- }
- ],
- },
+ "name": "age",
+ "type": "integer",
+ "label": {"English": "How old is your father?"},
+ },
+ ],
+ },
{
- u'children': [
+ "children": [
{
- u'bind': {
- 'calculate': "concat('uuid:', uuid())",
- 'readonly': 'true()'
+ "bind": {
+ "calculate": "concat('uuid:', uuid())",
+ "readonly": "true()",
},
- u'name': 'instanceID',
- u'type': 'calculate'
+ "name": "instanceID",
+ "type": "calculate",
}
],
- u'control': {
- 'bodyless': True
- },
- u'name': 'meta',
- u'type': u'group'
- }
- ],
- }
+ "control": {"bodyless": True},
+ "name": "meta",
+ "type": "group",
+ },
+ ],
+ }
self.maxDiff = None
self.assertEqual(x_results, expected_dict)
diff --git a/pyxform/tests/j2x_question_tests.py b/pyxform/tests/j2x_question_tests.py
index f880339e0..87987e068 100644
--- a/pyxform/tests/j2x_question_tests.py
+++ b/pyxform/tests/j2x_question_tests.py
@@ -1,9 +1,10 @@
+# -*- coding: utf-8 -*-
"""
Testing creation of Surveys using verbose methods
"""
from unittest import TestCase
-from pyxform import *
+from pyxform import Survey
from pyxform.builder import create_survey_element_from_dict
from pyxform.tests.utils import prep_class_config
@@ -26,25 +27,27 @@ def setUpClass(cls):
prep_class_config(cls=cls)
def setUp(self):
- self.s = Survey(name=u"test")
+ self.s = Survey(name="test")
def test_question_type_string(self):
simple_string_json = {
- u"label": {
- u"French": u"Nom du travailleur agricole:",
- u"English": u"Name of Community Agricultural Worker"
+ "label": {
+ "French": "Nom du travailleur agricole:",
+ "English": "Name of Community Agricultural Worker",
},
- u"type": u"text",
- u"name": u"enumerator_name"
+ "type": "text",
+ "name": "enumerator_name",
}
q = create_survey_element_from_dict(simple_string_json)
expected_string_control_xml = self.config.get(
- self.cls_name, "test_question_type_string_control")
+ self.cls_name, "test_question_type_string_control"
+ )
expected_string_binding_xml = self.config.get(
- self.cls_name, "test_question_type_string_binding")
+ self.cls_name, "test_question_type_string_binding"
+ )
self.s.add_child(q)
self.assertEqual(ctw(q.xml_control()), expected_string_control_xml)
@@ -57,49 +60,50 @@ def test_select_one_question_multilingual(self):
Test the lowest common denominator of question types.
"""
simple_select_one_json = {
- u"label": {u"f": u"ftext", u"e": u"etext"},
- u"type": u"select one",
- u"name": u"qname",
- u"choices": [
- {u"label": {u"f": u"fa", u"e": u"ea"}, u"name": u"a"},
- {u"label": {u"f": u"fb", u"e": u"eb"}, u"name": u"b"}
- ]
+ "label": {"f": "ftext", "e": "etext"},
+ "type": "select one",
+ "name": "qname",
+ "choices": [
+ {"label": {"f": "fa", "e": "ea"}, "name": "a"},
+ {"label": {"f": "fb", "e": "eb"}, "name": "b"},
+ ],
}
# I copied the response in, since this is not our method of testing
# valid return values.
expected_select_one_control_xml = self.config.get(
- self.cls_name, "test_select_one_question_multilingual_control")
+ self.cls_name, "test_select_one_question_multilingual_control"
+ )
expected_select_one_binding_xml = self.config.get(
- self.cls_name, "test_select_one_question_multilingual_binding")
+ self.cls_name, "test_select_one_question_multilingual_binding"
+ )
q = create_survey_element_from_dict(simple_select_one_json)
self.s.add_child(q)
self.assertEqual(ctw(q.xml_control()), expected_select_one_control_xml)
if TESTING_BINDINGS:
- self.assertEqual(ctw(q.xml_binding()),
- expected_select_one_binding_xml)
+ self.assertEqual(ctw(q.xml_binding()), expected_select_one_binding_xml)
def test_simple_integer_question_type_multilingual(self):
"""
not sure how integer questions should show up.
"""
simple_integer_question = {
- u"label": {u"f": u"fc", u"e": u"ec"},
- u"type": u"integer",
- u"name": u"integer_q",
- u"attributes": {}
+ "label": {"f": "fc", "e": "ec"},
+ "type": "integer",
+ "name": "integer_q",
+ "attributes": {},
}
expected_integer_control_xml = self.config.get(
- self.cls_name,
- "test_simple_integer_question_type_multilingual_control")
+ self.cls_name, "test_simple_integer_question_type_multilingual_control"
+ )
expected_integer_binding_xml = self.config.get(
- self.cls_name,
- "test_simple_integer_question_type_multilingual_binding")
+ self.cls_name, "test_simple_integer_question_type_multilingual_binding"
+ )
q = create_survey_element_from_dict(simple_integer_question)
@@ -114,17 +118,20 @@ def test_simple_date_question_type_multilingual(self):
"""
not sure how date questions should show up.
"""
- simple_date_question = {u"label": {u"f": u"fd", u"e": u"ed"},
- u"type": u"date", u"name": u"date_q",
- u"attributes": {}}
+ simple_date_question = {
+ "label": {"f": "fd", "e": "ed"},
+ "type": "date",
+ "name": "date_q",
+ "attributes": {},
+ }
expected_date_control_xml = self.config.get(
- self.cls_name,
- "test_simple_date_question_type_multilingual_control")
+ self.cls_name, "test_simple_date_question_type_multilingual_control"
+ )
expected_date_binding_xml = self.config.get(
- self.cls_name,
- "test_simple_date_question_type_multilingual_binding")
+ self.cls_name, "test_simple_date_question_type_multilingual_binding"
+ )
q = create_survey_element_from_dict(simple_date_question)
self.s.add_child(q)
@@ -138,72 +145,74 @@ def test_simple_phone_number_question_type_multilingual(self):
not sure how phone number questions should show up.
"""
simple_phone_number_question = {
- u"label": {u"f": u"fe", u"e": u"ee"},
- u"type": u"phone number",
- u"name": u"phone_number_q",
+ "label": {"f": "fe", "e": "ee"},
+ "type": "phone number",
+ "name": "phone_number_q",
}
expected_phone_number_control_xml = self.config.get(
- self.cls_name,
- "test_simple_phone_number_question_type_multilingual_control")
+ self.cls_name, "test_simple_phone_number_question_type_multilingual_control"
+ )
expected_phone_number_binding_xml = self.config.get(
- self.cls_name,
- "test_simple_phone_number_question_type_multilingual_binding")
+ self.cls_name, "test_simple_phone_number_question_type_multilingual_binding"
+ )
q = create_survey_element_from_dict(simple_phone_number_question)
self.s.add_child(q)
- self.assertEqual(ctw(q.xml_control()),
- expected_phone_number_control_xml)
+ self.assertEqual(ctw(q.xml_control()), expected_phone_number_control_xml)
if TESTING_BINDINGS:
- self.assertEqual(ctw(q.xml_binding()),
- expected_phone_number_binding_xml)
+ self.assertEqual(ctw(q.xml_binding()), expected_phone_number_binding_xml)
def test_simple_select_all_question_multilingual(self):
"""
not sure how select all questions should show up...
"""
simple_select_all_question = {
- u"label": {u"f": u"f choisit", u"e": u"e choose"},
- u"type": u"select all that apply",
- u"name": u"select_all_q",
- u"choices": [
- {u"label": {u"f": u"ff", u"e": u"ef"}, u"name": u"f"},
- {u"label": {u"f": u"fg", u"e": u"eg"}, u"name": u"g"},
- {u"label": {u"f": u"fh", u"e": u"eh"}, u"name": u"h"}
- ]
+ "label": {"f": "f choisit", "e": "e choose"},
+ "type": "select all that apply",
+ "name": "select_all_q",
+ "choices": [
+ {"label": {"f": "ff", "e": "ef"}, "name": "f"},
+ {"label": {"f": "fg", "e": "eg"}, "name": "g"},
+ {"label": {"f": "fh", "e": "eh"}, "name": "h"},
+ ],
}
expected_select_all_control_xml = self.config.get(
- self.cls_name,
- "test_simple_select_all_question_multilingual_control")
+ self.cls_name, "test_simple_select_all_question_multilingual_control"
+ )
expected_select_all_binding_xml = self.config.get(
- self.cls_name,
- "test_simple_select_all_question_multilingual_binding")
+ self.cls_name, "test_simple_select_all_question_multilingual_binding"
+ )
q = create_survey_element_from_dict(simple_select_all_question)
self.s.add_child(q)
self.assertEqual(ctw(q.xml_control()), expected_select_all_control_xml)
if TESTING_BINDINGS:
- self.assertEqual(ctw(q.xml_binding()),
- expected_select_all_binding_xml)
+ self.assertEqual(ctw(q.xml_binding()), expected_select_all_binding_xml)
def test_simple_decimal_question_multilingual(self):
"""
not sure how decimal should show up.
"""
- simple_decimal_question = {u"label": {u"f": u"f text", u"e": u"e text"},
- u"type": u"decimal", u"name": u"decimal_q",
- u"attributes": {}}
+ simple_decimal_question = {
+ "label": {"f": "f text", "e": "e text"},
+ "type": "decimal",
+ "name": "decimal_q",
+ "attributes": {},
+ }
expected_decimal_control_xml = self.config.get(
- self.cls_name, "test_simple_decimal_question_multilingual_control")
+ self.cls_name, "test_simple_decimal_question_multilingual_control"
+ )
expected_decimal_binding_xml = self.config.get(
- self.cls_name, "test_simple_decimal_question_multilingual_binding")
+ self.cls_name, "test_simple_decimal_question_multilingual_binding"
+ )
q = create_survey_element_from_dict(simple_decimal_question)
self.s.add_child(q)
diff --git a/pyxform/tests/j2x_test_creation.py b/pyxform/tests/j2x_test_creation.py
index 2ed3ddea7..ea8d2718a 100644
--- a/pyxform/tests/j2x_test_creation.py
+++ b/pyxform/tests/j2x_test_creation.py
@@ -1,8 +1,15 @@
+# -*- coding: utf-8 -*-
"""
Testing creation of Surveys using verbose methods
"""
from unittest import TestCase
-from pyxform import *
+
+from pyxform import (
+ InputQuestion,
+ MultipleChoiceQuestion,
+ Survey,
+ create_survey_from_xls,
+)
from pyxform.tests import utils
@@ -19,17 +26,12 @@ def test_survey_can_be_created_in_a_verbose_manner(self):
s.add_child(q)
expected_dict = {
- u'name': 'simple_survey',
- u'children': [
+ "name": "simple_survey",
+ "children": [
{
- u'name': 'cow_color',
- u'type': 'select one',
- u'children': [
- {
- u'label': 'Green',
- u'name': 'green',
- }
- ],
+ "name": "cow_color",
+ "type": "select one",
+ "children": [{"label": "Green", "name": "green"}],
}
],
}
@@ -38,24 +40,23 @@ def test_survey_can_be_created_in_a_verbose_manner(self):
def test_survey_can_be_created_in_a_slightly_less_verbose_manner(self):
option_dict_array = [
- {'name': 'red', 'label': 'Red'},
- {'name': 'blue', 'label': 'Blue'}
+ {"name": "red", "label": "Red"},
+ {"name": "blue", "label": "Blue"},
]
- q = MultipleChoiceQuestion(name="Favorite_Color",
- choices=option_dict_array)
- q.type = u"select one"
+ q = MultipleChoiceQuestion(name="Favorite_Color", choices=option_dict_array)
+ q.type = "select one"
s = Survey(name="Roses_are_Red", children=[q])
expected_dict = {
- u'name': 'Roses_are_Red',
- u'children': [
+ "name": "Roses_are_Red",
+ "children": [
{
- u'name': 'Favorite_Color',
- u'type': u'select one',
- u'children': [
- {u'label': 'Red', u'name': 'red'},
- {u'label': 'Blue', u'name': 'blue'}
+ "name": "Favorite_Color",
+ "type": "select one",
+ "children": [
+ {"label": "Red", "name": "red"},
+ {"label": "Blue", "name": "blue"},
],
}
],
@@ -67,18 +68,18 @@ def test_two_options_cannot_have_the_same_value(self):
q = MultipleChoiceQuestion(name="Favorite Color")
q.add_choice(name="grey", label="Gray")
q.add_choice(name="grey", label="Grey")
- self.assertRaises(Exception, q, 'validate')
+ self.assertRaises(Exception, q, "validate")
def test_one_section_cannot_have_two_conflicting_slugs(self):
q1 = InputQuestion(name="YourName")
q2 = InputQuestion(name="YourName")
s = Survey(name="Roses are Red", children=[q1, q2])
- self.assertRaises(Exception, s, 'validate')
+ self.assertRaises(Exception, s, "validate")
def allow_surveys_with_comment_rows(self):
"""assume that a survey with rows that don't have name, type, or label
headings raise warning only"""
- path = utils.path_to_text_fixture('allow_comment_rows_test.xls')
+ path = utils.path_to_text_fixture("allow_comment_rows_test.xls")
survey = create_survey_from_xls(path)
expected_dict = {
"default_language": "default",
@@ -86,10 +87,8 @@ def allow_surveys_with_comment_rows(self):
"children": [
{
"name": "farmer_name",
- "label": {
- "English": "First and last name of farmer"
- },
- "type": "text"
+ "label": {"English": "First and last name of farmer"},
+ "type": "text",
}
],
"name": "allow_comment_rows_test",
@@ -103,8 +102,8 @@ def allow_surveys_with_comment_rows(self):
"title": "allow_comment_rows_test",
"_xpath": {
"allow_comment_rows_test": "/allow_comment_rows_test",
- "farmer_name": "/allow_comment_rows_test/farmer_name"
+ "farmer_name": "/allow_comment_rows_test/farmer_name",
},
- "type": "survey"
+ "type": "survey",
}
self.assertEquals(survey.to_json_dict(), expected_dict)
diff --git a/pyxform/tests/j2x_test_instantiation.py b/pyxform/tests/j2x_test_instantiation.py
index 74de9ddd2..e779905b0 100644
--- a/pyxform/tests/j2x_test_instantiation.py
+++ b/pyxform/tests/j2x_test_instantiation.py
@@ -1,81 +1,93 @@
+# -*- coding: utf-8 -*-
"""
Testing the instance object for pyxform.
"""
from unittest import TestCase
-from pyxform import *
+
+from pyxform import Survey, SurveyInstance
from pyxform.builder import create_survey_element_from_dict
from pyxform.tests.utils import prep_class_config
class Json2XformExportingPrepTests(TestCase):
-
@classmethod
def setUpClass(cls):
prep_class_config(cls=cls)
def test_simple_survey_instantiation(self):
- surv = Survey(name=u"Simple")
+ surv = Survey(name="Simple")
q = create_survey_element_from_dict(
- {u"type": u"text",
- u"name": u"survey_question",
- u"label": u"Question"})
+ {"type": "text", "name": "survey_question", "label": "Question"}
+ )
surv.add_child(q)
-
+
i = surv.instantiate()
-
- self.assertEquals(i.keys(), [u"survey_question"])
- self.assertEquals(set(i.xpaths()),
- {u"/Simple", u"/Simple/survey_question"})
-
+
+ self.assertEquals(i.keys(), ["survey_question"])
+ self.assertEquals(set(i.xpaths()), {"/Simple", "/Simple/survey_question"})
+
def test_simple_survey_answering(self):
- surv = Survey(name=u"Water")
- q = create_survey_element_from_dict({
- u"type": u"text",
- u"name": u"color",
- u"label": u"Color"})
- q2 = create_survey_element_from_dict({
- u"type": u"text",
- u"name": u"feeling",
- u"label": u"Feeling"})
-
+ surv = Survey(name="Water")
+ q = create_survey_element_from_dict(
+ {"type": "text", "name": "color", "label": "Color"}
+ )
+ q2 = create_survey_element_from_dict(
+ {"type": "text", "name": "feeling", "label": "Feeling"}
+ )
+
surv.add_child(q)
surv.add_child(q2)
i = SurveyInstance(surv)
-
- i.answer(name=u"color", value=u"blue")
- self.assertEquals(i.answers()[u'color'], u"blue")
-
- i.answer(name=u"feeling", value=u"liquidy")
- self.assertEquals(i.answers()[u'feeling'], u"liquidy")
-
+
+ i.answer(name="color", value="blue")
+ self.assertEquals(i.answers()["color"], "blue")
+
+ i.answer(name="feeling", value="liquidy")
+ self.assertEquals(i.answers()["feeling"], "liquidy")
+
def test_answers_can_be_imported_from_xml(self):
- surv = Survey(name=u"data")
-
- surv.add_child(create_survey_element_from_dict({
- u'type': u'text', u'name': u'name', u"label": u"Name"}))
- surv.add_child(create_survey_element_from_dict({
- u'type': u'integer', u'name': u'users_per_month',
- u"label": u"Users per month"}))
- surv.add_child(create_survey_element_from_dict({
- u'type': u'gps', u'name': u'geopoint', u'label': u'gps'}))
- surv.add_child(create_survey_element_from_dict({
- u'type': u'imei', u'name': u'device_id'}))
-
+ surv = Survey(name="data")
+
+ surv.add_child(
+ create_survey_element_from_dict(
+ {"type": "text", "name": "name", "label": "Name"}
+ )
+ )
+ surv.add_child(
+ create_survey_element_from_dict(
+ {
+ "type": "integer",
+ "name": "users_per_month",
+ "label": "Users per month",
+ }
+ )
+ )
+ surv.add_child(
+ create_survey_element_from_dict(
+ {"type": "gps", "name": "geopoint", "label": "gps"}
+ )
+ )
+ surv.add_child(
+ create_survey_element_from_dict({"type": "imei", "name": "device_id"})
+ )
+
instance = surv.instantiate()
import_xml = self.config.get(
- self.cls_name, "test_answers_can_be_imported_from_xml")
+ self.cls_name, "test_answers_can_be_imported_from_xml"
+ )
instance.import_from_xml(import_xml)
-
+
def test_simple_registration_xml(self):
- reg_xform = Survey(name=u"Registration")
- name_question = create_survey_element_from_dict({
- u'type': u'text', u'name': u'name', u"label": u"Name"})
+ reg_xform = Survey(name="Registration")
+ name_question = create_survey_element_from_dict(
+ {"type": "text", "name": "name", "label": "Name"}
+ )
reg_xform.add_child(name_question)
-
+
reg_instance = reg_xform.instantiate()
-
- reg_instance.answer(name=u"name", value=u"bob")
-
+
+ reg_instance.answer(name="name", value="bob")
+
rx = reg_instance.to_xml()
expected_xml = self.config.get(
self.cls_name, "test_simple_registration_xml"
diff --git a/pyxform/tests/j2x_test_xform_build_preparation.py b/pyxform/tests/j2x_test_xform_build_preparation.py
index 9228a6a62..cec5f4f79 100644
--- a/pyxform/tests/j2x_test_xform_build_preparation.py
+++ b/pyxform/tests/j2x_test_xform_build_preparation.py
@@ -1,37 +1,39 @@
+# -*- coding: utf-8 -*-
"""
Testing preparation of values for XForm exporting
"""
from unittest import TestCase
-from pyxform import *
+from pyxform import MultipleChoiceQuestion, Survey
class Json2XformExportingPrepTests(TestCase):
-
def test_dictionary_consolidates_duplicate_entries(self):
-
+
yes_or_no_dict_array = [
{"label": {"French": "Oui", "English": "Yes"}, "name": "yes"},
- {"label": {"French": "Non", "English": "No"}, "name": "no"}
- ]
+ {"label": {"French": "Non", "English": "No"}, "name": "no"},
+ ]
first_yesno_question = MultipleChoiceQuestion(
- name="yn_q1", options=yes_or_no_dict_array, type="select one")
+ name="yn_q1", options=yes_or_no_dict_array, type="select one"
+ )
second_yesno_question = MultipleChoiceQuestion(
- name="yn_q2", options=yes_or_no_dict_array, type="select one")
-
+ name="yn_q2", options=yes_or_no_dict_array, type="select one"
+ )
+
s = Survey(name="yes_or_no_tests")
s.add_child(first_yesno_question)
s.add_child(second_yesno_question)
-
+
# begin the processes in survey.to_xml()
# 1. validate()
s.validate()
-
+
# 2. survey._build_options_list_from_descendants()
# options_list = s._build_options_list_from_descendants()
# Is this method called somewhere else now?
-
+
# desired_options_list = [first_yesno_question.children]
# todo: we need to think about whether we care about
diff --git a/pyxform/tests/js2x_test_import_from_json.py b/pyxform/tests/js2x_test_import_from_json.py
index ab28d5b04..064ea282c 100644
--- a/pyxform/tests/js2x_test_import_from_json.py
+++ b/pyxform/tests/js2x_test_import_from_json.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""
Testing our ability to import from a JSON text file.
"""
@@ -15,9 +16,9 @@ def test_simple_questions_can_be_imported_from_json(self):
{
"label": {"French": "Combien?", "English": "How many?"},
"type": "decimal",
- "name": "exchange_rate"
+ "name": "exchange_rate",
}
- ]
+ ],
}
s = create_survey_element_from_dict(json_text)
diff --git a/pyxform/tests/json2xform_test.py b/pyxform/tests/json2xform_test.py
index 9325cb8f9..bab2d9040 100644
--- a/pyxform/tests/json2xform_test.py
+++ b/pyxform/tests/json2xform_test.py
@@ -1,30 +1,32 @@
+# -*- coding: utf-8 -*-
"""
Testing simple cases for pyxform
"""
from unittest import TestCase
-from pyxform.survey import Survey
from pyxform.builder import create_survey_element_from_dict
-
+from pyxform.survey import Survey
# TODO:
# * test_two_questions_with_same_id_fails
# (get this working in json2xform)
+
class BasicJson2XFormTests(TestCase):
def test_survey_can_have_to_xml_called_twice(self):
"""
Test: Survey can have "to_xml" called multiple times
-
+
(This was not being allowed before.)
-
+
It would be good to know (with confidence) that a survey object
can be exported to_xml twice, and the same thing will be returned
both times.
"""
- survey = Survey(name=u"SampleSurvey")
+ survey = Survey(name="SampleSurvey")
q = create_survey_element_from_dict(
- {u'type': u'text', u'name': u'name', u'label': u'label'})
+ {"type": "text", "name": "name", "label": "label"}
+ )
survey.add_child(q)
str1 = survey.to_xml()
diff --git a/pyxform/tests/loop_tests.py b/pyxform/tests/loop_tests.py
index bc5cffb0b..54cf9da24 100644
--- a/pyxform/tests/loop_tests.py
+++ b/pyxform/tests/loop_tests.py
@@ -1,92 +1,95 @@
+# -*- coding: utf-8 -*-
+"""
+Test loop syntax.
+"""
from unittest import TestCase
+
from pyxform.builder import create_survey_from_xls
from pyxform.tests import utils
class LoopTests(TestCase):
def test_loop(self):
- path = utils.path_to_text_fixture('another_loop.xls')
+ path = utils.path_to_text_fixture("another_loop.xls")
survey = create_survey_from_xls(path)
self.maxDiff = None
expected_dict = {
- u'name': u'another_loop',
- u'id_string': u'another_loop',
- u'sms_keyword': u'another_loop',
- u'default_language': u'default',
- u'title': u'another_loop',
- u'type': u'survey',
- u'children': [
+ "name": "another_loop",
+ "id_string": "another_loop",
+ "sms_keyword": "another_loop",
+ "default_language": "default",
+ "title": "another_loop",
+ "type": "survey",
+ "children": [
{
- u'name': u'loop_vehicle_types',
- u'type': u'group',
- u'children': [
+ "name": "loop_vehicle_types",
+ "type": "group",
+ "children": [
{
- u'label': {u'English': u'Car',
- u'French': u'Voiture'},
- u'name': u'car',
- u'type': u'group',
- u'children': [
+ "label": {"English": "Car", "French": "Voiture"},
+ "name": "car",
+ "type": "group",
+ "children": [
{
- u'label': {
- u'English': u'How many do you have?',
- u'French': u'Combien avoir?'
+ "label": {
+ "English": "How many do you have?",
+ "French": "Combien avoir?",
},
- u'name': u'total',
- u'type': u'integer'
+ "name": "total",
+ "type": "integer",
},
{
- u'bind': {u'constraint': u'. <= ../total'},
- u'label': {
- u'English': u'How many are working?',
- u'French': u'Combien marcher?'
+ "bind": {"constraint": ". <= ../total"},
+ "label": {
+ "English": "How many are working?",
+ "French": "Combien marcher?",
},
- u'name': u'working',
- u'type': u'integer'
- }
+ "name": "working",
+ "type": "integer",
+ },
],
},
{
- u'label': {u'English': u'Motorcycle',
- u'French': u'Moto'},
- u'name': u'motor_cycle',
- u'type': u'group',
- u'children': [
+ "label": {"English": "Motorcycle", "French": "Moto"},
+ "name": "motor_cycle",
+ "type": "group",
+ "children": [
{
- u'label': {
- u'English': u'How many do you have?',
- u'French': u'Combien avoir?'
+ "label": {
+ "English": "How many do you have?",
+ "French": "Combien avoir?",
},
- u'name': u'total',
- u'type': u'integer'
+ "name": "total",
+ "type": "integer",
},
{
- u'bind': {u'constraint': u'. <= ../total'},
- u'label': {
- u'English': u'How many are working?',
- u'French': u'Combien marcher?'
+ "bind": {"constraint": ". <= ../total"},
+ "label": {
+ "English": "How many are working?",
+ "French": "Combien marcher?",
},
- u'name': u'working',
- u'type': u'integer'
- }
+ "name": "working",
+ "type": "integer",
+ },
],
- }]},
+ },
+ ],
+ },
{
- u'children': [
+ "children": [
{
- u'bind': {
- 'calculate': "concat('uuid:', uuid())",
- 'readonly': 'true()'
+ "bind": {
+ "calculate": "concat('uuid:', uuid())",
+ "readonly": "true()",
},
- u'name': 'instanceID',
- u'type': 'calculate'
+ "name": "instanceID",
+ "type": "calculate",
}
],
- u'control': {
- 'bodyless': True
- },
- u'name': 'meta',
- u'type': u'group'
- }
+ "control": {"bodyless": True},
+ "name": "meta",
+ "type": "group",
+ },
],
}
self.assertEquals(survey.to_json_dict(), expected_dict)
diff --git a/pyxform/tests/new_cascading_select_test.py b/pyxform/tests/new_cascading_select_test.py
index f94b20815..d92115f1c 100644
--- a/pyxform/tests/new_cascading_select_test.py
+++ b/pyxform/tests/new_cascading_select_test.py
@@ -1,6 +1,12 @@
-import unittest2 as unittest
+# -*- coding: utf-8 -*-
+"""
+Test cascading select syntax.
+"""
import codecs
import os
+
+import unittest2 as unittest
+
import pyxform
from pyxform.tests.utils import XFormTestCase
@@ -8,31 +14,36 @@
class MainTest(XFormTestCase):
-
+
maxDiff = None
-
+
def runTest(self):
- for filename in ["new_cascading_select.xls", "old_cascades.xls",
- "cascading_select_test.xls"]:
+ for filename in [
+ "new_cascading_select.xls",
+ "old_cascades.xls",
+ "cascading_select_test.xls",
+ ]:
self.get_file_path(filename)
expected_output_path = os.path.join(
- DIR, "test_expected_output", self.root_filename + ".xml")
+ DIR, "test_expected_output", self.root_filename + ".xml"
+ )
# Do the conversion:
- json_survey = pyxform.xls2json.parse_file_to_json(
- self.path_to_excel_file)
+ json_survey = pyxform.xls2json.parse_file_to_json(self.path_to_excel_file)
survey = pyxform.create_survey_element_from_dict(json_survey)
survey.print_xform_to_file(self.output_path)
# Compare with the expected output:
- with codecs.open(expected_output_path, 'rb', encoding="utf-8") as\
- expected_file:
- with codecs.open(self.output_path, 'rb', encoding="utf-8") as \
- actual_file:
- self.assertXFormEqual(expected_file.read(),
- actual_file.read())
-
-if __name__ == '__main__':
+ with codecs.open(
+ expected_output_path, "rb", encoding="utf-8"
+ ) as expected_file:
+ with codecs.open(
+ self.output_path, "rb", encoding="utf-8"
+ ) as actual_file:
+ self.assertXFormEqual(expected_file.read(), actual_file.read())
+
+
+if __name__ == "__main__":
unittest.main()
diff --git a/pyxform/tests/or_other_test.py b/pyxform/tests/or_other_test.py
index 60ba5b899..ff073a30f 100644
--- a/pyxform/tests/or_other_test.py
+++ b/pyxform/tests/or_other_test.py
@@ -1,9 +1,12 @@
+# -*- coding: utf-8 -*-
"""
-Some tests for the new (v0.9) spec is properly implemented.
+Some tests for the new (v0.9) spec is properly implemented.
"""
-import unittest2 as unittest
import codecs
import os
+
+import unittest2 as unittest
+
import pyxform
from pyxform.tests.utils import XFormTestCase
@@ -16,23 +19,23 @@ class MainTest(XFormTestCase):
def runTest(self):
filename = "or_other.xlsx"
self.get_file_path(filename)
- expected_output_path = os.path.join(DIR, "test_expected_output",
- self.root_filename + ".xml")
+ expected_output_path = os.path.join(
+ DIR, "test_expected_output", self.root_filename + ".xml"
+ )
# Do the conversion:
warnings = []
- json_survey = pyxform.xls2json.parse_file_to_json(self.path_to_excel_file,
- warnings=warnings)
+ json_survey = pyxform.xls2json.parse_file_to_json(
+ self.path_to_excel_file, warnings=warnings
+ )
survey = pyxform.create_survey_element_from_dict(json_survey)
survey.print_xform_to_file(self.output_path, warnings=warnings)
# print warnings
# Compare with the expected output:
- with codecs.open(expected_output_path, 'rb',
- encoding="utf-8") as expected_file:
- with codecs.open(self.output_path, 'rb',
- encoding="utf-8") as actual_file:
+ with codecs.open(expected_output_path, "rb", encoding="utf-8") as expected_file:
+ with codecs.open(self.output_path, "rb", encoding="utf-8") as actual_file:
self.assertXFormEqual(expected_file.read(), actual_file.read())
-if __name__ == '__main__':
+if __name__ == "__main__":
unittest.main()
diff --git a/pyxform/tests/select_one_external_test.py b/pyxform/tests/select_one_external_test.py
index b9edc0266..85a7df656 100644
--- a/pyxform/tests/select_one_external_test.py
+++ b/pyxform/tests/select_one_external_test.py
@@ -1,45 +1,54 @@
-import unittest2 as unittest
+# -*- coding: utf-8 -*-
+"""
+Test select one external syntax.
+"""
import codecs
import os
+
+import unittest2 as unittest
+
import pyxform
-from pyxform.utils import sheet_to_csv
from pyxform.tests.utils import XFormTestCase
+from pyxform.utils import sheet_to_csv
DIR = os.path.dirname(__file__)
class MainTest(XFormTestCase):
-
+
maxDiff = None
-
+
def runTest(self):
for filename in ["select_one_external.xlsx"]:
self.get_file_path(filename)
expected_output_path = os.path.join(
- DIR, "test_expected_output", self.root_filename + ".xml")
+ DIR, "test_expected_output", self.root_filename + ".xml"
+ )
- output_csv = os.path.join(
- DIR, "test_output", self.root_filename + ".csv")
+ output_csv = os.path.join(DIR, "test_output", self.root_filename + ".csv")
# Do the conversion:
- json_survey = pyxform.xls2json.parse_file_to_json(
- self.path_to_excel_file)
+ json_survey = pyxform.xls2json.parse_file_to_json(self.path_to_excel_file)
- self.assertTrue(sheet_to_csv(
- self.path_to_excel_file, output_csv, "external_choices"))
- self.assertFalse(sheet_to_csv(
- self.path_to_excel_file, output_csv, "non-existant sheet"))
+ self.assertTrue(
+ sheet_to_csv(self.path_to_excel_file, output_csv, "external_choices")
+ )
+ self.assertFalse(
+ sheet_to_csv(self.path_to_excel_file, output_csv, "non-existant sheet")
+ )
survey = pyxform.create_survey_element_from_dict(json_survey)
survey.print_xform_to_file(self.output_path)
# Compare with the expected output:
- with codecs.open(expected_output_path, 'rb', encoding="utf-8") as\
- expected_file:
- with codecs.open(self.output_path, 'rb', encoding="utf-8") as \
- actual_file:
- self.assertXFormEqual(
- expected_file.read(), actual_file.read())
-
-if __name__ == '__main__':
+ with codecs.open(
+ expected_output_path, "rb", encoding="utf-8"
+ ) as expected_file:
+ with codecs.open(
+ self.output_path, "rb", encoding="utf-8"
+ ) as actual_file:
+ self.assertXFormEqual(expected_file.read(), actual_file.read())
+
+
+if __name__ == "__main__":
unittest.main()
diff --git a/pyxform/tests/settings_test.py b/pyxform/tests/settings_test.py
index d63321415..3fcae2ff3 100644
--- a/pyxform/tests/settings_test.py
+++ b/pyxform/tests/settings_test.py
@@ -1,7 +1,13 @@
+# -*- coding: utf-8 -*-
+"""
+Test settings sheet syntax.
+"""
+
from unittest import TestCase
+
from pyxform.builder import create_survey_from_path
-from pyxform.xls2json import SurveyReader
from pyxform.tests import utils
+from pyxform.xls2json import SurveyReader
class SettingsTests(TestCase):
@@ -14,44 +20,42 @@ def setUp(self):
def test_survey_reader(self):
survey_reader = SurveyReader(self.path)
expected_dict = {
- u'id_string': u'new_id',
- u'sms_keyword': u'new_id',
- u'default_language': u'default',
- u'name': u'settings',
- u'title': u'My Survey',
- u'type': u'survey',
- u'attribute': {
- u'my_number': u'1234567890',
- u'my_string': u'lor\xe9m ipsum'
+ u"id_string": u"new_id",
+ u"sms_keyword": u"new_id",
+ u"default_language": u"default",
+ u"name": u"settings",
+ u"title": u"My Survey",
+ u"type": u"survey",
+ u"attribute": {
+ u"my_number": u"1234567890",
+ u"my_string": u"lor\xe9m ipsum",
},
- u'children': [
+ u"children": [
{
- u'name': u'your_name',
- u'label': {u'english': u'What is your name?'},
- u'type': u'text'
+ u"name": u"your_name",
+ u"label": {u"english": u"What is your name?"},
+ u"type": u"text",
},
{
- u'name': u'your_age',
- u'label': {u'english': u'How many years old are you?'},
- u'type': u'integer'
+ u"name": u"your_age",
+ u"label": {u"english": u"How many years old are you?"},
+ u"type": u"integer",
},
{
- 'children': [
+ "children": [
{
- 'bind': {
- 'calculate': "concat('uuid:', uuid())",
- 'readonly': 'true()'
+ "bind": {
+ "calculate": "concat('uuid:', uuid())",
+ "readonly": "true()",
},
- 'name': 'instanceID',
- 'type': 'calculate'
+ "name": "instanceID",
+ "type": "calculate",
}
],
- 'control': {
- 'bodyless': True
- },
- 'name': 'meta',
- 'type': 'group'
- }
+ "control": {"bodyless": True},
+ "name": "meta",
+ "type": "group",
+ },
],
}
self.assertEqual(survey_reader.to_json_dict(), expected_dict)
diff --git a/pyxform/tests/test_output/ODKValidateWarnings.xml b/pyxform/tests/test_output/ODKValidateWarnings.xml
deleted file mode 100644
index 741c494f8..000000000
--- a/pyxform/tests/test_output/ODKValidateWarnings.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-
-
-
- ODKValidateWarnings
-
-
-
-
- What's your father's phone number?
-
-
- What's your family name?
-
-
- asdf
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Enter numbers only.
-
-
-
-
-
-
diff --git a/pyxform/tests/test_output/cascades_old.xml b/pyxform/tests/test_output/cascades_old.xml
index 28d61b1d6..35cf77491 100644
--- a/pyxform/tests/test_output/cascades_old.xml
+++ b/pyxform/tests/test_output/cascades_old.xml
@@ -5,260 +5,212 @@
-
- Thaba-Tseka_C
-
-
- Qacha's Nek_C
-
-
- Quthing_C
-
-
- Ha Ts'oeu-Khala (TT-049)
-
-
- Raporong (MH-088)
-
-
- Ha Ralengoele (QN-068)
-
-
- Ha Monyake (MH-089)
-
-
- Ha Phatalla (MH-097)
-
-
- Ha Makhabane (MH-186)
-
-
- Ha Mokotane (MH-223)
-
-
- Phatlalla (MH-097)
-
-
- Rakoloi (MH-149)
-
-
- Ha Tumo (MH-384)
-
-
- Mataoeng (MH-219)
-
-
- Limapong (MK-268)
-
-
- Ha Nthimolane (MK-382)
-
-
- Ha Leaooa (LR-299)
+
+ Name 1
-
- Ha Ralikuku (LR-240)
+
+ Name 2
-
- Tsoenene (MK-002)
+
+ Name 3
-
- T'sila-nt'so (MK-233)
+
+ Other
-
- Moteetee (MK-037)
+
+ Yes
-
- Lithoteng (MK-131)
+
+ No
-
- Kopanong (BB-250)
+
+ Male
-
- Mantlakala (BB-221)
+
+ Female
-
- Community (BB-136)
+
+ A
-
- Ha 'Masekh'ou (BB-130)
+
+ A1
-
- Mphale (BB-156)
+
+ B
-
- Thoteng (BB-251)
+
+ C
-
- Masianokeng (BB-259)
+
+ Berea_A
-
- Motabola (BB-214)
+
+ Botha Bothe_A
-
- Ha Matala (LR-124)
+
+ Leribe_A
-
- Ha Mukemane (LR-507)
+
+ Mafeteng_A
-
- Ha Makhaketsa (LR-045)
+
+ Maseru_A
-
- Ha Manamolela (LR-429)
+
+ Mohale's Hoek_A
-
- Rankhelepe (LR-210)
+
+ Qacha's Nek_A
-
- Thaba-Phatsoa (LR-049)
+
+ Quthing_A
-
- Ha Ramapepe (LR-183)
+
+ Thaba-Tseka_A
-
- Ha Tsepe (LR-269)
+
+ Mokhotlong_A1
-
- Sekakeng (QT-335)
+
+ Qacha's Nek_A1
-
- Tiping (LR-428)
+
+ Thaba-Tseka_A1
-
- Ha Joele (MF-419)
+
+ Berea_B
-
- Mokomahatsi (BR-210)
+
+ Botha Bothe_B
-
- Rat'sit'so (QT-074)
+
+ Leribe_B
-
- Setibing (MS-581)
+
+ Mafeteng_B
-
- Polateng (MS-489)
+
+ Maseru_B
-
- Nazareth (MS-077)
+
+ Mohale's Hoek_B
-
- Mahlabatheng (MS-258)
+
+ Mokhotlong_B
-
- Joala Boholo (MS-155)
+
+ Qacha's Nek_B
-
- Ha Phallang (MS-488)
+
+ Quthing_B
-
- Ha Moruthoane (MS-191)
+
+ Thaba-Tseka_B
-
- Ha Leutsoa (MS-133)
+
+ Berea_C
-
- Salae (MF-529)
+
+ Botha Bothe_C
-
- Mathebe (MF-083)
+
+ Leribe_C
-
- Tlokotsane (MS-490)
+
+ Mafeteng_C
-
- B
+
+ Maseru_C
-
- Ha Bereng Matsoho (MH-115)
+
+ Maseru _C
-
- Ha Mohohlo (MH-404)
+
+ Mohale's Hoek_C
-
- Ha Senekane (MH-321)
+
+ Mokhotlong_C
-
- Malahleha (MH-036)
+
+ Qacha's Nek_C
-
- Ha Nkau (MH-293)
+
+ Quthing_C
-
- Ha Raboroko (MH-156)
+
+ Thaba-Tseka_C
-
- Ha Tebelo (MF- 679)
+
+ Ha Koone (BR-329)
-
- Ha Sebeli-Lehananeng (MF-054)
+
+ Ha Makebe (BR-011)
-
- Kubake Moreneng (MS-422)
+
+ Ha Mohatlane (BR-113)
-
- Machekoaneng (MS-104)
+
+ Ha Motloang (BR-103)
-
- Tale (LR-223)
+
+ Ha Ntlama (BR-048)
-
- Nkoeng (LR-361)
+
+ Ha Polaki/Matheneng (BR-141/2)
-
- Ha Manehella (MF-669)
+
+ Lekhalong (BR-400)
-
- Ha Likupa (MF-008)
+
+ Lifotholeng (BR-070)
-
- Ha Tobolela (LR-257)
+
+ Ha Lemphane (BB-195)
-
- Ha Tente (LR-258)
+
+ Kepile (BB-209)
-
- Metolong (LR-227)
+
+ Liteleng (Ha moluoane) (BB-248)
-
- Maqasane (LR-128)
+
+ Luma (BB-235)
-
- Khalimane/Lijoetsa (MF-415)
+
+ Motete (BB-146)
-
- Ha-Hlehlisi (MF-319)
+
+ Patuoe/Moepanyane (BB-201)
-
- Molleloa/Ha Patsa (MF-393)
+
+ Pokojoe-Khoaba (BB-095)
-
- Lithipeng/Mosuoane (MF-651)
+
+ Solane (BB-094)
-
- Ha- Ralintoane (MF-698)
+
+ Ha Mahlomola (LR-337)
-
- Ha Mashapha (MF-676)
+
+ Ha Matoli (LR-152)
-
- Ha Seoli (MF-816)
+
+ Ha Rantuba (LR-044)
-
- Ha Ramatima (MF-107)
+
+ Ha Senyenyane (LR-423)
-
- Ha Sechache (MS-497)
+
+ Ha Tsae (LR-256)
-
- Ha Mashenephe (MS-414)
+
+ Mapheaneng (LR-334)
-
- Maieaneng (MF-009)
+
+ Phahameng (LR-061)
-
- Makokotoaneng (MF-564)
+
+ Phelandaba (LR-164)
Ha Lepolesa (MF-098)
@@ -272,560 +224,611 @@
Ha Tjale (MF-583)
-
- Ha Tsae (LR-256)
+
+ Maieaneng (MF-009)
-
- Mapheaneng (LR-334)
+
+ Makokotoaneng (MF-564)
-
- Phahameng (LR-061)
+
+ Mathebe (MF-083)
-
- Phelandaba (LR-164)
+
+ Salae (MF-529)
-
- Ha Masasane (MK-381)
+
+ Ha Leutsoa (MS-133)
-
- Maluke (MH-473)
+
+ Ha Moruthoane (MS-191)
-
- Boranta (BR-327)
+
+ Ha Phallang (MS-488)
-
- No
+
+ Joala Boholo (MS-155)
-
- Setotoma (MH-110)
+
+ Mahlabatheng (MS-258)
-
- Yes
+
+ Nazareth (MS-077)
-
- Photha-Photha (QT-401)
+
+ Polateng (MS-489)
-
- Raseeng (QT-076)
+
+ Setibing (MS-581)
-
- Thaba-Chitja (QT-292)
+
+ Ha Makhabane (MH-186)
-
- Ha Long (TT-298)
+
+ Ha Mokotane (MH-223)
-
- Ha Moriana (TT-297)
+
+ Ha Monyake (MH-089)
-
- Ha Motake (TT-303)
+
+ Ha Phatalla (MH-097)
-
- Ha Nkune (TT-212)
+
+ Ha Tumo (MH-384)
-
- Ha Poko (TT-225)
+
+ Mataoeng (MH-219)
-
- Ha Ratau (TT-165)
+
+ Phatlalla (MH-097)
-
- Ha Sekhaupane (TT-344)
+
+ Rakoloi (MH-149)
-
- Khubetsoana & Sekoting (MS-508)
+
+ Raporong (MH-088)
-
- Ha Sofonia (MS-021)
+
+ Ha Ralengoele (QN-068)
-
- Letsoela (BR-008)
+
+ Ha Ramokakatela (QN-048)
-
- Lihlookong (MF-221)
+
+ Ha-Isaac (QN-059)
-
- Khasapane (MF-219)
+
+ Mosenekeng (QN-064)
-
- T'soeute (MF-750)
+
+ Sekoti (QN-069)
-
- Sekiring (MF-292)
+
+ Filoane (QT-358)
-
- Ha Nkoankoa (MS-447)
+
+ Kelebone (QT-034)
-
- Ha Lekhafola (MS-215/426)
+
+ Matebeleng (QT-078)
-
- Ha Salemone Morainyane (MS-475)
+
+ Mofetoli (QT-366)
-
- Ha Pita (MS-025)
+
+ Mots'oane (QT-075)
-
- Ha Senyenyane (LR-423)
+
+ Nosi (QT-355)
-
- Ha Rantuba (LR-044)
+
+ Seputeng (QT-354)
-
- Luma (BB-235)
+
+ Swatsi (QT-238)
-
- Liteleng (Ha moluoane) (BB-248)
+
+ Lihlabaneng Ha Morapeli (TT-170)
-
- Patuoe/Moepanyane (BB-201)
+
+ Mohlakeng (TT-011)
-
- Motete (BB-146)
+
+ Motsitseng (TT-160)
-
- Solane (BB-094)
+
+ Bokhina Pere (MK-304)
-
- Pokojoe-Khoaba (BB-095)
+
+ Ha Ralit'sepe (MK-393)
-
- Ha Matoli (LR-152)
+
+ Ha Senepi (MK-404)
-
- Ha Mahlomola (LR-337)
+
+ Makorong (MK-232)
-
- Bloodberg (MK-048)
+
+ Mpakatheng (MK-265)
-
- Ha Mokhati (BR-025)
+
+ Sekhutlong (MK-356)
-
- Ha Matjotjo (BR-175)
+
+ Sepatleng (MK-343)
-
- Tholanyane (TT-299)
+
+ Tseko (MK-154)
-
- Sekiring (QN-094)
+
+ Ha Isaac (QT-059)
-
- Ha Tumo (BR-043)
+
+ Ha Katela (QN-219)
-
- Ha Telukhunoana (BR-166)
+
+ Ha-Makhoa (QN-073)
-
- Ha Phoofolo (BR-090)
+
+ Maphotong (QN-216)
-
- Ha Phalatsane (BR-075)
+
+ Ha Laka (TT-009)
-
- Mokhethoaneng (BR-16)
+
+ Ha Sekhohola (TT-138)
-
- Kolojane (BR-036)
+
+ Mantsonyane (TT-051)
+
+
+ Ha Labane (TT-363)
+
+
+ Maholi-a Llang (TT-210)
+
+
+ Boranta (BR-327)
+
+
+ Letsoela (BR-008)
+
+
+ Maholong (BR-328)
+
+
+ Masaleng Ha Janki (BR-238)
+
+
+ Mokhachane (BR-330)
+
+
+ Mokomahatsi (BR-210)
+
+
+ Nokong (BR-060)
+
+
+ Qalaheng/Mafotholeng (BR-45/84)
+
+
+ Tsenoli (BR-001)
Ha Lesia (BB-258)
-
- Tsenoli (BR-001)
+
+ Ha Lishobana (BB-217)
Ha Rampai (BB-045)
-
- Ha Lishobana (BB-217)
+
+ Ha-Rakotoane (BB-185)
Hlakacha (BB-029)
-
- Ha-Rakotoane (BB-185)
+
+ Khutlo-sea-ja (BB-188)
Lekanyane (BB-212)
-
- Khutlo-sea-ja (BB-188)
+
+ Maphepheng (BB-147)
Setenong (BB-246)
-
- Maphepheng (BB-147)
+
+ Ha Makhaketsa (LR-045)
-
- Ha Sephelane (QN-154)
+
+ Ha Manamolela (LR-429)
-
- Ha Nkofo (QN-120)
+
+ Ha Matala (LR-124)
-
- Masakoane (TT-304)
+
+ Ha Mukemane (LR-507)
-
- Matsaile Manganeng (TT-411)
+
+ Ha Ramapepe (LR-183)
-
- Ha Molomo (QN-206)
+
+ Ha Tsepe (LR-269)
-
- Ha Ranqhongoana (QN-007)
+
+ Rankhelepe (LR-210)
-
- Mosenekeng (MK-341)
+
+ Thaba-Phatsoa (LR-049)
-
- Ha Lehata (QN-088)
+
+ Tiping (LR-428)
-
- Matebeleng (MK-334)
+
+ Ha Joele (MF-419)
-
- Matlong (MK-387)
+
+ Ha Mashapha (MF-676)
-
- Mabuleng (MK-269)
+
+ Ha- Ralintoane (MF-698)
-
- Maheneng (MK-342)
+
+ Ha Ramatima (MF-107)
-
- Ha Seholoholo (QN-055)
+
+ Ha Seoli (MF-816)
-
- Liboteng (QN-096)
+
+ Ha-Hlehlisi (MF-319)
-
- Thaba-Tseka_A
+
+ Khalimane/Lijoetsa (MF-415)
-
- Mokhotlong_A1
+
+ Lithipeng/Mosuoane (MF-651)
-
- Nokong (BR-060)
+
+ Molleloa/Ha Patsa (MF-393)
-
- Qalaheng/Mafotholeng (BR-45/84)
+
+ Ha Mashenephe (MS-414)
-
- Leribe_A
+
+ Ha Sechache (MS-497)
-
- Mafeteng_A
+
+ Mokotleng (MS-462)
-
- Berea_A
+
+ Mphephee/Moeaneng (MS-521)
-
- Botha Bothe_A
+
+ Ramakhaleng (MS-474)
-
- Qacha's Nek_A
+
+ Rothe (MS-001)
-
- Quthing_A
+
+ Tlokotsane (MS-490)
-
- Maseru_A
+
+ Tsutsulupa (MS-516)
-
- Mohale's Hoek_A
+
+ Keleke/Moleko (MS-465)
-
- Ha Isaac (QT-059)
+
+ Ha Boroko (MH-009)
-
- Tseko (MK-154)
+
+ Ha Hamo/Moko (MH-142)
-
- Sepatleng (MK-343)
+
+ Ha Mokhatla (MH-158)
-
- Sekhutlong (MK-356)
+
+ Ha Nthamaha (MH-478)
-
- Ha Laka (TT-009)
+
+ Ha Ranti (MH-538)
-
- Maphotong (QN-216)
+
+ Lecheche (MH-557)
-
- Ha-Makhoa (QN-073)
+
+ Matsoareng (MH-073)
-
- Ha Katela (QN-219)
+
+ Moru Motso (MH-507)
-
- Mantsonyane (TT-051)
+
+ Pontseng (MH-027)
-
- Ha Sekhohola (TT-138)
+
+ Draaihoek (MK-384)
-
- Maholong (BR-328)
+
+ Ha Tlenyane (MK-212)
-
- Masaleng Ha Janki (BR-238)
+
+ Khohlong (MK-312)
-
- Kepile (BB-209)
+
+ Liotloaneng (MK-379)
-
- Ha Lemphane (BB-195)
+
+ Mabuleng (MK-269)
-
- Ha Polaki/Matheneng (BR-141/2)
+
+ Maheneng (MK-342)
-
- Ha Ntlama (BR-048)
+
+ Matebeleng (MK-334)
-
- Lifotholeng (BR-070)
+
+ Matlong (MK-387)
-
- Lekhalong (BR-400)
+
+ Mosenekeng (MK-341)
-
- Ha Makebe (BR-011)
+
+ Ha Lehata (QN-088)
-
- Ha Koone (BR-329)
+
+ Ha Molomo (QN-206)
-
- Ha Motloang (BR-103)
+
+ Ha Ranqhongoana (QN-007)
-
- Ha Mohatlane (BR-113)
+
+ Ha Seholoholo (QN-055)
-
- Nkoto-Silase (QT-334)
+
+ Liboteng (QN-096)
-
- Ngoae & Sekokoaneng (QT-067)
+
+ Mankoe (QN-091)
-
- Matsaile Moreneng (TT-038)
+
+ Qenehellong (QN-070)
-
- Moeling (TT-268)
+
+ Sekiring (QN-094)
-
- Ha Ntsokoane (TT-301)
+
+ Tsolo (QN-104)
-
- Ha Labane (TT-363)
+
+ Mamokeli (QT-337)
-
- Qenehellong (QN-070)
+
+ Marakabei (QT-398)
+
+
+ Masuoaneng (QT-392)
+
+
+ Mats'ela-Habeli (QT-246)
+
+
+ Ngoae & Sekokoaneng (QT-067)
+
+
+ Nkoto-Silase (QT-334)
+
+
+ Photha-Photha (QT-401)
-
- Mankoe (QN-091)
+
+ Raseeng (QT-076)
-
- Tsolo (QN-104)
+
+ Thaba-Chitja (QT-292)
-
- Maholi-a Llang (TT-210)
+
+ Ha Long (TT-298)
-
- Marakabei (QT-398)
+
+ Ha Moriana (TT-297)
-
- Mamokeli (QT-337)
+
+ Ha Motake (TT-303)
-
- Mats'ela-Habeli (QT-246)
+
+ Ha Nkune (TT-212)
-
- Masuoaneng (QT-392)
+
+ Ha Poko (TT-225)
-
- Mokhotlong_B
+
+ Ha Ratau (TT-165)
-
- Qacha's Nek_B
+
+ Ha Sekhaupane (TT-344)
-
- Makunyapane (TT-083)
+
+ Masakoane (TT-304)
-
- Qacha's Nek_A1
+
+ Tholanyane (TT-299)
-
- Thaba-Tseka_A1
+
+ Ha Matjotjo (BR-175)
-
- Berea_B
+
+ Ha Mokhati (BR-025)
-
- Botha Bothe_B
+
+ Ha Phalatsane (BR-075)
-
- Leribe_B
+
+ Ha Phoofolo (BR-090)
-
- Mafeteng_B
+
+ Ha Telukhunoana (BR-166)
-
- Maseru_B
+
+ Ha Tumo (BR-043)
-
- Mohale's Hoek_B
+
+ Kolojane (BR-036)
-
- Seputeng (QT-354)
+
+ Mokhethoaneng (BR-16)
-
- Swatsi (QT-238)
+
+ Community (BB-136)
-
- Lihlabaneng Ha Morapeli (TT-170)
+
+ Ha 'Masekh'ou (BB-130)
-
- Mohlakeng (TT-011)
+
+ Kopanong (BB-250)
-
- Motsitseng (TT-160)
+
+ Mantlakala (BB-221)
-
- Bokhina Pere (MK-304)
+
+ Masianokeng (BB-259)
-
- Ha Ralit'sepe (MK-393)
+
+ Motabola (BB-214)
-
- Ha Senepi (MK-404)
+
+ Mphale (BB-156)
-
- Makorong (MK-232)
+
+ Thoteng (BB-251)
-
- Mpakatheng (MK-265)
+
+ Ha Leaooa (LR-299)
-
- Female
+
+ Ha Ralikuku (LR-240)
-
- Male
+
+ Ha Tente (LR-258)
-
- C
+
+ Ha Tobolela (LR-257)
-
- Other
+
+ Maqasane (LR-128)
-
- Name 3
+
+ Metolong (LR-227)
-
- Name 2
+
+ Nkoeng (LR-361)
-
- Name 1
+
+ Tale (LR-223)
-
- Ha Hamo/Moko (MH-142)
+
+ Ha Likupa (MF-008)
-
- Ha Mokhatla (MH-158)
+
+ Ha Manehella (MF-669)
-
- Thoteng (QT-034)
+
+ Ha Sebeli-Lehananeng (MF-054)
-
- Sello (QT-283)
+
+ Ha Tebelo (MF- 679)
-
- Mokhachane (BR-330)
+
+ Khasapane (MF-219)
-
- Tsutsulupa (MS-516)
+
+ Lihlookong (MF-221)
-
- Keleke/Moleko (MS-465)
+
+ Sekiring (MF-292)
-
- Ha Boroko (MH-009)
+
+ T'soeute (MF-750)
-
- Mokotleng (MS-462)
+
+ Ha Lekhafola (MS-215/426)
-
- Mphephee/Moeaneng (MS-521)
+
+ Ha Nkoankoa (MS-447)
-
- Ramakhaleng (MS-474)
+
+ Ha Pita (MS-025)
-
- Rothe (MS-001)
+
+ Ha Salemone Morainyane (MS-475)
-
- Mokhotlong_C
+
+ Ha Sofonia (MS-021)
-
- Mohale's Hoek_C
+
+ Khubetsoana & Sekoting (MS-508)
-
- Mafeteng_C
+
+ Kubake Moreneng (MS-422)
-
- Leribe_C
+
+ Machekoaneng (MS-104)
-
- Maseru _C
+
+ Ha Bereng Matsoho (MH-115)
-
- Maseru_C
+
+ Ha Mohohlo (MH-404)
-
- Thaba-Tseka_B
+
+ Ha Nkau (MH-293)
-
- Quthing_B
+
+ Ha Raboroko (MH-156)
-
- Botha Bothe_C
+
+ Ha Senekane (MH-321)
-
- Berea_C
+
+ Malahleha (MH-036)
-
- Ha Chooko (TT-312)
+
+ Maluke (MH-473)
-
- Nosi (QT-355)
+
+ Setotoma (MH-110)
-
- Mots'oane (QT-075)
+
+ Bloodberg (MK-048)
-
- Kelebone (QT-034)
+
+ Ha Masasane (MK-381)
-
- Filoane (QT-358)
+
+ Ha Nthimolane (MK-382)
-
- Mofetoli (QT-366)
+
+ Limapong (MK-268)
-
- Matebeleng (QT-078)
+
+