5

I am working on a simple text screen / terminal emulator (similar to the JQuery terminal plugin, but without RPC stuff and with window functionality). Each line of the screen is a table row (a HTML string) and a print command can insert text with some attributes (e.g. foreground and background color). Each printed text is enclosed by a span with style attributes, for example:

<span style="color:#000000;background-color:#111111">A</span><span style="color:#222222;background-color:#333333>BC</span>

This works fine. Now I want to add a function which gives me all attributes of a character at a given screen position, in the above line the character at position 0 (A) has the color #000000. So I have to count characters which don't belong to the span tag and get the last preceding styles. My first rather error prone solution is:

function getAttr(line, position) {
    var result = {foreground:'', background:''},
        ch = '', i, j = -1, tag = false;

    // Count characters
    for (i = 0; i < line.length && j < position; i++) {
        ch = line.charAt(i);

        if (ch == '<') {
            tag = true;
        }

        if (ch == '>') {
            tag = false;
        }
        else if (!tag) {
            j++;
        }
    }

    i--;

    // Find styles
    while (i > 0 && line.charAt(i) != '<') {
        if (line.substr(i, 6) == 'color:') {
            result.foreground = line.substr(i + 6, 7); 
        }
        if (line.substr(i, 17) == 'background-color:') {
            result.background = line.substr(i + 17, 7); 
        }
        i--;
    }

    return result;  
}

Is there a simpler solution without counting characters (maybe JQuery or a regex)?

This is similar to Get parent element of a selected text but I don't need a selection, just a character index.

Community
  • 1
  • 1
Fabian_Z071
  • 275
  • 1
  • 11

2 Answers2

1

A possible way to handle building a data structure that allows you to index each line and get at the character and it's associated styles could be done using the following snippet for each line. This assumes the markup you're generating for the HTML shown above is fairly stable as well (you could account for variations in the regex if needed):

var tagre = /\<span style="([^"]+)"\>([A-Za-z]+)\<\/span\>/ig,
    s = '<span style="color:#000000;background-color:#111111">A</span><span style="color:#222222;background-color:#333333">BC</span>';

var matches,
    positions = [];

while (matches = tagre.exec(s)) {
    var len = matches[2].length,
        chars = matches[2],
        styles = {};

    matches[1].split(';').forEach(function(o) {
        var _s = o.split(':'),
            key = _s[0],
            val = _s[1];
        styles[key] = val;
    });

    for (var i=0; i < len; i++) {
        var char = chars[i];
        positions.push({ 'char': char, 'styles': styles });
    }
}
console.log("positions=%o", positions);

This would give you an array for each line that looks like the following:

[
  { char: 'A',
    styles: { 'background-color': '#111111', 'color': '#000000' }
  },
  { char: 'B',
    styles: { 'background-color': '#333333', 'color': '#222222' }
  },
  { char: 'C',
    styles: { 'background-color': '#333333', 'color': '#222222' }
  }
]

That would let you index into each line by integer character position and get the character at that position along with the associated styles as an object.

David Atchley
  • 1,204
  • 8
  • 10
0

I'd leave the task of parsing HTML to browser and just use the resulting DOM tree. Here's some pseudo-code you could use based on the idea of using the DOM tree:

function getAttr(lineNumber, position) {
  var lineDom = getDOMContainerForLineNumber(lineNumber);
  var current = 0; // the current character position

  function getAttrRec(elems, foreground, background) {
    for(elem in elems) {
      if(elem is <span>) {
        var res = getAttrRec(elem.children, elem.foregroundColor, elem.backgroundColor);
        if(res != null)
          return res;
      } else if(elem is TEXT) {
        current += elem.textLength;
        if(current >= position)
          return {foreground: foreground, background: background};
      }
    }
    return null;
  }

  return getAttrRec(lineDom.children, black, black);
}

This is just a very rough sketch though. Especially you'll have to watch out for whitespaces - they are stripped pretty intensively by browsers. So directly relying on the text length might not work in your case. Also you might want to handle the case that a span tag does not contain foreground or background color information.

Hauke P.
  • 2,695
  • 1
  • 20
  • 43