Skip to content

Commit

Permalink
Merge pull request #672 from IanCa/dev_change_rooted2
Browse files Browse the repository at this point in the history
Change rooted implementation to new system
  • Loading branch information
VisLab committed May 30, 2023
2 parents 4f85345 + d993512 commit 83c61a5
Show file tree
Hide file tree
Showing 16 changed files with 209 additions and 43 deletions.
11 changes: 7 additions & 4 deletions hed/schema/hed_schema_section.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,17 +212,20 @@ def _check_if_duplicate(self, name, new_entry):
return new_entry

def _add_to_dict(self, name, new_entry, parent_index=None):
if new_entry.has_attribute(HedKey.Rooted):
del new_entry.attributes[HedKey.Rooted]
if new_entry.has_attribute(HedKey.InLibrary):
parent_name = new_entry.parent_name
if parent_name.lower() in self:
# Make sure we insert the new entry after all previous relevant ones, as order isn't assured
# for rooted tags
parent_entry = self.get(parent_name.lower())
parent_index = self.all_entries.index(parent_entry) + 1
parent_index = self.all_entries.index(parent_entry)
for i in range(parent_index, len(self.all_entries)):
parent_index = i + 1
if not self.all_entries[i].name.startswith(parent_entry.name):
break
if self.all_entries[i].name.startswith(parent_entry.name):
parent_index = i + 1
continue
break

return super()._add_to_dict(name, new_entry, parent_index)

Expand Down
29 changes: 28 additions & 1 deletion hed/schema/schema_compliance.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def check_compliance(hed_schema, check_for_warnings=True, name=None, error_handl
HedKey.RelatedTag: tag_exists_check,
HedKey.UnitClass: tag_is_placeholder_check,
HedKey.ValueClass: tag_is_placeholder_check,
HedKey.Rooted: attribute_does_not_exist_check, # This should be impossible to trigger unless loading fails
HedKey.Rooted: tag_exists_base_schema_check,
}

