import 'package:html/dom.dart'; import 'package:flutter/foundation.dart'; /// 네이버 지도 HTML 파서 /// /// 네이버 지도 페이지의 HTML에서 식당 정보를 추출합니다. class NaverHtmlParser { // CSS 셀렉터 상수 static const List _nameSelectors = [ 'span.GHAhO', 'h1.Qpe7b', 'span.Fc1rA', '[class*="place_name"]', 'meta[property="og:title"]', ]; static const List _categorySelectors = [ 'span.DJJvD', 'span.lnJFt', '[class*="category"]', ]; static const List _descriptionSelectors = [ 'span.IH7VW', 'div.vV_z_', 'meta[name="description"]', ]; static const List _phoneSelectors = [ 'span.xlx7Q', 'a[href^="tel:"]', '[class*="phone"]', ]; static const List _addressSelectors = [ 'span.IH7VW', 'span.jWDO_', '[class*="address"]', ]; static const List _businessHoursSelectors = [ 'time.aT6WB', 'div.O8qbU', '[class*="business"]', '[class*="hours"]', ]; /// HTML 문서에서 식당 정보 추출 Map parseRestaurantInfo(Document document) { return { 'name': extractName(document), 'category': extractCategory(document), 'subCategory': extractSubCategory(document), 'description': extractDescription(document), 'phone': extractPhoneNumber(document), 'roadAddress': extractRoadAddress(document), 'address': extractJibunAddress(document), 'latitude': extractLatitude(document), 'longitude': extractLongitude(document), 'businessHours': extractBusinessHours(document), }; } /// 식당 이름 추출 String? extractName(Document document) { try { for (final selector in _nameSelectors) { final element = document.querySelector(selector); if (element != null) { if (element.localName == 'meta') { return element.attributes['content']; } final text = element.text.trim(); if (text.isNotEmpty) { return text; } } } return null; } catch (e) { debugPrint('NaverHtmlParser: 이름 추출 실패 - $e'); return null; } } /// 카테고리 추출 String? extractCategory(Document document) { try { for (final selector in _categorySelectors) { final element = document.querySelector(selector); if (element != null) { final text = element.text.trim(); if (text.isNotEmpty) { // 첫 번째 카테고리만 추출 (예: "한식 > 국밥" -> "한식") return text.split('>').first.trim(); } } } return null; } catch (e) { debugPrint('NaverHtmlParser: 카테고리 추출 실패 - $e'); return null; } } /// 서브 카테고리 추출 String? extractSubCategory(Document document) { try { final element = document.querySelector('span.DJJvD, span.lnJFt'); if (element != null) { final text = element.text.trim(); if (text.contains('>')) { // 두 번째 카테고리 반환 (예: "한식 > 국밥" -> "국밥") return text.split('>').last.trim(); } } return null; } catch (e) { debugPrint('NaverHtmlParser: 서브 카테고리 추출 실패 - $e'); return null; } } /// 설명 추출 String? extractDescription(Document document) { try { for (final selector in _descriptionSelectors) { final element = document.querySelector(selector); if (element != null) { if (element.localName == 'meta') { return element.attributes['content']; } final text = element.text.trim(); if (text.isNotEmpty) { return text; } } } return null; } catch (e) { debugPrint('NaverHtmlParser: 설명 추출 실패 - $e'); return null; } } /// 전화번호 추출 String? extractPhoneNumber(Document document) { try { for (final selector in _phoneSelectors) { final element = document.querySelector(selector); if (element != null) { if (element.localName == 'a' && element.attributes['href'] != null) { return element.attributes['href']!.replaceFirst('tel:', ''); } final text = element.text.trim(); if (text.isNotEmpty && RegExp(r'[\d\-\+\(\)]+').hasMatch(text)) { return text; } } } return null; } catch (e) { debugPrint('NaverHtmlParser: 전화번호 추출 실패 - $e'); return null; } } /// 도로명 주소 추출 String? extractRoadAddress(Document document) { try { for (final selector in _addressSelectors) { final elements = document.querySelectorAll(selector); for (final element in elements) { final text = element.text.trim(); // 도로명 주소 패턴 확인 if (text.contains('로') || text.contains('길')) { return text; } } } return null; } catch (e) { debugPrint('NaverHtmlParser: 도로명 주소 추출 실패 - $e'); return null; } } /// 지번 주소 추출 String? extractJibunAddress(Document document) { try { for (final selector in _addressSelectors) { final elements = document.querySelectorAll(selector); for (final element in elements) { final text = element.text.trim(); // 지번 주소 패턴 확인 (숫자-숫자 형식 포함) if (RegExp(r'\d+\-\d+').hasMatch(text) && !text.contains('로') && !text.contains('길')) { return text; } } } return null; } catch (e) { debugPrint('NaverHtmlParser: 지번 주소 추출 실패 - $e'); return null; } } /// 위도 추출 double? extractLatitude(Document document) { try { // 메타 태그에서 좌표 정보 찾기 final metaElement = document.querySelector('meta[property="og:url"]'); if (metaElement != null) { final content = metaElement.attributes['content']; if (content != null) { // URL에서 좌표 파라미터 추출 (예: ?y=37.5666805) final RegExp latRegex = RegExp(r'[?&]y=(\d+\.\d+)'); final match = latRegex.firstMatch(content); if (match != null) { return double.tryParse(match.group(1)!); } } } // 자바스크립트 변수에서 추출 시도 final scripts = document.querySelectorAll('script'); for (final script in scripts) { final content = script.text; if (content.contains('latitude') || content.contains('lat')) { final RegExp latRegex = RegExp(r'(?:latitude|lat)["\s:]+(\d+\.\d+)'); final match = latRegex.firstMatch(content); if (match != null) { return double.tryParse(match.group(1)!); } } } return null; } catch (e) { debugPrint('NaverHtmlParser: 위도 추출 실패 - $e'); return null; } } /// 경도 추출 double? extractLongitude(Document document) { try { // 메타 태그에서 좌표 정보 찾기 final metaElement = document.querySelector('meta[property="og:url"]'); if (metaElement != null) { final content = metaElement.attributes['content']; if (content != null) { // URL에서 좌표 파라미터 추출 (예: ?x=126.9784147) final RegExp lonRegex = RegExp(r'[?&]x=(\d+\.\d+)'); final match = lonRegex.firstMatch(content); if (match != null) { return double.tryParse(match.group(1)!); } } } // 자바스크립트 변수에서 추출 시도 final scripts = document.querySelectorAll('script'); for (final script in scripts) { final content = script.text; if (content.contains('longitude') || content.contains('lng')) { final RegExp lonRegex = RegExp(r'(?:longitude|lng)["\s:]+(\d+\.\d+)'); final match = lonRegex.firstMatch(content); if (match != null) { return double.tryParse(match.group(1)!); } } } return null; } catch (e) { debugPrint('NaverHtmlParser: 경도 추출 실패 - $e'); return null; } } /// 영업시간 추출 String? extractBusinessHours(Document document) { try { for (final selector in _businessHoursSelectors) { final elements = document.querySelectorAll(selector); for (final element in elements) { final text = element.text.trim(); if (text.isNotEmpty && (text.contains('시') || text.contains(':') || text.contains('영업'))) { return text; } } } return null; } catch (e) { debugPrint('NaverHtmlParser: 영업시간 추출 실패 - $e'); return null; } } }