<commit_before># coding: utf8


"""
    Parsing module
    ====================

    This module contains classes required to create parsing operations.

    It contains 12 classes:

    - OperatorType
    - ParsingResultOrigin
    - ParsingResultType
    - Entity
    - ParsingEntity
    - ParsingOperator
    - ParsingCondition
    - EmptyParsingCondition
    - ParsingStructure
    - ParsingPipeline
    - ParsingBlock
    - ParsingResult

    They can be classified in 4 groups:

        - Enumerations
        - Parsing organisations
        - Parsing operations
        - Parsing results
"""


from enum import Enum
from collections import Counter

import abc
import copy


class OperatorType(Enum):
    """
        Parsing operators definition
        ============================

        This enumeration is a list containing all parsing operator ids (see ParsingOperator class).

        There are currently 3 operator ids:

            - AND
            - OR
            - XOR
    """

    AND = 'and'
    OR = 'or'
    XOR = 'xor'


class ParsingResultOrigin(Enum):
    """
        Parsing result origins definition
        =================================

        This enumeration is a list containing all parsing result origin ids.

        There are currently 2 parsing result origin ids:

            - READING : parsing results generated by stream reading objects from IO's module
            - MODIFICATION : parsing results generated by modification entities from this module
    """

    READING = 'reading'
    MODIFICATION = 'modification'


class ParsingResultType(Enum):
    """
        Parsing result types definition
        ===============================

        This enumeration is a list containing all parsing result type ids.

        There are currently 2 parsing result type ids:

            - VALUE : parsing results containing stream object inputs
            - REFERENCE : parsing results containing stream object reference
    """

    VALUE = 'value'
    REFERENCE = 'reference'


class Entity(metaclass=abc.ABCMeta):
    """
        Fundamental parsing class
        =========================

        Superclass for all parsing elements from this module.
    """

    @abc.abstractmethod
    def check(self, element, ref_position):
        """
            Check if the element is valid.

            :param element: element to check
            :param ref_position: reference position in the element (int >= 0)
            :return: tuple containing two values:

                - boolean : True if the element is valid else False
                - Counter : keys activated during checking if boolean is True else it is empty
        """

        pass

    @abc.abstractmethod
    def get_max_position(self):
        """
            Get the maximum relative position (in comparison to reference position) during check method execution.

            :return: maximum relative position (int >= 0)
        """

        pass

    @abc.abstractmethod
    def get_min_position(self):
        """
            Get the minimum relative position (in comparison to reference position) during check method execution.

            :return: minimum relative position (int <= 0)
        """

        pass


class ParsingEntity(Entity, metaclass=abc.ABCMeta):
    """
        Fundamental parsing operation class
        ===================================

        Superclass for all parsing operations from this module.
    """

    @abc.abstractmethod
    def __init__(self):
        self.isNot = False

    def __and__(self, other):
        """
            Associates two ParsingEntity objects with and logic operator.

            :param other: ParsingEntity object to associate
            :return: ParsingOperator object
        """

        if isinstance(self, ParsingEntity) and isinstance(other, ParsingEntity):
            parsing_operator = ParsingOperator(OperatorType.AND, self, other)
            return parsing_operator
        else:
            raise TypeError("Operands have to be ParsingEntity subclasses")

    def __or__(self, other):
        """
            Associates two ParsingEntity objects with 'or' logic operator.

            :param other: ParsingEntity object to associate
            :return: ParsingOperator object
        """

        if isinstance(self, ParsingEntity) and isinstance(other, ParsingEntity):
            parsing_operator = ParsingOperator(OperatorType.OR, self, other)
            return parsing_operator
        else:
            raise TypeError("Operands have to be ParsingEntity subclasses")

    def __xor__(self, other):
        """
            Associates two ParsingEntity objects with 'xor' logic operator.

            :param other: ParsingEntity object to associate
            :return: ParsingOperator object
        """

        if isinstance(self, ParsingEntity) and isinstance(other, ParsingEntity):
            parsing_operator = ParsingOperator(OperatorType.XOR, self, other)
            return parsing_operator
        else:
            raise TypeError("Operands have to be ParsingEntity subclasses")

    def __rshift__(self, other):
        """
            Defines ParsingEntity's limit

            :param other: limit to define (ParsingEntity)
            :return: ParsingBlock object
        """

        if isinstance(self, ParsingEntity) and (isinstance(other, ParsingEntity) or other is None):
            parsing_block = ParsingBlock(self, other)
            return parsing_block
        else:
            raise TypeError("Left operand has to be ParsingEntity subclass and right operand has to be"
                            " ParsingEntity subclass or None")

    def __ne__(self, other):
        """
            Inequality operator.

            :param other: ParsingEntity object to compare
            :return: bool
        """

        return not self.__eq__(other)

    def __invert__(self):
        """
            Associate ParsingEntity object with 'not' logic operator.

            :return: ParsingEntity object
        """

        result = copy.deepcopy(self)
        result.isNot = not result.isNot
        return result

    @abc.abstractmethod
    def __eq__(self, other):
        """
            Equality operator.

            :param other: ParsingEntity object to compare
            :return: bool
        """

        pass

    @abc.abstractmethod
    def __contains__(self, item):
        """
            Check if ParsingEntity object contains the selected item

            :param item: object to check
            :return: bool
        """

        pass

    @abc.abstractmethod
    def __str__(self):
        pass

    @abc.abstractmethod
    def __repr__(self):
        self.__str__()

    @abc.abstractmethod
    def __copy__(self):
        """
            ParsingEntity's shallow copy

            :return: ParsingEntity object
        """

        pass

    @abc.abstractmethod
    def __deepcopy__(self, memodict={}):
        """
            ParsingEntity's deep copy

            :return: ParsingEntity object
        """

        pass


