@@ -124,6 +124,12 @@ pub struct WebAnnoConfig {
124124
125125 /// Automatically generate a JSON-LD context alias for all URIs in keys, maps URI prefixes to namespace prefixes
126126 pub context_namespaces : Vec < ( String , String ) > ,
127+
128+ /// Adds an extra target alongside the usual target with TextPositionSelector. This can
129+ /// be used for provide a direct URL to fetch the exact textselection (if the backend system supports it).
130+ /// In the template, you should use the variables {resource} (which is the resource IRI), {begin}, and {end} , they will be substituted accordingly.
131+ /// A common value is {resource}/{begin}/{end} .
132+ pub extra_target_template : Option < String > ,
127133}
128134
129135impl Default for WebAnnoConfig {
@@ -137,6 +143,7 @@ impl Default for WebAnnoConfig {
137143 auto_generated : true ,
138144 auto_generator : true ,
139145 context_namespaces : Vec :: new ( ) ,
146+ extra_target_template : None ,
140147 }
141148 }
142149}
@@ -312,10 +319,33 @@ impl<'store> ResultItem<'store, Annotation> {
312319 ann_out += "}," ;
313320 }
314321
315- ann_out += & format ! (
316- " \" target\" : {}" ,
317- & output_selector( self . as_ref( ) . target( ) , self . store( ) , config, false )
322+ // a second pass may be needed if we have an extra_target_template AND nested targets
323+ let mut need_second_pass = false ;
324+ let output_selector_out = & output_selector (
325+ self . as_ref ( ) . target ( ) ,
326+ self . store ( ) ,
327+ config,
328+ false ,
329+ & mut need_second_pass,
330+ false , //first pass
318331 ) ;
332+ if need_second_pass {
333+ let second_pass_out = & output_selector (
334+ self . as_ref ( ) . target ( ) ,
335+ self . store ( ) ,
336+ config,
337+ false ,
338+ & mut need_second_pass,
339+ true , //second pass
340+ ) ;
341+ ann_out += & format ! (
342+ " \" target\" : [ {}, {} ]" ,
343+ output_selector_out, & second_pass_out
344+ ) ;
345+ } else {
346+ //normal situation
347+ ann_out += & format ! ( " \" target\" : {}" , & output_selector_out) ;
348+ }
319349
320350 ann_out += "}" ;
321351 ann_out
@@ -354,6 +384,8 @@ fn output_selector(
354384 store : & AnnotationStore ,
355385 config : & WebAnnoConfig ,
356386 nested : bool ,
387+ need_second_pass : & mut bool ,
388+ second_pass : bool ,
357389) -> String {
358390 let mut ann_out = String :: new ( ) ;
359391 match selector {
@@ -364,15 +396,44 @@ fn output_selector(
364396 . as_ref ( )
365397 . get ( * tsel_handle)
366398 . expect ( "text selection must exist" ) ;
367- ann_out += & format ! (
368- "{{ \" source\" : \" {}\" , \" selector\" : {{ \" type\" : \" TextPositionSelector\" , \" start\" : {}, \" end\" : {} }} }}" ,
369- into_iri(
370- resource. id( ) . expect( "resource must have ID" ) ,
371- & config. default_resource_iri
372- ) ,
373- textselection. begin( ) ,
374- textselection. end( ) ,
375- ) ;
399+ if !second_pass {
400+ if config. extra_target_template . is_some ( ) && !nested {
401+ ann_out += "[" ;
402+ }
403+ ann_out += & format ! (
404+ "{{ \" source\" : \" {}\" , \" selector\" : {{ \" type\" : \" TextPositionSelector\" , \" start\" : {}, \" end\" : {} }} }}" ,
405+ into_iri(
406+ resource. id( ) . expect( "resource must have ID" ) ,
407+ & config. default_resource_iri
408+ ) ,
409+ textselection. begin( ) ,
410+ textselection. end( ) ,
411+ ) ;
412+ }
413+ if ( !nested && !second_pass) || ( nested && second_pass) {
414+ if let Some ( extra_target_template) = config. extra_target_template . as_ref ( ) {
415+ let mut template = extra_target_template. clone ( ) ;
416+ template = template. replace (
417+ "{resource}" ,
418+ & into_iri (
419+ resource. id ( ) . expect ( "resource must have ID" ) ,
420+ & config. default_resource_iri ,
421+ ) ,
422+ ) ;
423+ template = template. replace ( "{begin}" , & format ! ( "{}" , textselection. begin( ) ) ) ;
424+ template = template. replace ( "{end}" , & format ! ( "{}" , textselection. end( ) ) ) ;
425+ if !ann_out. is_empty ( ) {
426+ ann_out. push ( ',' ) ;
427+ }
428+ ann_out += & format ! ( "\" {}\" " , & template) ;
429+ if !nested && !second_pass {
430+ ann_out += " ]" ;
431+ }
432+ }
433+ } else if config. extra_target_template . is_some ( ) && !second_pass {
434+ //we need a second pass to serialize the items using extra_target_template
435+ * need_second_pass = true ;
436+ }
376437 }
377438 Selector :: AnnotationSelector ( a_handle, None ) => {
378439 let annotation = store. annotation ( * a_handle) . expect ( "annotation must exist" ) ;
@@ -406,7 +467,10 @@ fn output_selector(
406467 Selector :: CompositeSelector ( selectors) => {
407468 ann_out += "{ \" type\" : \" http://www.w3.org/ns/oa#Composite\" , \" items\" : [" ;
408469 for ( i, selector) in selectors. iter ( ) . enumerate ( ) {
409- ann_out += & format ! ( "{}" , & output_selector( selector, store, config, true ) ) ;
470+ ann_out += & format ! (
471+ "{}" ,
472+ & output_selector( selector, store, config, true , need_second_pass, second_pass)
473+ ) ;
410474 if i != selectors. len ( ) - 1 {
411475 ann_out += "," ;
412476 }
@@ -416,7 +480,10 @@ fn output_selector(
416480 Selector :: MultiSelector ( selectors) => {
417481 ann_out += "{ \" type\" : \" http://www.w3.org/ns/oa#Independents\" , \" items\" : [" ;
418482 for ( i, selector) in selectors. iter ( ) . enumerate ( ) {
419- ann_out += & format ! ( "{}" , & output_selector( selector, store, config, true ) ) ;
483+ ann_out += & format ! (
484+ "{}" ,
485+ & output_selector( selector, store, config, true , need_second_pass, second_pass)
486+ ) ;
420487 if i != selectors. len ( ) - 1 {
421488 ann_out += "," ;
422489 }
@@ -426,7 +493,10 @@ fn output_selector(
426493 Selector :: DirectionalSelector ( selectors) => {
427494 ann_out += "{ \" type\" : \" http://www.w3.org/ns/oa#List\" , \" items\" : [" ;
428495 for ( i, selector) in selectors. iter ( ) . enumerate ( ) {
429- ann_out += & format ! ( "{}" , & output_selector( selector, store, config, true ) ) ;
496+ ann_out += & format ! (
497+ "{}" ,
498+ & output_selector( selector, store, config, true , need_second_pass, second_pass)
499+ ) ;
430500 if i != selectors. len ( ) - 1 {
431501 ann_out += "," ;
432502 }
@@ -444,7 +514,17 @@ fn output_selector(
444514 if nested {
445515 let subselectors: Vec < _ > = selector. iter ( store, false ) . collect ( ) ;
446516 for ( i, subselector) in subselectors. iter ( ) . enumerate ( ) {
447- ann_out += & format ! ( "{}" , & output_selector( & subselector, store, config, false ) ) ;
517+ ann_out += & format ! (
518+ "{}" ,
519+ & output_selector(
520+ & subselector,
521+ store,
522+ config,
523+ true ,
524+ need_second_pass,
525+ second_pass
526+ )
527+ ) ;
448528 if i != subselectors. len ( ) - 1 {
449529 ann_out += "," ;
450530 }
0 commit comments