Skip to content

Conversation

@lindsay-stevens
Copy link
Contributor

@lindsay-stevens lindsay-stevens commented Sep 6, 2025

Closes #775
Closes #619 (unique_names.py error NAMES005)
Addresses question name issue in #504 (questions only have to be unique within their parent context)

TODO:

File follow-up issue:

Why is this the best possible solution? Were any other approaches considered?

The spec involves putting a "meta" block inside each repeat as well as at the top-level survey/main-instance node. This conflicts with existing name validation. However it seems that the name rules in pyxform are inconsistent and too restrictive. To enable the new entities feature, the modifications are made to name validation such that the new rules would be as outlined below.

The main modification to the existing rules is to allow a group to be unique only within it's context, rather than anywhere in the survey (as will be the case for the meta block). The main constraint seems to be that Central will produce export files for the top-level survey, and any repeats; any group names in such files are kebab'd together within the context of the survey OR lowest common ancestor repeat e.g. 'g1-g2-question'.

As for the entity repeats changes, this (as far as I understand it) adheres to the specs, Q&A docs, forum posts available. The tests in test_create_repeat.py are somewhat prescriptive so these should be reviewed to ensure they aren't preventing anything that should be allowed, or allowing something that should be prevented.

Name rules

Not all of these are enforced well, or at all

  • all names are case-sensitive (survey, repeat, group, question, choice list, choice, entity)
    • if SAS or SPSS will be used to analyse data, it's recommended to make names case-insensitive unique within their context (e.g. avoid "q1" and "Q1").
  • repeat names must be unique anywhere in the survey (at any level of nesting), and cannot be the same as the survey instance name (default "data").
    • if the XLSForm was converted separately with pyxform (not via xlsform-online or Central) then the survey instance name is the file name.
  • external instances ("xml-external", "csv-external") must be unique anywhere in the survey (at any level of nesting).
  • question and group names must be unique within their context i.e. nearest parent group or repeat, or the survey if not inside a group or repeat.
    • if pyxform reference variables "${}" are used, referenced name (question, group, repeat) must unique anywhere in the survey
      • if this is not possible then the pyxform reference can be replaced by a manually written XPath expression.
    • a loop produces a group in the loop's context, and so loop names follows the same rule for group names.
    • if the (deprecated) "flat" setting is active, question names must be unique anywhere in the survey.
    • pyxform may generate the following questions or groups, so these names are reserved:
      • "meta" group which is used for survey metadata such as instanceID and entity configuration
        • member questions include: "audit", "entity", "instanceID", "instanceName"
      • "generated_note_name_{row_number}" question name if a note does not have a name.
      • "{repeat_name}_count" question if a repeat has a repeat_count that is anything other than a ${} reference.
      • within a "loop", a group named with the choice name will contain a copy of each question or group inside the loop
        • loops cannot contain repeats since this would produce duplicate repeat names
  • choice list names must be unique anywhere in the survey.
    • choice lists are internal instances so the names must not be the same as other instances from:
      • the file name (excluding extension) from any "select_one_from_file" or "select_multiple_from_file" in the survey
      • the first argument of any "pulldata" calls that refer to an instance that doesn't already exist
  • choice names must be unique within the choice list
    • if the "allow_choice_duplicates" setting is active ("y") then choice names can be duplicated within each list
    • duplicate choices and loops are not allowed, because this would generate duplicate groups in the same context

What are the regression risks?

I did some git archaeology but I might've missed a critical reason why groups have historically been required to be unique at any depth - maybe Aggregate used to split by group not just repeat? Otherwise hopefully it's relaxing existing rules and improving error messages.

Does this change require updates to documentation? If so, please file an issue here and include the link below.

getodk/xforms-spec#330
getodk/docs#1987
XLSForm/xlsform.github.io#281

Before submitting this PR, please make sure you have:

  • included test cases for core behavior and edge cases in tests
  • run python -m unittest and verified all tests pass
  • run ruff format pyxform tests and ruff check pyxform tests to lint code
  • verified that any code or assets from external sources are properly credited in comments

