<commit_before>var dom = require('./dom');

module.exports = Doc;

function Doc(formats, options) {
  this.formats = formats;
  this.blockTag = options && options.blockTag || 'div';
  this.inlineTag = options && options.inlineTag || 'span';
  this.root = document.createElement('div');
  this.newLine();
}

Doc.prototype.write = function(op) {
  if (op.insert === null || op.insert === undefined) {
    throw new Error('Cannot convert delta with non-insert operations');
  }

  var attrs = op.attributes || {};
  var text = (typeof op.insert === 'string') ?
    op.insert.replace(/\r\n?/g, '\n') : '!';
  var lineTexts = text.split('\n');

  // TODO: each loop iteration may be async
  lineTexts.forEach(function(lineText, i) {
    if (lineText.length) {
      this.appendToLine(lineText, attrs);
    }

    if (i < lineTexts.length - 1) {
      this.formatLine(attrs);
      this.newLine();
    }
  }.bind(this));

  return this;
};

Doc.prototype.getHTML = function() {
  // HACK: fix this
  return this.root.innerHTML.replace(/<([a-z\-]+)><\/\1>$/, '');
};

Doc.prototype.newLine = function() {
  var node = document.createElement(this.blockTag);
  this.root.appendChild(node);
  this.line = node;
};

Doc.prototype.appendToLine = function(text, attrs) {
  var node = document.createTextNode(text);

  this.line.appendChild(node);

  // TODO: loop through in priority order
  // TODO: each format function returns a promise
  Object.keys(attrs).forEach(function(name) {
    var format = this.formats[name];
    if (format && format.type !== 'line') {
      node = this.applyFormat(node, format, attrs[name]);
    }
  }.bind(this));

  // TODO: optimize line?
};

Doc.prototype.applyFormat = function(node, format, value) {
  if (typeof format === 'function') {
    return format(node, value);
  }

  if (format.parentTag) {
    var parentNode = document.createElement(format.parentTag);
    dom(node).wrap(parentNode);
    if (node.parentNode.previousSibling && node.parentNode.tagName === node.parentNode.previousSibling.tagName) {
      dom(node.parentNode.previousSibling).merge(node.parentNode);
    }
    if (node.parentNode.nextSibling && node.parentNode.tagName === node.parentNode.nextSibling.tagName) {
      dom(node.parentNode).merge(node.parentNode.nextSibling);
    }
  }

  if (format.tag) {
    if (format.type === 'line') {
      node = dom(node).switchTag(format.tag).get();
    } else {
      var newNode = document.createElement(format.tag);
      if (dom.VOID_TAGS[format.tag]) {
        if (node.parentNode) {
          dom(node).replaceWith(newNode);
        }
      } else {
        dom(node).wrap(newNode);
      }<commit_msg>Allow custom formats to change their parent<commit_after>var dom = require('./dom');

module.exports = Doc;

function Doc(formats, options) {
  this.formats = formats;
  this.blockTag = options && options.blockTag || 'div';
  this.inlineTag = options && options.inlineTag || 'span';
  this.root = document.createElement('div');
  this.newLine();
}

Doc.prototype.write = function(op) {
  if (op.insert === null || op.insert === undefined) {
    throw new Error('Cannot convert delta with non-insert operations');
  }

  var attrs = op.attributes || {};
  var text = (typeof op.insert === 'string') ?
    op.insert.replace(/\r\n?/g, '\n') : '!';
  var lineTexts = text.split('\n');

  // TODO: each loop iteration may be async
  lineTexts.forEach(function(lineText, i) {
    if (lineText.length) {
      this.appendToLine(lineText, attrs);
    }

    if (i < lineTexts.length - 1) {
      this.formatLine(attrs);
      this.newLine();
    }
  }.bind(this));

  return this;
};

Doc.prototype.getHTML = function() {
  // HACK: fix this
  return this.root.innerHTML.replace(/<([a-z\-]+)><\/\1>$/, '');
};

Doc.prototype.newLine = function() {
  var node = document.createElement(this.blockTag);
  this.root.appendChild(node);
  this.line = node;
};

Doc.prototype.appendToLine = function(text, attrs) {
  var node = document.createTextNode(text);

  this.line.appendChild(node);

  // TODO: loop through in priority order
  // TODO: each format function returns a promise
  Object.keys(attrs).forEach(function(name) {
    var format = this.formats[name];
    if (format && format.type !== 'line') {
      node = this.applyFormat(node, format, attrs[name]);
    }
  }.bind(this));

  // TODO: optimize line?
  this.line = this.root.lastChild;
};

Doc.prototype.applyFormat = function(node, format, value) {
  if (typeof format === 'function') {
    return format(node, value);
  }

  if (format.parentTag) {
    var parentNode = document.createElement(format.parentTag);
    dom(node).wrap(parentNode);
    if (node.parentNode.previousSibling && node.parentNode.tagName === node.parentNode.previousSibling.tagName) {
      dom(node.parentNode.previousSibling).merge(node.parentNode);
    }
    if (node.parentNode.nextSibling && node.parentNode.tagName === node.parentNode.nextSibling.tagName) {
      dom(node.parentNode).merge(node.parentNode.nextSibling);
    }
  }

  if (format.tag) {
    if (format.type === 'line') {
      node = dom(node).switchTag(format.tag).get();
    } else {
      var newNode = document.createElement(format.tag);
      if (dom.VOID_TAGS[format.tag]) {
        if (node.parentNode) {
          dom(node).replaceWith(newNode);
        }
      } else {
        dom(node).wrap(newNode);
      }
{'fraction_non_alphanumeric': 0.16551852541426176, 'fraction_numerical': 0.0007447402718301992, 'mean_word_length': 3.266878474980143, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3649324', 'n_tokens_mistral': 1732, 'n_tokens_neox': 1748, 'n_words': 342}