class ParsingOperator(ParsingEntity):
    def __init__(self, operator_type, operand_a, operand_b):
        super(ParsingOperator, self).__init__()

        if isinstance(operator_type, OperatorType):
            self.operatorType = operator_type
        else:
            raise TypeError('Operator type has to be an OperatorType value')

        if isinstance(operand_a, ParsingEntity) and isinstance(operand_b, ParsingEntity):
            self.operandA = operand_a
            self.operandB = operand_b
        else:
            raise TypeError("Operands have to be ParsingEntity subclasses")

    def __eq__(self, other):
        if isinstance(other, ParsingOperator):
            if self.operatorType == other.operatorType and self.isNot == other.isNot and\
                    ((self.operandA == other.operandA and self.operandB == other.operandB)
                     or (self.operandA == other.operandB and self.operandB == other.operandA)):
                return True
            else:
                return False
        else:
            return False

    def __contains__(self, item):
        if isinstance(item, ParsingEntity):
            if self.__eq__(item):
                return True
            else:
                if item in self.operandA or item in self.operandB:
                    return True
                else:
                    return False
        else:
            raise TypeError("Item has to be ParsingEntity subclasses")

    def __str__(self):
        return 'ParsingOperator object'

    def __repr__(self):
        return self.__str__()

    def __copy__(self):
        result = ParsingOperator(self.operatorType, self.operandA, self.operandB)
        result.isNot = self.isNot
        return result

    def __deepcopy__(self, memodict={}):
        result = ParsingOperator(self.operatorType, copy.deepcopy(self.operandA), copy.deepcopy(self.operandB))
        result.isNot = self.isNot
        return result

    def check(self, element, ref_position=0):
        result_a = self.operandA.check(element, ref_position)
        result_b = self.operandB.check(element, ref_position)
        key_words = result_a[1] + result_b[1]

        if self.operatorType is OperatorType.AND:
            if result_a[0] is True and result_b[0] is True:
                result = True, key_words
            else:
                result = False, Counter({})
        elif self.operatorType is OperatorType.OR:
            if result_a[0] is True or result_b[0] is True:
                result = True, key_words
            else:
                result = False, Counter({})
        else:
            if (result_a[0] is True) ^ (result_b[0] is True):
                result = True, key_words
            else:
                result = False, Counter({})

        if self.isNot is False:
            return result
        else:
            if result[0]:
                return False, Counter({})
            else:
                return True, key_words

    def get_max_position(self):
        operand_a = self.operandA.get_max_position()
        operand_b = self.operandB.get_max_position()

        if operand_a < 0 and operand_b < 0:
            return 0
        else:
            return max([operand_a, operand_b])

    def get_min_position(self):
        operand_a = self.operandA.get_min_position()
        operand_b = self.operandB.get_min_position()

        if operand_a > 0 and operand_b > 0:
            return 0
        else:
            return min([operand_a, operand_b])


class ParsingCondition(ParsingEntity):
    def __new__(cls,  ar_character, rel_position=0, key_word=None):
        if len(ar_character) > 1:
            result = ParsingCondition(ar_character[0], rel_position=rel_position, key_word=key_word)
            for i, element in enumerate(ar_character):
                if i > 0:
                    result = result & ParsingCondition(ar_character[i], rel_position=rel_position+i, key_word=key_word)
            return result
        elif len(ar_character) == 1:
            return super(ParsingCondition, cls).__new__(cls)
        else:
            return EmptyParsingCondition()

    def __init__(self, ar_character, rel_position=0, key_word=None):
        super(ParsingCondition, self).__init__()
        self.relPosition = rel_position
        self.character = ar_character
        self.keyWord = key_word

    def __eq__(self, other):
        if isinstance(other, ParsingCondition):
            if self.relPosition == other.relPosition and self.character == other.character \
                    and self.isNot == other.isNot and self.keyWord == other.keyWord:
                return True
            else:
                return False
        else:
            return False

    def __contains__(self, item):
        return self.__eq__(item)

    def __str__(self):
        return 'ParsingCondition object'

    def __repr__(self):
        return self.__str__()

    def __copy__(self):
        result = ParsingCondition(self.character, self.relPosition, self.keyWord)
        result.isNot = self.isNot
        return result

    def __deepcopy__(self, memodict={}):
        return self.__copy__()

    def check(self, element, ref_position=0):
        element_size = len(element)
        if 0 <= ref_position < element_size:
            position = ref_position + self.relPosition
            if 0 <= position < element_size:
                if self.character in element[position]:
                    result = (True, Counter({self.keyWord: 1}))
                else:
                    result = (False, Counter({}))

                if self.isNot is False:
                    return result
                else:
                    if result[0]:
                        return False, Counter({})
                    else:
                        return True, Counter({self.keyWord: 1})
            else:
                raise IndexError('relative position out of range ( 0 <= ref_position + rel_position < len(element) )')
        else:
            raise IndexError('reference position out of range ( 0 <= ref_position < len(element) )')

    def get_min_position(self):
        if self.relPosition > 0:
            return 0
        else:
            return self.relPosition

    def get_max_position(self):
        if self.relPosition < 0:
            return 0
        else:
            return self.relPosition


