Passed
Push — master ( 149a8e...a14c95 )
by Domenico
04:03
created

Xliff12   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 305
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 110
c 2
b 0
f 0
dl 0
loc 305
rs 8.96
wmc 43

7 Methods

Rating   Name   Duplication   Size   Complexity  
B tagOpen() 0 47 11
B rebuildTarget() 0 60 7
A createTargetTag() 0 6 1
C tagClose() 0 91 13
A prepareTranslation() 0 19 5
A rebuildMarks() 0 7 3
A getCurrentSegment() 0 6 3

How to fix   Complexity   

Complex Class

Complex classes like Xliff12 often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Xliff12, and based on these observations, apply Extract Interface, too.

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