diff --git a/composer.json b/composer.json index 9bec4e5..fd5e3a7 100644 --- a/composer.json +++ b/composer.json @@ -18,8 +18,10 @@ ], "require": { "php": "^8.3", + "illuminate/contracts": "^10.0||^11.0||^12.0", "spatie/laravel-package-tools": "^1.16", - "illuminate/contracts": "^10.0||^11.0||^12.0" + "symfony/css-selector": "7.4.x-dev", + "symfony/dom-crawler": "7.4.x-dev" }, "require-dev": { "laravel/pint": "^1.14", @@ -71,5 +73,5 @@ } }, "minimum-stability": "dev", - "prefer-stable": true + "prefer-stable": false } diff --git a/src/Cision.php b/src/Cision.php index 3369cd6..430c1bc 100755 --- a/src/Cision.php +++ b/src/Cision.php @@ -5,11 +5,16 @@ namespace Mattitja\Cision; use Carbon\Carbon; use Illuminate\Support\Facades\Http; use Illuminate\Http\Client\Response; +use Symfony\Component\DomCrawler\Crawler; +use Mattitja\Cision\Support\HtmlCleaner; class Cision { protected string $slug; + /** + * Constructor that accepts a slug identifier for the feed. + */ public function __construct(string $slug = null) { $this->slug = $slug ?? env('LARAVEL_CISION_FEED_SLUG', ''); @@ -51,6 +56,39 @@ class Cision return $this->parseResponse($response->body(), $page); } + /** + * Fetch and parse the HTML content of a Cision article URL. + */ + public function fetchContent(string $url): array + { + $html = Http::get($url)->body(); + $crawler = new Crawler($html); + + $article = $crawler->filter('article')->first(); + + $header = trim($article->filter('h1')->text()); + $publishedAt = $article->filter('time')->attr('datetime'); + + $allowedTags = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li']; + $allowedInline = ['strong', 'b', 'em', 'i', 'br']; + + $content = []; + + foreach ($article as $node) { + foreach ($node->childNodes as $child) { + $content[] = HtmlCleaner::renderNode($child, $allowedTags, $allowedInline); + } + } + + $cleaned = HtmlCleaner::clean(implode("\n", array_filter($content))); + + return [ + 'header' => $header, + 'published_at' => $publishedAt, + 'content' => $cleaned, + ]; + } + protected function makeRequest(int $page, ?string $type = null): Response|false { if (empty($this->slug)) { @@ -102,4 +140,4 @@ class Cision 'next_page' => count($items) === 24 ? $page + 1 : false, ]; } -} \ No newline at end of file +} diff --git a/src/Support/HtmlCleaner.php b/src/Support/HtmlCleaner.php new file mode 100644 index 0000000..6dd7383 --- /dev/null +++ b/src/Support/HtmlCleaner.php @@ -0,0 +1,107 @@ +wholeText; + } + + if (!($node instanceof DOMElement)) { + return null; + } + + $tag = $node->nodeName; + + // Special case: handle
within

+ if ($tag === 'br') { + if ($parentTag === 'p') { + $prev = $node->previousSibling; + $next = $node->nextSibling; + + $hasTextBefore = $prev instanceof DOMText && trim($prev->textContent) !== ''; + $hasTextAfter = $next instanceof DOMText && trim($next->textContent) !== ''; + + if ($hasTextBefore && $hasTextAfter) { + return '
'; + } + } + + // Skip all other
+ return null; + } + + // Recursively render children + $inner = ''; + foreach ($node->childNodes as $child) { + $rendered = self::renderNode($child, $allowedTags, $allowedInline, $tag); + if ($rendered !== null) { + $inner .= $rendered; + } + } + + // Handle block tags + if (in_array($tag, $allowedTags)) { + if ($tag === 'p') { + $html = $node->C14N(); + $isItalic = str_starts_with(trim($html), '" . trim($inner) . "

"; + } + + return "<{$tag}>" . trim($inner) . ""; + } + + // Handle inline tags + if (in_array($tag, $allowedInline)) { + return "<{$tag}>{$inner}"; + } + + // Unknown tags are ignored but their content is preserved + return $inner; + } + + /** + * Clean a raw HTML string: remove unwanted tags, empty elements, garbage phrases, and excessive whitespace. + */ + public static function clean(string $html): string + { + // Remove first

+ $html = preg_replace('/]*>.*?<\/h1>/i', '', $html, 1); + + // Remove empty tags like , + $html = preg_replace('/<(\w+)[^>]*>\s*<\/\1>/', '', $html); + + // Remove specific garbage strings + $unwantedStrings = [ + 'Report this content', + '

Taggar:

', + ]; + + foreach ($unwantedStrings as $str) { + $html = str_replace($str, '', $html); + } + + // Remove localized date lines like: fre, jul 04, 2025 09:55 CET + $html = preg_replace('/\b[a-z]{3},\s[a-z]{3}\s\d{2},\s\d{4}\s\d{2}:\d{2}\sCET\b/i', '', $html); + + // Normalize whitespace + $html = str_replace(["\u{A0}", "\t", "\r"], ' ', $html); // Remove non-breaking spaces and tabs + $html = preg_replace('/[ ]{2,}/', ' ', $html); // Collapse double spaces + $html = preg_replace("/(\n\s*){2,}/", "\n", $html); // Collapse multiple newlines + $html = str_replace([' ', ' \n'], '', $html); // Additional spacing clean + $html = trim($html); + + return $html; + } +} \ No newline at end of file