Skip to content

Commit 03ddf19

Browse files
committed
[FEATURE] Add MigrateEmailFlagToEmailTypeFlexFormFractor
1 parent 091fe6b commit 03ddf19

File tree

19 files changed

+654
-41
lines changed

19 files changed

+654
-41
lines changed

packages/fractor-xml/src/AbstractXmlFractor.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212

1313
abstract class AbstractXmlFractor implements DomNodeVisitor, XmlFractor
1414
{
15-
private ?File $file = null;
15+
protected ?File $file = null;
1616

17-
public function beforeTraversal(File $file, \DOMNode $rootNode): void
17+
public function beforeTraversal(File $file, \DOMDocument $rootNode): void
1818
{
1919
$this->file = $file;
2020
}
@@ -41,7 +41,7 @@ public function leaveNode(\DOMNode $node): void
4141
// no-op for now
4242
}
4343

44-
public function afterTraversal(\DOMNode $rootNode): void
44+
public function afterTraversal(\DOMDocument $rootNode): void
4545
{
4646
// no-op for now
4747
}

packages/fractor-xml/src/Contract/DomNodeVisitor.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
*/
1313
interface DomNodeVisitor
1414
{
15-
public function beforeTraversal(File $file, \DOMNode $rootNode): void;
15+
public function beforeTraversal(File $file, \DOMDocument $rootNode): void;
1616

1717
/**
1818
* @return \DOMNode|DomDocumentIterator::*
@@ -21,5 +21,5 @@ public function enterNode(\DOMNode $node): \DOMNode|int;
2121

2222
public function leaveNode(\DOMNode $node): void;
2323

24-
public function afterTraversal(\DOMNode $rootNode): void;
24+
public function afterTraversal(\DOMDocument $rootNode): void;
2525
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace a9f\FractorXml\Printer;
6+
7+
/**
8+
* @source https://github.com/shanethehat/pretty-xml/blob/master/src/PrettyXml/Formatter.php
9+
*/
10+
class PrettyXMLPrinter
11+
{
12+
private int $depth;
13+
14+
private int $indent = 4;
15+
16+
private string $padChar = ' ';
17+
18+
private bool $preserveWhitespace = false;
19+
20+
public function setIndentSize(int $indent): void
21+
{
22+
$this->indent = $indent;
23+
}
24+
25+
public function setIndentCharacter(string $indentCharacter): void
26+
{
27+
$this->padChar = $indentCharacter;
28+
}
29+
30+
public function format(string $xml): string
31+
{
32+
$output = '';
33+
$this->depth = 0;
34+
35+
$parts = $this->getXmlParts($xml);
36+
37+
if (str_starts_with($parts[0], '<?xml')) {
38+
$output = array_shift($parts) . PHP_EOL;
39+
}
40+
41+
foreach ($parts as $key => $part) {
42+
$element = preg_replace('/<([a-zA-Z0-9\-_]+).*/', '$1', $part);
43+
44+
if ($element && isset($parts[$key + 1]) && preg_replace('~</(.*)>~', '$1', $parts[$key + 1]) === $element) {
45+
$output .= $this->getOutputForPart($part, '');
46+
} else {
47+
$output .= $this->getOutputForPart($part);
48+
}
49+
}
50+
51+
return trim((string) preg_replace('~>' . $this->padChar . '+<~', '><', $output));
52+
}
53+
54+
/**
55+
* @return string[]
56+
*/
57+
private function getXmlParts(string $xml): array
58+
{
59+
$withNewLines = preg_replace('/(>)(<)(\/*)/', "$1\n$2$3", trim($xml));
60+
return explode("\n", (string) $withNewLines);
61+
}
62+
63+
private function getOutputForPart(string $part, string $eol = PHP_EOL): string
64+
{
65+
$output = '';
66+
$this->runPre($part);
67+
68+
if ($this->preserveWhitespace) {
69+
$output .= $part . $eol;
70+
} else {
71+
$part = trim($part);
72+
$output .= $this->getPaddedString($part) . $eol;
73+
}
74+
75+
$this->runPost($part);
76+
77+
return $output;
78+
}
79+
80+
private function runPre(string $part): void
81+
{
82+
if ($this->isClosingTag($part)) {
83+
$this->depth--;
84+
}
85+
}
86+
87+
private function runPost(string $part): void
88+
{
89+
if ($this->isOpeningCdataTag($part) && $this->isClosingCdataTag($part)) {
90+
return;
91+
}
92+
if ($this->isOpeningTag($part)) {
93+
$this->depth++;
94+
}
95+
if ($this->isClosingCdataTag($part)) {
96+
$this->preserveWhitespace = false;
97+
}
98+
if ($this->isOpeningCdataTag($part)) {
99+
$this->preserveWhitespace = true;
100+
}
101+
}
102+
103+
private function getPaddedString(string $part): string
104+
{
105+
return str_pad($part, strlen($part) + ($this->depth * $this->indent), $this->padChar, STR_PAD_LEFT);
106+
}
107+
108+
private function isOpeningTag(string $part): bool
109+
{
110+
return (bool) preg_match('/^<[^\/]*>$/', $part);
111+
}
112+
113+
private function isClosingTag(string $part): bool
114+
{
115+
return (bool) preg_match('/^\s*<\//', $part);
116+
}
117+
118+
private function isOpeningCdataTag(string $part): bool
119+
{
120+
return str_contains($part, '<![CDATA[');
121+
}
122+
123+
private function isClosingCdataTag(string $part): bool
124+
{
125+
return str_contains($part, ']]>');
126+
}
127+
}

packages/fractor-xml/src/XmlFileProcessor.php

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use a9f\Fractor\Application\ValueObject\File;
99
use a9f\Fractor\Exception\ShouldNotHappenException;
1010
use a9f\FractorXml\Contract\XmlFractor;
11+
use a9f\FractorXml\Printer\PrettyXMLPrinter;
1112
use a9f\FractorXml\ValueObjectFactory\DomDocumentFactory;
1213

1314
/**
@@ -20,6 +21,7 @@
2021
*/
2122
public function __construct(
2223
private DomDocumentFactory $domDocumentFactory,
24+
private PrettyXMLPrinter $prettyXMLPrinter,
2325
private iterable $rules
2426
) {
2527
}
@@ -42,6 +44,8 @@ public function handle(File $file, iterable $appliedRules): void
4244
$iterator->traverseDocument($file, $document);
4345

4446
$newXml = $this->saveXml($document);
47+
// TODO make the indentation configurable in fractor config
48+
$newXml = $this->prettyXMLPrinter->format($newXml);
4549

4650
if ($newXml === $originalXml) {
4751
return;

packages/fractor-xml/tests/DomDocumentIteratorTest.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ public function __construct(
231231
) {
232232
}
233233

234-
public function beforeTraversal(File $file, \DOMNode $rootNode): void
234+
public function beforeTraversal(File $file, \DOMDocument $rootNode): void
235235
{
236236
$this->calls[] = sprintf('%s:beforeTraversal:%s', $this->visitorName, $rootNode->nodeName);
237237
}
@@ -247,7 +247,7 @@ public function leaveNode(\DOMNode $node): void
247247
$this->calls[] = sprintf('%s:leaveNode:%s', $this->visitorName, $node->nodeName);
248248
}
249249

250-
public function afterTraversal(\DOMNode $rootNode): void
250+
public function afterTraversal(\DOMDocument $rootNode): void
251251
{
252252
$this->calls[] = sprintf('%s:afterTraversal:%s', $this->visitorName, $rootNode->nodeName);
253253
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace a9f\Fractor\Helper;
6+
7+
class ArrayUtility
8+
{
9+
/**
10+
* @return string[]
11+
*/
12+
public static function trimExplode(
13+
string $delimiter,
14+
string $string,
15+
bool $removeEmptyValues = false,
16+
int $limit = 0
17+
): array {
18+
if ($delimiter === '') {
19+
throw new \InvalidArgumentException('Please define a correct delimiter');
20+
}
21+
22+
$result = explode($delimiter, $string);
23+
24+
if ($removeEmptyValues) {
25+
$temp = [];
26+
foreach ($result as $value) {
27+
if (trim($value) !== '') {
28+
$temp[] = $value;
29+
}
30+
}
31+
32+
$result = $temp;
33+
}
34+
35+
if ($limit > 0 && count($result) > $limit) {
36+
$lastElements = array_splice($result, $limit - 1);
37+
$result[] = implode($delimiter, $lastElements);
38+
} elseif ($limit < 0) {
39+
$result = array_slice($result, 0, $limit);
40+
}
41+
42+
return array_map('trim', $result);
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace a9f\Fractor\Helper;
6+
7+
class StringUtility
8+
{
9+
/**
10+
* Check if an item exists in a comma-separated list of items.
11+
*
12+
* @param string $list Comma-separated list of items (string)
13+
* @param string $item Item to check for
14+
* @return bool TRUE if $item is in $list
15+
*/
16+
public static function inList(string $list, string $item): bool
17+
{
18+
return str_contains(',' . $list . ',', ',' . $item . ',');
19+
}
20+
}

packages/typo3-fractor/composer.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "a9f/typo3-fractor",
3-
"description": "TYPO3 extension for the File Read-Analyse-Change TOol. Allows modifying XML files",
3+
"description": "TYPO3 extension for the File Read-Analyse-Change Tool. Allows modifying XML files",
44
"license": "MIT",
55
"type": "fractor-extension",
66
"authors": [
@@ -12,12 +12,14 @@
1212
],
1313
"require": {
1414
"php": "^8.2",
15+
"ext-dom": "*",
1516
"a9f/fractor": "^0.2",
1617
"a9f/fractor-doc-generator": "^0.2",
1718
"a9f/fractor-extension-installer": "^0.2",
1819
"a9f/fractor-fluid": "^0.2",
1920
"a9f/fractor-xml": "^0.2",
20-
"a9f/fractor-yaml": "^0.2"
21+
"a9f/fractor-yaml": "^0.2",
22+
"symfony/string": "^7.0"
2123
},
2224
"require-dev": {
2325
"ergebnis/composer-normalize": "^2.42",

packages/typo3-fractor/config/typo3-11.php

+2
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
declare(strict_types=1);
44

5+
use FlexForm\MigrateEmailFlagToEmailTypeFlexFormFractor;
56
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
67

78
return static function (ContainerConfigurator $containerConfigurator): void {
89
$services = $containerConfigurator->services();
910
$services->defaults()
1011
->autoconfigure()
1112
->autowire();
13+
$services->set(MigrateEmailFlagToEmailTypeFlexFormFractor::class);
1214
};

0 commit comments

Comments
 (0)