From 555a67ef21d595bcbbac35790cd6858f12daf9d2 Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Tue, 16 Jun 2026 23:15:55 +0200 Subject: [PATCH] [BUGFIX] Read project version from the DOM so "0.10" is not coerced to 0.1 A in guides.xml was rendered as version 0.1 (title, objects.inv, every |version| substitution). XmlFileLoader parses guides.xml with XmlUtils::convertDomElementToArray(), which runs phpize() on every attribute value, coercing version-like strings into numbers: "0.10" becomes the float 0.1, "1.0" becomes 1, "13.0" becomes 13. Read the attributes (all strings) straight from the DOM, and detach the element before the conversion so phpize never sees them. The version is now read correctly at the source instead of being coerced and patched up afterwards, so the beforeNormalization workaround in the Symfony config is removed. Writing the version directly (version="0.10") now just works. The previous "escaped version" workaround -- version="'3.0'" with single quotes to dodge phpize -- is no longer necessary, but existing guides.xml files may still use it, so the surrounding single quotes are still stripped (for version and release) to keep those files rendering 3.0 rather than the literal '3.0'. A regression fixture covers the quoted form. Reported on docs.typo3.org for netresearch/nr-vault and nr-llm (0.10 / 0.12). Signed-off-by: Sebastian Mendel --- .../guides-cli/src/Config/XmlFileLoader.php | 39 ++++++++++++++++++ .../DependencyInjection/GuidesExtension.php | 41 +------------------ .../expected/index.html | 17 ++++++++ .../input/guides.xml | 12 ++++++ .../input/index.rst | 4 ++ .../expected/index.html | 13 +++++- .../version-from-guides-xml/input/guides.xml | 2 +- 7 files changed, 86 insertions(+), 42 deletions(-) create mode 100644 tests/Integration/tests/meta/version-from-guides-xml-quoted/expected/index.html create mode 100644 tests/Integration/tests/meta/version-from-guides-xml-quoted/input/guides.xml create mode 100644 tests/Integration/tests/meta/version-from-guides-xml-quoted/input/index.rst diff --git a/packages/guides-cli/src/Config/XmlFileLoader.php b/packages/guides-cli/src/Config/XmlFileLoader.php index 882fb5bb5..74fe366a1 100644 --- a/packages/guides-cli/src/Config/XmlFileLoader.php +++ b/packages/guides-cli/src/Config/XmlFileLoader.php @@ -13,6 +13,8 @@ namespace phpDocumentor\Guides\Cli\Config; +use DOMAttr; +use DOMElement; use Symfony\Component\Config\Loader\FileLoader; use Symfony\Component\Config\Util\Exception\XmlParsingException; use Symfony\Component\Config\Util\XmlUtils; @@ -22,6 +24,7 @@ use function is_array; use function is_string; use function sprintf; +use function trim; final class XmlFileLoader extends FileLoader { @@ -36,9 +39,45 @@ public function load(mixed $resource, string|null $type = null): array throw new XmlParsingException(sprintf('The XML file "%s" is not valid.', $resource)); } + // The attributes (title, version, release, copyright) are all + // strings and are read from the DOM directly. XmlUtils::convertDomElementToArray() + // below runs phpize() on every attribute value, which coerces version-like + // strings into numbers ("0.10" would become the float 0.1, "1.0" would + // become 1). Reading them straight from the DOM and detaching + // beforehand keeps the version exactly as written. + $projectConfig = null; + $project = $element->getElementsByTagName('project')->item(0); + if ($project instanceof DOMElement) { + $projectConfig = []; + foreach ($project->attributes as $attribute) { + if (!($attribute instanceof DOMAttr)) { + continue; + } + + $value = $attribute->value; + + // Backward compatibility: to stop the previous phpize() call from + // turning a version into a number, consumers wrapped it in single + // quotes (e.g. version="'3.0'"). The value is now read straight from + // the DOM so the quotes are no longer needed, but existing guides.xml + // files may still contain them; strip them for these two attributes. + if ($attribute->name === 'version' || $attribute->name === 'release') { + $value = trim($value, "'"); + } + + $projectConfig[$attribute->name] = $value; + } + + $project->parentNode?->removeChild($project); + } + $rootConfig = XmlUtils::convertDomElementToArray($element); assert(is_array($rootConfig)); + if ($projectConfig !== null) { + $rootConfig['project'] = $projectConfig; + } + $configs = []; if (isset($rootConfig['import'])) { foreach ((array) $rootConfig['import'] as $import) { diff --git a/packages/guides/src/DependencyInjection/GuidesExtension.php b/packages/guides/src/DependencyInjection/GuidesExtension.php index 9d7dfef94..5cdde6bd0 100644 --- a/packages/guides/src/DependencyInjection/GuidesExtension.php +++ b/packages/guides/src/DependencyInjection/GuidesExtension.php @@ -43,11 +43,8 @@ use function assert; use function dirname; use function is_array; -use function is_int; use function is_string; use function pathinfo; -use function trim; -use function var_export; final class GuidesExtension extends Extension implements CompilerPassInterface, ConfigurationInterface, PrependExtensionInterface { @@ -64,42 +61,8 @@ public function getConfigTreeBuilder(): TreeBuilder ->arrayNode('project') ->children() ->scalarNode('title')->end() - ->scalarNode('version') - ->beforeNormalization() - ->always( - // We need to revert the phpize call in XmlUtils. Version is always a string! - static function ($value) { - if (!is_int($value) && !is_string($value)) { - return var_export($value, true); - } - - if (is_string($value)) { - return trim($value, "'"); - } - - return $value; - }, - ) - ->end() - ->end() - ->scalarNode('release') - ->beforeNormalization() - ->always( - // We need to revert the phpize call in XmlUtils. Version is always a string! - static function ($value) { - if (!is_int($value) && !is_string($value)) { - return var_export($value, true); - } - - if (is_string($value)) { - return trim($value, "'"); - } - - return $value; - }, - ) - ->end() - ->end() + ->scalarNode('version')->end() + ->scalarNode('release')->end() ->scalarNode('copyright')->end() ->end() ->end() diff --git a/tests/Integration/tests/meta/version-from-guides-xml-quoted/expected/index.html b/tests/Integration/tests/meta/version-from-guides-xml-quoted/expected/index.html new file mode 100644 index 000000000..68c96d9e0 --- /dev/null +++ b/tests/Integration/tests/meta/version-from-guides-xml-quoted/expected/index.html @@ -0,0 +1,17 @@ + + + + Some Document - Render guides + + + + +
+

