use once_cell::sync::Lazy;
use regex::Regex;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RegexPattern {
RawScript,
JsonLdArticleType,
UnlikelyCandidates,
MaybeCandidate,
Positive,
Negative,
NormalizeWhitespace,
Comma,
ShareElements,
AdOrLoadingWords,
LazyImageUrl,
LazyImageSrcset,
TrailingChromeAttrs,
TrailingChromeText,
FootnoteReferenceAttrs,
FootnoteReferenceText,
LeadingDateText,
MobileMediaBlock,
CssRule,
DisplayDecl,
ShadowTemplateHtml,
FootnoteTrailingNumber,
BylinePrefix,
BylineTrailingDate,
}
impl RegexPattern {
pub fn to_regex(self) -> &'static Regex {
match self {
Self::RawScript => &RAW_SCRIPT,
Self::JsonLdArticleType => &JSON_LD_ARTICLE_TYPE,
Self::UnlikelyCandidates => &UNLIKELY_CANDIDATES,
Self::MaybeCandidate => &MAYBE_CANDIDATE,
Self::Positive => &POSITIVE,
Self::Negative => &NEGATIVE,
Self::NormalizeWhitespace => &NORMALIZE_WHITESPACE,
Self::Comma => &COMMA,
Self::ShareElements => &SHARE_ELEMENTS,
Self::AdOrLoadingWords => &AD_OR_LOADING_WORDS,
Self::LazyImageUrl => &LAZY_IMAGE_URL,
Self::LazyImageSrcset => &LAZY_IMAGE_SRCSET,
Self::TrailingChromeAttrs => &TRAILING_CHROME_ATTRS,
Self::TrailingChromeText => &TRAILING_CHROME_TEXT,
Self::FootnoteReferenceAttrs => &FOOTNOTE_REFERENCE_ATTRS,
Self::FootnoteReferenceText => &FOOTNOTE_REFERENCE_TEXT,
Self::LeadingDateText => &LEADING_DATE_TEXT,
Self::MobileMediaBlock => &MOBILE_MEDIA_BLOCK,
Self::CssRule => &CSS_RULE,
Self::DisplayDecl => &DISPLAY_DECL,
Self::ShadowTemplateHtml => &SHADOW_TEMPLATE_HTML,
Self::FootnoteTrailingNumber => &FOOTNOTE_TRAILING_NUMBER,
Self::BylinePrefix => &BYLINE_PREFIX,
Self::BylineTrailingDate => &BYLINE_TRAILING_DATE,
}
}
}
impl From<RegexPattern> for &'static Regex {
fn from(value: RegexPattern) -> Self {
value.to_regex()
}
}
static RAW_SCRIPT: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?is)<script\b[^>]*>.*?</script\s*>").expect("valid script regex"));
static JSON_LD_ARTICLE_TYPE: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"^Article|AdvertiserContentArticle|NewsArticle|AnalysisNewsArticle|AskPublicNewsArticle|BackgroundNewsArticle|OpinionNewsArticle|ReportageNewsArticle|ReviewNewsArticle|Report|SatiricalArticle|ScholarlyArticle|MedicalScholarlyArticle|SocialMediaPosting|BlogPosting|LiveBlogPosting|DiscussionForumPosting|TechArticle|APIReference$")
.expect("valid json-ld article type regex")
});
static UNLIKELY_CANDIDATES: Lazy<Regex> = Lazy::new(|| {
Regex::new(
r"(?i)-ad-|ai2html|banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|footer|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote",
)
.expect("valid unlikely-candidates regex")
});
static MAYBE_CANDIDATE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?i)and|article|body|column|content|main|mathjax|shadow").expect("valid ok-maybe regex"));
static POSITIVE: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(?i)article|body|content|entry|hentry|h-entry|main|page|pagination|post|text|blog|story")
.expect("valid positive regex")
});
static NEGATIVE: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(?i)-ad-|hidden|^hid$| hid$| hid |^hid |banner|combx|comment|\bcom-|contact|footer|gdpr|masthead|media|meta|outbrain|promo|related|scroll|share|shoutbox|sidebar|skyscraper|sponsor|shopping|tags|widget")
.expect("valid negative regex")
});
static NORMALIZE_WHITESPACE: Lazy<Regex> = Lazy::new(|| Regex::new(r"\s{2,}").expect("valid whitespace regex"));
static COMMA: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"\u{002C}|\u{060C}|\u{FE50}|\u{FE10}|\u{FE11}|\u{2E41}|\u{2E34}|\u{2E32}|\u{FF0C}")
.expect("valid comma regex")
});
static SHARE_ELEMENTS: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?i)(\b|_)(share|sharedaddy)(\b|_)").expect("valid share regex"));
static AD_OR_LOADING_WORDS: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(?iu)^(ad(vertising|vertisement)?|pub(licité)?|werb(ung)?|广告|Реклама|Anuncio|(loading|正在加载|Загрузка|chargement|cargando)(…|\.\.\.)?)$").expect("valid ad/loading regex")
});
static LAZY_IMAGE_URL: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?i)^\s*\S+\.(jpg|jpeg|png|webp)(\?\S*)?\s*$").expect("valid image url regex"));
static LAZY_IMAGE_SRCSET: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?i)\.(jpg|jpeg|png|webp)\S*\s+\d").expect("valid image srcset regex"));
static TRAILING_CHROME_ATTRS: Lazy<Regex> = Lazy::new(|| {
Regex::new(
r"(?ix)
\b(
after[-_]?post|bottom[-_]?of[-_]?article|comment(s|ary)?|comment[-_]?thread|court[-_]?case|
discussion|disqus|finance|follow[-_]?up|job(s)?|keep[-_]?reading|
mortgage|most[-_]?popular|most[-_]?read|most[-_]?viewed|newsletter|next[-_]?article|next[-_]?up|
onward[-_]?journey|outbrain|partner[-_]?offer|popular|promo|
read[-_]?also|read[-_]?more|read[-_]?next|recommend(ed|ation|ations)?|
recirc|related[-_]?posts?|related|signup|sign[-_]?up|sponsor(ed)?|subscribe|
subscription|taboola|widget|yarpp
)\b",
)
.expect("valid trailing chrome attribute regex")
});
static TRAILING_CHROME_TEXT: Lazy<Regex> = Lazy::new(|| {
Regex::new(
r"(?i)^\s*(comments?|join the (conversation|discussion)|related( articles| posts| stories)?|also in\b|court case:|affiliate:|continue reading|explore press release|more (from|in|on)|recommended|most (popular|read|viewed)|next article|read (also|more|next)|sponsored|partner offers?|(the )?\w*\s*newsletter|sign up|subscribe|jobs?|mortgage|finance)",
)
.expect("valid trailing chrome text regex")
});
static FOOTNOTE_REFERENCE_ATTRS: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(?i)\b(footnotes?|endnotes?|references?|bibliography|citations?)\b")
.expect("valid footnote reference attribute regex")
});
static FOOTNOTE_REFERENCE_TEXT: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(?i)^\s*(footnotes?|notes|references|bibliography|citations?)\s*$")
.expect("valid footnote reference text regex")
});
static LEADING_DATE_TEXT: Lazy<Regex> = Lazy::new(|| {
Regex::new(
r"(?ix)^\s*
(
(jan|feb|mar|apr|may|jun|jul|aug|sep|sept|oct|nov|dec)[a-z]*\.?\s+\d{1,2},?\s+\d{4}
|
\d{1,2}\s+(jan|feb|mar|apr|may|jun|jul|aug|sep|sept|oct|nov|dec)[a-z]*\.?\s+\d{4}
|
\d{4}[-/]\d{1,2}[-/]\d{1,2}
)
\s*$",
)
.expect("valid leading date text regex")
});
static MOBILE_MEDIA_BLOCK: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?is)@media[^{]*max-width\s*:\s*(\d+)px[^{]*\{").expect("valid media regex"));
static CSS_RULE: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(?is)(?P<selectors>[^{}]+)\{(?P<body>[^{}]*display\s*:\s*[^;}]+[^{}]*)\}")
.expect("valid css rule regex")
});
static DISPLAY_DECL: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?is)display\s*:\s*(?P<display>[a-z-]+)").expect("valid display regex"));
static SHADOW_TEMPLATE_HTML: Lazy<Regex> = Lazy::new(|| {
Regex::new(r#"(?is)<template\s+[^>]*(?:shadowrootmode|shadowroot)\s*=\s*["']?[^"'\s>]+["']?[^>]*>(?P<body>.*?)</template>"#)
.expect("valid shadow template regex")
});
static FOOTNOTE_TRAILING_NUMBER: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?i)(?:^|[-_:])(?:fn|ftnt|note|ref)?(\d+)$|(\d+)$").expect("valid footnote label regex"));
static BYLINE_PREFIX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?i)^\s*(by|author|authors|written by)\s*:?\s+").expect("valid byline prefix regex"));
static BYLINE_TRAILING_DATE: Lazy<Regex> = Lazy::new(|| {
Regex::new(
r"(?ix)
\s+
(
(published|updated|last\s+updated|posted|on)\b.*$
|
(jan|feb|mar|apr|may|jun|jul|aug|sep|sept|oct|nov|dec)[a-z]*\.?\s+\d{1,2},?\s+\d{4}.*$
|
\d{4}[-/]\d{1,2}[-/]\d{1,2}.*$
)
",
)
.expect("valid byline trailing date regex")
});