- the .xls files in the question_types directory haven't changed since
  2014 and since then new question types have just been added to
  QUESTION_TYPE_DICT and documented externally.
- deleted associated unused code from question_type_dictionary.py and
  xls2json.py which was mostly a duplication of existing workbook
  reading code.
- moved print_pyobj_to_json to utils.py since it doesn't relate to
  anything specific to xls2json.py and it so simplifies module imports
- loops currently are undocumented but in the interest of not breaking
  forms that might be using them, test coverage was improved.
- test_for_loop.py: deleted because it's not actually testing loops,
  and the aspects of repeats it does test are covered by test_repeat.py
- test_loop.py:
  - converted older tests about internal data structures to use markdown
    instead of XLS to make it easier to see what it's testing.
    - deleted "another_loop.xls" since it's not used anywhere else
    - retained "simple_loop.xls" since it's used by other ser/des tests
  - added test cases for:
    - general behaviour of loops
    - error conditions: groups, repeats, and reference variables
      - maybe more incompatibilities but these seemed like a good start
- existing case in not_closed_group_test.xls was equivalent to the new
  case test_group__no_end_error__with_another_closed_group
- no other tests seem to look for these errors or check the conditions
  in these new tests.
- moved older builder-related tests so there's one group test module
- add docstrings to recently converted loop tests
- for unmatched 'begin [type]', state the row number of the 'begin',
  previously the error just said the group name
- for unmatched 'end [type]', state the row and type. The previous
  control type is probably not useful information, and often the 'end'
  doesn't have a name so the control name would be 'None' which is
  also not useful.
- L545: remove the if/else because the stack will never be empty, it
  would always at least have the survey; when the stack.pop() is done
  that's after a check if only one item remains in the stack.
- L560/L775: remove unused 'question_name' / 'control_name' variables
- L795: remove code that was commented out since 2013
- some of this is implicitly tested in other places but this commit
  adds a reasonably thorough and explicitly set of tests for naming
  validation rules about uniqueness of questions, groups, and repeats:
  - questions vs groups, repeats, survey
  - groups vs repeats, survey
  - repeats vs repeats, survey
- test_fields.py:
  - moved choices-related tests into test_choices_sheet.py
- test_builder.py:
  - deleted old commented code, and old XLS test covered by new tests
- main changes:
  - case-insensitive duplicate is a warning rather than an error since
    it is not a hard requirement and only applies to some use cases
  - group names can be re-used in a different context rather than having
    to be unique anywhere in the survey structure (like repeats)
    - if pyxform reference variables ${} are used the target question,
      group, or repeat still has to be unique anywhere
  - add uniqueness validation to xls2json so the errors can use row refs
  - add an error message specific to the 'meta' reserved name
  - use same validation funcs in xls2json and section.py/survey.py
  - update test assertions to use message templates
- to match source structure and organise related tests
- the name suffix "survey" means that these tests are related to
  declaring entities at the survey level, as opposed to inside of
  repeats, nested repeats, etc.
- as much processing as possible in entities_parsing.py
- most tests about entity repeat position are in test_create_repeat.py
  - tests specific to update-related fields in test_update_repeat.py
- add more entities xpath helpers
@lindsay-stevens lindsay-stevens marked this pull request as ready for review September 10, 2025 18:17
Copy link
Contributor

@lognaturel lognaturel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is FANTASTIC! The commit breakdown and the commit comments really helped with review on this one and help make it feel low-risk. Thanks!

The only thing I see missing is the Entity spec version. If there's an Entity from a repeat, the spec version should be v2025.1.0.

)

def test_entity_repeat_in_repeat__error(self):
"""Should raise an error if the entity repeat is inside a repeat."""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be a fine restriction to start with but is it necessary? My apologies if we've discussed this, I can't find a record of it! @ktuite Central would support this, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure but it didn't seem part of the spec, and also having the lowest common ancestor repeat inside of a repeat affects how relative paths are calculated (relevant to #777)

@lognaturel
Copy link
Contributor

Name rules

