constructor(
    blockTypeMap: BlockTypeMap,
    disambiguate: (string, ?string) => ?string,
  ): void {
    this.clear();
    this.blockTypeMap = blockTypeMap;
    this.disambiguate = disambiguate;
  }

  /**
   * Clear the internal state of the ContentBlocksBuilder
   */
  clear(): void {
    this.characterList = List();
    this.blockConfigs = [];
    this.currentBlockType = 'unstyled';
    this.currentDepth = 0;
    this.currentEntity = null;
    this.currentText = '';
    this.contentState = ContentState.createFromText('');
    this.wrapper = null;
    this.contentBlocks = [];
  }

  /**
   * Add an HTMLElement to the ContentBlocksBuilder
   */
  addDOMNode(node: Node): ContentBlocksBuilder {
    this.contentBlocks = [];
    this.currentDepth = 0;
    // Converts the HTML node to block config
    this.blockConfigs.push(...this._toBlockConfigs([node], OrderedSet()));

    // There might be some left over text in the builder's
    // internal state, if so make a ContentBlock out of it.
    this._trimCurrentText();
    if (this.currentText !== '') {
      this.blockConfigs.push(this._makeBlockConfig());
    }

    // for chaining
    return this;
  }

  /**
   * Return the ContentBlocks and the EntityMap that corresponds
   * to the previously added HTML nodes.
   */
  getContentBlocks(): {
    contentBlocks: ?Array<BlockNodeRecord>,
    entityMap: EntityMap,
    ...
  } {
    if (this.contentBlocks.length === 0) {
      if (experimentalTreeDataSupport) {
        this._toContentBlocks(this.blockConfigs);
      } else {
        this._toFlatContentBlocks(this.blockConfigs);
      }
    }
    return {
      contentBlocks: this.contentBlocks,
      entityMap: this.contentState.getEntityMap(),
    };
  }

  // ***********************************WARNING******************************
  // The methods below this line are private - don't call them directly.

  /**
   * Generate a new ContentBlockConfig out of the current internal state
   * of the builder, then clears the internal state.
   */
  _makeBlockConfig(config: Object = {}): ContentBlockConfig {
    const key = config.key || generateRandomKey();
    const block = {
      key,
      type: this.currentBlockType,
      text: this.currentText,
      characterList: this.characterList,
      depth: this.currentDepth,
      parent: null,
      children: List<mixed>(),
      prevSibling: null,
      nextSibling: null,
      childConfigs: [],
      ...config,
    };
    this.characterList = List();
    this.currentBlockType = 'unstyled';
    this.currentText = '';
    return block;
  }

  /**
   * Converts an array of HTML elements to a multi-root tree of content
   * block configs. Some text content may be left in the builders internal
   * state to enable chaining sucessive calls.
   */
  _toBlockConfigs(
    nodes: Array<Node>,
    style: DraftInlineStyle,
  ): Array<ContentBlockConfig> {
    const blockConfigs = [];
    for (let i = 0; i < nodes.length; i++) {
      const node = nodes[i];
      const nodeName = node.nodeName.toLowerCase();

      if (nodeName === 'body' || isListNode(nodeName)) {
        // body, ol and ul are 'block' type nodes so create a block config
        // with the text accumulated so far (if any)
        this._trimCurrentText();
        if (this.currentText !== '') {
          blockConfigs.push(this._makeBlockConfig());
        }

        // body, ol and ul nodes are ignored, but their children are inlined in
        // the parent block config.
        const wasCurrentDepth = this.currentDepth;
        const wasWrapper = this.wrapper;
        if (isListNode(nodeName)) {
          this.wrapper = nodeName;
          if (isListNode(wasWrapper)) {
            this.currentDepth++;
          }
        }
        blockConfigs.push(
          ...this._toBlockConfigs(Array.from(node.childNodes), style),
        );
        this.currentDepth = wasCurrentDepth;
        this.wrapper = wasWrapper;
        continue;
      }

      let blockType = this.blockTypeMap.get(nodeName);
      if (blockType !== undefined) {
        // 'block' type node means we need to create a block config
        // with the text accumulated so far (if any)
        this._trimCurrentText();
        if (this.currentText !== '') {
          blockConfigs.push(this._makeBlockConfig());
        }

        const wasCurrentDepth = this.currentDepth;
        const wasWrapper = this.wrapper;
        this.wrapper = nodeName === 'pre' ? 'pre' : this.wrapper;

        if (typeof blockType !== 'string') {
          blockType =
            this.disambiguate(nodeName, this.wrapper) ||
            blockType[0] ||
            'unstyled';
        }

        if (
          !experimentalTreeDataSupport &&
          isHTMLElement(node) &&
          (blockType === 'unordered-list-item' ||
            blockType === 'ordered-list-item')
        ) {
          const htmlElement: HTMLElement = (node: any);
          this.currentDepth = getListItemDepth(htmlElement, this.currentDepth);
        }

        const key = generateRandomKey();
        const childConfigs = this._toBlockConfigs(
          Array.from(node.childNodes),
          style,
        );
        this._trimCurrentText();
        blockConfigs.push(
          this._makeBlockConfig({
            key,
            childConfigs,
            type: blockType,
          }),
        );

        this.currentDepth = wasCurrentDepth;
        this.wrapper = wasWrapper;
        continue;
      }

      if (nodeName === '#text') {
        this._addTextNode(node, style);
        continue;
      }

      if (nodeName === 'br') {
        this._addBreakNode(node, style);
        continue;
      }

      if (isValidImage(node)) {
        this._addImgNode(node, style);
        continue;
      }

      if (isValidAnchor(node)) {
        this._addAnchorNode(node, blockConfigs, style);
        continue;
      }

      let newStyle = style;
      if (HTMLTagToRawInlineStyleMap.has(nodeName)) {
        newStyle = newStyle.add(HTMLTagToRawInlineStyleMap.get(nodeName));
      }
      newStyle = styleFromNodeAttributes(node, newStyle);
      const inlineStyle = detectInlineStyle(node);
      if (inlineStyle != null) {
        newStyle = newStyle.add(inlineStyle);
      }
      blockConfigs.push(
        ...this._toBlockConfigs(Array.from(node.childNodes), newStyle),
      );
    }

    return blockConfigs;
  }
ContentBlocksBuilder builds a list of ContentBlocks and an Entity Map
out of one (or several) HTMLElement(s).

The algorithm has two passes: first it builds a tree of ContentBlockConfigs
by walking through the HTML nodes and their children, then it walks the
ContentBlockConfigs tree to compute parents/siblings and create
the actual ContentBlocks.

Typical usage is:
    new ContentBlocksBuilder()
       .addDOMNode(someHTMLNode)
       .addDOMNode(someOtherHTMLNode)
      .getContentBlocks();