.*?)\1[^>]*>';
private const PATTERNS = array(
'link' => '#]*?\bhref' . self::ADDRESS_VALUE
. '(?.*?)#is',
'image' => '#
]*?\bsrc' . self::ADDRESS_VALUE . '#is',
'embed' => '#<(?:iframe|video|audio|source|embed)\s[^>]*?\bsrc'
. self::ADDRESS_VALUE . '#is',
);
private const ALT_PATTERN = '#\balt\s*=\s*(["\'])(?.*?)\1#is';
public function __construct(
private AddressRules $address_rules,
private PlainText $plain_text
) {
}
/** @return list */
public function find_in( string $content ): array {
$found = array();
foreach ( self::PATTERNS as $kind => $pattern ) {
$matches = array();
preg_match_all(
$pattern,
$content,
$matches,
PREG_SET_ORDER | PREG_OFFSET_CAPTURE
);
foreach ( $matches as $match ) {
$address = $this->address_rules->normalize(
$match['address'][0]
);
if ( null === $address ) {
continue;
}
list( $tag, $tag_start ) = $match[0];
$found[] = new FoundAddress(
$address,
LinkKind::from( $kind ),
$this->text_of( $tag, $match['text'][0] ?? '' ),
$this->before( $content, $tag_start ),
$this->after( $content, $tag_start + strlen( $tag ) )
);
}
}
return $found;
}
private function text_of( string $tag, string $inner_html ): string {
if ( '' !== $inner_html ) {
return trim( $this->plain_text->from_html( $inner_html ) );
}
$alt = array();
preg_match( self::ALT_PATTERN, $tag, $alt );
return trim( $this->plain_text->from_html( $alt['alt'] ?? '' ) );
}
private function before( string $content, int $offset ): string {
$text = $this->plain_text->from_html( substr( $content, 0, $offset ) );
return mb_substr( $text, -self::CONTEXT_LENGTH );
}
private function after( string $content, int $offset ): string {
$text = $this->plain_text->from_html( substr( $content, $offset ) );
return mb_substr( $text, 0, self::CONTEXT_LENGTH );
}
}