class EmptyParsingCondition(ParsingEntity):
    def __init__(self):
        super(EmptyParsingCondition, self).__init__()

    def __eq__(self, other):
        if isinstance(other, EmptyParsingCondition):
            if self.isNot == other.isNot:
                return True
            else:
                return False
        else:
            return False

    def __contains__(self, item):
        return self.__eq__(item)

    def __str__(self):
        return 'EmptyParsingCondition object'

    def __repr__(self):
        return self.__str__()

    def __copy__(self):
        result = EmptyParsingCondition()
        result.isNot = self.isNot
        return result

    def __deepcopy__(self, memodict={}):
        return self.__copy__()

    def check(self, element, ref_position=0):
        return False, Counter({None: 1})

    def get_min_position(self):
        return 0

    def get_max_position(self):
        return 0


class ParsingStructure(Entity):
    def __add__(self, other):
        if isinstance(self, ParsingStructure) and (isinstance(other, ParsingStructure) or other is None):
            parsing_pipeline = ParsingPipeline(self)
            if other is not None:
                parsing_pipeline.add_structure(other)
            return parsing_pipeline
        else:
            raise TypeError("Operands have to be ParsingStructure subclasses")

    @abc.abstractmethod
    def check(self, element, ref_position):
        pass

    @abc.abstractmethod
    def get_max_position(self):
        pass

    @abc.abstractmethod
    def get_min_position(self):
        pass


class ParsingPipeline(ParsingStructure):
    def __init__(self, first_parsing_structure):
        if isinstance(first_parsing_structure, ParsingStructure):
            self.arParsingStructure = list()
            self.arParsingStructure.append(first_parsing_structure)
            self.current_parsing_block_index = 0
            self.isEnded = False
        else:
            raise TypeError("Object has to be ParsingStructure object")

    def check(self, element, ref_position=0):
        if not self.isEnded:
            result = self.arParsingStructure[self.current_parsing_block_index].check(element, ref_position)
            if result[1][0]:
                if self.current_parsing_block_index < len(self.arParsingStructure)-1:
                    self.current_parsing_block_index += 1
                else:
                    self.isEnded = True
            return result
        else:
            return None

    def get_min_position(self):
        ar_min_position = list()
        for parsing_structure in self.arParsingStructure:
            ar_min_position.append(parsing_structure.get_min_position())
        return min(ar_min_position)

    def get_max_position(self):
        ar_max_position = list()
        for parsing_structure in self.arParsingStructure:
            ar_max_position.append(parsing_structure.get_max_position())
        return max(ar_max_position)

    def add_structure(self, parsing_structure):
        if isinstance(parsing_structure, ParsingPipeline):
            self.arParsingStructure = self.arParsingStructure + parsing_structure.arParsingStructure
        elif isinstance(parsing_structure, ParsingStructure):
            self.arParsingStructure.append(parsing_structure)
        else:
            raise TypeError("Object to add has to be ParsingStructure object")

    def reset(self):
        self.current_parsing_block_index = 0
        self.isEnded = False


class ParsingBlock(ParsingStructure):
    def __init__(self, parser, border_condition):
        if isinstance(parser, ParsingEntity):
            self.parser = parser
        else:
            raise TypeError('parser has to be ParsingStructure subclass')

        if isinstance(border_condition, ParsingEntity):
            self.borderCondition = border_condition
        elif border_condition is None:
            self.borderCondition = EmptyParsingCondition()
        else:
            raise TypeError('border_condition has to be ParsingStructure subclass or None')

    def check(self, element, ref_position=0):
        parser_result = self.parser.check(element, ref_position)
        border_condition_result = self.borderCondition.check(element, ref_position)
        return parser_result, border_condition_result

    def get_min_position(self):
        if self.borderCondition is not None:
            return min([self.parser.get_min_position(), self.borderCondition.get_min_position()])
        else:
            return self.parser.get_min_position()

    def get_max_position(self):
        if self.borderCondition is not None:
            return max([self.parser.get_max_position(), self.borderCondition.get_max_position()])
        else:
            return self.parser.get_max_position()