What do you think about pulling this out into a markdown file? Could be in a pyxform/docs folder for now or maybe all of the naming-related error messages could go in one file with this summary as the comment? It feels like useful prose to have in an easy-to-find place.

pyxform may generate the following questions or groups

Another I can think of is when using a repeat_count, a <repeat name>_count field is introduced. #435

- feedback from pyxform 778, noting:
  - for the name in the error message for unmatched end to be useful,
    the user would have to have written the name which is optional
  - may or may not be helpful to additionally show the name from the
    previous control with a different type - could be due to a missing
    row or a typo in the control type.
- also fix typo in test_loop.py
- feedback in pyxform 778
  - remove unnecessary jargon for users "context"
  - remove potentially unfamiliar latin "i.e."
  - clarify that case-insensitive issue is a "should" not a "must"
@lindsay-stevens
Copy link
Contributor Author

What do you think about pulling this out into a markdown file?

It may be more visible on xlsform.org so I opened XLSForm/xlsform.github.io#281. Or it could go in the template XLSForm, or ODK docs as well?

Copy link
Contributor

@lognaturel lognaturel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updates and answers to questions so far look great, thanks!

@lognaturel lognaturel self-requested a review September 16, 2025 18:11
@lognaturel
Copy link
Contributor

As I was trying this out, I forgot to specify the repeat reference on the entities sheet. I put my savetos in the repeat and I still got a toplevel Entity declaration. Could we also show an error when the Entity declaration is toplevel and there's a saveto in a repeat? Or maybe more generally, if there's a saveto for a field for which the reference from the repeat column is not an immediate parent, error.

@lognaturel
Copy link
Contributor

It looks like all of the references produced in binds are off by one parenting level: lindsay-stevens/pyxform@pyxform-775...lognaturel:pyxform:hm_comments

I'm pretty sure that's because in EntityDeclaration. _get_bind_node, the insert_xpaths call uses self as the context which is the meta block for the Entity. Instead, it should use the attribute or node in the meta block. But of course that doesn't exist because it's the very thing we're building at that point! It seemed to work fine when at the toplevel because absolute references were produced anyway but it doesn't work in repeats where that additional parenting level is important.

It doesn't feel directly related to #777 because insert_xpaths is doing the right thing, it's just called with the wrong context. That said, I'll shift my attention back to that PR to see whether we could safely get it merged in before this one. I'm still hoping there's an easy fix for this issue that I'm not seeing. Maybe we can just use a dummy node as context?

I've made a list of must-haves for this PR in the original description so we don't forget anything. Thanks, @lindsay-stevens, lots of tricky stuff here that will hopefully benefit users a ton!

@ktuite
Copy link
Collaborator

ktuite commented Sep 16, 2025

In one of the forms I was experimenting with, I saw this line but it only had odk-instance-first-load in the event list so only the first new entity in the repeat got a UUID. Adding odk-new-repeat fixed it.

<setvalue ref="/data/plot/tree/meta/entity/@id" event="odk-instance-first-load odk-new-repeat" type="string" readonly="true()" value="uuid()"/>

@lindsay-stevens
Copy link
Contributor Author

insert_xpaths is doing the right thing

There were cases where it was tripped up by groups and/or repeats on the path between the reference source and lowest common ancestor repeat (LCAR), and groups and/or repeats on the path between the reference target and the LCAR. So that could affect where the save_to items could be placed, and the meta group itself may or not be a problem. Also, whether or not the LCAR is inside a repeat (parent or ancestor) affects how the relative path is calculated. I can try to press on with the current code but I think getting good test coverage and consistent behaviour will be a challenge without #777.

- in addition to diff resolution, entities_parsing.py was updated to
  use pyxform_reference.py instead of the regex directly. This required
  adding a `match_full` param parse_pyxform_references because the
  `is_pyxform_reference` func returns bool but for entities the match
  result is needed, and there are additional constraints to apply.
  Updated test cases in test_create_repeat.py to show when the different
  error types would be triggered.
- similar to question.py `get_setvalue_node_for_dynamic_default` except
  that the default isn't a factor and there are different output attrs.