Some Document

+ +

Project Render guides in version 3.0, release 3.0.0.

+ +
+ + + \ No newline at end of file diff --git a/tests/Integration/tests/meta/version-from-guides-xml-quoted/input/guides.xml b/tests/Integration/tests/meta/version-from-guides-xml-quoted/input/guides.xml new file mode 100644 index 000000000..ad1bfa7c7 --- /dev/null +++ b/tests/Integration/tests/meta/version-from-guides-xml-quoted/input/guides.xml @@ -0,0 +1,12 @@ + + + + diff --git a/tests/Integration/tests/meta/version-from-guides-xml-quoted/input/index.rst b/tests/Integration/tests/meta/version-from-guides-xml-quoted/input/index.rst new file mode 100644 index 000000000..b69d07817 --- /dev/null +++ b/tests/Integration/tests/meta/version-from-guides-xml-quoted/input/index.rst @@ -0,0 +1,4 @@ +Some Document +============= + +Project |project| in version |version|, release |release|. diff --git a/tests/Integration/tests/meta/version-from-guides-xml/expected/index.html b/tests/Integration/tests/meta/version-from-guides-xml/expected/index.html index 317e9b9d6..a73d60c97 100644 --- a/tests/Integration/tests/meta/version-from-guides-xml/expected/index.html +++ b/tests/Integration/tests/meta/version-from-guides-xml/expected/index.html @@ -1,8 +1,17 @@ + + + + Some Document - Render guides + + +

Some Document

- -

Project Render guides in version 3.0, release 3.0.0.

+ +

Project Render guides in version 0.10, release 3.0.0.

+ + \ No newline at end of file diff --git a/tests/Integration/tests/meta/version-from-guides-xml/input/guides.xml b/tests/Integration/tests/meta/version-from-guides-xml/input/guides.xml index 66a4a6264..c744e2edb 100644 --- a/tests/Integration/tests/meta/version-from-guides-xml/input/guides.xml +++ b/tests/Integration/tests/meta/version-from-guides-xml/input/guides.xml @@ -6,7 +6,7 @@ >