|
|
|
|
| |
| |
| |
| |
| interface Span { |
| type: string; |
| start: number; |
| end: number; |
| } |
|
|
| interface SpanTag { |
| span: Span; |
| tag: "start" | "end"; |
| } |
|
|
| class Displacy { |
| static sortSpans(spans: Span[]) { |
| spans.sort((a, b) => { |
| if (a.start === b.start) { |
| return b.end - a.end; |
| } |
| return a.start - b.start; |
| }); |
| |
| |
| spans.forEach((s, i) => { |
| if (i < spans.length - 1) { |
| const sNext = spans[i+1]; |
| if (s.start < sNext.start && s.end > sNext.start) { |
| console.log("ERROR", "Spans: strict overlapping"); |
| } |
| } |
| }); |
| } |
| |
| |
| |
| |
| |
| |
| |
| static render(text: string, spans: Span[]): string { |
| this.sortSpans(spans); |
| |
| const tags: { [index: number]: SpanTag[] } = {}; |
| const __addTag = (i: number, s: Span, tag: "start" | "end") => { |
| if (Array.isArray(tags[i])) { |
| tags[i].push({ span: s, tag: tag }); |
| } else { |
| tags[i] = [{ span: s, tag: tag }]; |
| } |
| }; |
| for (const s of spans) { |
| __addTag(s.start, s, "start"); |
| __addTag(s.end, s, "end"); |
| } |
| |
| |
| let out = { |
| __content: "", |
| append(s: string) { |
| this.__content += s; |
| } |
| }; |
| let offset = 0; |
| |
| const indexes = Object.keys(tags).map(k => parseInt(k, 10)).sort((a, b) => a - b); |
| for (const i of indexes) { |
| const spanTags = tags[i]; |
| |
| if (i > offset) { |
| out.append(text.slice(offset, i)); |
| } |
| |
| offset = i; |
| |
| for (const sT of spanTags) { |
| if (sT.tag === "start") { |
| out.append(`<mark data-entity="${ sT.span.type.toLowerCase() }" data-index="${ (<any>sT.span).index }">`); |
| const singleScore = (<any>sT.span).singleScore; |
| if (singleScore) { |
| out.append(`<span class="single-score">${ singleScore.toFixed(3) }</span>`); |
| } |
| } else { |
| out.append(`</mark>`); |
| } |
| } |
| } |
| |
| out.append(text.slice(offset, text.length)); |
| return out.__content; |
| } |
| } |
|
|