Passed
Push — master ( cd421c...e31be6 )
by Domenico
02:52
created

Xliff12   A

Complexity

Total Complexity 38

Size/Duplication

Total Lines 268
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 97
dl 0
loc 268
rs 9.36
c 1
b 0
f 0
wmc 38

6 Methods

Rating   Name   Duplication   Size   Complexity  
B rebuildTarget() 0 60 7
A createTargetTag() 0 6 1
B tagClose() 0 78 11
A prepareTranslation() 0 19 5
B tagOpen() 0 46 11
A rebuildMarks() 0 7 3
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * @author hashashiyyin [email protected] / [email protected]
5
 * Date: 02/08/24
6
 * Time: 11:45
7
 *
8
 */
9
10
namespace Matecat\XliffParser\XliffReplacer;
11
12
use Matecat\XliffParser\Utils\Strings;
13
14
class Xliff12 extends AbstractXliffReplacer {
15
16
    /**
17
     * @var array
18
     */
19
    protected array $nodesToBuffer = [
20
            'source',
21
            'seg-source',
22
            'note',
23
            'context',
24
            'context-group'
25
    ];
26
27
    /**
28
     * @inheritDoc
29
     */
30
    protected function tagOpen( $parser, string $name, array $attr ) {
31
32
        $this->handleOpenUnit( $name, $attr );
33
34
        $this->checkSetInTarget( $name );
35
36
        // open buffer
37
        $this->setInBuffer( $name );
38
39
        // check if we are inside a <target>, obviously this happen only if there are targets inside the trans-unit
40
        // <target> must be stripped to be replaced, so this check avoids <target> reconstruction
41
        if ( !$this->inTarget ) {
42
43
            $tag = '';
44
45
            // construct tag
46
            $tag .= "<$name ";
47
48
            foreach ( $attr as $k => $v ) {
49
50
                //if tag name is file, we must replace the target-language attribute
51
                if ( $name === 'file' && $k === 'target-language' && !empty( $this->targetLang ) ) {
52
                    //replace Target language with job language provided from constructor
53
                    $tag .= "$k=\"$this->targetLang\" ";
54
                } else {
55
                    $tag .= "$k=\"$v\" ";
56
                }
57
58
            }
59
60
            $seg = $this->getCurrentSegment();
61
62
            if ( $name === $this->tuTagName && !empty( $seg ) && isset( $seg[ 'sid' ] ) ) {
63
64
                // add `help-id` to xliff v.1*
65
                if ( strpos( $tag, 'help-id' ) === false ) {
66
                    if ( !empty( $seg[ 'sid' ] ) ) {
67
                        $tag .= "help-id=\"{$seg[ 'sid' ]}\" ";
68
                    }
69
                }
70
71
            }
72
73
            $tag = $this->handleOpenXliffTag( $name, $attr, $tag );
74
75
            $this->checkForSelfClosedTagAndFlush( $parser, $tag );
76
77
        }
78
79
    }
80
81
82
    /**
83
     * @inheritDoc
84
     */
85
    protected function tagClose( $parser, string $name ) {
86
        $tag = '';
87
88
        /**
89
         * if is a tag within <target> or
90
         * if it is an empty tag, do not add closing tag because we have already closed it in
91
         *
92
         * self::tagOpen method
93
         */
94
        if ( !$this->isEmpty ) {
95
96
            if ( !$this->inTarget ) {
97
                $tag = "</$name>";
98
            }
99
100
            if ( 'target' == $name ) {
101
102
                if ( isset( $this->transUnits[ $this->currentTransUnitId ] ) ) {
103
104
                    // get translation of current segment, by indirect indexing: id -> positional index -> segment
105
                    // actually there may be more than one segment to that ID if there are two mrk of the same source segment
106
                    $tag = $this->rebuildTarget();
107
108
                }
109
110
                $this->targetWasWritten = true;
111
                // signal we are leaving a target
112
                $this->inTarget = false;
113
                $this->postProcAndFlush( $this->outputFP, $tag, true );
114
115
            } elseif ( in_array( $name, $this->nodesToBuffer ) ) { // we are closing a critical CDATA section
116
117
                $this->bufferIsActive = false;
118
                $tag                  = $this->CDATABuffer . "</$name>";
119
                $this->CDATABuffer    = "";
120
121
                //flush to the pointer
122
                $this->postProcAndFlush( $this->outputFP, $tag );
123
124
            } elseif ( $name === $this->tuTagName ) {
125
126
                $tag = "";
127
128
                // handling </trans-unit> closure
129
                if ( !$this->targetWasWritten ) {
130
131
                    if ( isset( $this->transUnits[ $this->currentTransUnitId ] ) ) {
132
                        $tag = $this->rebuildTarget();
133
                    } else {
134
                        $tag = $this->createTargetTag( "", "" );
135
                    }
136
137
                }
138
139
                $tag                    .= "</$this->tuTagName>";
140
                $this->targetWasWritten = false;
141
                $this->postProcAndFlush( $this->outputFP, $tag );
142
143
            } elseif ( $this->bufferIsActive ) { // this is a tag ( <g | <mrk ) inside a seg or seg-source tag
144
                $this->CDATABuffer .= "</$name>";
145
                // Do NOT Flush
146
            } else { //generic tag closure do Nothing
147
                // flush to pointer
148
                $this->postProcAndFlush( $this->outputFP, $tag );
149
            }
150
151
        } else {
152
            //ok, nothing to be done; reset flag for next coming tag
153
            $this->isEmpty = false;
154
        }
155
156
        // check if we are leaving a <trans-unit> (xliff v1.*) or <unit> (xliff v2.*)
157
        if ( $this->tuTagName === $name ) {
158
            $this->currentTransUnitIsTranslatable = null;
159
            $this->inTU                           = false;
160
            $this->hasWrittenCounts               = false;
161
162
            $this->resetCounts();
163
        }
164
    }
165
166
    /**
167
     * prepare segment tagging for xliff insertion
168
     *
169
     * @param array  $seg
170
     * @param string $transUnitTranslation
171
     *
172
     * @return string
173
     */
174
    protected function prepareTranslation( array $seg, string $transUnitTranslation = "" ): string {
175
176
        $segment     = Strings::removeDangerousChars( $seg [ 'segment' ] );
177
        $translation = Strings::removeDangerousChars( $seg [ 'translation' ] );
178
179
        if ( $seg [ 'translation' ] == '' ) {
180
            $translation = $segment;
181
        } else {
182
            if ( $this->callback instanceof XliffReplacerCallbackInterface ) {
183
                $error = ( !empty( $seg[ 'error' ] ) ) ? $seg[ 'error' ] : null;
184
                if ( $this->callback->thereAreErrors( $seg[ 'sid' ], $segment, $translation, [], $error ) ) {
185
                    $translation = '|||UNTRANSLATED_CONTENT_START|||' . $segment . '|||UNTRANSLATED_CONTENT_END|||';
186
                }
187
            }
188
        }
189
190
        $transUnitTranslation .= $seg[ 'prev_tags' ] . $this->rebuildMarks( $seg, $translation ) . ltrim( $seg[ 'succ_tags' ] );
191
192
        return $transUnitTranslation;
193
    }
194
195
    protected function rebuildMarks( array $seg, string $translation ): string {
196
197
        if ( $seg[ 'mrk_id' ] !== null && $seg[ 'mrk_id' ] != '' ) {
198
            $translation = "<mrk mid=\"" . $seg[ 'mrk_id' ] . "\" mtype=\"seg\">" . $seg[ 'mrk_prev_tags' ] . $translation . $seg[ 'mrk_succ_tags' ] . "</mrk>";
199
        }
200
201
        return $translation;
202
203
    }
204
205
    /**
206
     * This function creates a <target>
207
     *
208
     * @param string $translation
209
     * @param string $stateProp
210
     *
211
     * @return string
212
     */
213
    private function createTargetTag( string $translation, string $stateProp ): string {
214
        $targetLang = ' xml:lang="' . $this->targetLang . '"';
215
        $tag        = "<target $targetLang $stateProp>$translation</target>";
216
        $tag        .= "\n<count-group name=\"$this->currentTransUnitId\"><count count-type=\"x-matecat-raw\">" . $this->counts[ 'raw_word_count' ] . "</count><count count-type=\"x-matecat-weighted\">" . $this->counts[ 'eq_word_count' ] . '</count></count-group>';
217
218
        return $tag;
219
220
    }
221
222
    protected function rebuildTarget(): string {
223
224
        // init translation and state
225
        $translation  = '';
226
        $lastMrkState = null;
227
        $stateProp    = '';
228
229
        // we must reset the lastMrkId found because this is a new segment.
230
        $lastMrkId = -1;
231
232
        foreach ( $this->lastTransUnit as $pos => $seg ) {
233
234
            /*
235
             * This routine works to respect the positional orders of markers.
236
             * In every cycle we check if the mrk of the segment is below or equal the last one.
237
             * When this is true, means that the mrk id belongs to the next segment with the same internal_id
238
             * so we MUST stop to apply markers and translations
239
             * and stop to add eq_word_count
240
             *
241
             * Begin:
242
             * pre-assign zero to the new mrk if this is the first one ( in this segment )
243
             * If it is null leave it NULL
244
             */
245
            if ( (int)$seg[ "mrk_id" ] < 0 && $seg[ "mrk_id" ] !== null ) {
246
                $seg[ "mrk_id" ] = 0;
247
            }
248
249
            /*
250
             * WARNING:
251
             * For those seg-source that doesn't have a mrk ( having a mrk id === null )
252
             * ( null <= -1 ) === true
253
             * so, cast to int
254
             */
255
            if ( (int)$seg[ "mrk_id" ] <= $lastMrkId ) {
256
                break;
257
            }
258
259
            // update counts
260
            if ( !empty( $seg ) ) {
261
                $this->updateSegmentCounts( $seg );
262
            }
263
264
            // delete translations so the prepareSegment
265
            // will put source content in target tag
266
            if ( $this->sourceInTarget ) {
267
                $seg[ 'translation' ] = '';
268
                $this->resetCounts();
269
            }
270
271
            // append $translation
272
            $translation = $this->prepareTranslation( $seg, $translation );
273
274
            $lastMrkId = $seg[ "mrk_id" ];
275
276
            [ $stateProp, $lastMrkState ] = StatusToStateAttribute::getState( $this->xliffVersion, $seg[ 'status' ], $lastMrkState );
277
278
        }
279
280
        //append translation
281
        return $this->createTargetTag( $translation, $stateProp );
282
283
    }
284
285
}