XMLNode.fromXMLString constructor

XMLNode.fromXMLString(
  1. String xml
)

Implementation

factory XMLNode.fromXMLString(String xml) {
  Map<String, String> parseAttributes(String attributeString) {
    final attributePattern = RegExp(r'(\w+)="([^"]*)"');
    return Map.fromEntries(
      attributePattern
          .allMatches(attributeString)
          .map((match) => MapEntry(match.group(1)!, match.group(2)!)),
    );
  }

  Pair<XMLNode, int> parseElement(String xml, int startIndex) {
    final openTagRegex = RegExp(r'<(\w+)([^>]*)>');
    final closeTagRegex = RegExp(r'</(\w+)>');
    final selfClosingTagRegex = RegExp(r'<(\w+)([^>]*)/>');

    final openTagMatch = openTagRegex.firstMatch(xml.substring(startIndex));
    final selfClosingTagMatch =
        selfClosingTagRegex.firstMatch(xml.substring(startIndex));

    if (openTagMatch != null &&
        (selfClosingTagMatch == null ||
            openTagMatch.start + startIndex <
                selfClosingTagMatch.start + startIndex)) {
      String tag = openTagMatch.group(1)!;
      String attrString = openTagMatch.group(2)!;
      Map<String, String> attributes = parseAttributes(attrString);
      List<XMLNode> children = [];
      int index = openTagMatch.end + startIndex;

      while (true) {
        final nextOpenTagMatch =
            openTagRegex.firstMatch(xml.substring(index));
        final nextCloseTagMatch =
            closeTagRegex.firstMatch(xml.substring(index));

        if (nextCloseTagMatch != null &&
            (nextOpenTagMatch == null ||
                nextCloseTagMatch.start + index <
                    nextOpenTagMatch.start + index)) {
          String closeTag = nextCloseTagMatch.group(1)!;
          if (closeTag == tag) {
            String innerXml = xml
                .substring(openTagMatch.end + startIndex,
                    nextCloseTagMatch.start + index)
                .trim();
            XMLBody body = children.isNotEmpty
                ? XMLChildren(children)
                : XMLValue(innerXml);
            return Pair(XMLNode(tag, attributes, body),
                nextCloseTagMatch.end + index);
          } else {
            throw ArgumentError(
                'Mismatched close tag: expected </$tag> but found </$closeTag>');
          }
        }

        if (nextOpenTagMatch != null &&
            (nextCloseTagMatch == null ||
                nextOpenTagMatch.start + index <
                    nextCloseTagMatch.start + index)) {
          var parsedElement = parseElement(xml, index);
          children.add(parsedElement.first);
          index = parsedElement.second;
        } else {
          break;
        }
      }

      throw ArgumentError('Missing close tag for <$tag>');
    } else if (selfClosingTagMatch != null) {
      String tag = selfClosingTagMatch.group(1)!;
      String attrString = selfClosingTagMatch.group(2)!;
      Map<String, String> attributes = parseAttributes(attrString);
      return Pair(XMLNode(tag, attributes, null),
          selfClosingTagMatch.end + startIndex);
    } else {
      throw ArgumentError('Invalid XML format');
    }
  }

  var result = parseElement(xml.trim(), 0);
  return result.first;
}