Passed
Branch fix-lack-of-untranslated-when-... (eeef8d)
by Domenico
04:05
created

Xliff12::tagOpen()   D

Complexity

Conditions 16
Paths 222

Size

Total Lines 62
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 16
eloc 26
nc 222
nop 3
dl 0
loc 62
rs 4.4583
c 1
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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, $name, $attr ) {
31
32
        $this->handleOpenUnit( $name, $attr );
33
34
        // check if we are entering into a <target>
35
        if ( 'target' === $name ) {
36
37
            if ( $this->currentTransUnitIsTranslatable === 'no' ) {
38
                $this->inTarget = false;
39
            } else {
40
                $this->inTarget = true;
41
            }
42
        }
43
44
        // open buffer
45
        if ( in_array( $name, $this->nodesToBuffer ) ) {
46
            $this->bufferIsActive = true;
47
        }
48
49
        // check if we are inside a <target>, obviously this happen only if there are targets inside the trans-unit
50
        // <target> must be stripped to be replaced, so this check avoids <target> reconstruction
51
        if ( !$this->inTarget ) {
52
53
            $tag = '';
54
55
            // construct tag
56
            $tag .= "<$name ";
57
58
            foreach ( $attr as $k => $v ) {
59
60
                //if tag name is file, we must replace the target-language attribute
61
                if ( $name === 'file' && $k === 'target-language' && !empty( $this->targetLang ) ) {
62
                    //replace Target language with job language provided from constructor
63
                    $tag .= "$k=\"$this->targetLang\" ";
64
                } else {
65
                    $tag .= "$k=\"$v\" ";
66
                }
67
68
            }
69
70
            $seg = $this->getCurrentSegment();
71
72
            if ( $name === $this->tuTagName && !empty( $seg ) and isset( $seg[ 'sid' ] ) ) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
73
74
                if ( strpos( $tag, 'help-id' ) === false ) {
75
                    if ( !empty( $seg[ 'sid' ] ) ) {
76
                        $tag .= "help-id=\"{$seg[ 'sid' ]}\" ";
77
                    }
78
                }
79
80
            }
81
82
            // Add MateCat specific namespace.
83
            // Add trgLang
84
            if ( $name === 'xliff' ) {
85
                if ( !array_key_exists( 'xmlns:mtc', $attr ) ) {
86
                    $tag .= ' xmlns:mtc="https://www.matecat.com" ';
87
                }
88
                $tag = preg_replace( '/trgLang="(.*?)"/', 'trgLang="' . $this->targetLang . '"', $tag );
89
            }
90
91
            $this->checkForSelfClosedTagAndFlush( $parser, $tag );
92
93
        }
94
95
    }
96
97
98
    /**
99
     * @inheritDoc
100
     */
101
    protected function tagClose( $parser, $name ) {
102
        $tag = '';
103
104
        /**
105
         * if is a tag within <target> or
106
         * if it is an empty tag, do not add closing tag because we have already closed it in
107
         *
108
         * self::tagOpen method
109
         */
110
        if ( !$this->isEmpty ) {
111
112
            if ( !$this->inTarget ) {
113
                $tag = "</$name>";
114
            }
115
116
            if ( 'target' == $name ) {
117
118
                if ( isset( $this->transUnits[ $this->currentTransUnitId ] ) ) {
119
120
                    // get translation of current segment, by indirect indexing: id -> positional index -> segment
121
                    // actually there may be more than one segment to that ID if there are two mrk of the same source segment
122
                    $tag = $this->rebuildTarget();
123
124
                }
125
126
                $this->targetWasWritten = true;
127
                // signal we are leaving a target
128
                $this->inTarget = false;
129
                $this->postProcAndFlush( $this->outputFP, $tag, true );
130
131
            } elseif ( in_array( $name, $this->nodesToBuffer ) ) { // we are closing a critical CDATA section
132
133
                $this->bufferIsActive = false;
134
                $tag                  = $this->CDATABuffer . "</$name>";
135
                $this->CDATABuffer    = "";
136
137
                //flush to the pointer
138
                $this->postProcAndFlush( $this->outputFP, $tag );
139
140
            } elseif ( $name === $this->tuTagName ) {
141
142
                $tag = "";
143
144
                // handling </trans-unit> closure
145
                if ( !$this->targetWasWritten ) {
146
147
                    if ( isset( $this->transUnits[ $this->currentTransUnitId ] ) ) {
148
                        $tag = $this->rebuildTarget();
149
                    } else {
150
                        $tag = $this->createTargetTag( "", "" );
151
                    }
152
153
                }
154
155
                $tag                    .= "</$this->tuTagName>";
156
                $this->targetWasWritten = false;
157
                $this->postProcAndFlush( $this->outputFP, $tag );
158
159
            } elseif ( $this->bufferIsActive ) { // this is a tag ( <g | <mrk ) inside a seg or seg-source tag
160
                $this->CDATABuffer .= "</$name>";
161
                // Do NOT Flush
162
            } else { //generic tag closure do Nothing
163
                // flush to pointer
164
                $this->postProcAndFlush( $this->outputFP, $tag );
165
            }
166
167
        } else {
168
            //ok, nothing to be done; reset flag for next coming tag
169
            $this->isEmpty = false;
170
        }
171
172
        // check if we are leaving a <trans-unit> (xliff v1.*) or <unit> (xliff v2.*)
173
        if ( $this->tuTagName === $name ) {
174
            $this->currentTransUnitIsTranslatable = null;
175
            $this->inTU                           = false;
176
            $this->hasWrittenCounts               = false;
177
178
            $this->resetCounts();
179
        }
180
    }
