Refactor
This commit is contained in:
1 parent
6d3092c736
commit
b325fff158
3 files changed
+149
-2
No files matched your search
+4
-2
@@ -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
|
||||
}
|
||||
@@ -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)) {
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Mattitja\Cision\Support;
|
||||
|
||||
use DOMNode;
|
||||
use DOMText;
|
||||
use DOMElement;
|
||||
|
||||
class HtmlCleaner
|
||||
{
|
||||
/**
|
||||
* Render a DOM node into safe HTML, preserving allowed block and inline tags.
|
||||
*/
|
||||
public static function renderNode(DOMNode $node, array $allowedTags, array $allowedInline, string $parentTag = ''): ?string
|
||||
{
|
||||
if ($node instanceof DOMText) {
|
||||
return $node->wholeText;
|
||||
}
|
||||
|
||||
if (!($node instanceof DOMElement)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$tag = $node->nodeName;
|
||||
|
||||
// Special case: handle <br> within <p>
|
||||
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 '<br>';
|
||||
}
|
||||
}
|
||||
|
||||
// Skip all other <br>
|
||||
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), '<em') || str_starts_with(trim($html), '<i');
|
||||
$class = $isItalic ? ' class="italic"' : '';
|
||||
return "<p{$class}>" . trim($inner) . "</p>";
|
||||
}
|
||||
|
||||
return "<{$tag}>" . trim($inner) . "</{$tag}>";
|
||||
}
|
||||
|
||||
// Handle inline tags
|
||||
if (in_array($tag, $allowedInline)) {
|
||||
return "<{$tag}>{$inner}</{$tag}>";
|
||||
}
|
||||
|
||||
// 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 <h1>
|
||||
$html = preg_replace('/<h1\b[^>]*>.*?<\/h1>/i', '', $html, 1);
|
||||
|
||||
// Remove empty tags like <i></i>, <span></span>
|
||||
$html = preg_replace('/<(\w+)[^>]*>\s*<\/\1>/', '', $html);
|
||||
|
||||
// Remove specific garbage strings
|
||||
$unwantedStrings = [
|
||||
'Report this content',
|
||||
'<p><strong>Taggar:</strong></p>',
|
||||
];
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user