class ParsingResult:
    def __init__(self, stream_class, origin, result_type, read_method, write_method, return_method, close_method,
                 seek_method, args, kwargs, ar_index):
        if hasattr(stream_class, '__name__'):
            self.streamClass = stream_class
        else:
            raise TypeError('Stream class has to have __name__ attribute')

        if isinstance(origin, ParsingResultOrigin):
            self.origin = origin
        else:
            raise TypeError('Origin has to be ParsingResultOrigin object')

        if isinstance(result_type, ParsingResultType):
            self.resultType = result_type
        else:
            raise TypeError('Type has to be ParsingResultType object')

        if isinstance(read_method, str) or read_method is None:
            self.readMethod = read_method
        else:
            raise TypeError('Read method has to be str object or None')

        if isinstance(write_method, str) or write_method is None:
            self.writeMethod = write_method
        else:
            raise TypeError('Write method has to be str object or None')

        if isinstance(return_method, str) or return_method is None:
            self.returnMethod = return_method
        else:
            raise TypeError('Return method has to be str object or None')

        if isinstance(close_method, str) or close_method is None:
            self.closeMethod = close_method
        else:
            raise TypeError('Close method has to be str object or None')

        if isinstance(seek_method, str) or seek_method is None:
            self.seekMethod = seek_method
        else:
            raise TypeError('Seek method has to be str object or None')

        if isinstance(args, (tuple, list)) and isinstance(kwargs, dict):
            self.arInput = {'args': args, 'kwargs': kwargs}
        else:
            raise TypeError('args has to be list object and kwargs has to be dict object')

        if isinstance(ar_index, list):
            self.arIndex = ar_index
        else:
            raise TypeError('ar_index has to be list object')

    def __add__(self, other):
        if isinstance(other, ParsingResult) and isinstance(self, ParsingResult):
            if ParsingResult.are_from_the_same_parsing(self, other):
                new_parsing_result = copy.deepcopy(self)
                for element in other.arIndex:
                    if (element[0] not in new_parsing_result or
                            (len(element) == 3 and (element[0], None, element[2]) not in new_parsing_result)) or \
                            (element[1] == '' and (element[0], element[1]) not in new_parsing_result):
                        new_parsing_result.arIndex.append(element)
                new_parsing_result.arIndex.sort()
                return new_parsing_result
            else:
                raise ValueError("Operands have to come from the same parsing")
        else:
            raise TypeError("Operands have to be ParsingResult classes or subclasses")

    def __contains__(self, item):
        if isinstance(item, int):
            for element in self.arIndex:
                if element[0] == item:
                    return True
            return False
        elif isinstance(item, tuple):
            if len(item) == 2:
                for element in self.arIndex:
                    if element[0] == item[0] and element[1] == item[1]:
                        return True
                    return False
            elif len(item) == 3:
                if item[1] is not None:
                    for element in self.arIndex:
                        if len(element) == 3 and element[0] == item[0] and element[1] == item[1] \
                                and element[2] == item[2]:
                            return True
                    return False
                else:
                    for element in self.arIndex:
                        if len(element) == 3 and element[0] == item[0] and element[2] == item[2]:
                            return True
                    return False
            else:
                return False
        else:
            return False

    def __str__(self):
        return str({'Stream class': str(self.streamClass.__name__), 'Origin': str(self.origin),
                    'Result type': str(self.resultType), 'Inputs': str(self.arInput),
                    'Index result': str(self.arIndex)})

    def __repr__(self):
        return 'Parsing result :' + '\n' + '   Stream class : ' + str(self.streamClass.__name__) + '\n' \
               + '   Origin : ' + str(self.origin) + '\n' + '   Result type : ' + str(self.resultType) + '\n' \
               + '   Inputs : ' + str(self.arInput) + '\n' + '   Index result : ' + str(self.arIndex)

    def __copy__(self):
        return ParsingResult(self.streamClass, self.origin, self.resultType, self.readMethod, self.writeMethod,
                             self.returnMethod, self.closeMethod, self.seekMethod, self.arInput['args'],
                             self.arInput['kwargs'], self.arIndex)

    def __deepcopy__(self, memodict={}):
        return self.__copy__()

    @staticmethod
    def are_from_the_same_parsing(parsing_result_a, parsing_result_b):
        if isinstance(parsing_result_a, ParsingResult) and isinstance(parsing_result_b, ParsingResult):
            return (parsing_result_a.streamClass == parsing_result_b.streamClass and
                    parsing_result_a.origin == parsing_result_b.origin and
                    parsing_result_a.resultType == parsing_result_b.resultType and
                    parsing_result_a.arInput['args'] == parsing_result_b.arInput['args'] and
                    parsing_result_a.arInput['kwargs'] == parsing_result_b.arInput['kwargs'] and
                    parsing_result_a.readMethod == parsing_result_b.readMethod and
                    parsing_result_a.writeMethod == parsing_result_b.writeMethod and
                    parsing_result_a.returnMethod == parsing_result_b.returnMethod and
                    parsing_result_a.closeMethod == parsing_result_b.closeMethod and
                    parsing_result_a.seekMethod == parsing_result_b.seekMethod)
        else:
            raise TypeError("Operands have to be ParsingResult classes or subclasses")

    def check_indexes(self):
        previous_index_value = -1
        for index in self.arIndex:
            if index[0] > previous_index_value:
                previous_index_value = index[0]
            else:
                raise ValueError('Indexes have to be sorted in ascending order')
            if len(index[1]) != 1 or not isinstance(index[1], str):
                raise TypeError("Indexes characters have to be 'str' objects with a length of 1")

        return True
<commit_msg>Fix typo<commit_after># coding: utf8


