Skip to content

Commit bbca0af

Browse files
committed
[FEATURE] Add MigrateEmailFlagToEmailTypeFlexFormFractor
1 parent be6a142 commit bbca0af

File tree

19 files changed

+651
-42
lines changed

19 files changed

+651
-42
lines changed

fractor-xml/src/AbstractXmlFractor.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
abstract class AbstractXmlFractor implements DomNodeVisitor, XmlFractor
1111
{
12-
public function beforeTraversal(\DOMNode $rootNode): void
12+
public function beforeTraversal(\DOMDocument $rootNode): void
1313
{
1414
// no-op for now
1515
}
@@ -28,7 +28,7 @@ public function leaveNode(\DOMNode $node): void
2828
// no-op for now
2929
}
3030

31-
public function afterTraversal(\DOMNode $rootNode): void
31+
public function afterTraversal(\DOMDocument $rootNode): void
3232
{
3333
// no-op for now
3434
}

fractor-xml/src/Contract/DomNodeVisitor.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
*/
1212
interface DomNodeVisitor
1313
{
14-
public function beforeTraversal(\DOMNode $rootNode): void;
14+
public function beforeTraversal(\DOMDocument $rootNode): void;
1515

1616
/**
1717
* @return \DOMNode|DomDocumentIterator::*
@@ -20,5 +20,5 @@ public function enterNode(\DOMNode $node): \DOMNode|int;
2020

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

23-
public function afterTraversal(\DOMNode $rootNode): void;
23+
public function afterTraversal(\DOMDocument $rootNode): void;
2424
}
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+
}

fractor-xml/src/XmlFileProcessor.php

+6-3
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
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;
12-
use DOMDocument;
1313
use Webmozart\Assert\Assert;
1414

1515
final readonly class XmlFileProcessor implements FileProcessor
@@ -19,7 +19,8 @@
1919
*/
2020
public function __construct(
2121
private iterable $rules,
22-
private DomDocumentFactory $domDocumentFactory
22+
private DomDocumentFactory $domDocumentFactory,
23+
private PrettyXMLPrinter $prettyXMLPrinter
2324
) {
2425
Assert::allIsInstanceOf($this->rules, XmlFractor::class);
2526
}
@@ -41,6 +42,8 @@ public function handle(File $file): void
4142
$iterator->traverseDocument($document);
4243

4344
$newFileContent = $this->saveXml($document);
45+
// TODO make the indentation configurable in fractor config
46+
$newFileContent = $this->prettyXMLPrinter->format($newFileContent);
4447

4548
$file->changeFileContent($newFileContent);
4649
}
@@ -50,7 +53,7 @@ public function allowedFileExtensions(): array
5053
return ['xml'];
5154
}
5255

53-
private function saveXml(DOMDocument $document): string
56+
private function saveXml(\DOMDocument $document): string
5457
{
5558
$xml = $document->saveXML();
5659
if ($xml === false) {

fractor-xml/tests/DomDocumentIteratorTest.php

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

233-
public function beforeTraversal(\DOMNode $rootNode): void
233+
public function beforeTraversal(\DOMDocument $rootNode): void
234234
{
235235
$this->calls[] = sprintf('%s:beforeTraversal:%s', $this->visitorName, $rootNode->nodeName);
236236
}
@@ -246,7 +246,7 @@ public function leaveNode(\DOMNode $node): void
246246
$this->calls[] = sprintf('%s:leaveNode:%s', $this->visitorName, $node->nodeName);
247247
}
248248

249-
public function afterTraversal(\DOMNode $rootNode): void
249+
public function afterTraversal(\DOMDocument $rootNode): void
250250
{
251251
$this->calls[] = sprintf('%s:afterTraversal:%s', $this->visitorName, $rootNode->nodeName);
252252
}

fractor/src/Helper/ArrayUtility.php

+44
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+
}

fractor/src/Helper/StringUtility.php

+20
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+
}

typo3-fractor/composer.json

+3-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": [
@@ -17,7 +17,8 @@
1717
"a9f/fractor-extension-installer": "@dev",
1818
"a9f/fractor-fluid": "@dev",
1919
"a9f/fractor-xml": "@dev",
20-
"a9f/fractor-yaml": "@dev"
20+
"a9f/fractor-yaml": "@dev",
21+
"symfony/string": "^7.0"
2122
},
2223
"require-dev": {
2324
"ergebnis/composer-normalize": "^2.42",

typo3-fractor/config/typo3-12.php

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

33
declare(strict_types=1);
44

5+
use a9f\Typo3Fractor\TYPO3v12\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)