- also consolidated entity xpath assertions for model/setvalue
- combine assertions about the entities meta block
  - model_instance_meta maybe a little unwieldy but probably preferable
    to having many similar but slightly different xpaths
- apply entities xpath helper to existing xpath test assertions
@lindsay-stevens
Copy link
Contributor Author

lindsay-stevens commented Sep 19, 2025

As of df6eee6 (but not necessarily due to that commit) the validate CI job (merged in c736045) is failing for 2 tests, due to a javarosa error in SetValueAction.java

org.javarosa.xpath.XPathTypeMismatchException: XPath evaluation: type mismatch 
You are trying to target a repeated field. Currently you may only target a field in a specific repeat instance.

XPath nodeset has more than one node [" + references + "].

It looks like the error message doesn't show the actual references because in the javarosa source code, the quotes surrounding the "references" symbol are escaped. The tests that fail don't seem like they should fail, especially considering that test_other_controls_before__ok passes - like it seems as though the addition of other non-entity repeats after the entity repeat is somehow triggering an error.

tests.entities.test_create_repeat.TestEntitiesCreateRepeat.test_other_controls_after__ok

        | survey |
        | | type         | name  | label |
        | | begin_repeat | r1    | R1    |
        | | text         | q1    | Q1    |
        | | end_repeat   |       |       |
        | | begin_group  | g1    | G1    |
        | | text         | q2    | Q2    |
        | | end_group    |       |       |
        | | begin_repeat | r2    | R2    |
        | | text         | q3    | Q3    |
        | | end_repeat   |       |       |

        | entities |
        | | list_name | label | repeat |
        | | e1        | ${q1} | ${r1}  |

tests.entities.test_create_repeat.TestEntitiesCreateRepeat.test_other_controls_before_and_after__ok

        | survey |
        | | type         | name  | label |
        | | begin_repeat | r1    | R1    |
        | | text         | q1    | Q1    |
        | | end_repeat   |       |       |
        | | begin_group  | g1    | G1    |
        | | text         | q2    | Q2    |
        | | end_group    |       |       |
        | | begin_repeat | r2    | R2    |
        | | text         | q3    | Q3    |
        | | end_repeat   |       |       |
        | | begin_group  | g2    | G2    |
        | | text         | q4    | Q4    |
        | | end_group    |       |       |
        | | begin_repeat | r3    | R3    |
        | | text         | q5    | Q5    |
        | | end_repeat   |       |       |

        | entities |
        | | list_name | label | repeat |
        | | e1        | ${q3} | ${r2}  |

If I remove the repeat column from the controls_after__ok test, the error changes to:

Error evaluating field 'label' (${meta}[1]/entity[1]/label[1]): The problem was located in Calculate expression for ${label}
XPath evaluation: type mismatch 
This field is repeated: 

${r1}[1]/q1[1];${r1}[2]/q1[1]

You may need to use the indexed-repeat() function to specify which value you want.
Caused by: org.javarosa.xpath.XPathTypeMismatchException: The problem was located in Calculate expression for ${label}
XPath evaluation: type mismatch 
This field is repeated: 

${r1}[1]/q1[1];${r1}[2]/q1[1]

You may need to use the indexed-repeat() function to specify which value you want.
	... 11 more

If I remove the label (and/or repeat) column from the controls_after__ok test, the error changes to:

pyxform.errors.PyXFormError: The entities sheet is missing the label column which is required when creating entities.

@lognaturel
Copy link
Contributor

lognaturel commented Sep 24, 2025

the quotes surrounding the "references" symbol are escaped

Whoops 😬

tests.entities.test_create_repeat.TestEntitiesCreateRepeat.test_other_controls_after__ok

pyxform either after this change or always puts setvalue triggered by the odk-new-repeat event in the model. From the spec: "Actions triggered by odk-new-repeat must be nested in the repeat form control."

It does look like Collect manages to do the right thing when there's only one repeat but things break when there are multiple and I bet are even worse when there's nesting involved. The ideal fix would be to put the setvalue action in the repeat and I've verified that does work with Validate/Collect. It's ok to put the odk-instance-first-load event on the same body-nested action.