"""
    Parsing module
    ====================

    This module contains classes required to create parsing operations.

    It contains 12 classes:

    - OperatorType
    - ParsingResultOrigin
    - ParsingResultType
    - Entity
    - ParsingEntity
    - ParsingOperator
    - ParsingCondition
    - EmptyParsingCondition
    - ParsingStructure
    - ParsingPipeline
    - ParsingBlock
    - ParsingResult

    They can be classified in 4 groups:

        - Enumerations
        - Parsing organisations
        - Parsing operations
        - Parsing results
"""


from enum import Enum
from collections import Counter

import abc
import copy


class OperatorType(Enum):
    """
        Parsing operators definition
        ============================

        This enumeration is a list containing all parsing operator ids (see ParsingOperator class).

        There are currently 3 operator ids:

            - AND
            - OR
            - XOR
    """

    AND = 'and'
    OR = 'or'
    XOR = 'xor'


class ParsingResultOrigin(Enum):
    """
        Parsing result origins definition
        =================================

        This enumeration is a list containing all parsing result origin ids.

        There are currently 2 parsing result origin ids:

            - READING : parsing results generated by stream reading objects from IO's module
            - MODIFICATION : parsing results generated by modification entities from this module
    """

    READING = 'reading'
    MODIFICATION = 'modification'


class ParsingResultType(Enum):
    """
        Parsing result types definition
        ===============================

        This enumeration is a list containing all parsing result type ids.

        There are currently 2 parsing result type ids:

            - VALUE : parsing results containing stream object inputs
            - REFERENCE : parsing results containing stream object reference
    """

    VALUE = 'value'
    REFERENCE = 'reference'


class Entity(metaclass=abc.ABCMeta):
    """
        Fundamental parsing class
        =========================

        Superclass for all parsing elements from this module.
    """

    @abc.abstractmethod
    def check(self, element, ref_position):
        """
            Check if the element is valid.

            :param element: element to check
            :param ref_position: reference position in the element (int >= 0)
            :return: tuple containing two values:

                - boolean : True if the element is valid else False
                - Counter : keys activated during checking if boolean is True else it is empty
        """

        pass

    @abc.abstractmethod
    def get_max_position(self):
        """
            Get the maximum relative position (in comparison to reference position) during check method execution.

            :return: maximum relative position (int >= 0)
        """

        pass

    @abc.abstractmethod
    def get_min_position(self):
        """
            Get the minimum relative position (in comparison to reference position) during check method execution.

            :return: minimum relative position (int <= 0)
        """

        pass


class ParsingEntity(Entity, metaclass=abc.ABCMeta):
    """
        Fundamental parsing operation class
        ===================================

        Superclass for all parsing operations from this module.
    """

    @abc.abstractmethod
    def __init__(self):
        self.isNot = False

    def __and__(self, other):
        """
            Associates two ParsingEntity objects with 'and' logic operator.

            :param other: ParsingEntity object to associate
            :return: ParsingOperator object
        """

        if isinstance(self, ParsingEntity) and isinstance(other, ParsingEntity):
            parsing_operator = ParsingOperator(OperatorType.AND, self, other)
            return parsing_operator
        else:
            raise TypeError("Operands have to be ParsingEntity subclasses")

    def __or__(self, other):
        """
            Associates two ParsingEntity objects with 'or' logic operator.

            :param other: ParsingEntity object to associate
            :return: ParsingOperator object
        """

        if isinstance(self, ParsingEntity) and isinstance(other, ParsingEntity):
            parsing_operator = ParsingOperator(OperatorType.OR, self, other)
            return parsing_operator
        else:
            raise TypeError("Operands have to be ParsingEntity subclasses")

    def __xor__(self, other):
        """
            Associates two ParsingEntity objects with 'xor' logic operator.

            :param other: ParsingEntity object to associate
            :return: ParsingOperator object
        """

        if isinstance(self, ParsingEntity) and isinstance(other, ParsingEntity):
            parsing_operator = ParsingOperator(OperatorType.XOR, self, other)
            return parsing_operator
        else:
            raise TypeError("Operands have to be ParsingEntity subclasses")

    def __rshift__(self, other):
        """
            Defines ParsingEntity's limit

            :param other: limit to define (ParsingEntity)
            :return: ParsingBlock object
        """

        if isinstance(self, ParsingEntity) and (isinstance(other, ParsingEntity) or other is None):
            parsing_block = ParsingBlock(self, other)
            return parsing_block
        else:
            raise TypeError("Left operand has to be ParsingEntity subclass and right operand has to be"
                            " ParsingEntity subclass or None")

    def __ne__(self, other):
        """
            Inequality operator.

            :param other: ParsingEntity object to compare
            :return: bool
        """

        return not self.__eq__(other)

    def __invert__(self):
        """
            Associate ParsingEntity object with 'not' logic operator.

            :return: ParsingEntity object
        """

        result = copy.deepcopy(self)
        result.isNot = not result.isNot
        return result

    @abc.abstractmethod
    def __eq__(self, other):
        """
            Equality operator.

            :param other: ParsingEntity object to compare
            :return: bool
        """

        pass

    @abc.abstractmethod
    def __contains__(self, item):
        """
            Check if ParsingEntity object contains the selected item

            :param item: object to check
            :return: bool
        """

        pass

    @abc.abstractmethod
    def __str__(self):
        pass

    @abc.abstractmethod
    def __repr__(self):
        self.__str__()

    @abc.abstractmethod
    def __copy__(self):
        """
            ParsingEntity's shallow copy

            :return: ParsingEntity object
        """

        pass

    @abc.abstractmethod
    def __deepcopy__(self, memodict={}):
        """
            ParsingEntity's deep copy

            :return: ParsingEntity object
        """

        pass


