You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@annotator.apache.org by ge...@apache.org on 2020/11/20 12:26:24 UTC

[incubator-annotator] 01/19: Fix normalizeRange edge case

This is an automated email from the ASF dual-hosted git repository.

gerben pushed a commit to branch import-dom-seek
in repository https://gitbox.apache.org/repos/asf/incubator-annotator.git

commit b972ddc5afee7bd021c066c79e7b17b6f215625f
Author: Gerben <ge...@treora.com>
AuthorDate: Mon Nov 16 20:38:01 2020 +0100

    Fix normalizeRange edge case
    
    A collapsed range add the start of the scope would end up, after being
    normalised, at the end of the text node just before the scope; which
    would not be shown by the chunker. The only solution I see is to tell
    the normalisation function about the scope.
---
 packages/dom/src/chunker.ts         |  4 +---
 packages/dom/src/normalize-range.ts | 22 +++++++++++++++++-----
 2 files changed, 18 insertions(+), 8 deletions(-)

diff --git a/packages/dom/src/chunker.ts b/packages/dom/src/chunker.ts
index 574b627..e636509 100644
--- a/packages/dom/src/chunker.ts
+++ b/packages/dom/src/chunker.ts
@@ -106,9 +106,7 @@ export class TextNodeChunker implements Chunker<PartialTextNode> {
   }
 
   rangeToChunkRange(range: Range): ChunkRange<PartialTextNode> {
-    const textRange = normalizeRange(range);
-    // FIXME: normalizeRange can mess up: a collapsed range at the very end of
-    // the chunker’s scope might move to the next text node outside the scope.
+    const textRange = normalizeRange(range, this.scope);
 
     const startChunk = this.nodeToChunk(textRange.startContainer);
     const startIndex = textRange.startOffset - startChunk.startOffset;
diff --git a/packages/dom/src/normalize-range.ts b/packages/dom/src/normalize-range.ts
index 0fece41..a4a758e 100644
--- a/packages/dom/src/normalize-range.ts
+++ b/packages/dom/src/normalize-range.ts
@@ -50,14 +50,24 @@ export interface TextRange extends Range {
 //
 // If there is no text between the start and end, they thus collapse onto one a
 // single position; and if there are multiple equivalent positions, it takes the
-// first one.
+// first one; or, if scope is passed, the first equivalent falling within scope.
 //
 // Note that if the given range does not contain non-empty text nodes, it will
-// end up pointing at a text node outside of it (after it if possible, else
-// before). If the document does not contain any text nodes, an error is thrown.
-export function normalizeRange(range: Range): TextRange {
+// end up pointing at a text node outside of it (before it if possible, else
+// after). If the document does not contain any text nodes, an error is thrown.
+export function normalizeRange(range: Range, scope?: Range): TextRange {
   const document = ownerDocument(range);
-  const walker = document.createTreeWalker(document, NodeFilter.SHOW_TEXT);
+  const walker = document.createTreeWalker(
+    document,
+    NodeFilter.SHOW_TEXT,
+    {
+      acceptNode(node: Text) {
+        return (!scope || scope.intersectsNode(node))
+          ? NodeFilter.FILTER_ACCEPT
+          : NodeFilter.FILTER_REJECT;
+      },
+    },
+  );
 
   let [ startContainer, startOffset ] = snapBoundaryPointToTextNode(range.startContainer, range.startOffset);
 
@@ -69,6 +79,7 @@ export function normalizeRange(range: Range): TextRange {
     startOffset = 0;
   }
 
+  // Set the range’s start; note this might move its end too.
   range.setStart(startContainer, startOffset);
 
   let [ endContainer, endOffset ] = snapBoundaryPointToTextNode(range.endContainer, range.endOffset);
@@ -81,6 +92,7 @@ export function normalizeRange(range: Range): TextRange {
     endOffset = endContainer.length;
   }
 
+  // Set the range’s end; note this might move its start too.
   range.setEnd(endContainer, endOffset);
 
   return range as TextRange;