def select_all(self) -> None: """Select all text of Enactment.""" if self.content: self._selection = TextPositionSet( TextPositionSelector(0, len(self.content))) else: self._selection = TextPositionSet() for child in self._children: child.select_all()
def test_error_for_unusable_selector(self, section_11_subdivided): schema = EnactmentSchema() section = schema.load(section_11_subdivided) selection = TextPositionSet(TextPositionSelector(0, 10), TextPositionSelector(1000, 1010)) with pytest.raises(ValueError): section.children[3].select(selection)
def select_more_text_from_changed_version(self, other: Enactment) -> None: """ Select more text from a different text version at the same citation path. :param other: An :class:`Enactment` representing different text enacted at a different time, at the same `node` (or USLM path citation) as self. This Element's :attr:`~Enactment.node` attribute must be the same string as self's node attribute. It's not sufficient for `other` to have an Enactment listed in its :attr:`~Enactment._children` attribute with the same node attribute, or for `other` to have the same node attribute as an ancestor of self. """ incoming_quote_selectors = [ selector.as_quote_selector(other.text) for selector in other.tree_selection() ] incoming_position_selectors = [] for quote_selector in incoming_quote_selectors: position = quote_selector.as_position(self.text) if position: incoming_position_selectors.append(position) if not quote_selector.is_unique_in(self.text): raise ValueError( f"Incoming text selection {quote_selector} cannot be placed because it " f"is not unique in the provision text.") self.select_more_text_in_current_branch( TextPositionSet(incoming_position_selectors))
def test_select_method_clears_previous_selection(self, test_client, section_8): old_version = test_client.read_from_json(section_8["children"][1]) old_selector = TextPositionSet(TextPositionSelector(start=0, end=65), ) old_version.select(old_selector) assert old_version.selected_text() == ( "Any such person issued a notice to remedy under subsection 1 must…" )
class HoldingWithAnchors(BaseModel): """A :class:`.Holding` with a :class:`.TextPositionSet` that anchors it.""" holding: Holding anchors: TextPositionSet = TextPositionSet() @validator("anchors", pre=True) def validate_anchors(cls, value: TextPositionSet) -> TextPositionSet: """Validate that the anchors are non-empty.""" if value is None: return TextPositionSet() return value
def select_from_text_positions( self, selection: TextPositionSet) -> TextPositionSet: """Select text using position selectors and return any unused position selectors.""" selections = self.select_from_text_positions_without_nesting(selection) selections = [ selection - self.padded_length for selection in selections ] for child in self.children: selections = child.select_from_text_positions( TextPositionSet(selections)) return selections
class EnactmentWithAnchors(BaseModel): """A term with a set of anchors.""" passage: EnactmentPassage anchors: TextPositionSet = TextPositionSet() @validator("anchors", pre=True) def validate_anchors(cls, value: TextPositionSet) -> TextPositionSet: """Validate that the anchors are non-empty.""" if value is None: return TextPositionSet() return value
class TermWithAnchors(BaseModel): """A term with a set of anchors.""" term: Union[Entity, Fact, Allegation, Pleading, Exhibit, Evidence] anchors: TextPositionSet = TextPositionSet() @validator("anchors", pre=True) def validate_anchors(cls, value: TextPositionSet) -> TextPositionSet: """Validate that the anchors are non-empty.""" if value is None: return TextPositionSet() return value
def test_select_nested_text_with_positions(self, section_11_subdivided): phrases = TextPositionSet( TextPositionSelector(0, 51), TextPositionSelector(61, 73), TextPositionSelector(112, 127), ) schema = EnactmentSchema() section = schema.load(section_11_subdivided) section.select(phrases) text_sequence = section.text_sequence() assert str(text_sequence) == ( "The Department of Beards may issue licenses to " "such…hairdressers…as they see fit…")
def convert_selection_to_set( self, selection: Union[bool, str, TextPositionSelector, TextPositionSet, TextQuoteSelector, Sequence[TextQuoteSelector], ], ) -> TextPositionSet: """Create a TextPositionSet from a different selection method.""" if selection is True: return TextPositionSet( TextPositionSelector(0, len(self.content) + 1, include_end=False)) factory = TextPositionSetFactory(self.text) return factory.from_selection(selection)
def __init__( self, children: List[Enactment] = None, selection: Union[bool, List[TextPositionSelector]] = True, *args, **kwargs, ): """Assign selected text as attr, including any selected text in child nodes.""" self._children = children or [] super().__init__(*args, **kwargs) self._selection = TextPositionSet() if selection: self.select_more(selection)
def __init__( self, children: List[str] = None, selection: Union[bool, List[TextPositionSelector]] = True, *args, **kwargs, ): """Select text of Enactment that can't have nested Enactments.""" self._children = children or [] super().__init__(*args, **kwargs) if selection: self.select_without_children(selection) else: self._selection = TextPositionSet()
def select_from_text_positions_without_nesting( self, selections: Union[List[TextPositionSelector], RangeSet] ) -> TextPositionSet: r""" Move selectors from `selection` to `self._selection` and return any that can't be used. Replaces any preexisting _selection attribute on this Enactment object. :param selection: A TextPositionSet of TextPositionSelectors to apply to this Enactment. :returns: Any unused selections (beyond the end of the node content) """ self._selection: List[TextPositionSelector] = [] if isinstance(selections, RangeSet): selections = selections.ranges() selections = deque(selections) while selections and selections[0].start < len(self.content): current = selections.popleft() if current.end < len(self.content): self._selection.append(current) else: self._selection.append( TextPositionSelector(start=current.start, end=len(self.content))) if current.end > self.padded_length: selections.appendleft( TextPositionSelector(start=self.padded_length, end=current.end)) selection_as_set = TextPositionSet(self._selection) self._selection = selection_as_set.add_margin(text=self.content, margin_width=4) return TextPositionSet(selections)
def test_get_positions_from_quotes(self, section_11_subdivided): schema = EnactmentSchema() section = schema.load(section_11_subdivided) quotes = [ TextQuoteSelector( exact="The Department of Beards may issue licenses to such"), TextQuoteSelector(exact="hairdressers", suffix=", or other male grooming"), TextQuoteSelector(exact="as they see fit"), ] positions = section.convert_quotes_to_position(quotes) assert positions == TextPositionSet( TextPositionSelector(0, 51), TextPositionSelector(61, 73), TextPositionSelector(112, 127), )
def test_get_recursive_selection(self, section_8, test_client): version = test_client.read_from_json(section_8["children"][1]) selector = TextPositionSet(TextPositionSelector(start=0, end=65), ) version.select(selector) version.children[4].select( "obtain a beardcoin from the Department of Beards") selector_set = version.tree_selection() ranges = selector_set.ranges() assert ranges[0].start == 0 assert ranges[0].end == 65 assert ranges[1].start == 218 assert ranges[1].end == 266 as_quotes = selector_set.as_quotes(version.text) assert as_quotes[ 1].exact == "obtain a beardcoin from the Department of Beards"
def posit_holding( self, holding: Union[Holding, Rule, HoldingWithAnchors], holding_anchors: Optional[Union[TextPositionSelector, TextQuoteSelector, TextPositionSet]] = None, named_anchors: Optional[List[TermWithAnchors]] = None, enactment_anchors: Optional[List[EnactmentWithAnchors]] = None, context: Optional[Sequence[Factor]] = None, ) -> None: r"""Record that this Opinion endorses specified :class:`Holding`\s.""" named_anchors = named_anchors or [] enactment_anchors = enactment_anchors or [] if isinstance(holding, HoldingWithAnchors): holding, holding_anchors = holding.holding, holding.anchors if isinstance(holding_anchors, (TextQuoteSelector, str)): holding_anchors = [holding_anchors] if isinstance(holding_anchors, List) and isinstance( holding_anchors[0], (str, TextQuoteSelector)): holding_anchors = TextPositionSet.from_quotes(holding_anchors) if isinstance(holding, Rule): logger.warning("posit_holding was called with a Rule " "that was automatically converted to a Holding") holding = Holding(rule=holding) if not isinstance(holding, Holding): raise TypeError('"holding" must be an object of type Holding.') for named_anchor in named_anchors: self.anchored_holdings.add_term(term=named_anchor.term, anchors=named_anchor.anchors) for enactment_anchor in enactment_anchors: self.anchored_holdings.add_enactment( enactment=enactment_anchor.passage, anchors=named_anchor.anchors) matching_holding = self.get_matching_holding(holding) if matching_holding: matching_holding.anchors += holding.anchors else: if context: holding = holding.new_context(context, source=self) self.anchored_holdings.holdings.append( HoldingWithAnchors(holding=holding, anchors=holding_anchors))
def test_insert_duplicate_anchor_in_factor_index(self): api = Entity(name="the Java API", generic=True, plural=False) quote_selector = TextQuoteSelector( exact="it possesses at least some minimal degree of creativity.") anchors = TextPositionSet(quotes=quote_selector) fact = Fact( predicate=Predicate( content= "$product possessed at least some minimal degree of creativity" ), terms=[api], ) term = TermWithAnchors(term=fact, anchors=anchors) index = AnchoredHoldings(holdings=[], named_anchors=[term]) index.add_term(term=fact, anchors=anchors) assert len(index.named_anchors) == 1 assert index.named_anchors[0].anchors.quotes == [quote_selector]
def test_add_selection_from_child_node(self, section_8, test_client): parent_version = test_client.read_from_json(section_8["children"][1]) parent_selector = TextPositionSet( TextPositionSelector(start=0, end=65), ) parent_version.select(parent_selector) parent_version.children[4].select( "obtain a beardcoin from the Department of Beards") assert parent_version.selected_text() == ( "Any such person issued a notice to remedy under subsection 1 must…" "obtain a beardcoin from the Department of Beards…") child_version = test_client.read_from_json( section_8["children"][1]["children"][3]) child_version.select() combined = parent_version + child_version assert combined.selected_text() == ( "Any such person issued a notice to remedy under subsection 1 must…" "remove the beard with a laser, or " "obtain a beardcoin from the Department of Beards…")
def validate_anchors(cls, value: TextPositionSet) -> TextPositionSet: """Validate that the anchors are non-empty.""" if value is None: return TextPositionSet() return value
def select_none(self) -> None: """Deselect any Enactment text, including in child nodes.""" self._selection = TextPositionSet() for child in self._children: child.select_none()
def tree_selection(self) -> TextPositionSet: """Return set of selectors for selected text in this provision and its children.""" new_selector_set, new_tree_length = self._tree_selection( selector_set=TextPositionSet(), tree_length=0) return new_selector_set