toXmlString method

String toXmlString({
  1. int indent = 0,
  2. required int boundsPrecision,
  3. Rect? parentBounds,
  4. BoxConstraints? parentConstraints,
})

Implementation

String toXmlString({
  int indent = 0,
  required int boundsPrecision,
  Rect? parentBounds,
  BoxConstraints? parentConstraints,
}) {
  final indentStr = '  ' * indent;
  final tag = widget.runtimeType
      .toString()
      .replaceAll('<', '-')
      .replaceAll('>', '')
      .replaceAll(',', '-')
      .replaceAll(' ', '');

  // associate properties with their values (opt-in selection)
  final diagnostics = widget.toDiagnosticsNode().getProperties();
  final allowedNames = _allowedPropertyNames(widget, diagnostics);
  final Map<String, dynamic> props = {};

  // Process properties, handling BoxDecoration specially
  for (final p in diagnostics) {
    if (p.name != null && p.value != null && allowedNames.contains(p.name)) {
      // Check if this is a BoxDecoration property (bg or decoration)
      if ((p.name == 'bg' || p.name == 'decoration') &&
          p.value is BoxDecoration) {
        // Flatten BoxDecoration properties directly onto the element
        final decoration = p.value as BoxDecoration;
        final flattenedProps = _serializeBoxDecoration(decoration);
        // Sanitize the flattened property values
        for (final entry in flattenedProps.entries) {
          props[entry.key] = _sanitizeAttributeValue(entry.value);
        }
      } else {
        // Regular property handling
        props[p.name!] = _sanitizeAttributeValue(
          _getPropertyValueString(p),
        );
      }
    }
  }

  // Determine if bounds should be included:
  // - Always include for root widget (parentBounds is null)
  // - Include if bounds differ from parent bounds
  final shouldIncludeBounds = bounds != null &&
      (parentBounds == null ||
          !_boundsMatchParent(bounds!, parentBounds, boundsPrecision));

  // Determine if constraints should be included:
  // - Always include for root widget (parentConstraints is null)
  // - Include if constraints differ from parent constraints
  final boxConstraints =
      constraints is BoxConstraints ? (constraints as BoxConstraints) : null;
  final shouldIncludeConstraints = boxConstraints != null &&
      (parentConstraints == null ||
          !_constraintsMatchParent(boxConstraints, parentConstraints));

  final attrs = [
    ...props.entries.map((e) {
      // Height and line height are conflicting properties
      if (e.key == "height") {
        return ' lineHeight="${e.value}"';
      }
      return ' ${e.key}="${e.value}"';
    }),
    if (shouldIncludeBounds) ...[
      if (!props.containsKey("left"))
        ' left="${bounds!.left.toStringAsFixed(boundsPrecision)}"',
      if (!props.containsKey("top"))
        ' top="${bounds!.top.toStringAsFixed(boundsPrecision)}"',
      ' width="${bounds!.width.toStringAsFixed(boundsPrecision)}"',
      ' height="${bounds!.height.toStringAsFixed(boundsPrecision)}"',
    ],
    if (shouldIncludeConstraints) ...[
      ' maxHeight="${boxConstraints.maxHeight.toStringAsFixed(boundsPrecision)}"',
      ' maxWidth="${boxConstraints.maxWidth.toStringAsFixed(boundsPrecision)}"',
      ' minHeight="${boxConstraints.minHeight.toStringAsFixed(boundsPrecision)}"',
      ' minWidth="${boxConstraints.minWidth.toStringAsFixed(boundsPrecision)}"',
    ],
    if (widget is RichText) ...[
      ' text="${_sanitizeAttributeValue((widget as RichText).text.toPlainText())}"',
      ' color="${_colorToHex((widget as RichText).text.style?.color)}"',
      ' family="${(widget as RichText).text.style?.fontFamily ?? 'null'}"',
      ' size="${(widget as RichText).text.style?.fontSize ?? 'null'}"',
      ' weight="${(widget as RichText).text.style?.fontWeight?.toString() ?? 'null'}"',
      ' lineHeight="${(widget as RichText).text.style?.height?.toString() ?? 'null'}"',
    ],
    if (widget is Text) ...[
      ' text="${_sanitizeAttributeValue((widget as Text).data ?? (widget as Text).textSpan?.toPlainText() ?? '')}"',
      ' overflow="${(widget as Text).overflow?.toString() ?? 'null'}"',
      ' alignment="${(widget as Text).textAlign?.toString() ?? 'null'}"',
      ' weight="${(widget as Text).style?.fontWeight?.toString() ?? 'null'}"',
      ' lineHeight="${(widget as Text).style?.height?.toString() ?? 'null'}"',
    ],
    // Handle LdText widget - check by runtimeType string to avoid import dependency
    if (widget.runtimeType.toString() == 'LdText')
      ...(() {
        try {
          final ldText = widget as dynamic;
          return [
            ' type="${ldText.type?.toString() ?? 'null'}"',
            ' size="${ldText.size?.toString() ?? 'null'}"',
          ];
        } catch (_) {
          return <String>[];
        }
      })(),
  ].join('');

  final content = children.isEmpty
      ? ''
      : [
            '',
            ...children.map((child) => child.toXmlString(
                indent: indent + 1,
                boundsPrecision: boundsPrecision,
                parentBounds: bounds,
                parentConstraints: boxConstraints)),
            '',
          ].join('\n') +
          indentStr;
  final slash = children.isEmpty ? ' /' : '';
  final closingTag = children.isEmpty ? '' : '</$tag>';
  final result = '$indentStr<$tag$attrs$slash>$content$closingTag'
      // replace UID hash codes with a generic placeholder
      .replaceAllMapped(
    RegExp(r'([a-zA-Z_>]+)#[0-9a-fA-F]+'),
    (match) => '${match.group(1)}#HASH',
  );
  return result;
}