181
182
    /**
183
     * prepare segment tagging for xliff insertion
184
     *
185
     * @param array  $seg
186
     * @param string $transUnitTranslation
187
     *
188
     * @return string
189
     */
190
    protected function prepareTranslation( array $seg, string $transUnitTranslation = "" ): string {
191
192
        $segment     = Strings::removeDangerousChars( $seg [ 'segment' ] );
193
        $translation = Strings::removeDangerousChars( $seg [ 'translation' ] );
194
195
        if ( $seg [ 'translation' ] == '' ) {
196
            $translation = $segment;
197
        } else {
198
            if ( $this->callback instanceof XliffReplacerCallbackInterface ) {
199
                $error = ( !empty( $seg[ 'error' ] ) ) ? $seg[ 'error' ] : null;
200
                if ( $this->callback->thereAreErrors( $seg[ 'sid' ], $segment, $translation, [], $error ) ) {
201
                    $translation = '|||UNTRANSLATED_CONTENT_START|||' . $segment . '|||UNTRANSLATED_CONTENT_END|||';
202
                }
203
            }
204
        }
205
206
        $transUnitTranslation .= $seg[ 'prev_tags' ] . $this->rebuildMarks( $seg, $translation ) . ltrim( $seg[ 'succ_tags' ] );
207
208
        return $transUnitTranslation;
209
    }
210
211
    protected function rebuildMarks( array $seg, string $translation ): string {
212
213
        if ( $seg[ 'mrk_id' ] !== null && $seg[ 'mrk_id' ] != '' ) {
214
            if ( $this->targetLang === 'ja-JP' ) {
215
                $seg[ 'mrk_succ_tags' ] = ltrim( $seg[ 'mrk_succ_tags' ] );
216
            }
217
218
            $translation = "<mrk mid=\"" . $seg[ 'mrk_id' ] . "\" mtype=\"seg\">" . $seg[ 'mrk_prev_tags' ] . $translation . $seg[ 'mrk_succ_tags' ] . "</mrk>";
219
        }
220
221
        return $translation;
222
223
    }
224
225
    /**
226
     * This function creates a <target>
227
     *
228
     * @param string $translation
229
     * @param string $stateProp
230
     *
231
     * @return string
232
     */
233
    private function createTargetTag( string $translation, string $stateProp ): string {
234
        $targetLang = ' xml:lang="' . $this->targetLang . '"';
235
        $tag        = "<target $targetLang $stateProp>$translation</target>";
236
        $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>';
237
238
        return $tag;
239
240
    }
241
242
    protected function rebuildTarget(): string {
243
244
        // init translation and state
245
        $translation  = '';
246
        $lastMrkState = null;
247
        $stateProp    = null;
248
249
        // we must reset the lastMrkId found because this is a new segment.
250
        $lastMrkId = -1;
251
252
        foreach ( $this->lastTransUnit as $pos => $seg ) {
253
254
            /*
255
             * This routine works to respect the positional orders of markers.
256
             * In every cycle we check if the mrk of the segment is below or equal the last one.
257
             * When this is true, means that the mrk id belongs to the next segment with the same internal_id
258
             * so we MUST stop to apply markers and translations
259
             * and stop to add eq_word_count
260
             *
261
             * Begin:
262
             * pre-assign zero to the new mrk if this is the first one ( in this segment )
263
             * If it is null leave it NULL
264
             */
265
            if ( (int)$seg[ "mrk_id" ] < 0 && $seg[ "mrk_id" ] !== null ) {
266
                $seg[ "mrk_id" ] = 0;
267
            }
268
269
            /*
270
             * WARNING:
271
             * For those seg-source that doesn't have a mrk ( having a mrk id === null )
272
             * ( null <= -1 ) === true
273
             * so, cast to int
274
             */
275
            if ( (int)$seg[ "mrk_id" ] <= $lastMrkId ) {
276
                break;
277
            }
278
279
            // update counts
280
            if ( !empty( $seg ) ) {
281
                $this->updateSegmentCounts( $seg );
282
            }
283
284
            // delete translations so the prepareSegment
285
            // will put source content in target tag
286
            if ( $this->sourceInTarget ) {
287
                $seg[ 'translation' ] = '';
288
                $this->resetCounts();
289
            }
290
291
            // append $translation
292
            $translation = $this->prepareTranslation( $seg, $translation );
293
294
            $lastMrkId = $seg[ "mrk_id" ];
295
296
            [ $stateProp, $lastMrkState ] = StatusToStateAttribute::getState( $seg[ 'status' ], $this->xliffVersion, $stateProp, $lastMrkState );
297
298
        }
299
300
        //append translation
301
        return $this->createTargetTag( $translation, $stateProp );
0 ignored issues
show
Bug introduced by
It seems like $stateProp can also be of type null; however, parameter $stateProp of Matecat\XliffParser\Xlif...ff12::createTargetTag() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

301
        return $this->createTargetTag( $translation, /** @scrutinizer ignore-type */ $stateProp );
Loading history...
302
303
    }
304
305
}