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) . "
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