Source code for scientific_spinbox.validators

"""
Validators module.

Provides the text validators and text processing utilities
to be used within a ScientificSpinBox.

Since:
  2024/02/06

Authors:
  - Breno H. Pelegrin S. <breno.pelegrin@usp.br>
"""

from qtpy import QtGui, PYSIDE2
from typing import Type

from .errors.validators import (
    CaptureGroupNameNotFoundOnRegex,
    InvalidValidatorStateError
)
if PYSIDE2:
    raise NotImplementedError(
        "Compatibility layer for `pyside2` is not yet implemented. This module is only compatible with `pyqt5`, `pyqt6` and `pyside6`."
    )
else:
    from qtpy.QtGui import QRegularExpressionValidator
    from qtpy.QtCore import QRegularExpression

[docs] class ScientificInputValidator(QRegularExpressionValidator): """Input validator for a ScientificSpinBox. Verifies if the provided input is a valid scientific number with or without units based on a regular expression. Implements methods for manipulating captured groups to extract parts from the scientific input. """ # Please note that QRegularExpression isn't compatible with Qt4. # The new QRegularExpression implements match group names that are # useful for this application. def __init__(self, parent=None): """ Args: parent (object): parent Qt object """ # Allows only patterns like "-0123456789.0123456789E+0123456789 /aBCµΩºde/FG***HI*Jkl*m*/no" # (?<full>(?<sci_repr>^(?<num_repr>(?<num_sgn>[+-])?(?<int_num>[0-9]+)?(?<dec_repr>(?<dec_sep>[.,])?(?<dec_num>[0-9]+))?)(?<exp_repr>[eE](?<exp_num>(?<exp_sgn>[+-])[0-9]+))?)(?<txt_repr> *(?<txt_unit>\µ?\Ω?\º?[a-zA-Z0-9?\/*\**\(*\)*]+))?)$ self._regex = QRegularExpression( r"^(?<full>(?<sci_repr>^(?<num_repr>(?<num_sgn>[+-])?(?<int_num>[0-9]+)?(?<dec_repr>(?<dec_sep>[.])?(?<dec_num>[0-9]+))?)(?<exp_repr>[eE](?<exp_num>(?<exp_sgn>[+-])[0-9]+))?)(?<txt_repr> *(?<txt_unit>\µ?\Ω?\º?[a-zA-Z0-9?\/*\**\(*\)*]+))?)$" ) super().__init__(self._regex, parent) @property def regExpCaptureGroups(self): """ Returns the named capture groups from the regex. Returns: list: list of named capture groups. """ groups = self._regex.namedCaptureGroups().copy() groups.pop(0) return groups
[docs] def getCapturedGroups(self, text): """Returns a ScientificCapturedGroups object for the given text. Args: text (str): text to fill the capture groups. Returns: ScientificCapturedGroups: object with the captured groups. """ return ScientificCapturedGroups(text, self)
[docs] def stateToStr(self, state: QtGui.QValidator.State): """ Transforms a QValidator state into a string. Args: state (QtGui.QValidator.State): QValidator state. Raises: InvalidValidatorStateError: if the provided state is not one of the known QValidator states (Acceptable, Invalid, Intermediate). Returns: str: string representation of the QValidator state. Can be `Acceptable`, `Invalid` or `Intermediate`. """ valid_states = [ QtGui.QValidator.State.Acceptable, QtGui.QValidator.State.Invalid, QtGui.QValidator.State.Intermediate ] if state not in valid_states: raise InvalidValidatorStateError(state) if state == QtGui.QValidator.State.Intermediate: return 'Intermediate' elif state == QtGui.QValidator.State.Invalid: return 'Invalid' elif state == QtGui.QValidator.State.Acceptable: return 'Acceptable'
[docs] def getRegExpGroupIdx(self, group_name: str): """ Gets the index of a named capture group based on its name. Args: group_name (str): name of the group to get index. Returns: int: index of the group. Raises: Exception: if the group doesn't exist on the regex. """ groups = self.regExpCaptureGroups if group_name in groups: return groups.index(group_name) raise CaptureGroupNameNotFoundOnRegex(group_name)
[docs] def interpretText(self, text): """ Replaces commas with dots on the text. Args: text (str): text to replace commas. Returns: str: text with only dots. """ text = str(text).replace(',', '.') return text
[docs] def getAllCaptured(self, text): """ Returns a dictionary with all the capture groups and the corresponding text captured by each group. Args: text (str): text to fill the capture groups. """ captured_dict = {} captured_dict = dict.fromkeys(self.regExpCaptureGroups) text = self.interpretText(text) match = self._regex.match(text) for key in captured_dict: captured_dict[key] = match.captured(key) return captured_dict
[docs] def validate(self, text, position): """ Reimplements the validate method from the parent class. On the current implementation, it just returns the result from the parent class method. Args: text (str): text to validate. position (int): position of the cursor before validation. Returns: state (QtGui.QValidator.State): state of the validator. text (str): text after the validation. position (int): position of the cursor after validation. """ pre_state, text, position = super().validate(text, position) return (pre_state, text, position)
[docs] def fixup(self, a0): """ Reimplements the fixup method from the parent class. On the current implementation, it just returns the result from the parent class method. Args: text (str): text to fixup. Returns: str: fixed up text. """ return super().fixup(a0)
[docs] class ScientificCapturedGroups: """Class that represents captured groups for a given text. Implements methods to access each group with ease. """ def __init__(self, text: str, validator: ScientificInputValidator): """ Args: text (str): text to fill the capture groups. validator (ScientificInputValidator): validator object reference. """ self._inputText = text self._captured = validator.getAllCaptured(text) @property def fullText(self) -> str | None: """Full captured text.""" return self._captured['full'] @property def scientificRepresentation(self) -> str | None: """Scientific representation, without unit text. Can contain: - numeric signal - integer numbers - decimal separator - decimal numbers - exponential symbol - exponential signal - exponential numbers. Doesn't contain: - unit text """ return self._captured['sci_repr'] # <num_repr> @property def numericRepresentation(self) -> str | None: """Numeric representation, without exponential. Can contain: - numeric signal - integer numbers - decimal separator - decimal numbers Doesn't contain: - exponential symbol - exponential signal - exponential numbers - unit text """ return self._captured['num_repr'] @property def numericSignal(self) -> str | None: """Signal of the numeric part.""" return self._captured['num_sgn'] @property def integerNumbers(self) -> str | None: """Integer part, not including signal.""" return self._captured['int_num'] # <dec_repr> @property def decimalRepresentation(self) -> str | None: """Decimal part, including separator and numbers.""" return self._captured['dec_repr'] @property def decimalSeparator(self) -> str | None: """Decimal separator.""" return self._captured['dec_sep'] @property def decimalNumbers(self) -> str | None: """Numbers of the decimal part.""" return self._captured['dec_num'] @property def exponentialRepresentation(self) -> str | None: """Exponential part, including exponential sign, number and signal.""" return self._captured['exp_repr'] @property def exponentialNumber(self) -> str | None: """Number after the exponential sign, including signal.""" return self._captured['exp_num'] @property def exponentialSignal(self) -> str | None: """Signal of the exponential.""" return self._captured['exp_sgn'] @property def unitRepresentation(self) -> str | None: """Unit part, with the preceding spaces.""" return self._captured['txt_repr'] @property def unitText(self) -> str | None: """Text of the unit part, without the preceding spaces.""" return self._captured['txt_unit']