Translate metadata to field content

Mark Waddingham mark at livecode.com
Fri Feb 21 03:51:41 EST 2020


On 2020-02-21 00:29, J. Landman Gay via use-livecode wrote:
> So glad you chimed in, Mark. This is pretty impressive. I'll need to
> use the "for each element" structure because my tags are not unique,
> but it still is much faster. When clicking a tag at the top of the
> document that links to the last anchor at the bottom of the text, I
> get a timing of about 25ms. If I omit the timing for loading the
> htmltext and the selection of the text at the end of the handler it
> brings the timing to almost 0. The test text is long, but not nearly
> as long as Bernd's sample.

Glad I could help - although to be fair, all I did was optimize what
Bernd (and Richard) had already proposed.

One thing I did notice through testing was that the actual styled 
content
makes a great deal of difference to performance. I also tried against 
the
DataGrid behavior (replicated several times), and then also against some
styled 'Lorem Ipsum' (https://loripsum.net/) of about the same length 
(around
8Mb of htmlText, with the anchor being search for on the last word). The
difference is that the DG has many more style runs (unsurprisingly) and
almost all are single words. So timings need to be taken against a
representative sample of the data you are actually working with.

> I need to select the entire range of text covered by the metadata
> span, not just a single word. I've got that working, but since we're
> on a roll here, I wonder if there's a more optimal way to do it.

I did wonder if that would be the case...

> I'm using chars instead of codepoints because when I tried it, they
> both gave the same number. Should I change that?

Both characters and codepoints run the risk of requiring a linear scan 
of
the string to calculate the length - strictly speaking his will occur if
the engine is not sure whether character / codepoint have a 1-1 map to
codeunits (for example if your string has Unicode chars and it hasn't
analysed it). Therefore you should definitely use codeunits.

> Also, I had to add 3 to tStartChar to get the right starting point but
> I can't figure out why. Otherwise it selects the last character before
> the metadata span as the starting point.

Was the anchor in the third paragraph by any chance?

The styledText representation makes the paragraph separator (return 
char)
implicit (as it is in the field object internally) - so you need to bump
the tTotalChars by one before the final end repeat to account for that 
(as the
codeunit ranges the field uses *include* the implicit return char)

So I couldn't help but fettle with this a little more. You mention that 
your
'anchors' are not unique in a document. This raises the question of what
happens if there is more than one match...

This handler finds all occurrences of a given anchor in the text. As we 
are
searching for all of them, it can use repeat for each key iteration in 
both
loops:

function FindAllAnchors pStyledText, pAnchor
    /* Return-delimited list of results, each line is of the form:
    *     start,finish,line
    * Each of these corresponds to a chunk of the form:
    *      CODEUNIT start TO finish OF LINE line OF field
    */
    local tResults

    /* Iterate over the lines of the text in arbitrary order - the order 
doesn't
    * matter as we keep the reference to the line any match is in. */
    repeat for each key tLineIndex in pStyledText
       /* Fetch the runs in the line, so we don't have to keep looking it 
up */
       local tRuns
       put pStyledText[tLineIndex]["runs"] into tRuns

       /* Iterate over the runs in arbitrary order - assuming that the 
number
       * of potentially matching runs is miniscule compared to the number 
of
       * non-matching runs, it is faster to iterate in hash-order. */
       repeat for each key tRunIndex in tRuns
          /* If we find a match, work out its offset in the line */
          if tRuns[tRunIndex]["metadata"] is pAnchor then
             /* Calculate the number of codeunits before this run */
             local tCodeunitCount
             put 0 into tCodeunitCount
             repeat with tPreviousRunIndex = 1 to tRunIndex - 1
                add the number of codeunits in 
tRuns[tPreviousRunIndex]["text"] to tCodeunitCount
             end repeat

             /* Append the result to the results list. */
             put tCodeunitCount + 1, \
                   tCodeunitCount + the number of codeunits in 
tRuns[tRunIndex]["text"], \
                   tLineIndex & \
                   return after tResults
          end if
       end repeat
    end repeat

    /* We want the results sorted first by line index, then by starting 
codeunit
    * within the line (so we get a top-to-bottom, left-to-right order). 
As the
    * 'sort' command is stable, we can do this by first sorting by the 
secondary
    * factor (codeunit start), then sorting again by the primary factor 
(line
    * index). */
    sort lines of tResults ascending numeric by item 1 of each
    sort lines of tResults ascending numeric by item 3 of each

    /* Return the set of results. */
    return tResults
end FindAllAnchors

Testing this on 8Mb of styled Lorem Ipsum text, with the same anchor at:
   word 1
   word 1000
   the middle word
   word -1000
   word -1

Then this handler takes slightly less time then searching for a single 
anchor
at word -1 of the field using 'repeat with' loops.

Whether this is helpful or not depends if you need to 'do something' 
when there
is more than one matching anchor in a document :)

Warmest Regards,

Mark.

-- 
Mark Waddingham ~ mark at livecode.com ~ http://www.livecode.com/
LiveCode: Everyone can create apps




More information about the use-livecode mailing list