2015-08-17 17:00:26 -07:00
< ? php
/*
* This file is part of the Symfony package .
*
* ( c ) Fabien Potencier < fabien @ symfony . com >
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
namespace Symfony\Component\Translation\Loader ;
use Symfony\Component\Config\Util\XmlUtils ;
use Symfony\Component\Translation\MessageCatalogue ;
use Symfony\Component\Translation\Exception\InvalidResourceException ;
use Symfony\Component\Translation\Exception\NotFoundResourceException ;
use Symfony\Component\Config\Resource\FileResource ;
/**
* XliffFileLoader loads translations from XLIFF files .
*
* @ author Fabien Potencier < fabien @ symfony . com >
*/
class XliffFileLoader implements LoaderInterface
{
/**
* { @ inheritdoc }
*/
public function load ( $resource , $locale , $domain = 'messages' )
{
if ( ! stream_is_local ( $resource )) {
throw new InvalidResourceException ( sprintf ( 'This is not a local file "%s".' , $resource ));
}
if ( ! file_exists ( $resource )) {
throw new NotFoundResourceException ( sprintf ( 'File "%s" not found.' , $resource ));
}
$catalogue = new MessageCatalogue ( $locale );
2016-04-20 09:56:34 -07:00
$this -> extract ( $resource , $catalogue , $domain );
if ( class_exists ( 'Symfony\Component\Config\Resource\FileResource' )) {
$catalogue -> addResource ( new FileResource ( $resource ));
}
return $catalogue ;
}
private function extract ( $resource , MessageCatalogue $catalogue , $domain )
{
try {
$dom = XmlUtils :: loadFile ( $resource );
} catch ( \InvalidArgumentException $e ) {
throw new InvalidResourceException ( sprintf ( 'Unable to load "%s": %s' , $resource , $e -> getMessage ()), $e -> getCode (), $e );
}
$xliffVersion = $this -> getVersionNumber ( $dom );
$this -> validateSchema ( $xliffVersion , $dom , $this -> getSchema ( $xliffVersion ));
if ( '1.2' === $xliffVersion ) {
$this -> extractXliff1 ( $dom , $catalogue , $domain );
}
if ( '2.0' === $xliffVersion ) {
$this -> extractXliff2 ( $dom , $catalogue , $domain );
}
}
/**
* Extract messages and metadata from DOMDocument into a MessageCatalogue .
*
* @ param \DOMDocument $dom Source to extract messages and metadata
* @ param MessageCatalogue $catalogue Catalogue where we ' ll collect messages and metadata
* @ param string $domain The domain
*/
private function extractXliff1 ( \DOMDocument $dom , MessageCatalogue $catalogue , $domain )
{
$xml = simplexml_import_dom ( $dom );
$encoding = strtoupper ( $dom -> encoding );
$xml -> registerXPathNamespace ( 'xliff' , 'urn:oasis:names:tc:xliff:document:1.2' );
2015-08-17 17:00:26 -07:00
foreach ( $xml -> xpath ( '//xliff:trans-unit' ) as $translation ) {
$attributes = $translation -> attributes ();
2015-10-08 11:40:12 -07:00
if ( ! ( isset ( $attributes [ 'resname' ]) || isset ( $translation -> source ))) {
2015-08-17 17:00:26 -07:00
continue ;
}
$source = isset ( $attributes [ 'resname' ]) && $attributes [ 'resname' ] ? $attributes [ 'resname' ] : $translation -> source ;
// If the xlf file has another encoding specified, try to convert it because
// simple_xml will always return utf-8 encoded values
2015-10-08 11:40:12 -07:00
$target = $this -> utf8ToCharset (( string ) ( isset ( $translation -> target ) ? $translation -> target : $source ), $encoding );
2015-08-17 17:00:26 -07:00
$catalogue -> set (( string ) $source , $target , $domain );
2016-04-20 09:56:34 -07:00
$metadata = array ();
if ( $notes = $this -> parseNotesMetadata ( $translation -> note , $encoding )) {
$metadata [ 'notes' ] = $notes ;
}
if ( isset ( $translation -> target ) && $translation -> target -> attributes ()) {
$metadata [ 'target-attributes' ] = array ();
foreach ( $translation -> target -> attributes () as $key => $value ) {
$metadata [ 'target-attributes' ][ $key ] = ( string ) $value ;
}
}
2015-08-17 17:00:26 -07:00
2016-04-20 09:56:34 -07:00
$catalogue -> setMetadata (( string ) $source , $metadata , $domain );
}
}
2015-08-17 17:00:26 -07:00
2016-04-20 09:56:34 -07:00
/**
* @ param \DOMDocument $dom
* @ param MessageCatalogue $catalogue
* @ param string $domain
*/
private function extractXliff2 ( \DOMDocument $dom , MessageCatalogue $catalogue , $domain )
{
$xml = simplexml_import_dom ( $dom );
$encoding = strtoupper ( $dom -> encoding );
$xml -> registerXPathNamespace ( 'xliff' , 'urn:oasis:names:tc:xliff:document:2.0' );
foreach ( $xml -> xpath ( '//xliff:unit/xliff:segment' ) as $segment ) {
$source = $segment -> source ;
2015-08-17 17:00:26 -07:00
2016-04-20 09:56:34 -07:00
// If the xlf file has another encoding specified, try to convert it because
// simple_xml will always return utf-8 encoded values
$target = $this -> utf8ToCharset (( string ) ( isset ( $segment -> target ) ? $segment -> target : $source ), $encoding );
$catalogue -> set (( string ) $source , $target , $domain );
$metadata = array ();
if ( isset ( $segment -> target ) && $segment -> target -> attributes ()) {
$metadata [ 'target-attributes' ] = array ();
foreach ( $segment -> target -> attributes () as $key => $value ) {
$metadata [ 'target-attributes' ][ $key ] = ( string ) $value ;
}
2015-08-17 17:00:26 -07:00
}
2016-04-20 09:56:34 -07:00
$catalogue -> setMetadata (( string ) $source , $metadata , $domain );
2015-08-17 17:00:26 -07:00
}
}
/**
* Convert a UTF8 string to the specified encoding .
*
* @ param string $content String to decode
* @ param string $encoding Target encoding
*
* @ return string
*/
private function utf8ToCharset ( $content , $encoding = null )
{
if ( 'UTF-8' !== $encoding && ! empty ( $encoding )) {
2016-04-20 09:56:34 -07:00
return mb_convert_encoding ( $content , $encoding , 'UTF-8' );
2015-08-17 17:00:26 -07:00
}
return $content ;
}
/**
2016-04-20 09:56:34 -07:00
* @ param string $file
* @ param \DOMDocument $dom
* @ param string $schema source of the schema
2015-08-17 17:00:26 -07:00
*
* @ throws InvalidResourceException
*/
2016-04-20 09:56:34 -07:00
private function validateSchema ( $file , \DOMDocument $dom , $schema )
2015-08-17 17:00:26 -07:00
{
$internalErrors = libxml_use_internal_errors ( true );
2016-04-20 09:56:34 -07:00
if ( !@ $dom -> schemaValidateSource ( $schema )) {
2015-08-27 12:03:05 -07:00
throw new InvalidResourceException ( sprintf ( 'Invalid resource provided: "%s"; Errors: %s' , $file , implode ( " \n " , $this -> getXmlErrors ( $internalErrors ))));
2015-08-17 17:00:26 -07:00
}
$dom -> normalizeDocument ();
libxml_clear_errors ();
libxml_use_internal_errors ( $internalErrors );
2016-04-20 09:56:34 -07:00
}
2015-08-17 17:00:26 -07:00
2016-04-20 09:56:34 -07:00
private function getSchema ( $xliffVersion )
{
if ( '1.2' === $xliffVersion ) {
$schemaSource = file_get_contents ( __DIR__ . '/schema/dic/xliff-core/xliff-core-1.2-strict.xsd' );
$xmlUri = 'http://www.w3.org/2001/xml.xsd' ;
} elseif ( '2.0' === $xliffVersion ) {
$schemaSource = file_get_contents ( __DIR__ . '/schema/dic/xliff-core/xliff-core-2.0.xsd' );
$xmlUri = 'informativeCopiesOf3rdPartySchemas/w3c/xml.xsd' ;
} else {
throw new \InvalidArgumentException ( sprintf ( 'No support implemented for loading XLIFF version "%s".' , $xliffVersion ));
}
return $this -> fixXmlLocation ( $schemaSource , $xmlUri );
}
/**
* Internally changes the URI of a dependent xsd to be loaded locally .
*
* @ param string $schemaSource Current content of schema file
* @ param string $xmlUri External URI of XML to convert to local
*
* @ return string
*/
private function fixXmlLocation ( $schemaSource , $xmlUri )
{
$newPath = str_replace ( '\\' , '/' , __DIR__ ) . '/schema/dic/xliff-core/xml.xsd' ;
$parts = explode ( '/' , $newPath );
if ( 0 === stripos ( $newPath , 'phar://' )) {
$tmpfile = tempnam ( sys_get_temp_dir (), 'sf2' );
if ( $tmpfile ) {
copy ( $newPath , $tmpfile );
$parts = explode ( '/' , str_replace ( '\\' , '/' , $tmpfile ));
}
}
$drive = '\\' === DIRECTORY_SEPARATOR ? array_shift ( $parts ) . '/' : '' ;
$newPath = 'file:///' . $drive . implode ( '/' , array_map ( 'rawurlencode' , $parts ));
return str_replace ( $xmlUri , $newPath , $schemaSource );
2015-08-17 17:00:26 -07:00
}
/**
* Returns the XML errors of the internal XML parser .
*
* @ param bool $internalErrors
*
* @ return array An array of errors
*/
private function getXmlErrors ( $internalErrors )
{
$errors = array ();
foreach ( libxml_get_errors () as $error ) {
$errors [] = sprintf ( '[%s %s] %s (in %s - line %d, column %d)' ,
LIBXML_ERR_WARNING == $error -> level ? 'WARNING' : 'ERROR' ,
$error -> code ,
trim ( $error -> message ),
$error -> file ? : 'n/a' ,
$error -> line ,
$error -> column
);
}
libxml_clear_errors ();
libxml_use_internal_errors ( $internalErrors );
return $errors ;
}
2016-04-20 09:56:34 -07:00
/**
* Gets xliff file version based on the root " version " attribute .
* Defaults to 1.2 for backwards compatibility .
*
* @ param \DOMDocument $dom
*
* @ throws \InvalidArgumentException
*
* @ return string
*/
private function getVersionNumber ( \DOMDocument $dom )
{
/** @var \DOMNode $xliff */
foreach ( $dom -> getElementsByTagName ( 'xliff' ) as $xliff ) {
$version = $xliff -> attributes -> getNamedItem ( 'version' );
if ( $version ) {
return $version -> nodeValue ;
}
$namespace = $xliff -> attributes -> getNamedItem ( 'xmlns' );
if ( $namespace ) {
if ( substr_compare ( 'urn:oasis:names:tc:xliff:document:' , $namespace -> nodeValue , 0 , 34 ) !== 0 ) {
throw new \InvalidArgumentException ( sprintf ( 'Not a valid XLIFF namespace "%s"' , $namespace ));
}
return substr ( $namespace , 34 );
}
}
// Falls back to v1.2
return '1.2' ;
}
/*
* @ param \SimpleXMLElement | null $noteElement
* @ param string | null $encoding
*
* @ return array
*/
private function parseNotesMetadata ( \SimpleXMLElement $noteElement = null , $encoding = null )
{
$notes = array ();
if ( null === $noteElement ) {
return $notes ;
}
foreach ( $noteElement as $xmlNote ) {
$noteAttributes = $xmlNote -> attributes ();
$note = array ( 'content' => $this -> utf8ToCharset (( string ) $xmlNote , $encoding ));
if ( isset ( $noteAttributes [ 'priority' ])) {
$note [ 'priority' ] = ( int ) $noteAttributes [ 'priority' ];
}
if ( isset ( $noteAttributes [ 'from' ])) {
$note [ 'from' ] = ( string ) $noteAttributes [ 'from' ];
}
$notes [] = $note ;
}
return $notes ;
}
2015-08-17 17:00:26 -07:00
}