class ParsingOperator(ParsingEntity):
    def __init__(self, operator_type, operand_a, operand_b):
        super(ParsingOperator, self).__init__()

        if isinstance(operator_type, OperatorType):
            self.operatorType = operator_type
        else:
            raise TypeError('Operator type has to be an OperatorType value')

        if isinstance(operand_a, ParsingEntity) and isinstance(operand_b, ParsingEntity):
            self.operandA = operand_a
            self.operandB = operand_b
        else:
            raise TypeError("Operands have to be ParsingEntity subclasses")

    def __eq__(self, other):
        if isinstance(other, ParsingOperator):
            if self.operatorType == other.operatorType and self.isNot == other.isNot and\
                    ((self.operandA == other.operandA and self.operandB == other.operandB)
                     or (self.operandA == other.operandB and self.operandB == other.operandA)):
                return True
            else:
                return False
        else:
            return False

    def __contains__(self, item):
        if isinstance(item, ParsingEntity):
            if self.__eq__(item):
                return True
            else:
                if item in self.operandA or item in self.operandB:
                    return True
                else:
                    return False
        else:
            raise TypeError("Item has to be ParsingEntity subclasses")

    def __str__(self):
        return 'ParsingOperator object'

    def __repr__(self):
        return self.__str__()

    def __copy__(self):
        result = ParsingOperator(self.operatorType, self.operandA, self.operandB)
        result.isNot = self.isNot
        return result

    def __deepcopy__(self, memodict={}):
        result = ParsingOperator(self.operatorType, copy.deepcopy(self.operandA), copy.deepcopy(self.operandB))
        result.isNot = self.isNot
        return result

    def check(self, element, ref_position=0):
        result_a = self.operandA.check(element, ref_position)
        result_b = self.operandB.check(element, ref_position)
        key_words = result_a[1] + result_b[1]

        if self.operatorType is OperatorType.AND:
            if result_a[0] is True and result_b[0] is True:
                result = True, key_words
            else:
                result = False, Counter({})
        elif self.operatorType is OperatorType.OR:
            if result_a[0] is True or result_b[0] is True:
                result = True, key_words
            else:
                result = False, Counter({})
        else:
            if (result_a[0] is True) ^ (result_b[0] is True):
                result = True, key_words
            else:
                result = False, Counter({})

        if self.isNot is False:
            return result
        else:
            if result[0]:
                return False, Counter({})
            else:
                return True, key_words

    def get_max_position(self):
        operand_a = self.operandA.get_max_position()
        operand_b = self.operandB.get_max_position()

        if operand_a < 0 and operand_b < 0:
            return 0
        else:
            return max([operand_a, operand_b])

    def get_min_position(self):
        operand_a = self.operandA.get_min_position()
        operand_b = self.operandB.get_min_position()

        if operand_a > 0 and operand_b > 0:
            return 0
        else:
            return min([operand_a, operand_b])


class ParsingCondition(ParsingEntity):
    def __new__(cls,  ar_character, rel_position=0, key_word=None):
        if len(ar_character) > 1:
            result = ParsingCondition(ar_character[0], rel_position=rel_position, key_word=key_word)
            for i, element in enumerate(ar_character):
                if i > 0:
                    result = result & ParsingCondition(ar_character[i], rel_position=rel_position+i, key_word=key_word)
            return result
        elif len(ar_character) == 1:
            return super(ParsingCondition, cls).__new__(cls)
        else:
            return EmptyParsingCondition()

    def __init__(self, ar_character, rel_position=0, key_word=None):
        super(ParsingCondition, self).__init__()
        self.relPosition = rel_position
        self.character = ar_character
        self.keyWord = key_word

    def __eq__(self, other):
        if isinstance(other, ParsingCondition):
            if self.relPosition == other.relPosition and self.character == other.character \
                    and self.isNot == other.isNot and self.keyWord == other.keyWord:
                return True
            else:
                return False
        else:
            return False

    def __contains__(self, item):
        return self.__eq__(item)

    def __str__(self):
        return 'ParsingCondition object'

    def __repr__(self):
        return self.__str__()

    def __copy__(self):
        result = ParsingCondition(self.character, self.relPosition, self.keyWord)
        result.isNot = self.isNot
        return result

    def __deepcopy__(self, memodict={}):
        return self.__copy__()

    def check(self, element, ref_position=0):
        element_size = len(element)
        if 0 <= ref_position < element_size:
            position = ref_position + self.relPosition
            if 0 <= position < element_size:
                if self.character in element[position]:
                    result = (True, Counter({self.keyWord: 1}))
                else:
                    result = (False, Counter({}))

                if self.isNot is False:
                    return result
                else:
                    if result[0]:
                        return False, Counter({})
                    else:
                        return True, Counter({self.keyWord: 1})
            else:
                raise IndexError('relative position out of range ( 0 <= ref_position + rel_position < len(element) )')
        else:
            raise IndexError('reference position out of range ( 0 <= ref_position < len(element) )')

    def get_min_position(self):
        if self.relPosition > 0:
            return 0
        else:
            return self.relPosition

    def get_max_position(self):
        if self.relPosition < 0:
            return 0
        else:
            return self.relPosition


