[annotator-dev] RFC: DOM friendly annotation target selector

Mitar mmitar at gmail.com
Mon Jul 28 13:11:26 UTC 2014


Hi!

While working on PeerLibrary, I noticed that current three annotation
target selectors available (we are using Hypothes.is' Annotator fork)
work very well all together, but that RangeSelector is a bit too
fragile because it uses xpath, which is useful for non-changing XML,
but not really for web. In our case we even control how we render DOM
but we still had issues. For example, we had such DOM structure:

div.page > div.text-layer > div.text-layer-segment[12]

The problem is if we made a text-layer sibling element before
text-layer in DOM, xpath did not work anymore (because it uses
relative count of same-type siblings).

First idea I had was to use CSS selectors instead of xpath, as CSS
classes might change less often (and it would be easier for us to make
sure we do not change them), but then I discovered what is the main
issue with RangeSelector. It stores only a subset of information about
DOM path and it stores this information when annotation is created.
This is a problematic approach because at that time you do not know
which information you really need later on to be able to anchor
annotation back.

So I made an implementation called DOMRangeSelector
(https://github.com/peerlibrary/peerlibrary/blob/development/client/lib/annotator/domanchors.coffee)
which stores all reasonable information about DOM path: relative and
absolute indexes based on siblings, CSS classes and DOM ids. The idea
is that we store various aspects of DOM path so that we can then use
various different strategies to anchor annotation back. The simplest
strategy, the one used in the code above, is to simply generate an
xpath and use that. But this is just a special case. Now, with this
data stored for each annotation, we can start designing smarter
strategies. You can try first xpath, if that fails, you can maybe
construct a CSS selector based on CSS class names, or maybe use IDs if
you think that is changing less. The main point is that we have
annotation target information available to try this various strategies
and that we do not throw them away.

Example of data is then:

{
  "type" : "DOMRangeSelector",
  "startContainer" : [
    {
      "t" : "div",
      "r" : 0,
      "a" : 0,
      "c" : "display-page",
      "i" : "display-page-1"
    },
    {
      "t" : "div",
      "r" : 1,
      "a" : 3,
      "c" : "text-layer"
    },
    {
      "t" : "div",
      "r" : 10,
      "a" : 10,
      "c" : "text-layer-segment"
    }
  ],
  "startOffset" : 20,
  "endContainer" : [
    {
      "t" : "div",
      "r" : 0,
      "a" : 0,
      "c" : "display-page",
      "i" : "display-page-1"
    },
    {
      "t" : "div",
      "r" : 1,
      "a" : 3,
      "c" : "text-layer"
    },
    {
      "t" : "div",
      "r" : 14,
      "a" : 14,
      "c" : "text-layer-segment"
    }
  ],
  "endOffset" : 23
}

Field names are short so that they do not consume much space, but they are:

- t: tag name
- r: relative index among siblings of same tag
- a: absolute index among siblings of any tag
- c: CSS classes
- i: element ID

I would propose that this or something like this is added to standard
set of stored annotation selectors. It allows for less fragile quicker
strategies to be used (before fallback to strategies which scan
content).


Mitar

-- 
http://mitar.tnode.com/
https://twitter.com/mitar_m



More information about the annotator-dev mailing list