Passed
Pull Request — master (#95)
by Mauro
06:03 queued 02:57
created

Xliff12::tagOpen()   C

Complexity

Conditions 13
Paths 25

Size

Total Lines 52
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 13
eloc 21
c 1
b 0
f 0
nc 25
nop 3
dl 0
loc 52
rs 6.6166

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