Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
22.22% covered (danger)
22.22%
6 / 27
CRAP
26.74% covered (danger)
26.74%
23 / 86
Utils
0.00% covered (danger)
0.00%
0 / 1
22.22% covered (danger)
22.22%
6 / 27
1523.80
26.74% covered (danger)
26.74%
23 / 86
 stripParsoidIdPrefix
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 stripNamespace
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 isParsoidObjectId
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 isVoidElement
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 recursiveClone
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 clone
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 3
 lastUniChar
0.00% covered (danger)
0.00%
0 / 1
42
0.00% covered (danger)
0.00%
0 / 8
 isUniWord
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 phpURLEncode
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 decodeURI
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 decodeURIComponent
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
8 / 8
 extractExtBody
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 3
 isValidOffset
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 1
 isValidDSR
0.00% covered (danger)
0.00%
0 / 1
56
0.00% covered (danger)
0.00%
0 / 5
 normalizeNamespaceName
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 decodeWtEntities
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 escapeWtEntities
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
6 / 6
 escapeHtml
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 entityEncodeAll
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 isProtocolValid
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 4
 getExtArgInfo
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 5
 parseMediaDimensions
0.00% covered (danger)
0.00%
0 / 1
42
0.00% covered (danger)
0.00%
0 / 10
 validateMediaParam
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 1
 getStar
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 magicMasqs
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 isLinkTrail
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 1
 bcp47n
0.00% covered (danger)
0.00%
0 / 1
72
0.00% covered (danger)
0.00%
0 / 11
<?php
declare( strict_types = 1 );
namespace Wikimedia\Parsoid\Utils;
use Wikimedia\Parsoid\Config\Env;
use Wikimedia\Parsoid\Core\DomSourceRange;
use Wikimedia\Parsoid\Core\Sanitizer;
use Wikimedia\Parsoid\Tokens\Token;
use Wikimedia\Parsoid\Wikitext\Consts;
/**
 * This file contains general utilities for token transforms.
 */
