summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/Translate/ffs/XliffFFS.php
blob: cef5d5bbfefaa512e1282c566c051abe1d18d589 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
<?php
/**
 * Partial support for the Xliff translation format.
 *
 * @file
 * @author Niklas Laxström
 * @license GPL-2.0-or-later
 */

/**
 * Partial support for the Xliff translation format.
 * @since 2013-04
 * @ingroup FFS
 */
class XliffFFS extends SimpleFFS {
	public static function isValid( $data ) {
		$doc = new DomDocument( '1.0' );
		$doc->loadXML( $data );

		$errors = libxml_get_errors();
		if ( $errors ) {
			return false;
		}

		if ( strpos( $data, 'version="1.2">' ) !== false ) {
			$schema = __DIR__ . '/../data/xliff-core-1.2-transitional.xsd';
			if ( !$doc->schemaValidate( $schema ) ) {
				return false;
			}
		}

		return true;
	}

	public function getFileExtensions() {
		return [ '.xlf', '.xliff', '.xml' ];
	}

	/**
	 * @param string $data
	 * @param string $element
	 * @return array Parsed data.
	 */
	public function readFromVariable( $data, $element = 'target' ) {
		$messages = [];
		$mangler = $this->group->getMangler();

		$reader = new SimpleXMLElement( $data );
		$reader->registerXPathNamespace(
			'xliff',
			'urn:oasis:names:tc:xliff:document:1.2'
		);

		$items = array_merge(
			$reader->xpath( '//trans-unit' ),
			$reader->xpath( '//xliff:trans-unit' )
		);

		foreach ( $items as $item ) {
			/** @var SimpleXMLElement $source */
			$source = $item->$element;

			if ( !$source ) {
				continue;
			}

			$key = (string)$item['id'];

			/* In case there are tags inside the element, preserve
			 * them. */
			$dom = new DOMDocument( '1.0' );
			$dom->loadXML( $source->asXML() );
			$value = self::getInnerXml( $dom->documentElement );

			/* This might not be 100% according to the spec, but
			 * for now if there is explicit approved=no, mark it
			 * as fuzzy, but don't do that if the attribute is not
			 * set */
			if ( (string)$source['state'] === 'needs-l10n' ) {
				$value = TRANSLATE_FUZZY . $value;
			}

			// Strip CDATA if present
			$value = preg_replace( '/<!\[CDATA\[(.*?)\]\]>/s', '\1', $value );

			$messages[$key] = $value;
		}

		return [
			'MESSAGES' => $mangler->mangle( $messages ),
		];
	}

	/**
	 * @param string $code Language code.
	 * @return array|bool
	 * @throws MWException
	 */
	public function read( $code ) {
		if ( !$this->exists( $code ) ) {
			return false;
		}

		$filename = $this->group->getSourceFilePath( $code );
		$input = file_get_contents( $filename );
		if ( $input === false ) {
			throw new MWException( "Unable to read file $filename." );
		}

		$element = $code === $this->group->getSourceLanguage() ? 'source' : 'target';

		return $this->readFromVariable( $input, $element );
	}

	/**
	 * Gets the html inside en element without the element itself.
	 *
	 * @param DomElement $node
	 * @return string
	 */
	public static function getInnerXml( DomElement $node ) {
		$text = '';
		foreach ( $node->childNodes as $child ) {
			$text .= $child->ownerDocument->saveXML( $child );
		}

		return $text;
	}

	protected function writeReal( MessageCollection $collection ) {
		$mangler = $this->group->getMangler();

		$template = new DomDocument( '1.0' );
		$template->preserveWhiteSpace = false;
		$template->formatOutput = true;

		// Try to use the definition file as template
		$sourceLanguage = $this->group->getSourceLanguage();
		$sourceFile = $this->group->getSourceFilePath( $sourceLanguage );
		if ( file_exists( $sourceFile ) ) {
			$template->load( $sourceFile );
		} else {
			// Else use standard template
			$template->load( __DIR__ . '/../data/xliff-template.xml' );
		}

		$list = $template->getElementsByTagName( 'body' )->item( 0 );
		$list->nodeValue = null;

		/** @var TMessage $m */
		foreach ( $collection as $key => $m ) {
			$key = $mangler->unmangle( $key );

			$value = $m->translation();
			$value = str_replace( TRANSLATE_FUZZY, '', $value );

			// @todo Support placeholder tags etc.
			$source = $template->createDocumentFragment();
			$source->appendXML( htmlspecialchars( $m->definition() ) );

			$target = $template->createDocumentFragment();
			$target->appendXML( htmlspecialchars( $value ) );

			$sourceElement = $template->createElement( 'source' );
			$sourceElement->appendChild( $source );

			$targetElement = $template->createElement( 'target' );
			$targetElement->appendChild( $target );
			if ( $m->getProperty( 'status' ) === 'fuzzy' ) {
				$targetElement->setAttribute( 'state', 'needs-l10n' );
			}
			if ( $m->getProperty( 'status' ) === 'proofread' ) {
				$targetElement->setAttribute( 'state', 'signed-off' );
			}

			$transUnit = $template->createElement( 'trans-unit' );
			$transUnit->setAttribute( 'id', $key );
			$transUnit->appendChild( $sourceElement );
			$transUnit->appendChild( $targetElement );

			$list->appendChild( $transUnit );
		}

		$template->encoding = 'UTF-8';

		return $template->saveXML();
	}

	public function supportsFuzzy() {
		return 'yes';
	}
}