@@ -31,6 +31,52 @@ type Block struct {
3131 // finds the first occurrence rather than the one that matched. A sentence
3232 // repeated in a document is the common case.
3333 Offset int
34+
35+ // Runs maps pieces of Text back to the source they were read from, for a
36+ // block that has no Offset of its own.
37+ //
38+ // Extraction drops inline markup, so `has <b>has</b>` arrives as `has has`
39+ // and the block is nowhere in Context as a whole. Its pieces are, though,
40+ // and each was placed as it was read. See #502.
41+ Runs []Run
42+ }
43+
44+ // A Run is a piece of a block's text and where it came from: At indexes the
45+ // block, Src the source, and N is how long both are.
46+ type Run struct {
47+ At , Src , N int
48+ }
49+
50+ // SourceOffset returns where index i of Text sits in Context, or -1 if that
51+ // part of the block was never mapped.
52+ func (b * Block ) SourceOffset (i int ) int {
53+ if b .Offset >= 0 {
54+ return b .Offset + i
55+ }
56+ for _ , r := range b .Runs {
57+ if i >= r .At && i < r .At + r .N {
58+ return r .Src + (i - r .At )
59+ }
60+ }
61+ return - 1
62+ }
63+
64+ // withRuns returns a copy of b carrying the runs of `parent` that fall within
65+ // [start, start+len(b.Text)), rebased onto b's own text.
66+ func (b Block ) withRuns (parent []Run , start int ) Block {
67+ if start < 0 || len (parent ) == 0 {
68+ return b
69+ }
70+
71+ end := start + len (b .Text )
72+ for _ , r := range parent {
73+ lo , hi := max (r .At , start ), min (r .At + r .N , end )
74+ if lo >= hi {
75+ continue
76+ }
77+ b .Runs = append (b .Runs , Run {At : lo - start , Src : r .Src + (lo - r .At ), N : hi - lo })
78+ }
79+ return b
3480}
3581
3682// NewBlock makes a new Block with prepared text and a Selector.
@@ -149,22 +195,27 @@ func (n *Info) Compute(block *Block, split bool) ([]Block, error) {
149195// cursor advances past each piece so that repeated text resolves to successive
150196// occurrences rather than always the first -- which is the whole point of
151197// tracking offsets instead of searching for them later.
152- func offsetOf (blk * Block , base int , piece string , cursor * int ) int {
153- if base < 0 || * cursor > len (blk .Text ) {
154- return - 1
198+ func offsetOf (blk * Block , base int , piece string , cursor * int ) ( int , int ) {
199+ if * cursor > len (blk .Text ) {
200+ return - 1 , - 1
155201 }
156202
157203 i := strings .Index (blk .Text [* cursor :], piece )
158204 if i < 0 {
159205 // A segmenter that rewrites text (a remote endpoint, say) can return
160206 // something that is not a substring of the input.
161- return - 1
207+ return - 1 , - 1
162208 }
163209
164210 start := * cursor + i
165211 * cursor = start + len (piece )
166212
167- return base + start
213+ if base < 0 {
214+ // The piece is placed within the block, which is itself unplaced; the
215+ // runs it inherits are what will locate it.
216+ return start , - 1
217+ }
218+ return start , base + start
168219}
169220
170221func (n * Info ) doNLP (blk * Block , seg segmenter , split bool ) ([]Block , error ) {
@@ -178,7 +229,8 @@ func (n *Info) doNLP(blk *Block, seg segmenter, split bool) ([]Block, error) {
178229 cursor := 0
179230 for _ , p := range strings .SplitAfter (blk .Text , "\n \n " ) {
180231 b := NewLinedBlock (ctx , p , "paragraph." + blk .Scope , idx )
181- blks = append (blks , b .at (offsetOf (blk , base , p , & cursor )))
232+ start , off := offsetOf (blk , base , p , & cursor )
233+ blks = append (blks , b .at (off ).withRuns (blk .Runs , start ))
182234 }
183235 }
184236
@@ -190,14 +242,15 @@ func (n *Info) doNLP(blk *Block, seg segmenter, split bool) ([]Block, error) {
190242 continue
191243 }
192244 b := NewLinedBlock (ctx , s , "sentence." + blk .Scope , idx )
193- blks = append (blks , b .at (offsetOf (blk , base , s , & cursor )))
245+ start , off := offsetOf (blk , base , s , & cursor )
246+ blks = append (blks , b .at (off ).withRuns (blk .Runs , start ))
194247 }
195248 }
196249
197250 // The block itself, which is what most rules run against. It needs the
198251 // offset as much as its children do.
199252 blks = append (
200- blks , NewLinedBlock (ctx , blk .Text , blk .Scope , idx ).at (base ))
253+ blks , NewLinedBlock (ctx , blk .Text , blk .Scope , idx ).at (base ). withRuns ( blk . Runs , 0 ) )
201254
202255 return blks , nil
203256}
0 commit comments