File: /home/zwafgyp/lacalita/wp-content/plugins/loco-translate/lib/compiled/gettext.php
<?php
/**
* Downgraded for PHP 5.6 compatibility. Do not edit.
* @noinspection ALL
*/
interface LocoArrayInterface extends ArrayAccess, Iterator, Countable, JsonSerializable {
public function getArrayCopy();
}
class LocoHeaders extends ArrayIterator implements LocoArrayInterface {
private $map = [];
public function __construct( array $raw = [] ) {
if ( $raw ) {
$keys = array_keys( $raw );
$this->map = array_combine( array_map( 'strtolower', $keys ), $keys );
parent::__construct( $raw );
}
}
public function normalize( $k ) {
$k = strtolower( $k );
return array_key_exists( $k, $this->map ) ? $this->map[ $k ] : null;
}
public function add( $key, $val ) {
$this->offsetSet( $key, $val );
return $this;
}
public function __toString() {
$pairs = [];
foreach ( $this as $key => $val ) {
$pairs[] = $key . ': ' . $val;
}
return implode( "\n", $pairs );
}
public function trimmed( $prop ) {
return trim( $this->__get( $prop ) );
}
public function has( $key ) {
return array_key_exists( strtolower( $key ), $this->map );
}
public function __get( $key ) {
return $this->offsetGet( $key );
}
public function __set( $key, $val ) {
$this->offsetSet( $key, $val );
}
#[ReturnTypeWillChange]
public function offsetExists( $key ) {
return $this->has( $key );
}
#[ReturnTypeWillChange]
public function offsetGet( $key ) {
$k = $this->normalize( $key );
if ( is_null( $k ) ) {
return '';
}
return parent::offsetGet( $k );
}
#[ReturnTypeWillChange]
public function offsetSet( $key, $value ) {
$k = strtolower( $key );
if ( isset( $this->map[ $k ] ) && $key !== $this->map[ $k ] ) {
parent::offsetUnset( $this->map[ $k ] );
}
$this->map[ $k ] = $key;
parent::offsetSet( $key, $value );
}
#[ReturnTypeWillChange]
public function offsetUnset( $key ) {
$k = strtolower( $key );
if ( isset( $this->map[ $k ] ) ) {
parent::offsetUnset( $this->map[ $k ] );
unset( $this->map[ $k ] );
}
}
#[ReturnTypeWillChange]
public function jsonSerialize() {
return $this->getArrayCopy();
}
}
function loco_normalize_charset( $cs ) {
if ( preg_match( '/^UTF-?8$/i', $cs ) ) {
return 'UTF-8';
}
try {
$aliases = @mb_encoding_aliases( $cs );
} catch ( ValueError $e ) {
$aliases = false;
}
if ( false === $aliases ) {
throw new InvalidArgumentException( 'Unsupported character encoding: ' . $cs );
}
if ( preg_grep( '/^ISO[-_]\\d+[-_]\\d+$/i', $aliases ) ) {
$cs = current( $aliases );
$cs = strtr( strtoupper( $cs ), '_', '-' );
} else if ( in_array( 'US-ASCII', $aliases, true ) ) {
$cs = 'US-ASCII';
}
return $cs;
}
class LocoPoHeaders extends LocoHeaders {
private $cs = null;
public function getCharset() {
$cs = $this->cs;
if ( is_null( $cs ) ) {
$cs = '';
$raw = $this->offsetGet( 'content-type' );
if ( $raw && preg_match( '!\\bcharset[= ]+([-\\w]+)!', $raw, $r ) ) {
try {
$cs = loco_normalize_charset( $r[1] );
} catch ( InvalidArgumentException $e ) {
} catch ( Exception $e ) {
trigger_error( $e->getMessage(), E_USER_NOTICE );
}
}
$this->cs = $cs;
}
return $cs;
}
public function setCharset( $to ) {
$to = loco_normalize_charset( $to );
$from = $this->getCharset();
$this->cs = $to;
$this['Content-Type'] = 'text/plain; charset=' . $to;
if ( '' !== $from && $from !== $to ) {
foreach ( $this as $key => $val ) {
$this[ $key ] = mb_convert_encoding( $val, $to, $from );
}
}
return $to;
}
public static function fromMsgstr( $str ) {
$headers = new LocoPoHeaders;
$key = '';
foreach ( preg_split( '/[\\r\\n]+/', $str ) as $line ) {
$i = strpos( $line, ':' );
if ( is_int( $i ) ) {
$key = trim( substr( $line, 0, $i ), " \t" );
$headers->offsetSet( $key, ltrim( substr( $line, ++ $i ), " \t" ) );
} else if ( '' !== $key ) {
$headers->offsetSet( $key, $headers->offsetGet( $key ) . "\n" . $line );
}
}
$cs = $headers->getCharset();
if ( '' !== $cs && 'UTF-8' !== $cs && 'UTF-8' !== mb_detect_encoding( $str, [ 'UTF-8', $cs ], true ) ) {
foreach ( $headers as $key => $val ) {
$headers[ $key ] = mb_convert_encoding( $val, 'UTF-8', [ $cs ] );
}
}
return $headers;
}
public static function fromSource( $raw ) {
$po = new LocoPoParser( $raw );
$po->parse( 0 );
return $po->getHeader();
}
}
function loco_convert_utf8( $str, $enc, $strict ) {
if ( '' === $enc || 'UTF-8' === $enc || 'US-ASCII' === $enc ) {
if ( false === preg_match( '//u', $str ) ) {
if ( $strict ) {
$e = new Loco_error_ParseException( $enc ? 'Invalid ' . $enc . ' encoding' : 'Unknown character encoding' );
if ( preg_match( '/^(?:[\\x00-\\x7F]|[\\xC0-\\xDF][\\x80-\\xBF]|[\\xE0-\\xEF][\\x80-\\xBF]{2}|[\\xF0-\\xFF][\\x80-\\xBF]{3})*/', $str, $r ) && $str !== $r[0] ) {
$e->setOffsetContext( strlen( $r[0] ), $str );
}
throw $e;
}
$str = loco_fix_utf8( $str );
}
} else if ( 'ISO-8859-1' === $enc ) {
$str = mb_convert_encoding( $str, 'UTF-8', 'cp1252' );
} else {
$str = mb_convert_encoding( $str, 'UTF-8', $enc );
}
return $str;
}
function loco_fix_utf8( $str ) {
$fix = '';
while ( is_string( $str ) && '' !== $str ) {
if ( preg_match( '/^(?:[\\x00-\\x7F]|[\\xC0-\\xDF][\\x80-\\xBF]|[\\xE0-\\xEF][\\x80-\\xBF]{2}|[\\xF0-\\xFF][\\x80-\\xBF]{3})+/', $str, $r ) ) {
$fix .= $r[0];
$str = substr( $str, strlen( $r[0] ) );
} else {
$fix .= mb_convert_encoding( $str[0], 'UTF-8', 'cp1252' );
$str = substr( $str, 1 );
}
}
return loco_convert_utf8( $fix, '', true );
}
abstract class LocoGettextParser {
private $head = null;
private $cs = '';
abstract public function parse( $limit = - 1 );
protected function setHeader( LocoPoHeaders $head ) {
$this->head = $head;
$cs = $head->getCharset();
if ( '' !== $cs ) {
if ( '' === $this->cs ) {
$this->setCharset( $cs );
}
}
return $head;
}
public function getHeader() {
return $this->head;
}
protected function setCharset( $cs ) {
$this->cs = $cs;
}
protected function getCharset() {
return $this->cs;
}
protected function str( $str ) {
if ( '' !== $str ) {
$str = loco_convert_utf8( $str, $this->cs, false );
}
return $str;
}
protected function initMsgKey( $key ) {
$r = explode( "\4", $key );
$value = [ 'source' => array_pop( $r ), 'target' => '', ];
if ( isset( $r[0] ) ) {
$value['context'] = $r[0];
}
return $value;
}
}
function loco_remove_bom( $s, &$c ) {
$bom = substr( $s, 0, 2 );
if ( "\xFF\xFE" === $bom ) {
$c = 'UTF-16LE';
return substr( $s, 2 );
}
if ( "\xFE\xFF" === $bom ) {
$c = 'UTF-16BE';
return substr( $s, 2 );
}
if ( "\xEF\xBB" === $bom && "\xBF" === $s[2] ) {
$c = 'UTF-8';
return substr( $s, 3 );
}
$c = '';
return $s;
}
function loco_parse_reference_id( $refs, &$_id ) {
if ( false === ( $n = strpos( $refs, 'loco:' ) ) ) {
$_id = '';
return $refs;
}
$_id = substr( $refs, $n + 5, 24 );
$refs = substr_replace( $refs, '', $n, 29 );
return trim( $refs );
}
class LocoPoParser extends LocoGettextParser implements Iterator {
private $lines = [];
private $i;
private $k;
private $m;
public function __construct( $src ) {
if ( '' !== $src ) {
$src = loco_remove_bom( $src, $cs );
if ( $cs && 'UTF-8' !== $cs ) {
$src = mb_convert_encoding( $src, 'UTF-8', $cs );
$cs = 'UTF-8';
}
if ( 'UTF-8' === $cs ) {
$this->setCharset( 'UTF-8' );
}
$this->lines = preg_split( '/(\\r\\n?|\\n)/', $src );
}
}
#[ReturnTypeWillChange]
public function rewind() {
$this->i = - 1;
$this->k = - 1;
$this->next();
}
#[ReturnTypeWillChange]
public function valid() {
return is_int( $this->i );
}
#[ReturnTypeWillChange]
public function key() {
return $this->k;
}
#[ReturnTypeWillChange]
public function current() {
return $this->m;
}
#[ReturnTypeWillChange]
public function next() {
$valid = false;
$entry = [ '#' => [], 'id' => [ null ], 'str' => [ null ] ];
$i = $this->i;
while ( array_key_exists( ++ $i, $this->lines ) ) {
$line = $this->lines[ $i ];
try {
if ( '' === $line ) {
if ( $valid ) {
break;
}
continue;
}
$c = $line[0];
if ( '#' === $c ) {
if ( $valid ) {
$i --;
break;
}
if ( '#' === $line ) {
continue;
}
$f = $line[1];
$entry['#'][ $f ][] = trim( substr( $line, 1 + strlen( $f ) ), "/ \n\r\t" );
} else if ( preg_match( '/^msg(id(?:_plural)?|ctxt|str(?:\\[(\\d+)])?)[ \\t]*/', $line, $r ) ) {
if ( isset( $r[2] ) ) {
$key = 'str';
$idx = (int) $r[2];
} else {
$key = $r[1];
$idx = 0;
}
if ( $valid && 'str' !== $key && null !== $entry['str'][0] ) {
$i --;
break;
}
$snip = strlen( $r[0] );
if ( '"' !== substr( $line, $snip, 1 ) ) {
throw new Exception( 'Expected " to follow msg' . $key );
}
$val = '';
$line = substr( $line, $snip );
while ( true ) {
if ( '"' === $line || ! substr( $line, - 1 ) === '"' ) {
throw new Exception( 'Unterminated msg' . $key );
}
$val .= substr( $line, 1, - 1 );
$j = $i + 1;
if ( array_key_exists( $j, $this->lines ) && ( $line = $this->lines[ $j ] ) && '"' === $line[0] ) {
$i = $j;
} else {
break;
}
}
if ( ! $valid ) {
$valid = true;
}
if ( 'id_plural' === $key ) {
$key = 'id';
$idx = 1;
}
$entry[ $key ][ $idx ] = stripcslashes( $val );
} else if ( preg_match( '/^[ \\t]+$/', $line ) ) {
if ( $valid ) {
break;
}
} else if ( '"' === $c ) {
throw new Exception( 'String encountered without keyword' );
} else {
throw new Exception( 'Junk' );
}
} catch ( Exception $e ) {
}
}
if ( $valid ) {
++ $this->k;
$this->i = $i;
$this->m = $entry;
} else {
$this->i = null;
$this->k = null;
$this->m = null;
}
}
public function parse( $limit = - 1 ) {
$this->rewind();
if ( ! $this->valid() ) {
throw new Loco_error_ParseException( 'Invalid PO file' );
}
$entry = $this->current();
if ( '' !== $entry['id'][0] || isset( $entry['ctxt'] ) || is_null( $entry['str'][0] ) ) {
$head = $this->setHeader( new LocoPoHeaders );
} else {
$head = $this->setHeader( LocoPoHeaders::fromMsgstr( $entry['str'][0] ) );
}
if ( 0 === $limit ) {
return [];
}
$i = - 1;
$assets = [];
$lk = $head['X-Loco-Lookup'];
while ( $this->valid() ) {
$entry = $this->current();
$msgid = $entry['id'][0];
if ( is_null( $msgid ) ) {
$this->next();
continue;
}
if ( ++ $i === $limit ) {
return $assets;
}
$asset = [
'source' => $this->str( $msgid ),
'target' => $this->str( (string) $entry['str'][0] ),
'context' => null,
];
$prev_entry = null;
if ( isset( $entry['ctxt'] ) ) {
$asset['context'] = $this->str( $entry['ctxt'][0] );
}
$cmt = $entry['#'];
if ( isset( $cmt[' '] ) ) {
$asset['comment'] = $this->str( implode( "\n", $cmt[' '] ) );
}
if ( isset( $cmt['.'] ) ) {
$asset['notes'] = $this->str( implode( "\n", $cmt['.'] ) );
}
if ( isset( $cmt[':'] ) ) {
if ( $refs = implode( ' ', $cmt[':'] ) ) {
$refs = $this->str( $refs );
if ( $refs = loco_parse_reference_id( $refs, $_id ) ) {
$asset['refs'] = $refs;
}
if ( $_id ) {
$asset['_id'] = $_id;
}
}
}
if ( isset( $cmt[','] ) ) {
foreach ( $cmt[','] as $flags ) {
foreach ( explode( ',', $flags ) as $flag ) {
if ( $flag = trim( $flag, " \t" ) ) {
if ( preg_match( '/^((?:no-)?\w+)-format/', $flag, $r ) ) {
$asset['format'] = $r[1];
} else if ( 'fuzzy' === $flag ) {
$asset['flag'] = 4;
}
}
}
}
}
if ( isset( $cmt['|'] ) ) {
$p = new LocoPoParser( '' );
$p->lines = $cmt['|'];
$p->setCharset( $this->getCharset() );
try {
$prev_entry = $p->parse();
} catch ( Loco_error_ParseException $e ) {
}
if ( $prev_entry ) {
$msgid = $prev_entry[0]['source'];
if ( $lk && 'text' !== $lk ) {
$asset[ $lk ] = $asset['source'];
$asset['source'] = $msgid;
} else if ( substr( $msgid, 0, 5 ) === 'loco:' ) {
$asset['_id'] = substr( $msgid, 5 );
} else {
$asset['prev'] = $prev_entry;
$prev_entry = null;
}
}
}
$assets[] = $asset;
if ( isset( $entry['id'][1] ) ) {
$idx = 0;
$pidx = count( $assets ) - 1;
$num = max( 2, count( $entry['str'] ) );
while ( ++ $idx < $num ) {
$plural = [
'source' => '',
'target' => isset( $entry['str'][ $idx ] ) ? $this->str( $entry['str'][ $idx ] ) : '',
'plural' => $idx,
'parent' => $pidx,
];
if ( 1 === $idx ) {
$plural['source'] = $this->str( $entry['id'][1] );
if ( is_array( $prev_entry ) && isset( $prev_entry[1] ) ) {
if ( $lk && 'text' !== $lk ) {
$plural[ $lk ] = $plural['source'];
$plural['source'] = $prev_entry[1]['source'];
}
}
}
if ( isset( $asset['flag'] ) ) {
$plural['flag'] = $asset['flag'];
}
$assets[] = $plural;
}
}
$this->next();
}
if ( - 1 === $i ) {
throw new Loco_error_ParseException( 'Invalid PO file' );
} else if ( 0 === $i && '' === $assets[0]['source'] && '' === $assets[0]['target'] ) {
throw new Loco_error_ParseException( 'Invalid PO file' );
}
return $assets;
}
}
class LocoMoParser extends LocoGettextParser {
private $bin;
private $be = null;
private $n = null;
private $o = null;
private $t = null;
private $v = null;
public function __construct( $bin ) {
$this->bin = $bin;
}
public function getAt( $idx ) {
$offset = $this->targetOffset();
$offset += ( $idx * 8 );
$len = $this->integerAt( $offset );
$idx = $this->integerAt( $offset + 4 );
$txt = $this->bytes( $idx, $len );
if ( false === strpos( $txt, "\0" ) ) {
return $txt;
}
return explode( "\0", $txt );
}
public function parse( $limit = - 1 ) {
$i = - 1;
$r = [];
$sourceOffset = $this->sourceOffset();
$targetOffset = $this->targetOffset();
$soffset = $sourceOffset;
$toffset = $targetOffset;
while ( $soffset < $targetOffset ) {
$len = $this->integerAt( $soffset );
$idx = $this->integerAt( $soffset + 4 );
$src = $this->bytes( $idx, $len );
$eot = strpos( $src, "\x04" );
if ( false === $eot ) {
$context = null;
} else {
$context = $this->str( substr( $src, 0, $eot ) );
$src = substr( $src, $eot + 1 );
}
$sources = explode( "\0", $src, 2 );
$len = $this->integerAt( $toffset );
$idx = $this->integerAt( $toffset + 4 );
$targets = explode( "\0", $this->bytes( $idx, $len ) );
if ( - 1 === $i && '' === $sources[0] && is_null( $context ) ) {
$this->setHeader( LocoPoHeaders::fromMsgstr( $targets[0] ) );
}
if ( ++ $i > $limit && - 1 !== $limit ) {
break;
}
$r[ $i ] = [
'source' => $this->str( $sources[0] ),
'target' => $this->str( $targets[0] ),
'context' => $context,
];
if ( isset( $sources[1] ) ) {
$p = count( $r ) - 1;
$nforms = max( 2, count( $targets ) );
for ( $n = 1; $n < $nforms; $n ++ ) {
$r[ ++ $i ] = [
'source' => isset( $sources[ $n ] ) ? $this->str( $sources[ $n ] ) : sprintf( '%s (plural %u)', $r[ $p ]['source'], $n ),
'target' => isset( $targets[ $n ] ) ? $this->str( $targets[ $n ] ) : '',
'parent' => $p,
'plural' => $n,
];
}
}
$soffset += 8;
$toffset += 8;
}
return $r;
}
public function isBigendian() {
if ( is_null( $this->be ) ) {
$str = $this->words( 0, 1 );
if ( "\xDE\x12\x04\x95" === $str ) {
$this->be = false;
} else if ( "\x95\x04\x12\xDE" === $str ) {
$this->be = true;
} else {
throw new Loco_error_ParseException( 'Invalid MO format' );
}
}
return $this->be;
}
public function version() {
if ( is_null( $this->v ) ) {
$this->v = $this->integerWord( 1 );
}
return $this->v;
}
#[ReturnTypeWillChange]
public function count() {
if ( is_null( $this->n ) ) {
$this->n = $this->integerWord( 2 );
}
return $this->n;
}
public function sourceOffset() {
if ( is_null( $this->o ) ) {
$this->o = $this->integerWord( 3 );
}
return $this->o;
}
public function targetOffset() {
if ( is_null( $this->t ) ) {
$this->t = $this->integerWord( 4 );
}
return $this->t;
}
public function getHashTable() {
$s = $this->integerWord( 5 );
$h = $this->integerWord( 6 );
return $this->bytes( $h, $s * 4 );
}
private function bytes( $offset, $length ) {
$s = substr( $this->bin, $offset, $length );
if ( strlen( $s ) !== $length ) {
throw new Loco_error_ParseException( 'Failed to read ' . $length . ' bytes at [' . $offset . ']' );
}
return $s;
}
private function words( $offset, $length ) {
return $this->bytes( $offset * 4, $length * 4 );
}
private function integerWord( $offset ) {
return $this->integerAt( $offset * 4 );
}
private function integerAt( $offset ) {
$str = $this->bytes( $offset, 4 );
$fmt = $this->isBigendian() ? 'N' : 'V';
$arr = unpack( $fmt, $str );
if ( ! isset( $arr[1] ) || ! is_int( $arr[1] ) ) {
throw new Loco_error_ParseException( 'Failed to read integer at byte ' . $offset );
}
return $arr[1];
}
}
class LocoJedParser extends LocoGettextParser {
private $ld;
public function __construct( array $struct ) {
$this->ld = $struct;
}
public function parse( $limit = - 1 ) {
$values = [];
foreach ( $this->ld as $messages ) {
$msgid = key( $messages );
if ( '' === $msgid ) {
$this->setHeader( new LocoJedHeaders( $messages[''] ) );
unset( $messages[''] );
} else {
$this->setHeader( new LocoJedHeaders );
}
$values[] = [ 'source' => '', 'target' => $this->getHeader(), ];
$i = - 1;
foreach ( $messages as $key => $list ) {
if ( ++ $i === $limit ) {
break;
}
$value = $this->initMsgKey( $key );
$index = count( $values );
foreach ( $list as $j => $msgstr ) {
if ( ! is_string( $msgstr ) ) {
throw new Loco_error_ParseException( 'msgstr must be scalar' );
}
$value['target'] = $msgstr;
if ( 0 < $j ) {
$value['plural'] = $j;
$value['parent'] = $index;
$value['source'] = '';
}
$values[] = $value;
}
}
}
return $values;
}
}
class LocoJedHeaders extends LocoPoHeaders {
public function __construct( array $raw = [] ) {
foreach ( [ 'Language' => 'lang', 'plural_forms' => 'Plural-Forms' ] as $canonical => $alias ) {
if ( array_key_exists( $alias, $raw ) && ! array_key_exists( $canonical, $raw ) ) {
$raw[ $canonical ] = $raw[ $alias ];
}
}
parent::__construct( $raw );
}
}
class LocoMoPhpParser extends LocoGettextParser {
private $msgs;
public function __construct( array $struct ) {
$this->msgs = $struct['messages'];
unset( $struct['messages'] );
$this->setHeader( new LocoPoHeaders( $struct ) );
}
public function parse( $limit = - 1 ) {
$values = [ [ 'source' => '', 'target' => $this->getHeader(), ] ];
$i = - 1;
foreach ( $this->msgs as $key => $bin ) {
if ( ++ $i === $limit ) {
break;
}
$value = $this->initMsgKey( $key );
$index = count( $values );
foreach ( explode( "\0", $bin ) as $i => $msgstr ) {
$value['target'] = $msgstr;
if ( 0 < $i ) {
$value['plural'] = $i;
$value['parent'] = $index;
$value['source'] = '';
}
$values[] = $value;
}
}
return $values;
}
}
abstract class LocoPo {
public static function pair( $key, $text, $width = 79, $eol = "\n", $esc = '\\n' ) {
if ( '' === $text ) {
return $key . ' ""';
}
$text = addcslashes( $text, "\t\x0B\x0C\x07\x08\\\"" );
if ( $esc ) {
$text = preg_replace( '/\\r\\n?|\\n/', $esc . $eol, $text, - 1, $nbr );
} else {
$eol = "\n";
$text = preg_replace_callback( '/\\r\\n?|\\n/', [ __CLASS__, 'replace_br' ], $text, - 1, $nbr );
}
if ( $nbr ) {
} else if ( $width && $width < mb_strlen( $text, 'UTF-8' ) + strlen( $key ) + 3 ) {
} else {
return $key . ' "' . $text . '"';
}
$lines = [ $key . ' "' ];
if ( $width ) {
$width -= 2;
$a = '/^.{0,' . ( $width - 1 ) . '}[-– .,:;?!)\\]}>]/u';
$b = '/^[^-– .,:;?!)\\]}>]+/u';
foreach ( explode( $eol, $text ) as $unwrapped ) {
$length = mb_strlen( $unwrapped, 'UTF-8' );
while ( $length > $width ) {
if ( preg_match( $a, $unwrapped, $r ) ) {
$line = $r[0];
} else if ( preg_match( $b, $unwrapped, $r ) ) {
$line = $r[0];
} else {
throw new Exception( 'Wrapping error' );
}
$lines[] = $line;
$trunc = mb_strlen( $line, 'UTF-8' );
$length -= $trunc;
$unwrapped = (string) substr( $unwrapped, strlen( $line ) );
if ( ( '' === $unwrapped && 0 !== $length ) || ( 0 === $length && '' !== $unwrapped ) ) {
throw new Exception( 'Truncation error' );
}
}
if ( 0 !== $length ) {
$lines[] = $unwrapped;
}
}
} else {
foreach ( explode( $eol, $text ) as $unwrapped ) {
$lines[] = $unwrapped;
}
}
return implode( '"' . $eol . '"', $lines ) . '"';
}
private static function replace_br( array $r ) {
return addcslashes( $r[0], "\r\n" ) . "\n";
}
public static function refs( $text, $width = 76, $eol = "\n" ) {
$text = preg_replace( '/\\s+/u', ' ', $text );
if ( $width ) {
$text = wordwrap( $text, $width, $eol . '#: ' );
}
return '#: ' . $text;
}
public static function prefix( $text, $prefix, $eol = "\n" ) {
return $prefix . implode( $eol . $prefix, self::split( $text ) );
}
public static function split( $text ) {
$lines = preg_split( '/\\R/u', $text );
if ( false === $lines ) {
if ( false === preg_match( '//u', $text ) ) {
$text = mb_convert_encoding( $text, 'UTF-8', 'cp1252' );
}
$lines = preg_split( '/\\r?\\n+/', $text );
}
return $lines;
}
public static function trim( $text ) {
$lines = [];
$deferred = null;
foreach ( explode( "\n", $text ) as $line ) {
if ( '' === $line ) {
continue;
}
if ( preg_match( '/^msg[a-z]+(?:\\[\\d+])? ""/', $line ) ) {
$deferred = $line;
continue;
}
if ( $deferred && '"' === $line[0] ) {
$lines[] = $deferred;
$deferred = null;
}
$lines[] = $line;
}
return implode( "\n", $lines );
}
}
class LocoPoIndex extends ArrayIterator {
public function compare( LocoPoMessage $a, LocoPoMessage $b ) {
$h = $a->getHash();
if ( ! isset( $this[ $h ] ) ) {
return 1;
}
$j = $b->getHash();
if ( ! isset( $this[ $j ] ) ) {
return - 1;
}
return $this[ $h ] > $this[ $j ] ? 1 : - 1;
}
}
class LocoPoMessage extends ArrayObject {
public function __construct( array $r ) {
$r['key'] = $r['source'];
parent::__construct( $r );
}
public function __get( $prop ) {
return $this->offsetExists( $prop ) ? $this->offsetGet( $prop ) : null;
}
public function isFuzzy() {
return 4 === $this->__get( 'flag' );
}
public function getFormat() {
$f = $this->__get( 'format' );
if ( is_string( $f ) && '' !== $f ) {
return $f;
}
return '';
}
private function getPoFlags() {
$flags = [];
foreach ( array_merge( [ $this ], $this->__get( 'plurals' ) ?: [] ) as $form ) {
if ( $form->isFuzzy() ) {
$flags[0] = 'fuzzy';
}
$f = $form->getFormat();
if ( '' !== $f ) {
$flags[1] = $f . '-format';
}
}
return array_values( $flags );
}
public function getHash() {
$hash = $this->getKey();
if ( $this->offsetExists( 'plurals' ) ) {
foreach ( $this->offsetGet( 'plurals' ) as $p ) {
$hash .= "\0" . $p->getHash();
break;
}
}
return $hash;
}
public function getKey() {
$msgid = (string) $this['source'];
$msgctxt = (string) $this->__get( 'context' );
if ( '' !== $msgctxt ) {
if ( '' === $msgid ) {
$msgid = '(' . $msgctxt . ')';
}
$msgid = $msgctxt . "\4" . $msgid;
}
return $msgid;
}
public function exportSerial( $f = 'target' ) {
$a = [ $this[ $f ] ];
if ( $this->offsetExists( 'plurals' ) ) {
$plurals = $this->offsetGet( 'plurals' );
if ( is_array( $plurals ) ) {
foreach ( $plurals as $p ) {
$a[] = $p[ $f ];
}
}
}
return $a;
}
public function __toString() {
return $this->render( 79, 76 );
}
public function render( $width, $ref_width, $max_forms = 0 ) {
$s = [];
try {
if ( $text = $this->__get( 'comment' ) ) {
$s[] = LocoPo::prefix( $text, '# ' );
}
if ( $text = $this->__get( 'notes' ) ) {
$s[] = LocoPo::prefix( $text, '#. ' );
}
if ( $text = $this->__get( 'refs' ) ) {
$s[] = LocoPo::refs( $text, $ref_width );
}
if ( $texts = $this->getPoFlags() ) {
$s[] = '#, ' . implode( ', ', $texts );
}
$prev = $this->__get( 'prev' );
if ( is_array( $prev ) && $prev ) {
foreach ( new LocoPoIterator( $prev ) as $p ) {
$text = $p->render( max( 0, $width - 3 ), 0 );
$s[] = LocoPo::prefix( LocoPo::trim( $text ), '#| ' );
break;
}
}
$text = $this->__get( 'context' );
if ( is_string( $text ) && '' !== $text ) {
$s[] = LocoPo::pair( 'msgctxt', $text, $width );
}
$s[] = LocoPo::pair( 'msgid', $this['source'], $width );
$target = $this['target'];
$plurals = $this->__get( 'plurals' );
if ( is_array( $plurals ) ) {
if ( array_key_exists( 0, $plurals ) ) {
$p = $plurals[0];
$s[] = LocoPo::pair( 'msgid_plural', $p['source'], $width );
$s[] = LocoPo::pair( 'msgstr[0]', $target, $width );
$i = 0;
while ( array_key_exists( $i, $plurals ) ) {
$p = $plurals[ $i ];
if ( ++ $i === $max_forms ) {
break;
}
$s[] = LocoPo::pair( 'msgstr[' . $i . ']', $p['target'], $width );
}
} else if ( isset( $this['plural_key'] ) ) {
$s[] = LocoPo::pair( 'msgid_plural', $this['plural_key'], $width );
$s[] = LocoPo::pair( 'msgstr[0]', $target, $width );
} else {
trigger_error( 'Missing plural_key in zero plural export' );
$s[] = LocoPo::pair( 'msgstr', $target, $width );
}
} else {
$s[] = LocoPo::pair( 'msgstr', $target, $width );
}
} catch ( Exception $e ) {
trigger_error( $e->getMessage(), E_USER_WARNING );
}
return implode( "\n", $s ) . "\n";
}
public function merge( LocoPoMessage $def, $translate = false ) {
if ( $def->getHash() !== $this->getHash() ) {
$prev = [ 'source' => '', 'target' => '' ];
$prev = $this->diff( 'source', $def, $prev );
$prev = $this->diff( 'context', $def, $prev );
$this['flag'] = 4;
$this['prev'] = [ $prev ];
$defPlural = $def->getPlural( 0 );
$ourPlural = $this->getPlural( 0 );
if ( $defPlural && $ourPlural ) {
$ourPlural->merge( $defPlural );
if ( $ourPlural->offsetExists( 'prev' ) ) {
$this['prev'][] = $ourPlural->prev[0] + [ 'parent' => 0, 'plural' => 1 ];
$ourPlural->offsetUnset( 'prev' );
}
} else if ( $defPlural ) {
$this['plurals'] = [ clone $defPlural ];
} else if ( $ourPlural ) {
$this['prev'][] = $ourPlural->exportBasic() + [ 'parent' => 0, 'plural' => 1 ];
$this->offsetUnset( 'plurals' );
}
}
foreach ( [ 'notes', 'refs', 'format' ] as $f ) {
if ( $def->offsetExists( $f ) ) {
$this->offsetSet( $f, $def->offsetGet( $f ) );
} else if ( $this->offsetExists( $f ) ) {
$this->offsetUnset( $f );
}
}
if ( $translate && '' === $this['target'] && '' !== $def['target'] ) {
$this['target'] = $def['target'];
if ( $def->offsetExists( 'comment' ) ) {
$this['comment'] = $def['comment'];
}
if ( $this->offsetExists( 'plurals' ) ) {
foreach ( $this['plurals'] as $i => $ourPlural ) {
if ( '' === $ourPlural['target'] ) {
$defPlural = $def->getPlural( $i );
if ( $defPlural ) {
$ourPlural['target'] = $defPlural['target'];
}
}
}
}
}
}
private function diff( $key, LocoPoMessage $def, array $prev ) {
$old = $this->__get( $key );
$new = $def->__get( $key );
if ( $new !== $old ) {
$this->offsetSet( $key, $new );
if ( is_string( $old ) && '' !== $old ) {
$prev[ $key ] = $old;
}
}
return $prev;
}
private function getPlural( $i ) {
if ( $this->offsetExists( 'plurals' ) ) {
$plurals = $this->offsetGet( 'plurals' );
if ( is_array( $plurals ) && array_key_exists( $i, $plurals ) ) {
return $plurals[ $i ];
}
}
return null;
}
private function exportBasic() {
return [ 'source' => $this['source'], 'context' => $this->context, 'target' => '', ];
}
public function export() {
$a = $this->getArrayCopy();
unset( $a['key'] );
if ( array_key_exists( 'plurals', $a ) ) {
foreach ( $a['plurals'] as $i => $p ) {
if ( $p instanceof ArrayObject ) {
$a['plurals'][ $i ] = $p->getArrayCopy();
}
}
}
return $a;
}
public function strip() {
$this['target'] = '';
$plurals = $this->plurals;
if ( is_array( $plurals ) ) {
foreach ( $plurals as $p ) {
$p->strip();
}
}
return $this;
}
public function translated() {
$n = 0;
if ( '' !== (string) $this['target'] ) {
$n ++;
}
if ( $this->offsetExists( 'plurals' ) ) {
foreach ( $this->offsetGet( 'plurals' ) as $plural ) {
if ( '' !== (string) $plural['target'] ) {
$n ++;
}
}
}
return $n;
}
}
class LocoPoIterator implements Iterator, Countable {
private $po;
private $headers = null;
private $i;
private $t;
private $j;
private $z = 0;
private $w = 79;
public function __construct( $po ) {
if ( is_array( $po ) ) {
$this->po = $po;
} else if ( $po instanceof Traversable ) {
$this->po = iterator_to_array( $po, false );
} else {
throw new InvalidArgumentException( 'PO data must be array or iterator' );
}
$this->t = count( $this->po );
if ( 0 === $this->t ) {
throw new InvalidArgumentException( 'Empty PO data' );
}
$h = $po[0];
if ( '' !== $h['source'] || ( isset( $h['context'] ) && '' !== $h['context'] ) || ( isset( $po[1]['parent'] ) && 0 === $po[1]['parent'] ) ) {
$this->z = - 1;
}
}
public function push( LocoPoMessage $p ) {
$raw = $p->export();
$plurals = $p->plurals;
unset( $raw['plurals'] );
$i = count( $this->po );
$this->po[ $i ] = $raw;
$this->t ++;
if ( is_array( $plurals ) ) {
$j = 0;
foreach ( $plurals as $p ) {
$raw = $p->export();
$raw['parent'] = $i;
$raw['plural'] = ++ $j;
$this->po[] = $raw;
$this->t ++;
}
}
}
public function concat( LocoPoIterator $more ) {
foreach ( $more as $message ) {
$this->push( $message );
}
return $this;
}
public function __clone() {
if ( $this->headers ) {
$this->headers = new LocoPoHeaders( $this->headers->getArrayCopy() );
}
}
#[ReturnTypeWillChange]
public function count() {
return $this->t - ( $this->z + 1 );
}
public function wrap( $width ) {
if ( $width > 0 ) {
$this->w = max( 15, $width );
} else {
$this->w = 0;
}
return $this;
}
#[ReturnTypeWillChange]
public function rewind() {
$this->i = $this->z;
$this->j = - 1;
$this->next();
}
#[ReturnTypeWillChange]
public function key() {
return $this->j;
}
#[ReturnTypeWillChange]
public function valid() {
return is_int( $this->i );
}
#[ReturnTypeWillChange]
public function next() {
$i = $this->i;
while ( ++ $i < $this->t ) {
if ( array_key_exists( 'parent', $this->po[ $i ] ) ) {
continue;
}
$this->j ++;
$this->i = $i;
return;
}
$this->i = null;
$this->j = null;
}
#[ReturnTypeWillChange]
public function current() {
return $this->item( $this->i );
}
private function item( $i ) {
$po = $this->po;
$parent = new LocoPoMessage( $po[ $i ] );
$plurals = [];
$nonseq = $parent->offsetExists( 'child' );
$j = $nonseq ? $parent['child'] : $i + 1;
while ( isset( $po[ $j ]['parent'] ) && $i === $po[ $j ]['parent'] ) {
$plurals[] = new LocoPoMessage( $po[ $j ++ ] );
}
if ( $plurals ) {
$parent['plurals'] = $plurals;
}
return $parent;
}
public function exportEntry( $i ) {
return $this->item( $i + ( 1 - $this->z ) );
}
public function getArrayCopy() {
$po = $this->po;
if ( 0 === $this->z ) {
$po[0]['target'] = (string) $this->getHeaders();
}
return $po;
}
public function clear() {
if ( 0 === $this->z ) {
$this->po = [ $this->po[0] ];
$this->t = 1;
} else {
$this->po = [];
$this->t = 0;
}
}
public function getHeaders() {
if ( is_null( $this->headers ) ) {
$header = $this->po[0];
if ( 0 === $this->z ) {
$value = $header['target'];
if ( is_string( $value ) ) {
$this->headers = LocoPoHeaders::fromMsgstr( $value );
} else if ( $value instanceof LocoPoHeaders ) {
$this->headers = $value;
} else if ( is_array( $value ) ) {
$this->headers = new LocoPoHeaders( $value );
}
} else {
$this->headers = new LocoPoHeaders;
}
}
return $this->headers;
}
public function setHeaders( LocoPoHeaders $head ) {
$this->headers = $head;
if ( 0 === $this->z ) {
$this->po[0]['target'] = null;
}
return $this;
}
public function initPo() {
if ( 0 === $this->z ) {
unset( $this->po[0]['flag'] );
}
return $this;
}
public function initPot() {
if ( 0 === $this->z ) {
$this->po[0]['flag'] = 4;
}
return $this;
}
public function strip() {
$po = $this->po;
$i = count( $po );
$z = $this->z;
while ( -- $i > $z ) {
$po[ $i ]['target'] = '';
}
$this->po = $po;
return $this;
}
public function __toString() {
try {
return $this->render();
} catch ( Exception $e ) {
trigger_error( $e->getMessage(), E_USER_WARNING );
return '';
}
}
public function render( callable $sorter = null ) {
$width = $this->w;
$ref_width = max( 0, $width - 3 );
$h = $this->exportHeader();
$msg = new LocoPoMessage( $h );
$s = $msg->render( $width, $ref_width );
if ( $sorter ) {
$msgs = [];
foreach ( $this as $msg ) {
$msgs[] = $msg;
}
usort( $msgs, $sorter );
} else {
$msgs = $this;
}
$h = $this->getHeaders()->offsetGet( 'Plural-Forms' );
if ( is_string( $h ) && preg_match( '/nplurals\\s*=\\s*(\\d)/', $h, $r ) ) {
$max_pl = (int) $r[1];
} else {
$max_pl = 0;
}
foreach ( $msgs as $msg ) {
$s .= "\n" . $msg->render( $width, $ref_width, $max_pl );
}
return $s;
}
public function exportJed() {
$head = $this->getHeaders();
$a = [
'' => [
'domain' => $head['domain'],
'lang' => $head['language'],
'plural-forms' => $head['plural-forms'],
]
];
foreach ( $this as $message ) {
if ( $message->translated() ) {
$a[ $message->getKey() ] = $message->exportSerial();
}
}
return $a;
}
private function exportHeader() {
if ( 0 === $this->z ) {
$h = $this->po[0];
} else {
$h = [ 'source' => '', 'target' => '' ];
}
if ( $this->headers ) {
$h['target'] = (string) $this->headers;
}
return $h;
}
public function exportRefs( $grep = '' ) {
$a = [];
if ( '' === $grep ) {
$grep = '/(\\S+):\\d+/';
} else {
$grep = '/(\\S*' . $grep . '):\\d+/';
}
$self = get_class( $this );
$base = [ $this->exportHeader() ];
foreach ( $this as $message ) {
if ( preg_match_all( $grep, (string) $message->refs, $r ) ) {
foreach ( $r[1] as $ref ) {
if ( array_key_exists( $ref, $a ) ) {
$po = $a[ $ref ];
} else {
$po = new $self( $base );
$a[ $ref ] = $po;
}
$po->push( $message );
}
}
}
return $a;
}
public function splitRefs( array $map = null ) {
$a = [];
$self = get_class( $this );
$base = [ $this->exportHeader() ];
if ( is_array( $map ) ) {
$grep = implode( '|', array_keys( $map ) );
} else {
$grep = '[a-z]+';
}
foreach ( $this as $message ) {
$refs = ltrim( (string) $message->refs );
if ( '' !== $refs ) {
if ( preg_match_all( '/\\S+\\.(' . $grep . '):\\d+/', $refs, $r, PREG_SET_ORDER ) ) {
$tmp = [];
foreach ( $r as $rr ) {
list( $ref, $ext ) = $rr;
$tmp[ $ext ][ $ref ] = true;
}
foreach ( $tmp as $ext => $refs ) {
if ( is_array( $map ) ) {
$ext = $map[ $ext ];
}
if ( array_key_exists( $ext, $a ) ) {
$po = $a[ $ext ];
} else {
$po = new $self( $base );
$a[ $ext ] = $po;
}
$message = clone $message;
$message['refs'] = implode( ' ', array_keys( $refs ) );
$po->push( $message );
}
}
}
}
return $a;
}
public function getHashes() {
$a = [];
foreach ( $this as $msg ) {
$a[] = $msg->getHash();
}
sort( $a, SORT_STRING );
return $a;
}
public function equalSource( LocoPoIterator $that ) {
$a = $this->getHashes();
$b = $that->getHashes();
if ( count( $a ) !== count( $b ) ) {
return false;
}
foreach ( $a as $i => $hash ) {
if ( $hash !== $b[ $i ] ) {
return false;
}
}
return true;
}
public function equal( LocoPoIterator $that ) {
if ( $this->t !== $that->t ) {
return false;
}
$i = $this->z;
$fields = [ 'source', 'context', 'notes', 'refs', 'target', 'comment', 'flag', 'parent', 'plural' ];
while ( ++ $i < $this->t ) {
$a = $this->po[ $i ];
$b = $that->po[ $i ];
foreach ( $fields as $f ) {
$af = isset( $a[ $f ] ) ? $a[ $f ] : '';
$bf = isset( $b[ $f ] ) ? $b[ $f ] : '';
if ( $af !== $bf ) {
return false;
}
}
}
return true;
}
public function sort( callable $func = null ) {
$order = [];
foreach ( $this as $msg ) {
$order[] = $msg;
}
usort( $order, $func ?: [ __CLASS__, 'compare' ] );
$this->clear();
foreach ( $order as $p ) {
$this->push( $p );
}
return $this;
}
public static function compare( LocoPoMessage $a, LocoPoMessage $b ) {
$h = $a->getHash();
$j = $b->getHash();
$n = strcasecmp( $h, $j );
if ( 0 === $n ) {
$n = strcmp( $h, $j );
if ( 0 === $n ) {
return 0;
}
}
return $n > 0 ? 1 : - 1;
}
public function createSorter() {
$index = [];
foreach ( $this as $i => $msg ) {
$index[ $msg->getHash() ] = $i;
}
$obj = new LocoPoIndex( $index );
return [ $obj, 'compare' ];
}
}
class LocoMoTable {
private $size = 0;
private $bin = '';
private $map = null;
public function __construct( $data = '' ) {
if ( is_array( $data ) ) {
$this->compile( $data );
} else if ( '' !== $data ) {
$this->parse( $data );
}
}
#[ReturnTypeWillChange]
public function count() {
if ( is_null( $this->size ) ) {
if ( $this->bin ) {
$this->size = (int) ( strlen( $this->bin ) / 4 );
} else if ( is_array( $this->map ) ) {
$this->size = count( $this->map );
} else {
return 0;
}
if ( ! self::is_prime( $this->size ) || $this->size < 3 ) {
throw new Exception( 'Size expected to be prime number above 2, got ' . $this->size );
}
}
return $this->size;
}
public function bytes() {
return $this->count() * 4;
}
public function __toString() {
return $this->bin;
}
public function export() {
if ( is_null( $this->map ) ) {
$this->parse( $this->bin );
}
return $this->map;
}
private function reset( $length ) {
$this->size = max( 3, self::next_prime( $length * 4 / 3 ) );
$this->bin = '';
$this->map = [];
return $this->size;
}
public function compile( array $msgids ) {
$hash_tab_size = $this->reset( count( $msgids ) );
$packed = array_fill( 0, $hash_tab_size, "\0\0\0\0" );
$j = 0;
foreach ( $msgids as $msgid ) {
$hash_val = self::hashpjw( $msgid );
$idx = $hash_val % $hash_tab_size;
if ( array_key_exists( $idx, $this->map ) ) {
$incr = 1 + ( $hash_val % ( $hash_tab_size - 2 ) );
do {
$idx += $incr;
if ( $hash_val === $idx ) {
throw new Exception( 'Unable to find empty slot in hash table' );
}
$idx %= $hash_tab_size;
} while ( array_key_exists( $idx, $this->map ) );
}
$this->map[ $idx ] = $j;
$packed[ $idx ] = pack( 'V', ++ $j );
}
$this->bin = implode( '', $packed );
}
public function lookup( $msgid, array $msgids ) {
$hash_val = self::hashpjw( $msgid );
$idx = $hash_val % $this->size;
$incr = 1 + ( $hash_val % ( $this->size - 2 ) );
while ( true ) {
if ( ! array_key_exists( $idx, $this->map ) ) {
break;
}
$j = $this->map[ $idx ];
if ( isset( $msgids[ $j ] ) && $msgid === $msgids[ $j ] ) {
return $j;
}
$idx += $incr;
if ( $idx === $hash_val ) {
break;
}
$idx %= $this->size;
}
return - 1;
}
private function parse( $bin ) {
$this->bin = $bin;
$this->size = null;
$hash_tab_size = $this->count();
$this->map = [];
$idx = - 1;
$byte = 0;
while ( ++ $idx < $hash_tab_size ) {
$word = substr( $this->bin, $byte, 4 );
if ( "\0\0\0\0" !== $word ) {
list( , $j ) = unpack( 'V', $word );
$this->map[ $idx ] = $j - 1;
}
$byte += 4;
}
}
public static function hashpjw( $str ) {
$i = - 1;
$hval = 0;
$len = strlen( $str );
while ( ++ $i < $len ) {
$ord = ord( substr( $str, $i, 1 ) );
$hval = ( $hval << 4 ) + $ord;
$g = $hval & 0xf0000000;
if ( $g !== 0 ) {
$hval ^= $g >> 24;
$hval ^= $g;
}
}
return $hval;
}
private static function next_prime( $seed ) {
$seed = (int) floor( $seed );
$seed |= 1;
while ( ! self::is_prime( $seed ) ) {
$seed += 2;
}
return $seed;
}
private static function is_prime( $num ) {
if ( 1 === $num ) {
return false;
}
if ( 2 === $num ) {
return true;
}
if ( $num % 2 == 0 ) {
return false;
}
for ( $i = 3; $i <= ceil( sqrt( $num ) ); $i = $i + 2 ) {
if ( $num % $i == 0 ) {
return false;
}
}
return true;
}
}
class LocoMo {
private $bin;
private $msgs;
private $head;
private $hash = null;
private $use_fuzzy = false;
private $cs = null;
public function __construct( Iterator $export, LocoPoHeaders $head = null ) {
if ( $head ) {
$this->head = $head;
} else {
$this->head = new LocoPoHeaders;
$this->setHeader( 'Project-Id-Version', 'Loco' );
}
$this->msgs = $export;
$this->bin = '';
}
public function setCharset( $cs ) {
$cs = $this->head->setCharset( $cs );
$this->cs = 'UTF-8' === $cs ? null : $cs;
}
public function enableHash() {
$this->hash = new LocoMoTable;
}
public function useFuzzy() {
$this->use_fuzzy = true;
}
public function setHeader( $key, $val ) {
$this->head->add( $key, $val );
return $this;
}
private function str( $s ) {
if ( $cs = $this->cs ) {
$s = mb_convert_encoding( $s, $cs, [ 'UTF-8' ] );
}
return $s;
}
public function compile() {
$table = [ '' ];
$sources = [ '' ];
$targets = [ (string) $this->head ];
$fuzzy_flag = 4;
$skip_fuzzy = ! $this->use_fuzzy;
if ( $this->head->has( 'Plural-Forms' ) && preg_match( '/^nplurals=(\\d)/', $this->head->trimmed( 'Plural-Forms' ), $r ) ) {
$nplurals = (int) $r[1];
$maxplural = max( 0, $nplurals - 1 );
} else {
$maxplural = 1;
}
foreach ( $this->msgs as $r ) {
if ( $skip_fuzzy && isset( $r['flag'] ) && $fuzzy_flag === $r['flag'] ) {
continue;
}
$msgid = $this->str( $r['key'] );
if ( isset( $r['context'] ) ) {
$msgctxt = $this->str( $r['context'] );
if ( '' !== $msgctxt ) {
if ( '' === $msgid ) {
$msgid = '(' . $msgctxt . ')';
}
$msgid = $msgctxt . "\x04" . $msgid;
}
}
if ( '' === $msgid ) {
continue;
}
$msgstr = $this->str( $r['target'] );
if ( '' === $msgstr ) {
continue;
}
$table[] = $msgid;
if ( isset( $r['plurals'] ) ) {
if ( $r['plurals'] ) {
$i = 0;
foreach ( $r['plurals'] as $i => $p ) {
if ( $i === 0 ) {
$msgid .= "\0" . $this->str( $p['key'] );
}
$msgstr .= "\0" . $this->str( $p['target'] );
}
while ( $maxplural > ++ $i ) {
$msgstr .= "\0";
}
} else if ( isset( $r['plural_key'] ) ) {
$msgid .= "\0" . $this->str( $r['plural_key'] );
}
}
$sources[] = $msgid;
$targets[] = $msgstr;
}
asort( $sources, SORT_STRING );
$this->bin = "\xDE\x12\x04\x95\x00\x00\x00\x00";
$n = count( $sources );
$this->writeInteger( $n );
$offset = 28;
$this->writeInteger( $offset );
$offset += $n * 8;
$this->writeInteger( $offset );
if ( $this->hash ) {
sort( $table, SORT_STRING );
$this->hash->compile( $table );
$s = $this->hash->count();
} else {
$s = 0;
}
$this->writeInteger( $s );
$offset += $n * 8;
$this->writeInteger( $offset );
if ( $s ) {
$offset += $s * 4;
}
$source = '';
foreach ( $sources as $str ) {
$source .= $str . "\0";
$this->writeInteger( $strlen = strlen( $str ) );
$this->writeInteger( $offset );
$offset += $strlen + 1;
}
$target = '';
foreach ( array_keys( $sources ) as $i ) {
$str = $targets[ $i ];
$target .= $str . "\0";
$this->writeInteger( $strlen = strlen( $str ) );
$this->writeInteger( $offset );
$offset += $strlen + 1;
}
if ( $this->hash ) {
$this->bin .= $this->hash->__toString();
}
$this->bin .= $source;
$this->bin .= $target;
return $this->bin;
}
private function writeInteger( $num ) {
$this->bin .= pack( 'V', $num );
}
}
interface LocoTokensInterface extends Iterator {
public function advance();
public function ignore( ...$symbols );
}
class LocoTokenizer implements LocoTokensInterface {
const T_LITERAL = 0;
const T_UNKNOWN = - 1;
private $src;
private $pos;
private $line;
private $col;
private $max;
private $rules = [];
private $skip = [];
private $tok;
private $len;
public function __construct( $src = '' ) {
$this->init( $src );
}
public function parse( $src ) {
return iterator_to_array( $this->generate( $src ) );
}
public function generate( $src ) {
$this->init( $src );
while ( $this->valid() ) {
yield $this->current();
$this->next();
}
}
public function init( $src ) {
$this->src = $src;
$this->rewind();
return $this;
}
public function define( $grep, $t = 0 ) {
if ( '^' !== $grep[1] ) {
throw new InvalidArgumentException( 'Expression ' . $grep . ' isn\'t anchored' );
}
if ( ! is_int( $t ) && ! is_callable( $t ) ) {
throw new InvalidArgumentException( 'Non-integer token must be valid callback' );
}
$sniff = $grep[2];
if ( $sniff === preg_quote( $sniff, $grep[0] ) ) {
$this->rules[ $sniff ][] = [ $grep, $t ];
} else {
$this->rules[''][] = [ $grep, $t ];
}
return $this;
}
public function ignore( ...$symbols ) {
$this->skip += array_fill_keys( $symbols, true );
return $this;
}
#[ReturnTypeWillChange]
public function current() {
return $this->tok;
}
public function advance() {
$tok = $this->current();
$this->next();
return $tok;
}
#[ReturnTypeWillChange]
public function next() {
$tok = null;
$offset = $this->pos;
$column = $this->col;
$line = $this->line;
while ( $offset <= $this->max ) {
$t = null;
$s = '';
$text = substr( $this->src, $offset );
foreach ( [ $text[0], '' ] as $k ) {
if ( isset( $this->rules[ $k ] ) ) {
foreach ( $this->rules[ $k ] as $rule ) {
if ( preg_match( $rule[0], $text, $match ) ) {
$s = $match[0];
$t = $rule[1];
if ( ! is_int( $t ) ) {
$t = call_user_func( $t, $s, $match );
}
break 2;
}
}
}
}
if ( is_null( $t ) ) {
$n = preg_match( '/^./u', $text, $match );
if ( false === $n ) {
$s = $text[0];
$match = [ mb_convert_encoding( $s, 'UTF-8', 'cp1252' ) ];
}
$s = (string) $match[0];
$t = self::T_UNKNOWN;
}
$length = strlen( $s );
if ( 0 === $length ) {
throw new Loco_error_ParseException( 'Failed to match anything' );
}
$offset += $length;
$lines = preg_split( '/\\r?\\n/', $s );
$nlines = count( $lines );
if ( $nlines > 1 ) {
$next_line = $line + ( $nlines - 1 );
$next_column = strlen( end( $lines ) );
} else {
$next_line = $line;
$next_column = $column + $length;
}
if ( array_key_exists( $t, $this->skip ) ) {
$line = $next_line;
$column = $next_column;
continue;
}
$tok = self::T_LITERAL === $t ? $s : [ $t, $s, $line, $column ];
$line = $next_line;
$column = $next_column;
$this->len ++;
break;
}
$this->tok = $tok;
$this->pos = $offset;
$this->col = $column;
$this->line = $line;
}
#[ReturnTypeWillChange]
public function key() {
return $this->len ? $this->len - 1 : null;
}
#[ReturnTypeWillChange]
public function valid() {
return null !== $this->tok;
}
#[ReturnTypeWillChange]
public function rewind() {
$this->len = 0;
$this->pos = 0;
$this->col = 0;
$this->line = 1;
$this->max = strlen( $this->src ) - 1;
$this->next();
}
}
function loco_utf8_chr( $u ) {
if ( $u < 0x80 ) {
if ( $u < 0 ) {
throw new RangeException( sprintf( '%d is out of Unicode range', $u ) );
}
return chr( $u );
}
if ( $u < 0x800 ) {
return chr( ( $u >> 6 ) & 0x1F | 0xC0 ) . chr( $u & 0x3F | 0x80 );
}
if ( $u < 0x10000 ) {
return chr( $u >> 12 & 15 | 0xE0 ) . chr( $u >> 6 & 0x3F | 0x80 ) . chr( $u & 0x3F | 0x80 );
}
if ( $u < 0x110000 ) {
return chr( $u >> 18 & 7 | 0xF0 ) . chr( $u >> 12 & 0x3F | 0x80 ) . chr( $u >> 6 & 0x3F | 0x80 ) . chr( $u & 0x3F | 0x80 );
}
throw new RangeException( sprintf( '\\x%X is out of Unicode range', $u ) );
}
function loco_resolve_surrogates( $s ) {
return preg_replace_callback( '/\\xED([\\xA0-\\xAF])([\\x80-\\xBF])\\xED([\\xB0-\\xBF])([\\x80-\\xBF])/', '_loco_resolve_surrogates', $s );
}
function _loco_resolve_surrogates( array $r ) {
return loco_utf8_chr( ( ( ( ( 832 | ( ord( $r[1] ) & 0x3F ) ) << 6 ) | ( ord( $r[2] ) & 0x3F ) ) - 0xD800 ) * 0x400 + ( ( ( ( 832 | ( ord( $r[3] ) & 0x3F ) ) << 6 ) | ( ord( $r[4] ) & 0x3F ) ) - 0xDC00 ) + 0x10000 );
}
class LocoEscapeParser {
private $map;
private $grep;
public function __construct( array $map = [] ) {
$this->map = $map;
$rules = [ '\\\\' ];
if ( $map ) {
$rules[] = '[' . implode( array_keys( $map ) ) . ']';
}
if ( ! isset( $map['U'] ) ) {
$rules[] = 'U[0-9A-Fa-f]{5,8}';
}
if ( ! isset( $map['u'] ) ) {
$rules[] = 'u(?:\\{[0-9A-Fa-f]+\\}|[0-9A-Fa-f]{1,4})(?:\\\\u(?:\\{[0-9A-Fa-f]+\\}|[0-9A-Fa-f]{1,4}))*';
}
$this->grep = '/\\\\(' . implode( '|', $rules ) . ')/';
}
final public function unescape( $s ) {
if ( '' !== $s ) {
return $this->stripSlashes( preg_replace_callback( $this->grep, [ $this, 'unescapeMatch' ], $s ) );
}
return '';
}
final public function unescapeMatch( array $r ) {
$s = $r[0];
$c = $s[1];
if ( isset( $this->map[ $c ] ) ) {
return $this->map[ $c ];
}
if ( 'u' === $c ) {
$str = '';
$surrogates = false;
foreach ( explode( '\\u', $s ) as $i => $h ) {
if ( '' !== $h ) {
$h = ltrim( trim( $h, '{}' ), '0' );
$u = intval( $h, 16 );
$str .= loco_utf8_chr( $u );
if ( ! $surrogates ) {
$surrogates = $u >= 0xD800 && $u <= 0xDBFF;
}
}
}
if ( $surrogates ) {
$str = loco_resolve_surrogates( $str );
}
return $str;
}
if ( 'U' === $c ) {
return loco_utf8_chr( intval( substr( $s, 2 ), 16 ) );
}
if ( 'x' === $c ) {
return chr( intval( substr( $s, 2 ), 16 ) );
}
if ( ctype_digit( $c ) ) {
return chr( intval( substr( $s, 1 ), 8 ) );
}
return $s;
}
protected function stripSlashes( $s ) {
return stripcslashes( $s );
}
}
class LocoJsTokens extends LocoTokenizer {
const T_KWORD = 1;
const T_REGEX = 2;
private static $lex = null;
protected static $words = [
'true' => 1,
'false' => 1,
'null' => 1,
'break' => T_BREAK,
'else' => T_ELSE,
'new' => T_NEW,
'var' => 1,
'case' => T_CASE,
'finally' => T_FINALLY,
'return' => T_RETURN,
'void' => 1,
'catch' => T_CATCH,
'for' => T_FOR,
'switch' => T_SWITCH,
'while' => T_WHILE,
'continue' => T_CONTINUE,
'function' => T_FUNCTION,
'this' => T_STRING,
'with' => 1,
'default' => T_DEFAULT,
'if' => T_IF,
'throw' => T_THROW,
'delete' => 1,
'in' => 1,
'try' => T_TRY,
'do' => T_DO,
'instanceof' => 1,
'typeof' => 1,
];
public static function decapse( $encapsed ) {
$s = substr( $encapsed, 1, - 1 );
$l = self::$lex;
if ( is_null( $l ) ) {
$l = new LocoEscapeParser( [ 'U' => 'U', 'a' => 'a', ] );
self::$lex = $l;
}
return $l->unescape( $s );
}
public function __construct( $src = '' ) {
$this->ignore( T_WHITESPACE );
$this->define( '/^(?:\\\\u[0-9A-F]{4,4}|[$_\\pL\\p{Nl}])(?:\\\\u[0-9A-F]{4}|[$_\\pL\\pN\\p{Mn}\\p{Mc}\\p{Pc}])*/ui', [
$this,
'matchWord'
] );
$this->define( '/^\\s+/u', T_WHITESPACE );
$this->define( '!^//.*!', T_COMMENT );
$this->define( '!^/\\*.*\\*/!Us', [ $this, 'matchComment' ] );
$this->define( '/^"(?:\\\\.|[^\\r\\n\\p{Zl}\\p{Zp}"\\\\])*"/u', T_CONSTANT_ENCAPSED_STRING );
$this->define( '/^\'(?:\\\\.|[^\\r\\n\\p{Zl}\\p{Zp}\'\\\\])*\'/u', T_CONSTANT_ENCAPSED_STRING );
$this->define( '/^[-+;,<>.=:|&^!?*%~(){}[\\]]/' );
parent::__construct( $src );
}
public function matchWord( $s ) {
if ( array_key_exists( $s, self::$words ) ) {
return self::$words[ $s ];
}
return T_STRING;
}
public function matchComment( $s ) {
if ( substr( $s, 0, 3 ) === '/**' ) {
return T_DOC_COMMENT;
}
return T_COMMENT;
}
}
interface LocoExtractorInterface {
public function setDomain( $default );
public function tokenize( $src );
public function extract( LocoExtracted $strings, LocoTokensInterface $tokens, $fileref = '' );
public function extractSource( $src, $fileref );
}
class LocoExtracted implements Countable {
private $exp = [];
private $reg = [];
private $dom = [];
private $dflt = '';
public function extractSource( LocoExtractorInterface $ext, $src, $fileref = '' ) {
$ext->extract( $this, $ext->tokenize( $src ), $fileref );
return $this;
}
public function export() {
return $this->exp;
}
#[ReturnTypeWillChange]
public function count() {
return count( $this->exp );
}
public function getDomainCounts() {
return $this->dom;
}
public function setDomain( $default ) {
$this->dflt = $default;
return $this;
}
public function getDomain() {
return $this->dflt;
}
private function key( array $entry ) {
$key = (string) $entry['source'];
foreach ( [ 'context', 'domain' ] as $i => $prop ) {
if ( array_key_exists( $prop, $entry ) ) {
$add = (string) $entry[ $prop ];
if ( '' !== $add ) {
$key .= ord( $i ) . $add;
}
}
}
return $key;
}
public function pushEntry( array $entry, $domain ) {
if ( '' === $domain || '*' === $domain ) {
$domain = $this->dflt;
}
$entry['id'] = '';
$entry['target'] = '';
$entry['domain'] = $domain;
$key = $this->key( $entry );
if ( isset( $this->reg[ $key ] ) ) {
$index = $this->reg[ $key ];
$clash = $this->exp[ $index ];
if ( $value = $this->mergeField( $clash, $entry, 'refs', ' ' ) ) {
$this->exp[ $index ]['refs'] = $value;
}
if ( $value = $this->mergeField( $clash, $entry, 'notes', "\n" ) ) {
$this->exp[ $index ]['notes'] = $value;
}
} else {
$index = count( $this->exp );
$this->reg[ $key ] = $index;
$this->exp[ $index ] = $entry;
if ( isset( $this->dom[ $domain ] ) ) {
$this->dom[ $domain ] ++;
} else {
$this->dom[ $domain ] = 1;
}
}
return $index;
}
public function pushPlural( array $entry, $sindex ) {
$parent = $this->exp[ $sindex ];
$domain = $parent['domain'];
$pkey = $this->key( $parent ) . "\2";
if ( ! array_key_exists( $pkey, $this->reg ) ) {
$pindex = count( $this->exp );
$this->reg[ $pkey ] = $pindex;
$entry += [
'id' => '',
'target' => '',
'plural' => 1,
'parent' => $sindex,
'domain' => $domain,
];
$this->exp[ $pindex ] = $entry;
if ( isset( $entry['format'] ) && ! isset( $parent['format'] ) ) {
$this->exp[ $sindex ]['format'] = $entry['format'];
}
if ( $pindex !== $sindex + $entry['plural'] ) {
$this->exp[ $sindex ]['child'] = $pindex;
}
}
}
public function mergeField( array $old, array $new, $field, $glue ) {
$prev = isset( $old[ $field ] ) ? $old[ $field ] : '';
if ( isset( $new[ $field ] ) ) {
$text = $new[ $field ];
if ( '' !== $prev && $prev !== $text ) {
if ( 'notes' === $field && preg_match( '/^' . preg_quote( rtrim( $text, '. ' ), '/' ) . '[. ]*$/mu', $prev ) ) {
$text = $prev;
} else {
$text = $prev . $glue . $text;
}
}
return $text;
}
return $prev;
}
public function filter( $domain ) {
if ( '' === $domain ) {
$domain = $this->dflt;
}
$map = [];
$newOffset = 1;
$matchAll = '*' === $domain;
$raw = [ [ 'id' => '', 'source' => '', 'target' => '', 'domain' => $matchAll ? '' : $domain, ] ];
foreach ( $this->exp as $oldOffset => $r ) {
if ( isset( $r['parent'] ) ) {
if ( isset( $map[ $r['parent'] ] ) ) {
$r['parent'] = $map[ $r['parent'] ];
$raw[ $newOffset ++ ] = $r;
}
} else {
if ( $matchAll ) {
$match = true;
} else if ( isset( $r['domain'] ) ) {
$match = $domain === $r['domain'];
} else {
$match = $domain === '';
}
if ( $match ) {
$map[ $oldOffset ] = $newOffset;
$raw[ $newOffset ++ ] = $r;
}
}
}
return $raw;
}
}
abstract class LocoExtractor implements LocoExtractorInterface {
private $rules;
private $wp = [];
private $domain = '';
abstract protected function fsniff( $str );
abstract protected function decapse( $raw );
abstract protected function comment( $comment );
public function __construct( array $rules ) {
$this->rules = $rules;
}
public function setDomain( $default ) {
$this->domain = $default;
return $this;
}
public function headerize( array $tags, $domain = '' ) {
if ( isset( $this->wp[ $domain ] ) ) {
$this->wp[ $domain ] += $tags;
} else {
$this->wp[ $domain ] = $tags;
}
return $this;
}
protected function getHeaders() {
return $this->wp;
}
final public function extractSource( $src, $fileref ) {
$strings = new LocoExtracted;
$this->extract( $strings, $this->tokenize( $src ), $fileref );
return $strings;
}
public function rule( $keyword ) {
return isset( $this->rules[ $keyword ] ) ? $this->rules[ $keyword ] : '';
}
protected function push( LocoExtracted $strings, $rule, array $args, $comment = '', $ref = '' ) {
$s = strpos( $rule, 's' );
$p = strpos( $rule, 'p' );
$c = strpos( $rule, 'c' );
$d = strpos( $rule, 'd' );
if ( false === $s || ! isset( $args[ $s ] ) ) {
return null;
}
$msgid = $args[ $s ];
if ( ! is_string( $msgid ) ) {
return null;
}
$entry = [ 'source' => $msgid, ];
if ( is_int( $c ) && isset( $args[ $c ] ) ) {
$entry['context'] = $args[ $c ];
} else if ( '' === $msgid ) {
return null;
}
if ( $ref ) {
$entry['refs'] = $ref;
}
if ( is_int( $d ) && array_key_exists( $d, $args ) ) {
$domain = $args[ $d ];
if ( is_null( $domain ) ) {
$domain = '';
}
} else if ( '' === $this->domain ) {
$domain = $strings->getDomain();
} else {
$domain = $this->domain;
}
$format = '';
$comment = $this->comment( $comment );
if ( '' !== $comment ) {
if ( preg_match( '/^xgettext:\\s*([-a-z]+)-format\\s*/mi', $comment, $r, PREG_OFFSET_CAPTURE ) ) {
$format = $r[1][0];
$entry['format'] = $format;
$comment = trim( substr_replace( $comment, '', $r[0][1], strlen( $r[0][0] ) ) );
}
if ( preg_match( '/^references?:( *.+:\\d+)*\\s*/mi', $comment, $r, PREG_OFFSET_CAPTURE ) ) {
$entry['refs'] = trim( $r[1][0], ' ' );
$comment = trim( substr_replace( $comment, '', $r[0][1], strlen( $r[0][0] ) ) );
}
$entry['notes'] = $comment;
}
$msgid_plural = is_int( $p ) && isset( $args[ $p ] ) ? $args[ $p ] : '';
if ( '' === $format ) {
$format = $this->fsniff( $msgid );
if ( '' !== $format ) {
$entry['format'] = $format;
} else if ( '' !== $msgid_plural ) {
$format = $this->fsniff( $msgid_plural );
if ( '' !== $format ) {
$entry['format'] = $format;
}
}
}
$index = $strings->pushEntry( $entry, $domain );
if ( '' !== $msgid_plural ) {
$entry = [ 'source' => $msgid_plural, ];
if ( '' !== $format ) {
$entry['format'] = $format;
}
$strings->pushPlural( $entry, $index );
}
return $index;
}
protected function utf8( $str ) {
if ( false === preg_match( '//u', $str ) ) {
$str = mb_convert_encoding( $str, 'UTF-8', 'cp1252' );
}
return $str;
}
}
class LocoPHPTokens implements LocoTokensInterface, Countable {
private $i = null;
private $tokens;
private $skip_tokens;
private $literal_tokens;
public function __construct( array $tokens ) {
$this->tokens = $tokens;
$this->reset();
}
public function reset() {
$this->rewind();
$this->literal_tokens = [];
$this->skip_tokens = [];
}
public function literal( ...$symbols ) {
$this->literal_tokens += array_fill_keys( $symbols, true );
return $this;
}
public function ignore( ...$symbols ) {
$this->skip_tokens += array_fill_keys( $symbols, true );
return $this;
}
public function export() {
return array_values( iterator_to_array( $this ) );
}
public function advance() {
if ( $this->valid() ) {
$tok = $this->current();
$this->next();
return $tok;
}
return null;
}
#[ReturnTypeWillChange]
public function rewind() {
$this->i = ( false === reset( $this->tokens ) ? null : key( $this->tokens ) );
}
#[ReturnTypeWillChange]
public function valid() {
while ( is_int( $this->i ) ) {
$tok = $this->tokens[ $this->i ];
if ( array_key_exists( is_array( $tok ) ? $tok[0] : $tok, $this->skip_tokens ) ) {
$this->next();
} else {
return true;
}
}
return false;
}
#[ReturnTypeWillChange]
public function key() {
return $this->i;
}
#[ReturnTypeWillChange]
public function next() {
$this->i = ( false === next( $this->tokens ) ? null : key( $this->tokens ) );
}
#[ReturnTypeWillChange]
public function current() {
$tok = $this->tokens[ $this->i ];
if ( is_array( $tok ) && isset( $this->literal_tokens[ $tok[0] ] ) ) {
return $tok[1];
}
return $tok;
}
public function __toString() {
$s = [];
foreach ( $this as $token ) {
$s[] = is_array( $token ) ? $token[1] : $token;
}
return implode( '', $s );
}
#[ReturnTypeWillChange]
public function count() {
return count( $this->tokens );
}
}
class LocoPHPEscapeParser extends LocoEscapeParser {
public function __construct() {
parent::__construct( [
'n' => "\n",
'r' => "\r",
't' => "\t",
'v' => "\x0B",
'f' => "\x0C",
'e' => "\x1B",
'$' => '$',
'\\' => '\\',
'"' => '"',
] );
}
protected function stripSlashes( $s ) {
return preg_replace_callback( '/\\\\(x[0-9A-Fa-f]{1,2}|[0-3]?[0-7]{1,2})/', [
$this,
'unescapeMatch'
], $s, - 1, $n );
}
}
function loco_unescape_php_string( $s ) {
static $l;
if ( is_null( $l ) ) {
$l = new LocoPHPEscapeParser;
}
return $l->unescape( $s );
}
function loco_decapse_php_string( $s ) {
if ( '' === $s ) {
return '';
}
$q = $s[0];
if ( "'" === $q ) {
return str_replace( [ '\\' . $q, '\\\\' ], [ $q, '\\' ], substr( $s, 1, - 1 ) );
}
if ( '"' !== $q ) {
return $s;
}
return loco_unescape_php_string( substr( $s, 1, - 1 ) );
}
function loco_parse_php_comment( $comment ) {
$comment = trim( $comment, "/ \n\r\t" );
if ( '' !== $comment && '*' === $comment[0] ) {
$lines = [];
$junk = "\r\t/ *";
foreach ( explode( "\n", $comment ) as $line ) {
$line = trim( $line, $junk );
if ( '' !== $line ) {
$lines[] = $line;
}
}
$comment = implode( "\n", $lines );
}
return $comment;
}
function loco_parse_wp_comment( $block ) {
$header = [];
if ( '/*' === substr( $block, 0, 2 ) ) {
$junk = "\r\t/ *";
foreach ( explode( "\n", $block ) as $line ) {
if ( false !== ( $i = strpos( $line, ':' ) ) ) {
$key = substr( $line, 0, $i );
$val = substr( $line, ++ $i );
$header[ trim( $key, $junk ) ] = trim( $val, $junk );
}
}
}
return $header;
}
class LocoPHPExtractor extends LocoExtractor {
private $defs = [];
public function tokenize( $src ) {
return new LocoPHPTokens( token_get_all( $src ) );
}
public function decapse( $raw ) {
return loco_decapse_php_string( $raw );
}
public function fsniff( $str ) {
$format = '';
$offset = 0;
while ( preg_match( '/%(?:[1-9]\\d*\\$)?(?:\'.|[-+0 ])*\\d*(?:\\.\\d+)?(.|$)/', $str, $r, PREG_OFFSET_CAPTURE, $offset ) ) {
$type = $r[1][0];
list( $match, $offset ) = $r[0];
if ( '%' === $type && '%%' !== $match ) {
return '';
}
if ( '' === $type || ! preg_match( '/^[bcdeEfFgGosuxX%]/', $type ) ) {
return '';
}
$offset += strlen( $match );
if ( preg_match( '/^% +[a-z]/i', $match ) || preg_match( '/^%[b-ou-x]/i', $match ) ) {
continue;
}
$format = 'php';
}
return $format;
}
protected function comment( $comment ) {
return preg_replace( '/^translators:\\s+/mi', '', loco_parse_php_comment( $comment ) );
}
public function define( $name, $value ) {
$this->defs[ $name ] = $value;
return $this;
}
public function extract( LocoExtracted $strings, LocoTokensInterface $tokens, $fileref = '' ) {
$tokens->ignore( T_WHITESPACE );
$n = 0;
$depth = 0;
$comment = '';
$narg = 0;
$args = [];
$ref = '';
$rule = '';
$wp = $this->getHeaders();
$tokens->rewind();
while ( $tok = $tokens->advance() ) {
if ( is_string( $tok ) ) {
$s = $tok;
$t = null;
} else {
$t = $tok[0];
$s = $tok[1];
}
if ( $depth ) {
if ( ')' === $s || ']' === $s ) {
if ( 0 === -- $depth ) {
if ( $this->push( $strings, $rule, $args, $comment, $ref ) ) {
$n ++;
}
$comment = '';
}
} else if ( '(' === $s || '[' === $s ) {
$depth ++;
$args[ $narg ] = null;
} else if ( 1 === $depth ) {
if ( ',' === $s ) {
$narg ++;
} else if ( T_CONSTANT_ENCAPSED_STRING === $t ) {
$s = self::utf8( $s );
$args[ $narg ] = $this->decapse( $s );
} else if ( T_STRING === $t && array_key_exists( $s, $this->defs ) ) {
$args[ $narg ] = $this->defs[ $s ];
} else {
$args[ $narg ] = null;
}
}
} else if ( T_COMMENT === $t || T_DOC_COMMENT === $t ) {
$was_header = false;
$s = self::utf8( $s );
if ( 0 === $n ) {
if ( false !== strpos( $s, '* @package' ) ) {
$was_header = true;
}
if ( $wp && ( $header = loco_parse_wp_comment( $s ) ) ) {
foreach ( $wp as $domain => $tags ) {
foreach ( array_intersect_key( $header, $tags ) as $tag => $text ) {
$ref = $fileref ? $fileref . ':' . $tok[2] : '';
$meta = $tags[ $tag ];
if ( is_string( $meta ) ) {
$meta = [ 'notes' => $meta ];
trigger_error( $tag . ' header defaulted to "notes"', E_USER_DEPRECATED );
}
$strings->pushEntry( [ 'source' => $text, 'refs' => $ref ] + $meta, (string) $domain );
$was_header = true;
}
}
}
}
if ( ! $was_header ) {
$comment = $s;
}
} else if ( T_STRING === $t && '(' === $tokens->advance() && ( $rule = $this->rule( $s ) ) ) {
$ref = $fileref ? $fileref . ':' . $tok[2] : '';
$depth = 1;
$args = [];
$narg = 0;
} else if ( '' !== $comment && ! preg_match( '!^[/* ]+(translators|xgettext):!im', $comment ) ) {
$comment = '';
}
}
}
}
class LocoJsExtractor extends LocoPHPExtractor {
public function tokenize( $src ) {
return new LocoJsTokens( $src );
}
public function fsniff( $str ) {
return parent::fsniff( $str ) ? 'javascript' : '';
}
public function decapse( $raw ) {
return LocoJsTokens::decapse( $raw );
}
}
class LocoTwigExtractor extends LocoPHPExtractor {
public function tokenize( $src ) {
return parent::tokenize( '<?php ' . preg_replace( '/{#([^#]+)#}/su', '/*\\1*/', $src ) );
}
}
class LocoBladeExtractor extends LocoPHPExtractor {
public function tokenize( $src ) {
return parent::tokenize( '<?php ' . preg_replace( '/{{--(.+)--}}/su', '/*\\1*/', $src ) );
}
}
class LocoWpJsonExtractor implements LocoExtractorInterface {
private static $types = [];
private $base = '.';
private $domain = '';
public function __construct() {
if ( defined( 'ABSPATH' ) ) {
$this->setBase( rtrim( ABSPATH, '/' ) . '/wp-includes' );
}
}
public function setBase( $path ) {
$this->base = $path;
}
private function getType( $type ) {
if ( array_key_exists( $type, self::$types ) ) {
return self::$types[ $type ];
}
$path = $this->base . '/' . $type . '-i18n.json';
if ( ! file_exists( $path ) ) {
throw new Exception( basename( $path ) . ' not found in ' . $this->base );
}
return json_decode( file_get_contents( $path ) );
}
public function tokenize( $src ) {
$raw = json_decode( $src, true );
if ( ! is_array( $raw ) || ! array_key_exists( '$schema', $raw ) ) {
throw new InvalidArgumentException( 'Invalid JSON' );
}
if ( ! preg_match( '!^https?://schemas.wp.org/trunk/(block|theme)\\.json!', $raw['$schema'], $r ) ) {
throw new InvalidArgumentException( 'Unsupported schema' );
}
if ( '' === $this->domain && array_key_exists( 'textdomain', $raw ) ) {
$this->domain = $raw['textdomain'];
}
return new LocoWpJsonStrings( $raw, $this->getType( $r[1] ) );
}
public function setDomain( $default ) {
$this->domain = $default;
return $this;
}
public function extract( LocoExtracted $strings, LocoTokensInterface $tokens, $fileref = '' ) {
if ( ! preg_match( '/:\\d+$/', $fileref ) ) {
$fileref .= ':1';
}
$tokens->rewind();
while ( $tok = $tokens->advance() ) {
$tok['refs'] = $fileref;
$strings->pushEntry( $tok, $this->domain );
}
}
final public function extractSource( $src, $fileref ) {
$strings = new LocoExtracted;
$this->extract( $strings, $this->tokenize( $src ), $fileref );
return $strings;
}
}
class LocoWpJsonStrings extends ArrayIterator implements LocoTokensInterface {
public function __construct( array $raw, stdClass $tpl ) {
parent::__construct();
$this->walk( $tpl, $raw );
}
public function advance() {
$tok = $this->current();
$this->next();
return $tok;
}
public function ignore( ...$symbols ) {
return $this;
}
private function walk( $tpl, $raw ) {
if ( is_string( $tpl ) && is_string( $raw ) ) {
$this->offsetSet( null, [ 'context' => $tpl, 'source' => $raw, ] );
return;
}
if ( is_array( $tpl ) && is_array( $raw ) ) {
foreach ( $raw as $value ) {
self::walk( $tpl[0], $value );
}
} else if ( is_object( $tpl ) && is_array( $raw ) ) {
$group_key = '*';
foreach ( $raw as $key => $value ) {
if ( isset( $tpl->$key ) ) {
$this->walk( $tpl->$key, $value );
} else if ( isset( $tpl->$group_key ) ) {
$this->walk( $tpl->$group_key, $value );
}
}
}
}
}
function loco_wp_extractor( $type = 'php', $ext = '' ) {
if ( 'json' === $type ) {
return new LocoWpJsonExtractor;
}
static $rules = [
'__' => 'sd',
'_e' => 'sd',
'_c' => 'sd',
'_n' => 'sp_d',
'_n_noop' => 'spd',
'_nc' => 'sp_d',
'__ngettext' => 'spd',
'__ngettext_noop' => 'spd',
'_x' => 'scd',
'_ex' => 'scd',
'_nx' => 'sp_cd',
'_nx_noop' => 'spcd',
'esc_attr__' => 'sd',
'esc_html__' => 'sd',
'esc_attr_e' => 'sd',
'esc_html_e' => 'sd',
'esc_attr_x' => 'scd',
'esc_html_x' => 'scd',
];
if ( 'php' === $type ) {
return substr( $ext, - 9 ) === 'blade.php' ? new LocoBladeExtractor( $rules ) : new LocoPHPExtractor( $rules );
}
if ( 'js' === $type ) {
return new LocoJsExtractor( $rules );
}
if ( 'twig' === $type ) {
return new LocoTwigExtractor( $rules );
}
throw new InvalidArgumentException( 'No extractor for ' . $type );
}
function loco_string_percent( $n, $t ) {
if ( ! $t || ! $n ) {
return '0';
}
if ( $t === $n ) {
return '100';
}
$dp = 0;
$n = 100 * $n / $t;
if ( $n > 99 ) {
return number_format( min( $n, 99.9 ), ++ $dp );
}
if ( $n < 0.5 ) {
$n = max( $n, 0.0001 );
do {
$s = number_format( $n, ++ $dp );
} while ( preg_match( '/^0\\.0+$/', $s ) && $dp < 4 );
return substr( $s, 1 );
}
return number_format( $n, $dp );
}
function loco_print_progress( $translated, $untranslated, $flagged ) {
$total = $translated + $untranslated;
$complete = loco_string_percent( $translated - $flagged, $total );
$class = 'progress';
if ( ! $translated && ! $flagged ) {
$class .= ' empty';
} else if ( '100' === $complete ) {
$class .= ' done';
}
echo '<div class="', $class, '"><div class="t">';
if ( $flagged ) {
$s = loco_string_percent( $flagged, $total );
echo '<div class="bar f" style="width:', $s, '%"> </div>';
}
if ( '0' === $complete ) {
echo ' ';
} else {
$class = 'bar p';
$p = (int) $complete;
$class .= sprintf( ' p-%u', 10 * floor( $p / 10 ) );
$style = 'width:' . $complete . '%';
if ( $flagged ) {
$remain = 100.0 - (float) $s;
$style .= '; max-width: ' . sprintf( '%s', $remain ) . '%';
}
echo '<div class="', $class, '" style="' . $style . '"> </div>';
}
echo '</div><div class="l">', $complete, '%</div></div>';
}
class LocoFuzzyMatcher implements Countable {
private $pot = [];
private $po = [];
private $diff = [];
private $dmax = .20;
#[ReturnTypeWillChange]
public function count() {
return count( $this->pot );
}
public function unmatched() {
return array_values( $this->pot );
}
public function redundant() {
return array_values( $this->po );
}
public function setFuzziness( $s ) {
if ( $this->po ) {
throw new LogicException( 'Cannot setFuzziness() after calling match()' );
}
$this->dmax = (float) max( 0, min( (int) $s, 100 ) ) / 100;
}
public function add( $a ) {
$source = isset( $a['source'] ) ? (string) $a['source'] : '';
$context = isset( $a['context'] ) ? (string) $a['context'] : '';
$key = $source . "\4" . $context;
$this->pot[ $key ] = $a;
}
private function key( $a ) {
$source = isset( $a['source'] ) ? (string) $a['source'] : '';
$context = isset( $a['context'] ) ? (string) $a['context'] : '';
return $source . "\4" . $context;
}
protected function getRef( $a ) {
$key = $this->key( $a );
return array_key_exists( $key, $this->pot ) ? $this->pot[ $key ] : null;
}
public function match( $a ) {
$old = $this->key( $a );
if ( isset( $this->pot[ $old ] ) ) {
$new = $this->pot[ $old ];
unset( $this->pot[ $old ] );
return $new;
}
$this->po[ $old ] = $a;
$target = isset( $a['target'] ) ? (string) $a['target'] : '';
$comment = isset( $a['comment'] ) ? (string) $a['comment'] : '';
if ( '' === $target && '' === $comment ) {
return null;
}
if ( 0 < $this->dmax ) {
foreach ( $this->pot as $new => $_ ) {
$dist = $this->distance( $old, $new );
if ( - 1 !== $dist ) {
$this->diff[] = [ $old, $new, $dist ];
}
}
}
return null;
}
private function distance( $a, $b ) {
$a = strtolower( $a );
$b = strtolower( $b );
if ( $a === $b ) {
return 0;
}
$lenA = strlen( $a );
$lenB = strlen( $b );
$lenDiff = abs( $lenA - $lenB );
$max = min( $lenA, $lenB ) + $lenDiff;
$max = (int) ceil( $this->dmax * $max );
if ( $max < $lenDiff ) {
return - 1;
}
$len = max( $lenA, $lenB );
if ( $len < 256 ) {
$d = levenshtein( $a, $b );
return $d > $max ? - 1 : $d;
}
$d = 0;
for ( $i = 0; $i < $len; $i += $max ) {
$aa = substr( $a, $i, $max );
$bb = substr( $b, $i, $max );
$d += levenshtein( $aa, $bb );
if ( $d > $max ) {
return - 1;
}
}
return $d;
}
public function getFuzzyMatches() {
$pairs = [];
usort( $this->diff, [ __CLASS__, 'compareDistance' ] );
foreach ( $this->diff as $pair ) {
list( $old, $new ) = $pair;
if ( ! array_key_exists( $new, $this->pot ) || ! array_key_exists( $old, $this->po ) ) {
continue;
}
$pairs[] = [ $this->po[ $old ], $this->pot[ $new ], ];
unset( $this->po[ $old ] );
unset( $this->pot[ $new ] );
if ( ! $this->po || ! $this->pot ) {
break;
}
}
$this->diff = [];
return $pairs;
}
private static function compareDistance( array $a, array $b ) {
return $a[2] - $b[2];
}
}
defined( 'T_FINALLY' ) || define( 'T_FINALLY', 500 );
if ( function_exists( 'loco_check_extension' ) ) {
loco_check_extension( 'mbstring' );
}