# Check attributes
Expand Down Expand Up @@ -170,6 +170,33 @@ def tag_exists_check(hed_schema, tag_entry, possible_tags, force_issues_as_warni
return issues


def tag_exists_base_schema_check(hed_schema, tag_entry, tag_name, force_issues_as_warnings=True):
""" Check if the single tag is a partnered schema tag
Parameters:
hed_schema (HedSchema): The schema to check if the tag exists.
tag_entry (HedSchemaEntry): The schema entry for this tag.
tag_name (str): The tag to verify, can be any form.
force_issues_as_warnings (bool): If True, set all the severity levels to warning.
Returns:
list: A list of issues. Each issue is a dictionary.
"""
issues = []
rooted_tag = tag_name.lower()
if rooted_tag not in hed_schema.all_tags:
issues += ErrorHandler.format_error(ValidationErrors.NO_VALID_TAG_FOUND,
rooted_tag,
index_in_tag=0,
index_in_tag_end=len(rooted_tag))

if force_issues_as_warnings:
for issue in issues:
issue['severity'] = ErrorSeverity.WARNING
return issues


def validate_schema_term(hed_term):
""" Check short tag for capitalization and illegal characters.
Expand Down
17 changes: 4 additions & 13 deletions hed/schema/schema_io/schema2base.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,6 @@ def _write_tag_entry(self, tag_entry, parent=None, level=0):
def _write_entry(self, entry, parent_node, include_props=True):
raise NotImplementedError("This needs to be defined in the subclass")

def _write_rooted_parent_entry(self, tag_entry, schema_node):
parent_copy = copy.deepcopy(tag_entry._parent_tag)
parent_copy.attributes = {HedKey.Rooted: True}
parent_copy.description = ""

parent_node = self._write_tag_entry(parent_copy, schema_node, 0)

return parent_node

def _output_tags(self, all_tags):
schema_node = self._start_section(HedSectionKey.AllTags)

Expand All @@ -98,11 +89,11 @@ def _output_tags(self, all_tags):
else:
# Only output the rooted parent nodes if they have a parent(for duplicates that don't)
if tag_entry.has_attribute(HedKey.InLibrary) and tag_entry.parent and \
not tag_entry.parent.has_attribute(HedKey.InLibrary):
not tag_entry.parent.has_attribute(HedKey.InLibrary) and not self._save_merged:
if tag_entry.parent.name not in all_nodes:
parent_node = self._write_rooted_parent_entry(tag_entry, schema_node)
all_nodes[tag_entry.parent.name] = parent_node
level_adj = level - 1
level_adj = level
tag_entry = copy.deepcopy(tag_entry)
tag_entry.attributes[HedKey.Rooted] = tag_entry.parent.short_tag_name

parent_node = all_nodes.get(tag_entry.parent_name, schema_node)
child_node = self._write_tag_entry(tag_entry, parent_node, level - level_adj)
Expand Down
7 changes: 4 additions & 3 deletions hed/schema/schema_io/wiki2schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,11 +305,12 @@ def _read_schema(self, lines):
continue

try:
rooted_entry = schema_validation_util.check_rooted_errors(tag_entry, self._schema, self._loading_merged)
rooted_entry = schema_validation_util.find_rooted_entry(tag_entry, self._schema, self._loading_merged)
if rooted_entry:
parent_tags = rooted_entry.long_tag_name.split("/")
level_adj = len(parent_tags) - 1
continue
level_adj = len(parent_tags)
# Create the entry again for rooted tags, to get the full name.
tag_entry = self._add_tag_line(parent_tags, line_number, line)
except HedFileError as e:
self._add_fatal_error(line_number, line, e.message, e.code)
continue
Expand Down
11 changes: 7 additions & 4 deletions hed/schema/schema_io/xml2schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,14 @@ def _populate_tag_dictionaries(self):
tag = tag.replace(loading_from_chain_short, loading_from_chain)
tag_entry = self._parse_node(tag_element, HedSectionKey.AllTags, tag)

rooted_entry = schema_validation_util.check_rooted_errors(tag_entry, self._schema, self._loading_merged)
rooted_entry = schema_validation_util.find_rooted_entry(tag_entry, self._schema, self._loading_merged)
if rooted_entry:
loading_from_chain = rooted_entry.name
loading_from_chain_short = rooted_entry.short_tag_name
continue
loading_from_chain = rooted_entry.name + "/" + tag_entry.short_tag_name
loading_from_chain_short = tag_entry.short_tag_name

tag = tag.replace(loading_from_chain_short, loading_from_chain)
tag_entry = self._parse_node(tag_element, HedSectionKey.AllTags, tag)

self._add_to_dict(tag_entry, HedSectionKey.AllTags)

def _populate_unit_class_dictionaries(self):
Expand Down
21 changes: 14 additions & 7 deletions hed/schema/schema_validation_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def validate_attributes(attrib_dict, filename):


# Might move this to a baseclass version if one is ever made for wiki2schema/xml2schema
def check_rooted_errors(tag_entry, schema, loading_merged):
def find_rooted_entry(tag_entry, schema, loading_merged):
""" This semi-validates rooted tags, raising an exception on major errors
Parameters:
Expand All @@ -115,11 +115,8 @@ def check_rooted_errors(tag_entry, schema, loading_merged):
HedValueError: Raises if the tag doesn't exist or similar
"""
if tag_entry.has_attribute(constants.HedKey.Rooted):
if tag_entry.parent_name:
raise HedFileError(HedExceptions.ROOTED_TAG_INVALID,
f'Found rooted tag \'{tag_entry.short_tag_name}\' as a non root node.',
schema.filename)
rooted_tag = tag_entry.has_attribute(constants.HedKey.Rooted, return_value=True)
if rooted_tag is not None:
if not schema.with_standard:
raise HedFileError(HedExceptions.ROOTED_TAG_INVALID,
f"Rooted tag attribute found on '{tag_entry.short_tag_name}' in a standard schema.",
Expand All @@ -129,7 +126,17 @@ def check_rooted_errors(tag_entry, schema, loading_merged):
f'Found rooted tag \'{tag_entry.short_tag_name}\' in schema without unmerged="True"',
schema.filename)

rooted_entry = schema.all_tags.get(tag_entry.name.lower())
if not isinstance(rooted_tag, str):
raise HedFileError(HedExceptions.ROOTED_TAG_INVALID,
f'Rooted tag \'{tag_entry.short_tag_name}\' is not a string."',
schema.filename)

if tag_entry.parent_name:
raise HedFileError(HedExceptions.ROOTED_TAG_INVALID,
f'Found rooted tag \'{tag_entry.short_tag_name}\' as a non root node.',
schema.filename)

rooted_entry = schema.all_tags.get(rooted_tag)
if not rooted_entry or rooted_entry.has_attribute(constants.HedKey.InLibrary):
raise HedFileError(HedExceptions.ROOTED_TAG_DOES_NOT_EXIST,
f"Rooted tag '{tag_entry.short_tag_name}' not found in paired standard schema",
Expand Down
29 changes: 29 additions & 0 deletions tests/data/schema_tests/merge_tests/basic_root.mediawiki
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
HED version="1.0.2" library="testlib" withStandard="8.2.0" unmerged="True"

'''Prologue'''
This schema is the first official release that includes an xsd and requires unit class, unit modifier, value class, schema attribute and property sections.

!# start schema

'''Oboe-sound''' <nowiki>{rooted=Instrument-sound}</nowiki>
* Oboe-subsound

'''Violin-sound''' <nowiki>{rooted=Instrument-sound}</nowiki>
* Violin-subsound
* Violin-subsound2


!# end schema

'''Unit classes'''

'''Unit modifiers'''

'''Value classes'''

'''Schema attributes'''

'''Properties'''
'''Epilogue'''

!# end hed
34 changes: 34 additions & 0 deletions tests/data/schema_tests/merge_tests/basic_root.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" ?>
<HED version="1.0.2" library="testlib" withStandard="8.2.0" unmerged="True">
<prologue>This schema is the first official release that includes an xsd and requires unit class, unit modifier, value class, schema attribute and property sections.</prologue>
<schema>
<node>
<name>Oboe-sound</name>
<attribute>
<name>rooted</name>
<value>Instrument-sound</value>
</attribute>
<node>
<name>Oboe-subsound</name>
</node>
</node>
<node>
<name>Violin-sound</name>
<attribute>
<name>rooted</name>
<value>Instrument-sound</value>
</attribute>
<node>
<name>Violin-subsound</name>
</node>
<node>
<name>Violin-subsound2</name>
</node>
</node>
</schema>
<unitClassDefinitions/>
<unitModifierDefinitions/>
<valueClassDefinitions/>
<schemaAttributeDefinitions/>
<propertyDefinitions/>
</HED>
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ This schema is the first official release that includes an xsd and requires unit

!# start schema

'''NotRealTag'''{rooted}
'''NotRealTag'''{rooted=AlsoNotRealTag}
* Oboe-sound

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,15 @@ This schema is the first official release that includes an xsd and requires unit

!# start schema

'''Instrument-sound'''{rooted}
* Oboe-sound
** Oboe-subsound
* Violin-sound
** Violin-subsound
** Violin-subsound2
'''Oboe-sound''' {rooted=Instrument-sound}
* Oboe-subsound
'''Violin-sound''' {rooted=Instrument-sound}
* Violin-subsound
* Violin-subsound2
'''Instrument-sound'''{rooted}
* Oboe-sound

'''Oboe-sound''' {rooted=Instrument-sound}

!# end schema

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
HED library="testlib" version="1.0.2"

'''Prologue'''
This schema is the first official release that includes an xsd and requires unit class, unit modifier, value class, schema attribute and property sections.

!# start schema

'''DummyTagToAvoidIssue'''

'''Instrument-sound'''{rooted=DummyTagToAvoidIssue}
* Oboe-sound

!# end schema

'''Epilogue'''

!# end hed
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
HED library="testlib" version="1.0.2" withStandard="8.2.0"

'''Prologue'''
This schema is the first official release that includes an xsd and requires unit class, unit modifier, value class, schema attribute and property sections.

!# start schema

'''DummyTagToAvoidIssue'''

'''Instrument-sound'''{rooted=DummyTagToAvoidIssue}
* Oboe-sound

!# end schema

'''Epilogue'''

!# end hed
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
HED library="testlib" version="1.0.2" withStandard="8.2.0" unmerged="true"

'''Prologue'''
This schema is the first official release that includes an xsd and requires unit class, unit modifier, value class, schema attribute and property sections.

!# start schema

'''Instrument-sound'''{rooted}
* Oboe-sound

!# end schema

'''Epilogue'''

!# end hed
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This schema is the first official release that includes an xsd and requires unit
!# start schema

'''Instrument-sound'''
* Oboe-sound {rooted}
* Oboe-sound {rooted=Instrument-sound}

!# end schema
Expand Down
20 changes: 20 additions & 0 deletions tests/schema/test_hed_schema_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from hed.errors import HedExceptions


# todo: speed up these tests
class TestHedSchema(unittest.TestCase):

def test_load_invalid_schema(self):
Expand Down Expand Up @@ -180,6 +181,9 @@ def _base_merging_test(self, files):
# print(s1.filename)
# print(s2.filename)
self.assertTrue(result)
reload1 = load_schema(path1)
reload2 = load_schema(path2)
self.assertEqual(reload1, reload2)
finally:
os.remove(path1)
os.remove(path2)
Expand All @@ -189,6 +193,10 @@ def _base_merging_test(self, files):
path2 = s2.save_as_mediawiki(save_merged=save_merged)
result = filecmp.cmp(path1, path2)
self.assertTrue(result)

reload1 = load_schema(path1)
reload2 = load_schema(path2)
self.assertEqual(reload1, reload2)
finally:
os.remove(path1)
os.remove(path2)
Expand All @@ -212,6 +220,14 @@ def test_saving_merged(self):

self._base_merging_test(files)

def test_saving_merged_rooted(self):
files = [
load_schema(os.path.join(self.full_base_folder, "basic_root.mediawiki")),
load_schema(os.path.join(self.full_base_folder, "basic_root.xml")),
]

self._base_merging_test(files)

def _base_added_class_tests(self, schema):
tag_entry = schema.all_tags["Modulator"]
self.assertEqual(tag_entry.attributes["suggestedTag"], "Event")
Expand Down Expand Up @@ -298,6 +314,10 @@ def test_cannot_load_schemas(self):
files = [
os.path.join(self.full_base_folder, "issues_tests/HED_badroot_0.0.1.mediawiki"),
os.path.join(self.full_base_folder, "issues_tests/HED_root_wrong_place_0.0.1.mediawiki"),
os.path.join(self.full_base_folder, "issues_tests/HED_root_invalid1.mediawiki"),
os.path.join(self.full_base_folder, "issues_tests/HED_root_invalid2.mediawiki"),
os.path.join(self.full_base_folder, "issues_tests/HED_root_invalid3.mediawiki"),

]
for file in files:
with self.assertRaises(HedFileError):
Expand Down

0 comments on commit 83c61a5

Please sign in to comment.