class EmptyParsingCondition(ParsingEntity):
    def __init__(self):
        super(EmptyParsingCondition, self).__init__()

    def __eq__(self, other):
        if isinstance(other, EmptyParsingCondition):
            if self.isNot == other.isNot:
                return True
            else:
                return False
        else:
            return False

    def __contains__(self, item):
        return self.__eq__(item)

    def __str__(self):
        return 'EmptyParsingCondition object'

    def __repr__(self):
        return self.__str__()

    def __copy__(self):
        result = EmptyParsingCondition()
        result.isNot = self.isNot
        return result

    def __deepcopy__(self, memodict={}):
        return self.__copy__()

    def check(self, element, ref_position=0):
        return False, Counter({None: 1})

    def get_min_position(self):
        return 0

    def get_max_position(self):
        return 0


class ParsingStructure(Entity):
    def __add__(self, other):
        if isinstance(self, ParsingStructure) and (isinstance(other, ParsingStructure) or other is None):
            parsing_pipeline = ParsingPipeline(self)
            if other is not None:
                parsing_pipeline.add_structure(other)
            return parsing_pipeline
        else:
            raise TypeError("Operands have to be ParsingStructure subclasses")

    @abc.abstractmethod
    def check(self, element, ref_position):
        pass

    @abc.abstractmethod
    def get_max_position(self):
        pass

    @abc.abstractmethod
    def get_min_position(self):
        pass


class ParsingPipeline(ParsingStructure):
    def __init__(self, first_parsing_structure):
        if isinstance(first_parsing_structure, ParsingStructure):
            self.arParsingStructure = list()
            self.arParsingStructure.append(first_parsing_structure)
            self.current_parsing_block_index = 0
            self.isEnded = False
        else:
            raise TypeError("Object has to be ParsingStructure object")

    def check(self, element, ref_position=0):
        if not self.isEnded:
            result = self.arParsingStructure[self.current_parsing_block_index].check(element, ref_position)
            if result[1][0]:
                if self.current_parsing_block_index < len(self.arParsingStructure)-1:
                    self.current_parsing_block_index += 1
                else:
                    self.isEnded = True
            return result
        else:
            return None

    def get_min_position(self):
        ar_min_position = list()
        for parsing_structure in self.arParsingStructure:
            ar_min_position.append(parsing_structure.get_min_position())
        return min(ar_min_position)

    def get_max_position(self):
        ar_max_position = list()
        for parsing_structure in self.arParsingStructure:
            ar_max_position.append(parsing_structure.get_max_position())
        return max(ar_max_position)

    def add_structure(self, parsing_structure):
        if isinstance(parsing_structure, ParsingPipeline):
            self.arParsingStructure = self.arParsingStructure + parsing_structure.arParsingStructure
        elif isinstance(parsing_structure, ParsingStructure):
            self.arParsingStructure.append(parsing_structure)
        else:
            raise TypeError("Object to add has to be ParsingStructure object")

    def reset(self):
        self.current_parsing_block_index = 0
        self.isEnded = False


class ParsingBlock(ParsingStructure):
    def __init__(self, parser, border_condition):
        if isinstance(parser, ParsingEntity):
            self.parser = parser
        else:
            raise TypeError('parser has to be ParsingStructure subclass')

        if isinstance(border_condition, ParsingEntity):
            self.borderCondition = border_condition
        elif border_condition is None:
            self.borderCondition = EmptyParsingCondition()
        else:
            raise TypeError('border_condition has to be ParsingStructure subclass or None')

    def check(self, element, ref_position=0):
        parser_result = self.parser.check(element, ref_position)
        border_condition_result = self.borderCondition.check(element, ref_position)
        return parser_result, border_condition_result

    def get_min_position(self):
        if self.borderCondition is not None:
            return min([self.parser.get_min_position(), self.borderCondition.get_min_position()])
        else:
            return self.parser.get_min_position()

    def get_max_position(self):
        if self.borderCondition is not None:
            return max([self.parser.get_max_position(), self.borderCondition.get_max_position()])
        else:
            return self.parser.get_max_position()


