WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit cf12efd

Browse files
authored
Merge pull request #778 from lindsay-stevens/pyxform-775
775: Support Entities from repeats
2 parents 7fcc25f + 3c8287e commit cf12efd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+4116
-1003
lines changed

pyxform/aliases.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,6 @@
123123
"audio": survey_header["audio"],
124124
"video": survey_header["video"],
125125
}
126-
# Note that most of the type aliasing happens in all.xls
127126
_type_alias_map = {
128127
"imei": "deviceid",
129128
"image": "photo",

pyxform/constants.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
OSM_TYPE = "binary"
8787

8888
NAMESPACES = "namespaces"
89+
META = "meta"
8990

9091
# The following are the possible sheet names:
9192
SUPPORTED_SHEET_NAMES = {
@@ -113,10 +114,15 @@
113114
# The ODK XForms version that generated forms comply to
114115
CURRENT_XFORMS_VERSION = "1.0.0"
115116

117+
116118
# The ODK entities spec version that generated forms comply to
117-
ENTITIES_OFFLINE_VERSION = "2024.1.0"
119+
class EntityVersion(StrEnum):
120+
v2024_1_0 = "2024.1.0"
121+
v2025_1_0 = "2025.1.0"
122+
123+
118124
ENTITY = "entity"
119-
ENTITY_FEATURES = "entity_features"
125+
ENTITY_VERSION = "entity_version"
120126
ENTITIES_RESERVED_PREFIX = "__"
121127

122128

@@ -125,6 +131,7 @@ class EntityColumns(StrEnum):
125131
ENTITY_ID = "entity_id"
126132
CREATE_IF = "create_if"
127133
UPDATE_IF = "update_if"
134+
REPEAT = "repeat"
128135
LABEL = "label"
129136

130137

@@ -169,3 +176,4 @@ class EntityColumns(StrEnum):
169176
}
170177
SUPPORTED_MEDIA_TYPES = {"image", "big-image", "audio", "video"}
171178
OR_OTHER_CHOICE = {NAME: "other", LABEL: "Other"}
179+
RESERVED_NAMES_SURVEY_SHEET = {META}

pyxform/elements/__init__.py

Whitespace-only changes.

pyxform/elements/action.py

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
from enum import Enum
2+
3+
from pyxform.elements.element import Element
4+
from pyxform.util.enum import StrEnum
5+
from pyxform.utils import node
6+
7+
8+
class Event(StrEnum):
9+
"""
10+
Supported W3C XForms 1.1 Events and ODK extensions.
11+
"""
12+
13+
# For actions in model under /html/head/model
14+
ODK_INSTANCE_FIRST_LOAD = "odk-instance-first-load"
15+
ODK_INSTANCE_LOAD = "odk-instance-load"
16+
# For actions in repeat control under /html/body
17+
ODK_NEW_REPEAT = "odk-new-repeat"
18+
# For actions in question control under /html/body
19+
XFORMS_VALUE_CHANGED = "xforms-value-changed"
20+
21+
22+
class Action(Element):
23+
"""
24+
Base class for supported Action elements.
25+
26+
https://getodk.github.io/xforms-spec/#actions
27+
"""
28+
29+
__slots__ = ("event", "kwargs", "ref", "value")
30+
31+
def __init__(
32+
self,
33+
ref: str,
34+
event: Event,
35+
value: str | None = None,
36+
**kwargs,
37+
):
38+
super().__init__()
39+
self.ref: str = ref
40+
self.event: Event = event
41+
self.value: str | None = value
42+
43+
def node(self):
44+
return node(self.name, ref=self.ref, event=self.event.value)
45+
46+
47+
class Setvalue(Action):
48+
"""
49+
Explicitly sets the value of the specified instance data node.
50+
51+
https://getodk.github.io/xforms-spec/#action:setvalue
52+
"""
53+
54+
name = "setvalue"
55+
56+
def __init__(self, ref: str, event: Event, value: str | None = None, **kwargs):
57+
super().__init__(ref=ref, event=event, value=value, **kwargs)
58+
59+
def node(self):
60+
result = super().node()
61+
if self.value:
62+
result.setAttribute("value", self.value)
63+
return result
64+
65+
66+
class ODKSetGeopoint(Action):
67+
"""
68+
Sets the current location's geopoint value in the instance data node specified in the
69+
ref attribute.
70+
71+
https://getodk.github.io/xforms-spec/#action:setgeopoint
72+
"""
73+
74+
name = "odk:setgeopoint"
75+
76+
def __init__(
77+
self,
78+
ref: str,
79+
event: Event,
80+
value: str | None = None,
81+
**kwargs,
82+
):
83+
super().__init__(ref=ref, event=event, value=value, **kwargs)
84+
85+
86+
class ODKRecordAudio(Action):
87+
"""
88+
Records audio starting at the triggering event, saves the audio to a file, and writes
89+
the filename to the node specified in the ref attribute.
90+
91+
https://getodk.github.io/xforms-spec/#action:recordaudio
92+
"""
93+
94+
__slots__ = ("quality",)
95+
name = "odk:recordaudio"
96+
97+
def __init__(
98+
self,
99+
ref: str,
100+
event: Event,
101+
value: str | None = None,
102+
**kwargs,
103+
):
104+
super().__init__(ref=ref, event=event, value=value, **kwargs)
105+
self.quality: str | None = kwargs.get("odk:quality")
106+
107+
def node(self):
108+
result = super().node()
109+
if self.quality:
110+
result.setAttribute("odk:quality", self.quality)
111+
return result
112+
113+
114+
ACTION_CLASSES = {
115+
"setvalue": Setvalue,
116+
"odk:setgeopoint": ODKSetGeopoint,
117+
"odk:recordaudio": ODKRecordAudio,
118+
}
119+
120+
121+
class LibraryMember:
122+
def __init__(self, name: str, event: str):
123+
self.name: str = name
124+
self.event: str = event
125+
126+
def to_dict(self):
127+
return {"name": self.name, "event": self.event}
128+
129+
130+
class ActionLibrary(Enum):
131+
"""
132+
A collection of action/event configs used by pyxform.
133+
"""
134+
135+
setvalue_first_load = LibraryMember(
136+
name=Setvalue.name,
137+
event=Event.ODK_INSTANCE_FIRST_LOAD.value,
138+
)
139+
setvalue_new_repeat = LibraryMember(
140+
name=Setvalue.name,
141+
event=Event.ODK_NEW_REPEAT.value,
142+
)
143+
setvalue_value_changed = LibraryMember(
144+
name=Setvalue.name,
145+
event=Event.XFORMS_VALUE_CHANGED.value,
146+
)
147+
odk_setgeopoint_first_load = LibraryMember(
148+
name=ODKSetGeopoint.name,
149+
event=Event.ODK_INSTANCE_FIRST_LOAD.value,
150+
)
151+
odk_setgeopoint_value_changed = LibraryMember(
152+
name=ODKSetGeopoint.name,
153+
event=Event.XFORMS_VALUE_CHANGED.value,
154+
)
155+
odk_recordaudio_instance_load = LibraryMember(
156+
name=ODKRecordAudio.name,
157+
event=Event.ODK_INSTANCE_LOAD.value,
158+
)

pyxform/elements/element.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from typing import TYPE_CHECKING
2+
3+
if TYPE_CHECKING:
4+
from pyxform.utils import DetachableElement
5+
6+
7+
class Element:
8+
"""
9+
Base class for Element interface and default behaviour.
10+
11+
Unlike SurveyElement which may emit multiple elements for a particular survey
12+
component, this class is for defining individual elements for output.
13+
"""
14+
15+
name: str
16+
17+
def node(self) -> "DetachableElement":
18+
"""
19+
Create the element.
20+
"""
21+
raise NotImplementedError()

0 commit comments

Comments
 (0)