function chop_tag_or_text(element) {
  if (element.nodeName == "#text") {
    var text = element.nodeValue;

    // Strip off any ellipsis added by a previous run.
    if (text.substr(text.length - 3) == "...") text = text.substr(0, text.length - 3);
    
    if (text.length > 0) {
      var index = (text.lastIndexOf(' ') == -1 ? text.length - 1 : text.lastIndexOf(' '));
      
      text = text.substr(0, index);

      element.data = text + "...";

      return true;
    } else {
      return false;
    }
  }    

  if (element.lastChild) {
    if (!chop_tag_or_text(element.lastChild)) {
      element.removeChild(element.lastChild);
    }

    return true;
  } else {
    return false;
  }
}

function append_until_too_high(target, source, original, copy)
{
    for (var index = 0; index < source.childNodes.length; index++) {
      if (source.childNodes[index].nodeName == "#text") {
        var start = 0; var length = 20;
        var data = source.childNodes[index].nodeValue;

        var new_node = document.createTextNode('');
        target.appendChild(new_node);

        while (start < data.length) {
          new_node.nodeValue += data.substr(start, length);
          start += length;

          if (copy.offsetHeight > original.offsetHeight) break;
        }

        if (copy.offsetHeight > original.offsetHeight) break;
      } else {
        var next_node = source.childNodes[index].cloneNode(false);
        
        target.appendChild(next_node);
        if (copy.offsetHeight > original.offsetHeight) break;

        if (source.childNodes[index].childNodes.length > 0) {
          append_until_too_high(next_node, source.childNodes[index], original, copy);
          if (copy.offsetHeight > original.offsetHeight) break;
        }
      }
    }
}

Element.addMethods({
  ellipsis: function(element) {
    element = $(element);

    var t = element.cloneNode(false);
    Element.extend(t);

    t.setStyle({ 'width': element.getWidth(),
                 'height': 'auto',
                 'overflow': 'visible'
                });

    element.up().insertBefore(t, element);

    append_until_too_high(t, element, element, t);

    while (t.childNodes.length > 0 && t.offsetHeight > element.offsetHeight) {
      if (!chop_tag_or_text(t.lastChild)) {
        t.removeChild(t.lastChild);
      }
    }
    
    while (element.firstChild) {
      element.removeChild(element.firstChild);
    }
    while (t.firstChild) {
      element.appendChild(t.firstChild);
    }
    
    var parent = t.up();
    parent.removeChild(t);
  }
});
