def _merge_into_node(self, node): """Merges a single alias node into this record node.""" # allow only alias nodes to be merged if not isinstance(self._event, pyyaml.AliasEvent): temp = self._create_node(node) # skip the node to avoid parsing error notification = Notification.from_name('NoReferenceToMerge') notification.span = temp.span self.notification_handler.report(notification) return anchor = self._extract_anchor() if anchor.value not in self.anchors: notification = Notification.from_name('UndefinedAnchor', anchor.value) notification.span = anchor.span self.notification_handler.report(notification) return anchor_node = self.anchors[anchor.value] if anchor_node.implementation != DataNode.Implementation.mapping: notification = Notification.from_name('InvalidReferenceForMerge', anchor.value) notification.span = anchor.span self.notification_handler.report(notification) return for child in anchor_node.children: if child.key.value not in node.children_keys: node.set_child(copy.deepcopy(child)) return
def _get_transformation_array_size(cls, node, input_type): """Return transformation array size.""" # find a children node that has an array instead of record or scalar for child in node.children: # the key is not specified in input type if 'keys' not in input_type or child.key.value not in input_type['keys']: continue child_type = input_type['keys'][child.key.value]['type'] if child.implementation == DataNode.Implementation.sequence: if child_type['base_type'] == 'Record': notification = Notification.from_name("InvalidTransposition") notification.span = child.span raise notification elif child_type['base_type'] != 'Array': if cls.array_size is None: cls.array_size = len(child.children) cls.paths_to_convert.append('/'.join(cls.current_path + [child.key.value])) elif cls.array_size != len(child.children): notification = Notification.from_name( "DifferentArrayLengthForTransposition") notification.span = child.span raise notification else: cls.paths_to_convert.append('/'.join(cls.current_path + [child.key.value])) # verify array size recursively cls.current_path.append(child.key.value) cls._get_transformation_array_size(child, child_type) cls.current_path.pop() return cls.array_size
def _validate_node(self, node, input_type): """ Determines if node contains correct value. Method verifies node recursively. All descendant nodes are checked. """ if node is None: raise Notification.from_name('ValidationError', 'Invalid node (None)') if input_type['base_type'] != 'AbstractRecord' and hasattr(node, 'type') \ and node.type is not None and 'implemented_abstract_record' not in input_type: notification = Notification.from_name('UselessTag', node.type.value) notification.span = node.type.span self.notification_handler.report(notification) node.input_type = input_type if is_scalar(input_type): self._validate_scalar(node, input_type) elif input_type['base_type'] == 'Record': self._validate_record(node, input_type) elif input_type['base_type'] == 'AbstractRecord': self._validate_abstract(node, input_type) elif input_type['base_type'] == 'Array': self._validate_array(node, input_type) else: notification = Notification.from_name('InputTypeNotSupported', input_type['base_type']) self._report_notification(notification)
def convert(node, input_type): """Convert value of Scalar node to expected type. node: :py:class:`DataNode` data structure input_type: definition of input_type """ conversions = { 'Bool': ScalarConverter._convert_to_bool, 'Integer': ScalarConverter._convert_to_int, 'Double': ScalarConverter._convert_to_float, 'String': ScalarConverter._convert_to_string, 'FileName': ScalarConverter._convert_to_string, 'Selection': ScalarConverter._convert_to_string, } base_type = input_type['base_type'] if base_type in conversions and node.value is not None: try: value = conversions[base_type](node.value) except ValueError: notification = Notification.from_name('ValueConversionError', node.value, base_type) notification.span = node.span notification_handler.report(notification) return node.value = value
def _validate_record(self, node, input_type): """Validates a Record node.""" if not node.implementation == DataNode.Implementation.mapping: notification = Notification.from_name('ValidationTypeError', 'Record') notification.span = get_node_key(node).notification_span self._report_notification(notification) return keys = node.children_keys node.options = input_type['keys'].keys() keys.extend(input_type['keys'].keys()) for key in set(keys): if node.origin == DataNode.Origin.error: continue child = node.get_child(key) try: checks.check_record_key(node.children_keys, key, input_type) except Notification as notification: if notification.name == 'UnknownRecordKey': notification.span = child.notification_span else: notification.span = get_node_key(node).notification_span self._report_notification(notification) else: if child is not None: child_input_type = input_type['keys'][key]['type'] self._validate_node(child, child_input_type)
def _register_anchor(self, anchor, node): """Registers an anchor to a node.""" node.anchor = anchor if anchor.value in self.anchors: notification = Notification.from_name('OverridingAnchor', anchor.value) notification.span = anchor.span self.notification_handler.report(notification) self.anchors[anchor.value] = node
def _validate_node(self, node, input_type): """ Determines if node contains correct value. Method verifies node recursively. All descendant nodes are checked. """ if node is None: raise Notification.from_name('ValidationError', 'Invalid node (None)') # parameters # TODO: enable parameters in unknown IST? if hasattr(node, 'value'): match = is_param(node.value) if match: # extract parameters new_param = Parameter(match.group(1)) exists = False for param in self.params: if param.name == new_param.name: exists = True break if not exists: self.params.append(new_param) node.input_type = input_type # assume parameters are correct, do not validate further return if input_type['base_type'] != 'Abstract' and hasattr(node, 'type') \ and node.type is not None and 'implemented_abstract_record' not in input_type: notification = Notification.from_name('UselessTag', node.type.value) notification.span = node.type.span self.notification_handler.report(notification) node.input_type = input_type if is_scalar(input_type): self._validate_scalar(node, input_type) elif input_type['base_type'] == 'Record': self._validate_record(node, input_type) elif input_type['base_type'] == 'Abstract': self._validate_abstract(node, input_type) elif input_type['base_type'] == 'Array': self._validate_array(node, input_type) else: notification = Notification.from_name('InputTypeNotSupported', input_type['base_type']) self._report_notification(notification)
def make_transposition(cls, node, input_type): """Transpose a record or scalar into an array.""" assert input_type['base_type'] == 'Array', "Only Array can be a result of transposition" cls.init() # if node is scalar, convert it to array if node.implementation == DataNode.Implementation.scalar: return cls._expand_value_to_array(node) # verify that subtype is record subtype = input_type['subtype'] if subtype['base_type'] != 'Record': notification = Notification.from_name('UnsupportedTransposition', input_type['base_type']) notification.span = node.span notification_handler.report(notification) return node assert node.implementation == DataNode.Implementation.mapping,\ "Can not perform transposition on array" # get array size try: cls._get_transformation_array_size(node, subtype) except Notification as notification: notification_handler.report(notification) return node if cls.array_size is None: cls.array_size = 1 # create array array_node = SequenceDataNode(node.key, node.parent) array_node.span = node.span array_node.input_type = node.input_type array_node.origin = DataNode.Origin.ac_array template_node = deepcopy(node) template_node.parent = array_node template_node.input_type = subtype template_node.origin = DataNode.Origin.ac_transposition # create and transpose items of the array for i in range(cls.array_size): child_node = deepcopy(template_node) child_node.key = TextValue(str(i)) # convert array to value for path in cls.paths_to_convert: node_to_convert = child_node.get_node_at_path(path) converted_node = node_to_convert.children[i] converted_node.parent = node_to_convert.parent converted_node.key = node_to_convert.key node_to_convert.parent.set_child(converted_node) array_node.children.append(child_node) return array_node
def _validate_array(self, node, input_type): """Validates an Array node.""" if not node.implementation == DataNode.Implementation.sequence: notification = Notification.from_name('ValidationTypeError', 'Array') notification.span = get_node_key(node).notification_span self._report_notification(notification) return try: checks.check_array(node.children, input_type) except Notification as notification: notification.span = get_node_key(node).notification_span self._report_notification(notification) for child in node.children: self._validate_node(child, input_type['subtype'])
def _create_record_key(self): """Creates `TextValue` of record key.""" # check if key is scalar if not isinstance(self._event, pyyaml.ScalarEvent): start_pos = Position.from_mark(self._event.start_mark) self._create_fatal_error_node(start_pos) notification = Notification.from_name('ComplexRecordKey') notification.span = self._fatal_error_node.span self.notification_handler.report(notification) self._event = None self._iterate_events = False return None key = TextValue() key.value = self._event.value key.span = Span.from_event(self._event) return key
def _create_alias_node(self, anchor): """Creates an alias node.""" if anchor.value not in self.anchors: notification = Notification.from_name('UndefinedAnchor', anchor.value) notification.span = anchor.span self.notification_handler.report(notification) return None ref = self.anchors[anchor.value] node = copy.deepcopy(ref) node.anchor = anchor node.ref = ref # set correct node.span node.span = copy.deepcopy(node.anchor.span) start = node.span.start node.span.start = Position(start.line, start.column - 1) return node
def _create_scalar_node(self): """Creates a ScalarDataNode.""" node = ScalarDataNode() tag = self._event.tag if tag is None or not tag.startswith('tag:yaml.org,2002:'): tag = resolve_scalar_tag(self._event.value) node.span = Span.from_event(self._event) try: node.value = construct_scalar(self._event.value, tag) except Exception as error: notification = Notification.from_name('ConstructScalarError', error.args[0]) notification.span = node.span self.notification_handler.report(notification) return node if node.value is None: # alter position of empty node (so it can be selected) node.span.end.column += 1 return node
def _next_parse_event(self): """ Attempts to get next parsing event and handles errors. When there are no more valid parsing events, event is set to None. """ if self._iterate_events: try: # get next parsing event self._event = next(self._event_generator) except pyyaml.MarkedYAMLError as error: # handle parsing error self._event = None self._iterate_events = False start_pos = Position.from_yaml_error(error) self._create_fatal_error_node(start_pos) notification = Notification.from_name('SyntaxFatalError') notification.span = self._fatal_error_node.span self.notification_handler.report(notification) except StopIteration: # handle end of parsing events self._event = None self._iterate_events = False