@lognaturel
Copy link
Contributor

lognaturel commented Sep 24, 2025

There's a bug in JavaRosa that allowed the non-compliant form structure to go through Validate: getodk/javarosa#829

These were really good tests to add, thanks.

A concrete example -- validate-issue-entity-repeat.xlsx:

- action elements (setvalue, setgeopoint, recordaudio) defined and
  generated in a few different ways, this commit goes some way towards:
  - define the supported action, event types, and combinations thereof
  - allow more than one action per element e.g. first-load, new-repeat
  - split first-load from new-repeat to avoid bugs from output location
  - declare them via "actions" property in xls2json (except triggers)
  - generate from "actions" via xml_actions (except triggers)
  - triggers are a special case that seemed to require some trickier
    refactoring to consolidate with xml_action at this stage
- calculate and store if the default is dynamic during xls2json step
  to avoid re-parsing the default (falls back to re-parsing if the new
  key isn't found)
@lindsay-stevens
Copy link
Contributor Author

@lognaturel the setvalue issue is addressed in 1e481b9 - overview in commit message but below are some detailed notes on old vs. new. I found actions generation quite confusing but I think it's clearer now and hopefully you agree.

One downside of making actions 1:1 with event types is that trigger expressions can appear verbatim in 2 places for when the trigger is in a repeat, but I would expect that usually these expressions are not tremendously long such that they would dramatically increase XForm file size. I couldn't see any other reason besides brevity to combine the setvalue for that case but please let me know otherwise - it should be possible to allow a sequence of events to combine the actions again.

Also I assumed it was a typo that the entity setvalue had until now included the attributes readonly="true()" and type="string" - these are common attrs for bind but I don't know what if anything they mean for an action (they aren't standard XForm action attributes and don't seem to appear in ODK XForm or Entity specs). If they are not typos please let me know and I'll put them back in.

Old output locations, call paths, action/event types, reasons for generation

  • output to model:
    • call path: Survey/xml/xml_model/xml_descendent_bindings -> Question/get_setvalue_node_for_dynamic_default
    • setvalue: odk-instance-first-load
      • any question type with a dynamic default
  • output to model:
    • call path: Survey/xml/xml_model/xml_descendent_bindings -> EntityDeclaration/xml_bindings/_get_setvalue_node_for_id
    • setvalue: odk-instance-first-load
      • entities
  • output to model:
    • call path: Survey/xml/xml_model/xml_actions -> Question/xml_action
    • odk:setgeopoint: odk-instance-first-load
      • question type start-geopoint
    • odk:recordaudio: odk-instance-load
      • question type background-audio
  • output to body/repeat:
    • call path: Survey/xml/xml_control -> RepeatingSection/xml_control/dynamic_defaults_helper -> Question/get_setvalue_node_for_dynamic_default
    • setvalue: odk-instance-first-load, odk-new-repeat
      • any question type with a dynamic default, where the question is in a repeat
  • output to body/question control:
    • call path: Survey/xml/xml_control -> Question/xml_control
    • setvalue: xforms-value-changed
      • any question with a trigger
    • odk:setgeopoint: xforms-value-changed
      • background-geopoint

New output locations, call paths, action/event types, reasons for generation

  • output to model:
    • call path: Survey/xml/xml_model/xml_model_bindings -> Question/xml_action or EntityDeclaration/xml_action
    • odk:setgeopoint: odk-instance-first-load
      • question type start-geopoint
    • odk:recordaudio: odk-instance-load
      • question type background-audio
    • setvalue: odk-instance-first-load
      • any question type with a dynamic default
      • entities
  • output to body/repeat:
    • call path: Survey/xml/xml_control -> RepeatingSection/xml_control -> Question/xml_action
    • setvalue: odk-new-repeat
      • any question type with a dynamic default, where the question is in a repeat
  • output to body/question control:
    • call path: Survey/xml/xml_control -> Question/xml_control
    • setvalue: xforms-value-changed
      • any question with a trigger
    • odk:setgeopoint: xforms-value-changed
      • background-geopoint

- the bind for the entity label was one path too low to find the
  correct target, because the `insert_xpaths` context was the entity
  rather than the label. It seems like the path is OK for other entity
  attributes because their context is the entity element. In order to
  use existing insert_xpaths functionality that navigates the form
  structure using .parent/.children, the entity label must be a
  SurveyElement so a new class is created for it.
@lindsay-stevens
Copy link
Contributor Author

It looks like all of the references produced in binds are off by one parenting level

@lognaturel in 026bfe0 I've fixed this for label - see commit message for details. The paths for the other attribute bindings seemed like they should be OK, assuming that current() resolves to the entity element - but if that is not the case and the attribute binds also need to go up one level please confirm.

For example from test_update_repeat.py test test_all_fields__ok, this is the repeat output XML fragment:

          <r1>
            <q1/>
            <meta>
              <entity dataset="e1" id="" update="1" baseVersion="" trunkVersion="" branchId="" create="1">
                <label/>
              </entity>
            </meta>
          </r1>

And there's a __version binding as follows so it'd be current() = entity and 2 steps up through r1/meta to find q1:

      <bind
          nodeset="/test_name/r1/meta/entity/@baseVersion"
          calculate="instance('e1')/root/item[name= current()/../../q1 ]/__version"
          type="string" readonly="true()"/>

@lognaturel
Copy link
Contributor

So exciting to see you've got fixes galore! Looks good after a first pass, will keep digging. One quick thing jumps out:

And there's a __version binding as follows so it'd be current() = entity and 2 steps up through r1/meta to find q1:

  <bind
      nodeset="/test_name/r1/meta/entity/@baseVersion"
      calculate="instance('e1')/root/item[name= current()/../../q1 ]/__version"
      type="string" readonly="true()"/>

As I understand it, current() evaluates to the node the expression is bound to. That includes attribute nodes like @baseVersion. So we need one level to parent out of the attribute, then out of its parent, and in this case we also need to go out of meta. I'm not quickly finding an authoritative source that confirms this but it's how Collect works and it feels the most consistent with how attributes are generally treated -- they're just a terminal node type. I'll keep searching for an authoritative answer on this or another way I can verify it but wanted to share that thinking in case you have a quick way to validate.

@lognaturel
Copy link
Contributor

current() definition: https://www.w3.org/TR/xslt-10/#function-current
binding expressions in XForms: https://www.w3.org/TR/2001/WD-xforms-20010608/slice6.html#expr-canonical

If current() behaved differently for element nodes and attribute nodes, I think one of those resources would call it out. As the definition for current() states, the intent is for . and current() to evaluate to the same context node when in an expression that's not in a predicate. . in an expression bound to an attribute evaluates to that attribute node so current() should as well. Does that sound right?

https://getodk.github.io/xforms-spec/#action:setgeopoint
"""

name = "odk:setgeopoint"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels slightly uncomfortable to me to hard code the namespace prefix here since it has to match the namespace declaration. An alternative would be to define a constant for the prefix somewhere and use it consistently here and in the declaration. It's not super important because it's an obviously good convention that doesn't require much discipline to maintain but wanted to mention it. I probably feel discomfort because I'm used to seeing code on the form client side where it's important to support any prefix.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point for improvement though it was hard-coded previously (question_type_dictionary.py etc)

@lognaturel
Copy link
Contributor

It's feeling really close! I think the only must-have is getting clarity on current() in an expression bound to an attribute. I'm pretty confident in my reasoning above but would like your careful scrutiny and possibly some more brains on it. Erroring when savetos are not in scope could come in a follow-on, possibly along with some thinking about multiple Entity declarations in a form def.

I found actions generation quite confusing

You are not alone. They've been conceptually tricky to fit in everywhere in the ODK XForms world and because we've introduced different actions and events over time the implementations have all gotten messy.

Did I understand correctly that dynamic defaults in repeats were correctly generating odk-new-repeat-triggered actions in the body and that the issue was actually just with the the new Entity-related actions? If that's right, it might make sense to move forward with getodk/javarosa#829. Prior to this PR, were there ever odk-new-repeat-triggered actions generated in the model?

I couldn't see any other reason besides brevity to combine the setvalue for that case

I can't either so it looks fine to me. As far as I know, any client implementation would need to separate out the actions by triggering event anyway so it makes no difference on the client side.

a typo that the entity setvalue had until now included the attributes readonly="true()" and type="string"

Oh yes, good catch! I feel like my brain tripped up on that a few times but not enough to notice it didn't make sense.

@lognaturel
Copy link
Contributor

lognaturel commented Oct 6, 2025

Here's a way to confirm that the "current" node for an attribute is the attribute itself, not its parent:

  1. Go to https://xslttest.appspot.com/
  2. Specify the following XML:
<data>
<r1>
  <q1/>
  <meta>
    <entity dataset="e1" id="" update="1" baseVersion="foo" trunkVersion="" branchId="" create="1">
      <label>foo label</label>
    </entity>
  </meta>
</r1>
<r1>
  <q1/>
  <meta>
    <entity dataset="e1" id="" update="1" baseVersion="bar" trunkVersion="" branchId="" create="1">
      <label>bar label</label>
    </entity>
  </meta>
</r1>
</data>
  1. Specify the following XSLT:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/">
  <xsl:for-each select="data/r1/meta/entity/@baseVersion">
    Current node: <xsl:value-of select="current()"/>
    <br />
  </xsl:for-each>
</xsl:template>

</xsl:stylesheet> 
  1. Confirm that the output is
    Current node: foo<br/>
    Current node: bar<br/>

- the bind for the entity attributes was one path too low to find the
  correct target, because the `insert_xpaths` context was the entity
  rather than the attribute. In order to use existing insert_xpaths
  functionality that navigates the form structure using
  .parent/.children, the entity attributes must be SurveyElements so a
  new class is created for it.
  - overall it's moving from procedural generation in entity_declaration
    to a declarative generation in entities_parsing.py, and attributes
    which are the target of bind/actions/etc are responsible for the
    generation of those nodes.
- also simplify xls2json.py entities sheet processing since
  EntityColumns contains all the valid column names, and the slot names
  add a lot of column names which should not be user-specified.
@lindsay-stevens
Copy link
Contributor Author

Yes it seems now like a pretty foundational concept; I didn't consider attributes as a "node" as such. But the spec says everything is a node, and 0014ca9 is a moving towards everything is a SurveyElement. I still think the Element class idea added in 1e481b9 for actions could be useful though, for defining the [type / supported attributes / values] of generated nodes e.g. bind, type, control, etc.

https://www.w3.org/TR/xforms11/%23context-xpath-evaluation#fn-current

7.10.2 The current() Function

node-set current()

Returns the context node used to initialize the evaluation of the containing XPath expression.

https://www.w3.org/TR/1999/REC-xpath-19991116/#section-Introduction

XPath models an XML document as a tree of nodes. There are different types of nodes, including element nodes, attribute nodes and text nodes.

https://www.w3.org/TR/1999/REC-xpath-19991116/#data-model

There are seven types of node:

  • root nodes
  • element nodes
  • text nodes
  • attribute nodes
  • namespace nodes
  • processing instruction nodes
  • comment nodes

- in this case a top-level entity instance would be generated and so it
  wouldn't be populated properly by the item in the repeat. Either a
  mistake in where the save_to was placed, or the entity repeat column
  was left blank by mistake.
- added checks to entities tests that extra setvalue nodes not emitted
- moved truth-table docstring over from entity_declaration.py
  - reformatted docstring cols to sort all ascending
  - renamed docstring cols / func symbols to match column names
  - organised if expressions to match docstring cols order
Copy link
Contributor

@lognaturel lognaturel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks so much for bringing this to the finish line along with some nice misc improvements! 🎉

@lognaturel lognaturel merged commit cf12efd into XLSForm:master Oct 9, 2025
14 checks passed
@lindsay-stevens lindsay-stevens deleted the pyxform-775 branch October 10, 2025 00:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support Entities from repeats Show more helpful message if form contains a group with name meta

3 participants