diff --git a/changedetectionio/blueprint/tags/templates/edit-tag.html b/changedetectionio/blueprint/tags/templates/edit-tag.html index 99f86005a..a713cf6a9 100644 --- a/changedetectionio/blueprint/tags/templates/edit-tag.html +++ b/changedetectionio/blueprint/tags/templates/edit-tag.html @@ -89,11 +89,13 @@ xpath://body/div/span[contains(@class, 'example-class')]", {{ render_field(form.subtractive_selectors, rows=5, placeholder="header footer nav -.stockticker") }} +.stockticker +//*[contains(text(), 'Advertisement')]") }} diff --git a/changedetectionio/forms.py b/changedetectionio/forms.py index f92b854ad..d3ac30fca 100644 --- a/changedetectionio/forms.py +++ b/changedetectionio/forms.py @@ -469,7 +469,7 @@ class processor_text_json_diff_form(commonSettingsForm): include_filters = StringListField('CSS/JSONPath/JQ/XPath Filters', [ValidateCSSJSONXPATHInput()], default='') - subtractive_selectors = StringListField('Remove elements', [ValidateCSSJSONXPATHInput(allow_xpath=False, allow_json=False)]) + subtractive_selectors = StringListField('Remove elements', [ValidateCSSJSONXPATHInput(allow_json=False)]) extract_text = StringListField('Extract text', [ValidateListRegex()]) @@ -481,7 +481,9 @@ class processor_text_json_diff_form(commonSettingsForm): method = SelectField('Request method', choices=valid_method, default=default_method) ignore_status_codes = BooleanField('Ignore status codes (process non-2xx status codes as normal)', default=False) check_unique_lines = BooleanField('Only trigger when unique lines appear in all history', default=False) + remove_duplicate_lines = BooleanField('Remove duplicate lines of text', default=False) sort_text_alphabetically = BooleanField('Sort text alphabetically', default=False) + trim_text_whitespace = BooleanField('Trim whitespace before and after text', default=False) filter_text_added = BooleanField('Added lines', default=True) filter_text_replaced = BooleanField('Replaced/changed lines', default=True) @@ -576,7 +578,7 @@ class globalSettingsApplicationForm(commonSettingsForm): empty_pages_are_a_change = BooleanField('Treat empty pages as a change?', default=False) fetch_backend = RadioField('Fetch Method', default="html_requests", choices=content_fetchers.available_fetchers(), validators=[ValidateContentFetcherIsReady()]) global_ignore_text = StringListField('Ignore Text', [ValidateListRegex()]) - global_subtractive_selectors = StringListField('Remove elements', [ValidateCSSJSONXPATHInput(allow_xpath=False, allow_json=False)]) + global_subtractive_selectors = StringListField('Remove elements', [ValidateCSSJSONXPATHInput(allow_json=False)]) ignore_whitespace = BooleanField('Ignore whitespace') password = SaltyPasswordField() pager_size = IntegerField('Pager size', diff --git a/changedetectionio/html_tools.py b/changedetectionio/html_tools.py index ffe00cd04..7c2e1eba8 100644 --- a/changedetectionio/html_tools.py +++ b/changedetectionio/html_tools.py @@ -1,4 +1,5 @@ from typing import List +from lxml import etree import json import re @@ -57,11 +58,26 @@ def subtractive_css_selector(css_selector, html_content): item.decompose() return str(soup) +def subtractive_xpath_selector(xpath_selector, html_content): + html_tree = etree.HTML(html_content) + elements_to_remove = html_tree.xpath(xpath_selector) + + for element in elements_to_remove: + element.getparent().remove(element) + + modified_html = etree.tostring(html_tree, method="html").decode("utf-8") + return modified_html def element_removal(selectors: List[str], html_content): - """Joins individual filters into one css filter.""" - selector = ",".join(selectors) - return subtractive_css_selector(selector, html_content) + """Removes elements that match a list of CSS or xPath selectors.""" + modified_html = html_content + for selector in selectors: + if selector.startswith(('xpath:', 'xpath1:', '//')): + xpath_selector = selector.removeprefix('xpath:').removeprefix('xpath1:') + modified_html = subtractive_xpath_selector(xpath_selector, modified_html) + else: + modified_html = subtractive_css_selector(selector, modified_html) + return modified_html def elementpath_tostring(obj): """ diff --git a/changedetectionio/model/__init__.py b/changedetectionio/model/__init__.py index e439de4fc..3b95c91c1 100644 --- a/changedetectionio/model/__init__.py +++ b/changedetectionio/model/__init__.py @@ -60,6 +60,8 @@ class watch_base(dict): 'time_between_check_use_default': True, 'title': None, 'track_ldjson_price_data': None, + 'trim_text_whitespace': False, + 'remove_duplicate_lines': False, 'trigger_text': [], # List of text or regex to wait for until a change is detected 'url': '', 'uuid': str(uuid.uuid4()), diff --git a/changedetectionio/processors/text_json_diff/processor.py b/changedetectionio/processors/text_json_diff/processor.py index 28f864734..50e911e9b 100644 --- a/changedetectionio/processors/text_json_diff/processor.py +++ b/changedetectionio/processors/text_json_diff/processor.py @@ -218,11 +218,19 @@ class perform_site_check(difference_detection_processor): is_rss=is_rss)) #1874 activate the