class ParsingResult:
    def __init__(self, stream_class, origin, result_type, read_method, write_method, return_method, close_method,
                 seek_method, args, kwargs, ar_index):
        if hasattr(stream_class, '__name__'):
            self.streamClass = stream_class
        else:
            raise TypeError('Stream class has to have __name__ attribute')

        if isinstance(origin, ParsingResultOrigin):
            self.origin = origin
        else:
            raise TypeError('Origin has to be ParsingResultOrigin object')

        if isinstance(result_type, ParsingResultType):
            self.resultType = result_type
        else:
            raise TypeError('Type has to be ParsingResultType object')

        if isinstance(read_method, str) or read_method is None:
            self.readMethod = read_method
        else:
            raise TypeError('Read method has to be str object or None')

        if isinstance(write_method, str) or write_method is None:
            self.writeMethod = write_method
        else:
            raise TypeError('Write method has to be str object or None')

        if isinstance(return_method, str) or return_method is None:
            self.returnMethod = return_method
        else:
            raise TypeError('Return method has to be str object or None')

        if isinstance(close_method, str) or close_method is None:
            self.closeMethod = close_method
        else:
            raise TypeError('Close method has to be str object or None')

        if isinstance(seek_method, str) or seek_method is None:
            self.seekMethod = seek_method
        else:
            raise TypeError('Seek method has to be str object or None')

        if isinstance(args, (tuple, list)) and isinstance(kwargs, dict):
            self.arInput = {'args': args, 'kwargs': kwargs}
        else:
            raise TypeError('args has to be list object and kwargs has to be dict object')

        if isinstance(ar_index, list):
            self.arIndex = ar_index
        else:
            raise TypeError('ar_index has to be list object')

    def __add__(self, other):
        if isinstance(other, ParsingResult) and isinstance(self, ParsingResult):
            if ParsingResult.are_from_the_same_parsing(self, other):
                new_parsing_result = copy.deepcopy(self)
                for element in other.arIndex:
                    if (element[0] not in new_parsing_result or
                            (len(element) == 3 and (element[0], None, element[2]) not in new_parsing_result)) or \
                            (element[1] == '' and (element[0], element[1]) not in new_parsing_result):
                        new_parsing_result.arIndex.append(element)
                new_parsing_result.arIndex.sort()
                return new_parsing_result
            else:
                raise ValueError("Operands have to come from the same parsing")
        else:
            raise TypeError("Operands have to be ParsingResult classes or subclasses")

    def __contains__(self, item):
        if isinstance(item, int):
            for element in self.arIndex:
                if element[0] == item:
                    return True
            return False
        elif isinstance(item, tuple):
            if len(item) == 2:
                for element in self.arIndex:
                    if element[0] == item[0] and element[1] == item[1]:
                        return True
                    return False
            elif len(item) == 3:
                if item[1] is not None:
                    for element in self.arIndex:
                        if len(element) == 3 and element[0] == item[0] and element[1] == item[1] \
                                and element[2] == item[2]:
                            return True
                    return False
                else:
                    for element in self.arIndex:
                        if len(element) == 3 and element[0] == item[0] and element[2] == item[2]:
                            return True
                    return False
            else:
                return False
        else:
            return False

    def __str__(self):
        return str({'Stream class': str(self.streamClass.__name__), 'Origin': str(self.origin),
                    'Result type': str(self.resultType), 'Inputs': str(self.arInput),
                    'Index result': str(self.arIndex)})

    def __repr__(self):
        return 'Parsing result :' + '\n' + '   Stream class : ' + str(self.streamClass.__name__) + '\n' \
               + '   Origin : ' + str(self.origin) + '\n' + '   Result type : ' + str(self.resultType) + '\n' \
               + '   Inputs : ' + str(self.arInput) + '\n' + '   Index result : ' + str(self.arIndex)

    def __copy__(self):
        return ParsingResult(self.streamClass, self.origin, self.resultType, self.readMethod, self.writeMethod,
                             self.returnMethod, self.closeMethod, self.seekMethod, self.arInput['args'],
                             self.arInput['kwargs'], self.arIndex)

    def __deepcopy__(self, memodict={}):
        return self.__copy__()

    @staticmethod
    def are_from_the_same_parsing(parsing_result_a, parsing_result_b):
        if isinstance(parsing_result_a, ParsingResult) and isinstance(parsing_result_b, ParsingResult):
            return (parsing_result_a.streamClass == parsing_result_b.streamClass and
                    parsing_result_a.origin == parsing_result_b.origin and
                    parsing_result_a.resultType == parsing_result_b.resultType and
                    parsing_result_a.arInput['args'] == parsing_result_b.arInput['args'] and
                    parsing_result_a.arInput['kwargs'] == parsing_result_b.arInput['kwargs'] and
                    parsing_result_a.readMethod == parsing_result_b.readMethod and
                    parsing_result_a.writeMethod == parsing_result_b.writeMethod and
                    parsing_result_a.returnMethod == parsing_result_b.returnMethod and
                    parsing_result_a.closeMethod == parsing_result_b.closeMethod and
                    parsing_result_a.seekMethod == parsing_result_b.seekMethod)
        else:
            raise TypeError("Operands have to be ParsingResult classes or subclasses")

    def check_indexes(self):
        previous_index_value = -1
        for index in self.arIndex:
            if index[0] > previous_index_value:
                previous_index_value = index[0]
            else:
                raise ValueError('Indexes have to be sorted in ascending order')
            if len(index[1]) != 1 or not isinstance(index[1], str):
                raise TypeError("Indexes characters have to be 'str' objects with a length of 1")

        return True

{'fraction_non_alphanumeric': 0.08390380201576689, 'fraction_numerical': 0.0035924558427302663, 'mean_word_length': 2.0352556336321785, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4759353', 'n_tokens_mistral': 13367, 'n_tokens_neox': 12714, 'n_words': 3649}