import 'package:flutter/material.dart'; import 'package:html/parser.dart' as html_parser; import 'package:html/dom.dart' as dom; import 'dart:convert'; /// Парсер контента новостей (поддерживает HTML и JSON-формат) class ContentParser { /// Парсит текст новости: сначала проверяет textJson, потом text static List parseContent({String? textJson, String? text}) { if (textJson != null && textJson.isNotEmpty) { return _parseJsonText(textJson); } else if (text != null && text.isNotEmpty) { return _parseHtmlText(text); } return const []; } // 🔹 HTML-парсинг static List _parseHtmlText(String htmlText) { final parsedHtml = html_parser.parse(htmlText); final List elements = parsedHtml.body?.nodes ?? []; return _parseHtmlElements(elements); } static List _parseHtmlElements(List nodes) { List widgets = []; for (final node in nodes) { if (node is dom.Element) { final text = node.text.trim(); if (text.isEmpty) continue; switch (node.localName) { case 'h1': case 'h2': case 'h3': widgets.add(_buildHeader(text)); break; case 'p': widgets.add(_buildParagraph(text)); break; case 'ul': widgets.add(_buildUnorderedList(node.nodes)); break; default: widgets.add(_buildDefaultText(text)); } } else if (node is dom.Text) { final text = node.text.trim(); if (text.isNotEmpty) { widgets.add(_buildDefaultText(text)); } } } return widgets; } // 🔹 JSON-парсинг static List _parseJsonText(String jsonString) { try { final dynamic data = jsonDecode(jsonString); return _parseJsonNode(data); } catch (e) { return [ const Text( 'Ошибка отображения текста новости', style: TextStyle(color: Colors.red, fontSize: 14), ), ]; } } static List _parseJsonNode(dynamic node) { List widgets = []; if (node is List) { for (final item in node) { widgets.addAll(_parseJsonNode(item)); } } else if (node is Map) { final type = node['type'] as String?; final content = node['content']; switch (type) { case 'div': if (content is List) { widgets.addAll(_parseJsonNode(content)); } break; case 'h1': case 'h2': case 'h3': final text = _extractJsonText(content); if (text != null && text.isNotEmpty) { widgets.add(_buildHeader(text)); } break; case 'p': final text = _extractJsonText(content); if (text != null && text.isNotEmpty) { widgets.add(_buildParagraph(text)); } break; case 'ul': if (content is List) { widgets.add(_buildJsonUnorderedList(content)); } break; case 'li': final text = _extractJsonText(content); if (text != null && text.isNotEmpty) { widgets.add(_buildListItem(text)); } break; default: final text = _extractJsonText(content); if (text != null && text.isNotEmpty) { widgets.add(_buildDefaultText(text)); } } } return widgets; } // 🔹 Вспомогательные методы извлечения текста из JSON static String? _extractJsonText(dynamic content) { if (content == null) return null; if (content is List) { return content.map((e) => e?.toString() ?? '').where((s) => s.isNotEmpty).join(' '); } return content.toString(); } // 🔹 Виджеты для отображения (принимают НЕ nullable String) static Widget _buildHeader(String text) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Text( text, // ✅ text — не nullable style: const TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold, ), ), ); } static Widget _buildParagraph(String text) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Text( text, style: const TextStyle( color: Colors.white70, fontSize: 14, height: 1.5, ), ), ); } static Widget _buildDefaultText(String text) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Text( text, style: const TextStyle( color: Colors.white70, fontSize: 14, ), ), ); } static Widget _buildUnorderedList(List children) { final items = []; for (final child in children) { if (child is dom.Element && child.localName == 'li') { final text = child.text.trim(); if (text.isNotEmpty) { items.add(_buildListItem(text)); } } } return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: items, ), ); } static Widget _buildJsonUnorderedList(List content) { final items = []; for (final item in content) { if (item is Map && item['type'] == 'li') { final text = _extractJsonText(item['content']); if (text != null && text.isNotEmpty) { items.add(_buildListItem(text)); } } } return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: items, ), ); } static Widget _buildListItem(String text) { return Padding( padding: const EdgeInsets.only(left: 16, top: 4, bottom: 4), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('• ', style: TextStyle( color: Colors.white70, fontSize: 14, )), Expanded( child: Text( text, style: const TextStyle( color: Colors.white70, fontSize: 14, height: 1.5, ), ), ), ], ), ); } }