class Utils {
    /**
     * Regular expression fragment for matching wikitext comments.
     * Meant for inclusion in other regular expressions.
     */
    // Maintenance note: this is used in /x regexes so all whitespace and # should be escaped
    public const COMMENT_REGEXP_FRAGMENT = '<!--(?>[\s\S]*?-->)';
    /** Regular fragment for matching a wikitext comment */
    public const COMMENT_REGEXP = '/' . self::COMMENT_REGEXP_FRAGMENT . '/';
    /**
     * Strip Parsoid id prefix from aboutID
     *
     * @param string $aboutId aboud ID string
     * @return string
     */
    public static function stripParsoidIdPrefix( string $aboutId ): string {
        // 'mwt' is the prefix used for new ids
        return preg_replace( '/^#?mwt/', '', $aboutId );
    }
    /**
     * Strip PHP namespace from the fully qualified class name
     * @param string $className
     * @return string
     */
    public static function stripNamespace( string $className ): string {
        return preg_replace( '/.*\\\\/', '', $className );
    }
    /**
     * Check for Parsoid id prefix in an aboutID string
     *
     * @param string $aboutId aboud ID string
     * @return bool
     */
    public static function isParsoidObjectId( string $aboutId ): bool {
        // 'mwt' is the prefix used for new ids
        return str_starts_with( $aboutId, '#mwt' );
    }
    /**
     * Determine if the named tag is void (can not have content).
     *
     * @param string $name tag name
     * @return bool
     */
    public static function isVoidElement( string $name ): bool {
        return isset( Consts::$HTML['VoidTags'][$name] );
    }
    /**
     * recursive deep clones helper function
     *
     * @param object $el object
     * @return object
     */
    private static function recursiveClone( $el ) {
        return self::clone( $el, true );
    }
    /**
     * deep clones by default.
     * FIXME, see T161647
     * @param object|array $obj any plain object not tokens or DOM trees
     * @param bool $deepClone
     * @return object|array
     */
    public static function clone( $obj, $deepClone = true ) {
        if ( !$deepClone && is_object( $obj ) ) {
            return clone $obj;
        }
        return unserialize( serialize( $obj ) );
    }
    /**
     * Extract the last *unicode* character of the string.
     * This might be more than one byte, if the last character
     * is non-ASCII.
     * @param string $str
     * @param ?int $idx The index *after* the character to extract; defaults
     *   to the length of $str, which will extract the last character in
     *   $str.
     * @return string
     */
    public static function lastUniChar( string $str, ?int $idx = null ): string {
        if ( $idx === null ) {
            $idx = strlen( $str );
        } elseif ( $idx <= 0 || $idx > strlen( $str ) ) {
            return '';
        }
        $c = $str[--$idx];
        while ( ( ord( $c ) & 0xC0 ) === 0x80 ) {
            $c = $str[--$idx] . $c;
        }
        return $c;
    }
    /**
     * Return true if the first character in $s is a unicode word character.
     * @param string $s
     * @return bool
     */
    public static function isUniWord( string $s ): bool {
        return preg_match( '#^\w#u', $s ) === 1;
    }
    /**
     * This should not be used.
     * @param string $txt URL to encode using PHP encoding
     * @return string
     */
    public static function phpURLEncode( $txt ) {
        // @phan-suppress-previous-line PhanPluginNeverReturnMethod
        throw new \BadMethodCallException( 'Use urlencode( $txt ) instead' );
    }
    /**
     * Percent-decode only valid UTF-8 characters, leaving other encoded bytes alone.
     *
     * Distinct from `decodeURIComponent` in that certain escapes are not decoded,
     * matching the behavior of JavaScript's decodeURI().
     *
     * @see https://www.ecma-international.org/ecma-262/6.0/#sec-decodeuri-encodeduri
     * @param string $s URI to be decoded
     * @return string
     */
    public static function decodeURI( string $s ): string {
        // Escape the '%' in sequences for the reserved characters, then use decodeURIComponent.
        $s = preg_replace( '/%(?=2[346bcfBCF]|3[abdfABDF]|40)/', '%25', $s );
        return self::decodeURIComponent( $s );
    }
    /**
     * Percent-decode only valid UTF-8 characters, leaving other encoded bytes alone.
     *
     * @param string $s URI to be decoded
     * @return string
     */
    public static function decodeURIComponent( string $s ): string {
        // Most of the time we should have valid input
        $ret = rawurldecode( $s );
        if ( mb_check_encoding( $ret, 'UTF-8' ) ) {
            return $ret;
        }
        // Extract each encoded character and decode it individually
        return preg_replace_callback(
            // phpcs:ignore Generic.Files.LineLength.TooLong
            '/%[0-7][0-9A-F]|%[CD][0-9A-F]%[89AB][0-9A-F]|%E[0-9A-F](?:%[89AB][0-9A-F]){2}|%F[0-4](?:%[89AB][0-9A-F]){3}/i',
            static function ( $match ) {
                $ret = rawurldecode( $match[0] );
                return mb_check_encoding( $ret, 'UTF-8' ) ? $ret : $match[0];
            }, $s
        );
    }
    /**
     * Extract extension source from the token
     *
     * @param Token $token token
     * @return string
     */
    public static function extractExtBody( Token $token ): string {
        $src = $token->getAttribute( 'source' );
        $extTagOffsets = $token->dataAttribs->extTagOffsets;
        '@phan-var \Wikimedia\Parsoid\Core\DomSourceRange $extTagOffsets';
        return $extTagOffsets->stripTags( $src );
    }
    /**
     * Helper function checks numeric values
     *
     * @param ?int $n checks parameters for numeric type and value zero or positive
     * @return bool
     */
    private static function isValidOffset( ?int $n ): bool {
        return $n !== null && $n >= 0;
    }
    /**
     * Check for valid DSR range(s)
     * DSR = "DOM Source Range".
     *
     * @param ?DomSourceRange $dsr DSR source range values
     * @param bool $all Also check the widths of the container tag
     * @return bool
     */
    public static function isValidDSR(
        ?DomSourceRange $dsr, bool $all = false
    ): bool {
        return $dsr !== null &&
            self::isValidOffset( $dsr->start ) &&
            self::isValidOffset( $dsr->end ) &&
            ( !$all || ( self::isValidOffset( $dsr->openWidth ) &&
                self::isValidOffset( $dsr->closeWidth )
                ) );
    }
    /**
     * Cannonicalizes a namespace name.
     *
     * @param string $name Non-normalized namespace name.
     * @return string
     */
    public static function normalizeNamespaceName( string $name ): string {
        return strtr( mb_strtolower( $name ), ' ', '_' );
    }
    /**
     * Decode HTML5 entities in wikitext.
     *
     * NOTE that wikitext only allows semicolon-terminated entities, while
     * HTML allows a number of "legacy" entities to be decoded without
     * a terminating semicolon.  This function deliberately does not
     * decode these HTML-only entity forms.
     *
     * @param string $text
     * @return string
     */
    public static function decodeWtEntities( string $text ): string {
        // Note that HTML5 allows semicolon-less entities which
        // wikitext does not: in wikitext all entities must end in a
        // semicolon.
        // By normalizing before decoding, this routine deliberately
        // does not decode entity references which are invalid in wikitext
        // (mostly because they decode to invalid codepoints).