parseTree method

InlineSpan parseTree(
  1. RenderContext context,
  2. StyledElement tree
)

parseTree converts a tree of StyledElements to an InlineSpan tree.

parseTree is responsible for handling the customRender parameter and deciding what different Style.display options look like as Widgets.

Implementation

InlineSpan parseTree(RenderContext context, StyledElement tree) {
  // Merge this element's style into the context so that children
  // inherit the correct style
  RenderContext newContext = RenderContext(
    buildContext: context.buildContext,
    parser: this,
    tree: tree,
    style: context.style.copyOnlyInherited(tree.style),
  );

  if (customRender.containsKey(tree.name)) {
    final render = customRender[tree.name]!.call(
      newContext,
      ContainerSpan(
        key: AnchorKey.of(key, tree),
        newContext: newContext,
        style: tree.style,
        shrinkWrap: context.parser.shrinkWrap,
        children: tree.children.map((tree) => parseTree(newContext, tree)).toList(),
      ),
    );
    if (render != null) {
      assert(render is InlineSpan || render is Widget);
      return render is InlineSpan
          ? render
          : WidgetSpan(
              child: ContainerSpan(
                key: AnchorKey.of(key, tree),
                newContext: newContext,
                style: tree.style,
                shrinkWrap: context.parser.shrinkWrap,
                child: render,
              ),
            );
    }
  }

  //Return the correct InlineSpan based on the element type.
  if (tree.style.display == Display.BLOCK &&
      (tree.children.isNotEmpty || tree.element?.localName == "hr")) {
    if (newContext.parser.selectable) {
      return TextSpan(
        style: newContext.style.generateTextStyle(),
        children: tree.children
            .expandIndexed((i, childTree) => [
          if (childTree.style.display == Display.BLOCK &&
              i > 0 &&
              tree.children[i - 1] is ReplacedElement)
            TextSpan(text: "\n"),
          parseTree(newContext, childTree),
          if (i != tree.children.length - 1 &&
              childTree.style.display == Display.BLOCK &&
              childTree.element?.localName != "html" &&
              childTree.element?.localName != "body")
            TextSpan(text: "\n"),
        ])
            .toList(),
      );
    }
    return WidgetSpan(
      child: ContainerSpan(
        key: AnchorKey.of(key, tree),
        newContext: newContext,
        style: tree.style,
        shrinkWrap: context.parser.shrinkWrap,
        children: tree.children
            .expandIndexed((i, childTree) => [
                  if (shrinkWrap &&
                      childTree.style.display == Display.BLOCK &&
                      i > 0 &&
                      tree.children[i - 1] is ReplacedElement)
                    TextSpan(text: "\n"),
                  parseTree(newContext, childTree),
                  if (shrinkWrap &&
                      i != tree.children.length - 1 &&
                      childTree.style.display == Display.BLOCK &&
                      childTree.element?.localName != "html" &&
                      childTree.element?.localName != "body")
                    TextSpan(text: "\n"),
                ])
            .toList(),
      ),
    );
  } else if (tree.style.display == Display.LIST_ITEM) {
    List<InlineSpan> getChildren(StyledElement tree) {
      List<InlineSpan> children = tree.children.map((tree) => parseTree(newContext, tree)).toList();
      if (tree.style.listStylePosition == ListStylePosition.INSIDE) {
        final tabSpan = WidgetSpan(
          child: Text("\t", textAlign: TextAlign.right, style: TextStyle(fontWeight: FontWeight.w400)),
        );
        children.insert(0, tabSpan);
      }
      return children;
    }

    return WidgetSpan(
      child: ContainerSpan(
        key: AnchorKey.of(key, tree),
        newContext: newContext,
        style: tree.style,
        shrinkWrap: context.parser.shrinkWrap,
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.start,
          mainAxisSize: MainAxisSize.min,
          textDirection: tree.style.direction,
          children: [
            tree.style.listStylePosition == ListStylePosition.OUTSIDE ?
            Padding(
              padding: tree.style.padding?.nonNegative ?? EdgeInsets.only(left: tree.style.direction != TextDirection.rtl ? 10.0 : 0.0, right: tree.style.direction == TextDirection.rtl ? 10.0 : 0.0),
              child: newContext.style.markerContent
            ) : Container(height: 0, width: 0),
            Text("\t", textAlign: TextAlign.right, style: TextStyle(fontWeight: FontWeight.w400)),
            Expanded(
                child: Padding(
                    padding: tree.style.listStylePosition == ListStylePosition.INSIDE ?
                      EdgeInsets.only(left: tree.style.direction != TextDirection.rtl ? 10.0 : 0.0, right: tree.style.direction == TextDirection.rtl ? 10.0 : 0.0) : EdgeInsets.zero,
                    child: StyledText(
                      textSpan: TextSpan(
                        children: getChildren(tree)..insertAll(0, tree.style.listStylePosition == ListStylePosition.INSIDE ?
                          [
                            WidgetSpan(alignment: PlaceholderAlignment.middle, child: newContext.style.markerContent ?? Container(height: 0, width: 0))
                          ] : []),
                        style: newContext.style.generateTextStyle(),
                      ),
                      style: newContext.style,
                      renderContext: context,
                    )
                )
            )
          ],
        ),
      ),
    );
  } else if (tree is ReplacedElement) {
    if (tree is TextContentElement) {
      return TextSpan(text: tree.text?.transformed(tree.style.textTransform));
    } else {
      return WidgetSpan(
        alignment: tree.alignment,
        baseline: TextBaseline.alphabetic,
        child: tree.toWidget(newContext)!,
      );
    }
  } else if (tree is InteractableElement) {
    InlineSpan addTaps(InlineSpan childSpan, TextStyle childStyle) {
      if (childSpan is TextSpan) {
        return TextSpan(
          mouseCursor: SystemMouseCursors.click,
          text: childSpan.text,
          children: childSpan.children
              ?.map((e) => addTaps(e, childStyle.merge(childSpan.style)))
              .toList(),
          style: newContext.style.generateTextStyle().merge(
              childSpan.style == null
                  ? childStyle
                  : childStyle.merge(childSpan.style)),
          semanticsLabel: childSpan.semanticsLabel,
          recognizer: TapGestureRecognizer()
            ..onTap =
                _onAnchorTap != null ? () => _onAnchorTap!(tree.href, context, tree.attributes, tree.element) : null,
        );
      } else {
        return WidgetSpan(
          child: MouseRegion(
            key: AnchorKey.of(key, tree),
            cursor: SystemMouseCursors.click,
            child: MultipleTapGestureDetector(
              onTap: _onAnchorTap != null
              ? () => _onAnchorTap!(tree.href, context, tree.attributes, tree.element)
                  : null,
              child: GestureDetector(
                key: AnchorKey.of(key, tree),
                onTap: _onAnchorTap != null
                ? () => _onAnchorTap!(tree.href, context, tree.attributes, tree.element)
                    : null,
                child: (childSpan as WidgetSpan).child,
              ),
            ),
          ),
        );
      }
    }

    return TextSpan(
      mouseCursor: SystemMouseCursors.click,
      children: tree.children
              .map((tree) => parseTree(newContext, tree))
              .map((childSpan) {
        return addTaps(childSpan,
          newContext.style.generateTextStyle().merge(childSpan.style));
        }).toList(),
    );
  } else if (tree is LayoutElement) {
    return WidgetSpan(
      child: tree.toWidget(context)!,
    );
  } else if (tree.style.verticalAlign != null &&
      tree.style.verticalAlign != VerticalAlign.BASELINE) {
    late double verticalOffset;
    switch (tree.style.verticalAlign) {
      case VerticalAlign.SUB:
        verticalOffset = tree.style.fontSize!.size! / 2.5;
        break;
      case VerticalAlign.SUPER:
        verticalOffset = tree.style.fontSize!.size! / -2.5;
        break;
      default:
        break;
    }
    //Requires special layout features not available in the TextStyle API.
    return WidgetSpan(
      child: Transform.translate(
        key: AnchorKey.of(key, tree),
        offset: Offset(0, verticalOffset),
        child: StyledText(
          textSpan: TextSpan(
            style: newContext.style.generateTextStyle(),
            children: tree.children.map((tree) => parseTree(newContext, tree)).toList(),
          ),
          style: newContext.style,
          renderContext: newContext,
        ),
      ),
    );
  } else {
    ///[tree] is an inline element.
    return TextSpan(
      style: newContext.style.generateTextStyle(),
      children: tree.children
          .expand((tree) => [
                parseTree(newContext, tree),
                if (tree.style.display == Display.BLOCK &&
                    tree.element?.localName != "html" &&
                    tree.element?.localName != "body")
                  TextSpan(text: "\n"),
              ])
          .toList(),
    );
  }
}