import re
from goose3.article import Article
from lxml import etree
from lxml.html import HtmlElement


class SmartExtractorUtility:
    # 标题最小长度
    title_min_len = 6

    @staticmethod
    def extract_publish_date(html):
        pattern_list = [
            # 2010-10-1 8:00:00
            r"20\d{2}-\d{1,2}-\d{1,2} \d{1,2}:\d{1,2}:\d{1,2}",
            # 2010-10-1 8:00
            r"20\d{2}-\d{1,2}-\d{1,2} \d{1,2}:\d{1,2}",
            # 2010年10月1日 8:00:00
            r"20\d{2}年\d{1,2}月\d{1,2}日 \d{1,2}:\d{1,2}:\d{1,2}",
            # 2010年10月1日 8:00
            r"20\d{2}年\d{1,2}月\d{1,2}日 \d{1,2}:\d{1,2}",
            # 2010/10/1 8:00:00
            r"20\d{2}/\d{1,2}/\d{1,2} \d{1,2}:\d{1,2}:\d{1,2}",
            # 2010/10/1 8:00
            r"20\d{2}/\d{1,2}/\d{1,2} \d{1,2}:\d{1,2}",
            # 2010-10-1
            r"20\d{2}-\d{1,2}-\d{1,2}",
            # 2010年10月1日
            r"20\d{2}年\d{1,2}月\d{1,2}日",
            # 2010/10/1
            r"20\d{2}/\d{1,2}/\d{1,2}",
            # 2022.08.28
            r"20\d{2}\.\d{1,2}\.\d{1,2}"
            # 12-07-02 10:10
            r"\d{2}-\d{1,2}-\d{1,2} \d{1,2}:\d{1,2}",
            # 1月前
            r"\d+(&nbsp;| )*月前",
            # 12天前
            r"\d+(&nbsp;| )*天前",
            # 2小时前
            r"\d+(&nbsp;| )*小时前",
            # 15分钟前
            r"\d+(&nbsp;| )*分钟前",
            # 昨天&nbsp;17:59
            r"昨天(&nbsp;| )*\d{1,2}:\d{1,2}",
        ]

        # 尝试匹配所有正则式
        for pattern in pattern_list:
            # 提取可见日期：
            # 1、必须在标签内部，不能提取HTML标签属性中的日期
            # 2、提取规则：必须在 > 和 < 之间，且中间不能再有 >
            tag_pattern = f'>[^>]*(?P<date>{pattern})[^>]*<'
            # 搜索第一个匹配项
            match = re.search(tag_pattern, html)
            # 如果匹配成功，返回正确的发布时间
            if match:
                return match.group('date')

        # 所有正则式匹配失败，返回空字符串
        return ""

    @staticmethod
    def add_html_br(cleaned_text):
        # 包装HTML标记：换行
        # 1、优先替换双换行：使用goose提取到的cleaned_text，都是双换行
        cleaned_text = cleaned_text.replace("\n\n", "<br>")
        cleaned_text = cleaned_text.replace("\n", "<br>")
        return cleaned_text

    @staticmethod
    def get_article_title(article: Article, link_text=''):
        #
        # 优先提取h1、div、span、td元素中的标题
        # 1、测试任务：2.智能采集\1.测试任务\国资委-新闻发布
        #    a. 原title标题：中国能建：聚焦价值创造 打造国企改革发展“红色引擎”－国务院国有资产监督管理委员会
        #    b. div元素中的标题：中国能建：聚焦价值创造 打造国企改革发展“红色引擎”
        # 2、测试任务：2.智能采集\1.测试任务\国家林业和草原局-地方动态
        #    a. 原title标题：上海完成森林资源年度监测遥感解译图斑市级质量检查_地方动态_国家林业和草原局政府网
        #    b. span元素中的标题：上海完成森林资源年度监测遥感解译图斑市级质量检查
        #
        # 根据xpath，查询标题元素时：
        # 1、标签优先级：h1、特殊元素（id或class包含title）、h2、h3、div、span、td
        #
        title_element_list = [
            'h1',
            'h2',
            'h3',
            'div',
            'span',
            'td',
            'p',
        ]

        # 对比标题前，统一将空格剔除（2022-09-21）：
        # 1、测试任务：3.马荣：一带一路，配置不成功\中国电力建设集团（测试：标题采集失败）
        # 2、相比列表中的链接文本、title标签中的内容，元素中的标题，“秉承丝路精髓  抒写锦绣华章”中间多出一个空格
        link_text = link_text.replace(" ", "")
        tag_title = article.title.replace(" ", "")

        title = None
        for title_element in title_element_list:
            element_list = article.raw_doc.getroottree().xpath(f'//{title_element}')
            # 查询XPath成功，遍历所有元素
            for element in element_list:
                # 取纯文本内容，包括子元素
                text = etree.tounicode(element, method='text').strip()
                text_no_space = text.replace(" ", "")
                # 判断标题：
                # 1、如果智能采集的原title标题，以“元素内容”开头，则取元素内容
                # 2、查找成功后，返回text作为标题，否则继续下一个循环
                # 判断是否以“元素中的标题”开始：
                # 1、title必须以“元素中的标题”开始，不能判断“包含”
                # 2、测试URL：http://v.people.cn/n1/2022/0901/c444662-32517559.html
                # 3、title标签：<title>亿缕阳光丨小生意，大格局--人民视频--人民网</title>
                #    a. 如果判断“包含”，会采集到：人民网
                #    b. 因为存在元素：<a href="http://www.people.com.cn/" class="clink">人民网</a>
                #    c. 如果判断以“元素中的标题”开始，采集到：亿缕阳光丨小生意，大格局
                #    d. 标题元素：<h2>亿缕阳光丨小生意，大格局</h2>
                # 新方案：
                # 1、对比常用元素：仍判断是否以“元素中的标题”开始
                # 2、优先对比“链接文本”，其次对比“title元素”
                # 3、满足最少字数：6个字
                # 新方案（2022-09-21）：
                # 1、对比“链接文本”、“title元素”时，除了判断开始，同时允许结尾
                # 2、测试任务：3.马荣：一带一路，配置不成功\中国电力建设集团（测试：标题采集失败）
                #    a. 列表中的链接文本：【“一带一路”旗舰篇】秉承丝路精髓 抒写锦绣华章——河北电...
                #    b. title标签中的内容：<title>中国电力建设集团 公司要闻 【“一带一路”旗舰篇】秉承丝路精髓 抒写锦绣华章——河北电建一公司摘取“一带一路”上的“鲁班奖”桂冠</title>
                #    c. 元素中的标题：【“一带一路”旗舰篇】秉承丝路精髓  抒写锦绣华章——河北电建一公司摘取“一带一路”上的“鲁班奖”桂冠
                if text_no_space is not None and text_no_space != '' and len(
                        text_no_space) >= SmartExtractorUtility.title_min_len:
                    # 优先判断6个字，以方便调试：排除短文本元素
                    if link_text.startswith(text_no_space) or link_text.endswith(text_no_space) or tag_title.startswith(
                            text_no_space) or tag_title.endswith(text_no_space):
                        # 返回时，仍返回未剔除空格后的标题
                        return text

        if title:
            # 查找成功，返回元素中的标题
            return title
        else:
            # 查找失败，返回提取到的title属性
            # return article.title
            # 新考虑：标题采集失败后，返回空值
            # 1、原因：article.title 不可靠，只是提取了 title 标签中的内容
            return ''

    @staticmethod
    def get_publish_date(article: Article):
        # 优先使用正则式提取日期
        # 1、测试任务：加德满都邮报-national-security
        #    a. 使用 publish_datetime_utc 提取英文日期后，提取错误
        #    b. 实际日期：Friday, August 19, 2022，但提取到了：2015-02-05
        #    c. 原因：在下方JS中，有一段JSON文本： "datePublished": "2015-02-05T08:00:00+08:00"
        # 2、注意：中文网站，都必须使用正则式
        publish_date = SmartExtractorUtility.extract_publish_date(article.raw_html)
        if publish_date != '':
            return publish_date
        else:
            if article.publish_datetime_utc:
                # 优先使用提取成功的 datetime
                return article.publish_datetime_utc.strftime('%Y-%m-%d')
            elif article.publish_date:
                # 其次使用提取成功的 date 字符串
                return article.publish_date
            else:
                # 全部提取失败，返回字符串
                return ''

    @staticmethod
    def get_article_text(article: Article):
        # 第一种方法：在纯文本（cleaned_text）基础上，添加br标签
        # 1、缺点：无法获取图片，同时会丢掉原有的p标签（只能用br替补）
        # text = SmartExtractor.add_html_br(article.cleaned_text)

        # 第二种方法：直接获取 top_node 的HTML内容
        # 1、优点：可保留原有的p标签等
        # 2、缺点：无法获取图片，img标签未被保留
        # text = etree.tounicode(article.top_node, method='html')

        # 测试抛出异常
        # raise Exception("测试抛出异常")

        # 第三种方法：获取到 top_node 的xpath，再通过xpath查询原始doc
        # 1、可行：通过查询原始doc，可以获取“正文”的所有HTML内容
        # 2、遇到问题：获取到 top_node 的xpath不准确，与原位置偏移一个元素
        #    a. 测试URL：https://news.cctv.com/2022/08/16/ARTIERrXbbVtVUaQU0pMzQxf220816.shtml
        #    b. 获取到的xpath：/html/body/div/div[1]/div[2]/div[4]
        #    c. 实际xpath：/html/body/div/div[1]/div[2]/div[5]
        # 3、解决办法：
        #    a. 优先使用id、class查询，如果没有id、class，再查询 top_node 的xpath
        xpath = None
        if type(article.top_node) is HtmlElement:
            if 'id' in article.top_node.attrib:
                xpath = "//*[@id='{}']".format(article.top_node.attrib['id'])
            elif 'class' in article.top_node.attrib:
                xpath = "//*[@class='{}']".format(article.top_node.attrib['class'])
            else:
                xpath = article.top_node.getroottree().getpath(article.top_node)
        else:
            # article.top_node 有时为空：
            # 1、测试URL：https://baijiahao.baidu.com/s?id=1741311527693101670
            # 2、输出日志：article.top_node 不是 HtmlElement 对象：None
            print("SmartExtractor：article.top_node 为 {}，不是 HtmlElement 对象。".format(article.top_node))

            # article.top_node 为空时，直接输出 cleaned_text：
            # 1、在纯文本（cleaned_text）基础上，添加br标签
            text = SmartExtractorUtility.add_html_br(article.cleaned_text)
            return text

        # 根据xpath，查询元素
        element_list = article.raw_doc.getroottree().xpath(xpath)
        if element_list:
            # 查询XPath成功，获取第一个元素的HTML
            text = etree.tounicode(element_list[0], method='html')
        else:
            # 查询XPath失败，返回 top_node 原有的HTML
            # 1、缺点：无法获取图片，img标签未被保留
            text = etree.tounicode(article.top_node, method='html')

        return text
