import 'dart:convert'; import 'package:xml/xml.dart'; import 'package:yaml/yaml.dart'; /// A syntax problem found by a validator: 1-based line and column. class Problem { final String message; final int? line, column; const Problem(this.message, {this.line, this.column}); } /// Which validator applies to a syntax id, if any. String? validatorFor(String syntax) => switch (syntax) { 'json' || 'jsonl' => syntax, 'yaml' => 'yaml', 'xml' || 'plist' || 'svg' => 'xml', _ => null, }; (int, int) _position(String text, int offset) { var line = 1, column = 1; for (var i = 0; i < offset && i < text.length; i++) { if (text.codeUnitAt(i) == 0x0a) { line++; column = 1; } else { column++; } } return (line, column); } /// Checks [text] without changing it. Returns null when it is valid. Problem? validate(String syntax, String text) { switch (validatorFor(syntax)) { case 'json': try { jsonDecode(text); return null; } on FormatException catch (e) { final offset = e.offset; if (offset == null) return Problem(e.message); final (line, column) = _position(text, offset); return Problem(e.message, line: line, column: column); } case 'jsonl': final lines = text.split('\n'); for (var i = 0; i < lines.length; i++) { final l = lines[i].trim(); if (l.isEmpty) continue; try { jsonDecode(l); } on FormatException catch (e) { return Problem(e.message, line: i + 1, column: (e.offset ?? 0) + 1); } } return null; case 'yaml': try { loadYamlStream(text); return null; } on YamlException catch (e) { final span = e.span; return Problem( e.message, line: span == null ? null : span.start.line + 1, column: span == null ? null : span.start.column + 1, ); } case 'xml': try { XmlDocument.parse(text); return null; } on XmlParserException catch (e) { return Problem(e.message, line: e.line, column: e.column); } on XmlTagException catch (e) { return Problem(e.message, line: e.line, column: e.column); } on XmlException catch (e) { return Problem(e.message); } } return null; } /// Pretty-printed JSON or XML, produced only when the user asks for it. /// Returns null when the syntax has no formatter or the text is invalid. String? formatText(String syntax, String text, {int indent = 2}) { try { switch (validatorFor(syntax)) { case 'json': return JsonEncoder.withIndent(' ' * indent).convert(jsonDecode(text)); case 'xml': return XmlDocument.parse( text, ).toXmlString(pretty: true, indent: ' ' * indent); } } catch (_) { return null; } return null; }