def resolve_column(column: Column, in_row: Row, definitions: dict, content_resolver=None, field_resolver=None) -> (str, ColumnResolutionData): """ Return the content of a column by recursively resolving any fields within. """ column_references = [] definition_references = [] is_resolving_definition = in_row.data == definitions resolved_column_content = column.content for reference_field in fields(resolved_column_content): # determine whether this field is a row reference reference_column, reference_row, is_invalid_reference = get_row_reference( reference_field, in_reference_row=in_row, in_reference_column=column) if is_invalid_reference: # it was a row reference, but the reference was invalid- so we gotta move on continue if reference_column is None: # it was not a row reference reference_column = reference_field.inner_content # determine if the field occurs as a definition is_definition = reference_column in definitions # determine if the field occurs as a column in the current row- note that if # the current row is actually the definitions, then it actually *is* a column, # but it should not be treated as such is_column = reference_column in reference_row.data and not is_resolving_definition if not is_column and not is_definition: # the field is not a reference that can be resolved right now, so skip it # (it might be an image reference, an include field or similar) continue context = (os.path.basename(reference_row.data_path) if reference_row.data_path is not None else ('definitions' if is_resolving_definition else '')) # this field refers to the column in the same row that is already being resolved; # i.e. an infinite cycle (if it was another row it might not be infinite) is_infinite_column_ref = reference_column == column.name and reference_row is in_row # this definition field refers to itself; also leading to an infinite cycle is_infinite_definition_ref = (is_infinite_column_ref and is_definition and not is_column) if is_infinite_definition_ref: WarningDisplay.unresolved_infinite_definition_reference( WarningContext(context, row_index=reference_row.row_index, column=column.name), reference_field.inner_content) continue if is_infinite_column_ref: WarningDisplay.unresolved_infinite_column_reference( WarningContext(context, row_index=reference_row.row_index, column=column.name), reference_field.inner_content) continue # only resolve further if it would not lead to an infinite cycle use_column = is_column and not is_infinite_column_ref # however, the field might ambiguously refer to a definition too, # so if this could be resolved, use the definition value instead use_definition = (is_definition and not use_column and not is_infinite_definition_ref) if not use_column and not use_definition: # could not resolve this field at all WarningDisplay.unresolved_reference( WarningContext(context, row_index=reference_row.row_index, column=column.name), reference_field.inner_content) continue if use_column: # prioritize the column reference by resolving it first, # even if it could also be a definition instead (but warn about that later) column_reference_content, resolution_data = get_column_contentd( reference_column, reference_row, definitions, content_resolver, field_resolver) elif use_definition: # resolve the definition reference, keeping track of any discovered references column_reference_content, resolution_data = get_definition_contentd( definition=reference_column, in_definitions=definitions, content_resolver=content_resolver, field_resolver=field_resolver) column_references.extend(list(resolution_data.column_references)) definition_references.extend( list(resolution_data.definition_references)) occurences = 0 if field_resolver is not None: resolved_column_content, occurences = field_resolver( reference_field.inner_content, column_reference_content, resolved_column_content) if occurences > 0: if use_column: column_references.append(reference_column) elif use_definition: definition_references.append(reference_column) if is_definition and is_column and not is_resolving_definition: # the reference could point to both a column and a definition if use_column: # the column data was preferred over the definition data WarningDisplay.ambiguous_reference_used_column( WarningContext(context), reference_column, column_reference_content) elif use_definition: # the definition data was preferred over the column data; # this is likely because the column reference was an infinite reference # don't inform about that detail, but do warn that the definition was used WarningDisplay.ambiguous_reference_used_definition( WarningContext(context), reference_column, column_reference_content) resolution_data = ColumnResolutionData(set(column_references), set(definition_references)) return resolved_column_content, resolution_data
def resolve_column(column: Column, in_row: Row, definitions: dict, content_resolver=None, field_resolver=None) -> (str, ColumnResolutionData): """ Return the content of a column by recursively resolving any fields within. """ column_references = [] definition_references = [] is_resolving_definition = in_row.data == definitions resolved_column_content = column.content for reference_field in fields(resolved_column_content): # determine whether this field is a row reference reference_column, reference_row, is_invalid_reference = get_row_reference( reference_field, in_reference_row=in_row, in_reference_column=column) if is_invalid_reference: # it was a row reference, but the reference was invalid- so we gotta move on continue if reference_column is None: # it was not a row reference reference_column = reference_field.inner_content # determine if the field occurs as a definition is_definition = reference_column in definitions # determine if the field occurs as a column in the current row- note that if # the current row is actually the definitions, then it actually *is* a column, # but it should not be treated as such is_column = reference_column in reference_row.data and not is_resolving_definition if not is_column and not is_definition: # the field is not a reference that can be resolved right now, so skip it # (it might be an image reference, an include field or similar) continue context = (os.path.basename(reference_row.data_path) if reference_row.data_path is not None else ('definitions' if is_resolving_definition else '')) # this field refers to the column in the same row that is already being resolved; # i.e. an infinite cycle (if it was another row it might not be infinite) is_infinite_column_ref = reference_column == column.name and reference_row is in_row # this definition field refers to itself; also leading to an infinite cycle is_infinite_definition_ref = (is_infinite_column_ref and is_definition and not is_column) if is_infinite_definition_ref: WarningDisplay.unresolved_infinite_definition_reference( WarningContext(context, row_index=reference_row.row_index, column=column.name), reference_field.inner_content) continue if is_infinite_column_ref: WarningDisplay.unresolved_infinite_column_reference( WarningContext(context, row_index=reference_row.row_index, column=column.name), reference_field.inner_content) continue # only resolve further if it would not lead to an infinite cycle use_column = is_column and not is_infinite_column_ref # however, the field might ambiguously refer to a definition too, # so if this could be resolved, use the definition value instead use_definition = (is_definition and not use_column and not is_infinite_definition_ref) if not use_column and not use_definition: # could not resolve this field at all WarningDisplay.unresolved_reference( WarningContext(context, row_index=reference_row.row_index, column=column.name), reference_field.inner_content) continue if use_column: # prioritize the column reference by resolving it first, # even if it could also be a definition instead (but warn about that later) column_reference_content, resolution_data = get_column_contentd( reference_column, reference_row, definitions, content_resolver, field_resolver) elif use_definition: # resolve the definition reference, keeping track of any discovered references column_reference_content, resolution_data = get_definition_contentd( definition=reference_column, in_definitions=definitions, content_resolver=content_resolver, field_resolver=field_resolver) column_references.extend(list(resolution_data.column_references)) definition_references.extend(list(resolution_data.definition_references)) occurences = 0 if field_resolver is not None: resolved_column_content, occurences = field_resolver( reference_field.inner_content, column_reference_content, resolved_column_content) if occurences > 0: if use_column: column_references.append(reference_column) elif use_definition: definition_references.append(reference_column) if is_definition and is_column and not is_resolving_definition: # the reference could point to both a column and a definition if use_column: # the column data was preferred over the definition data WarningDisplay.ambiguous_reference_used_column( WarningContext(context), reference_column, column_reference_content) elif use_definition: # the definition data was preferred over the column data; # this is likely because the column reference was an infinite reference # don't inform about that detail, but do warn that the definition was used WarningDisplay.ambiguous_reference_used_definition( WarningContext(context), reference_column, column_reference_content) resolution_data = ColumnResolutionData( set(column_references), set(definition_references)) return resolved_column_content, resolution_data