parseTree method
parseTree converts a tree of StyledElement
s 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) {
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) {
InlineSpan tabSpan = WidgetSpan(child: Text("\t", textAlign: TextAlign.right));
List<InlineSpan> children = tree.children.map((tree) => parseTree(newContext, tree)).toList();
if (tree.style.listStylePosition == ListStylePosition.INSIDE) {
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 ??
EdgeInsets.only(
left: tree.style.direction != TextDirection.rtl ? 10.0 : 0.0,
right: tree.style.direction == TextDirection.rtl ? 10.0 : 0.0),
child: Text("${newContext.style.markerContent}",
textAlign: TextAlign.right, style: newContext.style.generateTextStyle()),
)
: Container(height: 0, width: 0),
Text("\t", textAlign: TextAlign.right),
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(
text: (tree.style.listStylePosition == ListStylePosition.INSIDE)
? '${newContext.style.markerContent}'
: null,
children: getChildren(tree),
style: newContext.style.generateTextStyle(),
),
style: newContext.style,
renderContext: context,
)))
],
),
),
);
} else if (tree is ReplacedElement) {
if (tree is TextContentElement) {
return TextSpan(text: tree.text);
} else {
return WidgetSpan(
alignment: tree.alignment,
baseline: TextBaseline.alphabetic,
child: tree.toWidget(context)!,
);
}
} 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(),
);
}
}