Skip to content

Commit

Permalink
transpose: implemented support for complex transpositions
Browse files Browse the repository at this point in the history
  • Loading branch information
proycon committed Mar 4, 2024
1 parent 3618fc6 commit 1e25dd7
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 10 deletions.
7 changes: 6 additions & 1 deletion src/api/annotation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,12 @@ impl<'store> ResultItem<'store, Annotation> {
textselections.push(tsel);
}
}
Some(textselections.into_iter().collect())
//important check because into_iter().collect() [from_iter()] panics if passed an empty iter!
if textselections.is_empty() {
None
} else {
Some(textselections.into_iter().collect())
}
}

/// Groups text selections targeting the same resource together in a TextSelectionSet.
Expand Down
2 changes: 1 addition & 1 deletion src/api/textselection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1171,7 +1171,7 @@ impl<'store> FromIterator<ResultTextSelection<'store>> for ResultTextSelectionSe
.collect();
ResultTextSelectionSet {
tset,
rootstore: store.expect("Iterator may not be empty"),
rootstore: store.expect("Iterator may not be empty"), //TODO: this is suboptimal, it will panic when an empty iterator is passed!
}
}
}
86 changes: 79 additions & 7 deletions src/api/transpose.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::collections::VecDeque;

use crate::api::*;
use crate::datavalue::DataValue;
use crate::selector::{Offset, OffsetMode, SelectorBuilder};
Expand Down Expand Up @@ -116,21 +118,58 @@ impl<'store> Transposable<'store> for ResultTextSelectionSet<'store> {
let mut relative_offsets = Vec::new();
let mut selectors_per_side: SmallVec<[Vec<SelectorBuilder<'static>>; 2]> = SmallVec::new();

let resource = self.resource();
let mut source_found = false;
let mut simple_transposition = true; //falsify

let mut tselbuffer: VecDeque<TextSelection> =
self.inner().iter().map(|x| x.clone()).collect(); //MAYBE TODO: slightly waste of time/space if the transposition turns out to be a simple transposition rather than a complex one

// match the textselectionset against the sides in a complex transposition (or ascertain that we are dealing with a simple transposition instead)
// the source side that matches can never be the same as the target side that is mappped to
for annotation in via.annotations_in_targets(AnnotationDepth::One) {
simple_transposition = false;
if let Some(refset) = annotation.textselectionset_in(self.resource()) {
//TODO
while let Some(tsel) = tselbuffer.pop_front() {
for (side_i, annotation) in via.annotations_in_targets(AnnotationDepth::One).enumerate()
{
simple_transposition = false;
if selectors_per_side.len() <= side_i {
selectors_per_side.push(Vec::new());
}
if let Some(refset) = annotation.textselectionset_in(self.resource()) {
// We may have multiple text selections to transpose (all must be found)
for reftsel in refset.iter() {
if reftsel.resource() == resource
&& (source_side.is_none() || source_side == Some(side_i))
{
if let Some((intersection, remainder, _)) =
tsel.intersection(reftsel.inner())
{
source_side = Some(side_i);
source_found = true; //source_side might have been pre-set so we need this extra flag
let relative_offset = intersection
.relative_offset(reftsel.inner(), OffsetMode::default())
.expect("intersection offset must be valid");
relative_offsets.push(relative_offset);
selectors_per_side[side_i].push(SelectorBuilder::TextSelector(
resource.handle().into(),
intersection.into(),
));
if let Some(remainder) = remainder {
//the text selection was not matched/consumed entirely
//add the remainder of the text selection back to the buffer
tselbuffer.push_front(remainder);
}
break;
}
}
}
}
}
if simple_transposition {
break;
}
}

if simple_transposition {
let resource = self.resource();
let mut source_found = false;

// We may have multiple text selections to transpose (all must be found)
for tsel in self.inner().iter() {
// each text selection in a simple transposition corresponds to a side
Expand Down Expand Up @@ -196,6 +235,39 @@ impl<'store> Transposable<'store> for ResultTextSelectionSet<'store> {
}
}
}
} else {
//complex transposition
if !tselbuffer.is_empty() {
return Err(StamError::TransposeError(
format!(
"Not all source fragments were found in the complex transposition {}, not enough to transpose",
via.id().unwrap_or("(no-id)"),
),
"",
));
}

// now map the targets (there may be multiple target sides)
for (side_i, annotation) in via.annotations_in_targets(AnnotationDepth::One).enumerate()
{
if selectors_per_side.len() <= side_i {
selectors_per_side.push(Vec::new());
}
if source_side != Some(side_i) {
for reftsel in annotation.textselections() {
let resource = reftsel.resource().handle();
for offset in relative_offsets.iter() {
let mapped_tsel = reftsel.textselection(&offset)?;
let mapped_selector: SelectorBuilder<'static> =
SelectorBuilder::TextSelector(
resource.into(),
mapped_tsel.inner().into(),
);
selectors_per_side[side_i].push(mapped_selector);
}
}
}
}
}

match selectors_per_side[source_side.expect("source side must exist at this point")].len() {
Expand Down
55 changes: 55 additions & 0 deletions tests/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2147,3 +2147,58 @@ fn transpose_over_simple_transposition() -> Result<(), StamError> {
assert_eq!(tsel.end(), 5, "transposed offset check");
Ok(())
}

#[test]
#[cfg(feature = "transpose")]
fn transpose_over_complex_transposition() -> Result<(), StamError> {
let mut store = setup_example_8b()?;
store.annotate(
AnnotationBuilder::new()
.with_id("A3")
.with_data("mydataset", "species", "homo sapiens")
.with_target(SelectorBuilder::textselector(
"humanrights",
Offset::simple(4, 9),
)),
)?;
let transposition = store.annotation("ComplexTransposition1").or_fail()?;
let source = store.annotation("A3").or_fail()?;
assert_eq!(
source.text_simple(),
Some("human"),
"sanity check for source annotation"
);
let config = TransposeConfig {
transposition_id: Some("NewTransposition".to_string()),
target_side_ids: vec!["A3t".to_string()],
..Default::default()
};
let targets =
store.annotate_from_iter(source.transpose(&transposition, config)?.into_iter())?;
assert_eq!(targets.len(), 2);
let new_transposition = store.annotation("NewTransposition").or_fail()?;
assert_eq!(
new_transposition
.annotations_in_targets(AnnotationDepth::One)
.count(),
2,
"new transposition must have two target annotations (source annotation and transposed annotation)"
);
let source = store.annotation("A3").or_fail()?; //reobtain (otherwise borrow checker complains after mutation)
let transposed = store.annotation("A3t").or_fail()?;
eprintln!("{:?}", transposed);
assert_eq!(
transposed.text_simple(),
source.text_simple(),
"transposed annotation must reference same text as source"
);
let tsel = transposed.textselections().next().unwrap();
assert_eq!(
tsel.resource().id(),
Some("warhol"),
"transposed annotation must reference the target resource"
);
assert_eq!(tsel.begin(), 0, "transposed offset check");
assert_eq!(tsel.end(), 5, "transposed offset check");
Ok(())
}
2 changes: 1 addition & 1 deletion tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -628,7 +628,7 @@ pub fn setup_example_8b() -> Result<AnnotationStore, StamError> {
)?
.with_annotation(
AnnotationBuilder::new()
.with_id("SimpleTransposition1")
.with_id("ComplexTransposition1")
.with_target(SelectorBuilder::DirectionalSelector(vec![
SelectorBuilder::annotationselector("A1", None),
SelectorBuilder::annotationselector("A2", None),
Expand Down

0 comments on commit 1e25dd7

Please sign in to comment.