[DOC-CVS] [doc-base] master: configure.php validation rewrite, some modularizations, error messages (#292)
[email protected] (alfsb via GitHub) Wed, 3 Jun 2026 19:03:44 +0000
| Newsgroups | php.doc.cvs |
|---|---|
| Message-ID | <[email protected]> |
Author: alfsb (alfsb)
Committer: GitHub (web-flow)
Pusher: alfsb
Date: 2026-06-03T16:03:41-03:00
Commit: https://github.com/php/doc-base/commit/adaaf1c03322f64f28e444d29f3b4717281c4949
Raw diff: https://github.com/php/doc-base/commit/adaaf1c03322f64f28e444d29f3b4717281c4949.diff
configure.php validation rewrite, some modularizations, error messages (#292)
* Move git startup related code into functions.
* Move DTD related code to one place.
* Nudge idempotent paths instead of historical paths in messages.
* Better diagnostic messages about Structural vs XInclude xml:ids
* Rewrite of validation errors. Java/Jing by default, native libxml otherwise.
* Make the difference between Structural vs XInclude IDs more visible in docs.
* Use only one RelaxNG validation.
Changed paths:
M configure.php
M docs/structure.md
M scripts/file-entities.php
M scripts/translation/libqa/QaxmlPairList.php
Diff:
diff --git a/configure.php b/configure.php
index ee3fabc275..c20259a5d8 100755
--- a/configure.php
+++ b/configure.php
@@ -22,17 +22,19 @@
ini_set( 'display_errors' , 1 );
ini_set( 'display_startup_errors' , 1 );
error_reporting( E_ALL );
+
ob_implicit_flush();
+libxml_use_internal_errors(true);
echo "configure.php on PHP " . phpversion() . ", libxml " . LIBXML_DOTTED_VERSION . "\n\n";
-// gereral structure/ordeing for refactoring this code
+// general structure/ordering for refactoring this code
// init_parse() // todo: argv parsing into a typed static class, remove all global
// init_check() // todo: move all checks in one place
// init_usage()
-// git_clean() partial done
-// git_status() partial done
+// git_clean() done
+// git_status() done
// dtd_conf_entities() done
// dtd_file_entities() done
// dom_load/save() done
@@ -40,7 +42,7 @@
// xinclude_xpointer() done
// xinclude_residua() done
// xml_partial_output
-// xml_validation
+// xml_validate() done
// phd_acronym() done
// phd_sources() done
// phd_version() done
@@ -103,7 +105,7 @@ function realpain( string $path , bool $touch = false , bool $mkdir = false ) :
// pain is real
// care for external XML tools (realpath() everywhere)
- // care for Windows builds (foward slashes everywhere)
+ // care for Windows builds (forward slashes everywhere)
// avoid `cd` and chdir() like the plague
$path = str_replace( "\\" , '/' , $path );
@@ -260,19 +262,6 @@ function find_xml_files($path) // {{{
}
} // }}}
-if ( true ) # Initial clean up
-{
- $dir = escapeshellarg( __DIR__ );
- $cmd = "git -C $dir clean temp -fdx --quiet";
- $ret = 0;
- passthru( $cmd , $ret );
- if ( $ret != 0 )
- {
- echo "doc-base/temp clean up FAILED.\n";
- exit( 1 );
- }
-}
-
$srcdir = dirname(__FILE__);
$workdir = $srcdir;
$basedir = $srcdir;
@@ -543,32 +532,53 @@ function find_xml_files($path) // {{{
$ac["ONLYDIR"] = dirname(realpath($ac["GENERATE"]));
}
+globbetyglob("{$ac['basedir']}/scripts", 'make_scripts_executable');
-// Show local repository status to facilitate remote debugging
+git_clean(); // Idempotent clean up
+git_status(); // Show local repository status
-$repos = array();
-$repos['doc-base'] = $ac['basedir'];
-$repos['en'] = "{$ac['rootdir']}/{$ac['EN_DIR']}";
-$repos[$ac['LANG']] = "{$ac['rootdir']}/{$ac['LANG']}";
-$repos = array_unique($repos);
+function git_clean()
+{
+ $dir = escapeshellarg( __DIR__ );
+ $cmd = "git -C $dir clean temp -fdx --quiet";
+ $ret = 0;
+ passthru( $cmd , $ret );
+ if ( $ret != 0 )
+ {
+ echo "doc-base/temp clean up FAILED.\n";
+ exit( 1 );
+ }
+}
-$output = "";
-foreach ( $repos as $name => $path )
+function git_status()
{
- $path = escapeshellarg( $path );
- $branch = trim( shell_exec( "git -C $path rev-parse --abbrev-ref HEAD" ));
- $branch = $branch == "master" ? "" : " (branch $branch)";
- $output .= str_pad( "$name:" , 10 );
- $output .= rtrim( shell_exec( "git -C $path rev-parse HEAD" ) ?? "" . $branch ) . "\n";
- $output .= rtrim( shell_exec( "git -C $path status -s") ?? "" ) . "\n";
+ global $ac;
+
+ $repos = array();
+ $repos['doc-base'] = $ac['basedir'];
+ $repos['en'] = "{$ac['rootdir']}/{$ac['EN_DIR']}";
+ $repos[$ac['LANG']] = "{$ac['rootdir']}/{$ac['LANG']}";
+
+ $output = "";
+ foreach ( $repos as $name => $path )
+ {
+ $path = escapeshellarg( $path );
+ $branch = trim( shell_exec( "git -C $path rev-parse --abbrev-ref HEAD" ));
+ $branch = $branch == "master" ? "" : " (branch $branch)";
+ $output .= str_pad( "$name:" , 10 );
+ $output .= rtrim( shell_exec( "git -C $path rev-parse HEAD" ) ?? "" . $branch ) . "\n";
+ $output .= rtrim( shell_exec( "git -C $path status -s") ?? "" ) . "\n";
+ }
+ while( str_contains( $output , "\n\n" ) )
+ $output = str_replace( "\n\n" , "\n" , $output );
+ echo "\n" , trim( $output ) . "\n\n";
}
-while( str_contains( $output , "\n\n" ) )
- $output = str_replace( "\n\n" , "\n" , $output );
-echo "\n" , trim( $output ) . "\n\n";
-// DTD configuration before first loading
+// DTD layer before first XML loading
dtd_conf_entities();
+dtd_file_entities();
+
function dtd_conf_entities()
{
global $ac;
@@ -605,11 +615,6 @@ function dtd_conf_entities()
file_put_contents( __DIR__ . "/temp/manual.conf" , implode( "\n" , $conf ) );
}
-libxml_use_internal_errors(true);
-
-globbetyglob("{$ac['basedir']}/scripts", 'make_scripts_executable');
-
-dtd_file_entities();
function dtd_file_entities()
{
global $ac;
@@ -663,13 +668,14 @@ function dom_saveload( DOMDocument $dom , string $filename = "" ) : string
if ( $filename == "" )
$filename = __DIR__ . "/temp/manual.xml";
+ libxml_clear_errors();
$dom->save( $filename );
dom_load( $dom , $filename );
return $filename;
}
-echo "Loading and parsing {$ac["INPUT_FILENAME"]}... ";
+echo "Creating monolithic temp/manual.xml... ";
$dom = new DOMDocument();
if ( dom_load( $dom , "{$ac['srcdir']}/{$ac["INPUT_FILENAME"]}" ) )
@@ -703,7 +709,7 @@ function individual_xml_broken_check()
}
}
-echo "Running XInclude/XPointer... ";
+echo "Expanding XIncludes... ";
$total = xinclude_run_byid( $dom );
$total += xinclude_run_xpointer( $dom );
@@ -711,17 +717,17 @@ function individual_xml_broken_check()
if ( $total == 0 )
echo "failed.\n";
else
- echo "done. Performed $total XIncludes.\n";
+ echo "done: $total tags replaced.\n";
xinclude_residual_fixup( $dom );
function xinclude_run_byid( DOMDocument $dom )
{
- // libxml does not implements the XInclude 1.1 spec,
- // so we need to simulate its *recursive* nature here.
+ // libxml does not implements the XInclude 1.1 feature,
+ // so we need to *simulate* its *recursive* nature here.
+
$total = 0;
- $maxrun = 10;
- for( $run = 0 ; $run < $maxrun ; $run++ )
+ for( $run = 0 ; $run < 10 ; $run++ )
{
$xpath = new DOMXPath( $dom );
$xpath->registerNamespace( "xi" , "http://www.w3.org/2001/XInclude" );
@@ -791,32 +797,25 @@ function xinclude_run_xpointer( DOMDocument $dom ) : int
function xinclude_residual_fixup( DOMDocument $dom )
{
- // XInclude failures are soft errors on translations, so remove
- // residual XInclude tags on translations to keep them building.
+ // XInclude failures are soft errors on translations, so we replace
+ // residual XInclude tags on translations to keep them validating.
- $debugFile1 = "temp/xinclude-fixup-before.xml";
- $debugFile2 = "temp/xinclude-fixup-result.xml";
+ $fixups = 0;
+ $hardfail = false;
+ dom_saveload( $dom , __DIR__ . "/temp/manual.err" );
$nodes = xinclude_residual_list( $dom );
- if ( count( $nodes ) > 0 )
- {
- unset( $nodes );
- dom_saveload( $dom , __DIR__ . "/{$debugFile1}" );
- $nodes = xinclude_residual_list( $dom );
- }
-
- $fixups = 0;
- $explain = false;
foreach( $nodes as $node )
{
$fixup = null;
$parent = $node->parentNode->nodeName;
$target = $node->getAttribute("xpointer");
- $alert = "[Failed XInclude '$target']";
+ $alert = "[[[Failed XInclude '$target']]]";
if ( $fixups === 0 )
- echo "\nFailed XIncludes, manual parts will be missing. Failed XInclude targets:\n";
+ echo "\nFailed XIncludes, manual parts will be missing. Unresolved xpointers:\n";
+
echo "- {$target}\n";
$fixups++;
@@ -838,7 +837,7 @@ function xinclude_residual_fixup( DOMDocument $dom )
break;
default:
echo " (Unknown parent of failed XInclude: $parent)\n";
- $explain = true;
+ $hardfail = true;
continue 2;
}
@@ -854,11 +853,13 @@ function xinclude_residual_fixup( DOMDocument $dom )
}
$node->parentNode->removeChild( $node );
}
+ unset( $nodes );
- if ( $explain )
- {
- dom_saveload( $dom , __DIR__ . "/{$debugFile2}" );
+ if ( $fixups > 0 )
+ echo "Dumped file: temp/manual.err. Inspect residual xi:include tags in this file.\n\n";
+ if ( $hardfail )
+ {
echo <<<MSG
If you are seeing this message in a translation, it means that
@@ -867,25 +868,24 @@ function xinclude_residual_fixup( DOMDocument $dom )
Please report any "Unknown parent" messages to the doc-base repository
and focus on fixing all the XInclude/XPointers failures listed above.
-Dumped {$debugFile1} .
-Dumped {$debugFile2} .
-
MSG;
exit( 1 ); // stop here, do not let more messages further confuse the matter
}
- if ( $fixups > 0 )
- echo "\n";
-
- // XInclude by xml:id never duplicates xml:id. Horever, using
- // XInclude by XPath/XPointer with a XInclude 1.0 library will cause
- // xml:id duplication, as xml:id have no special tratament in this version.
- // See docs/structure.md for details.
+ // A real implementation of XInclude 1.1 will never duplicate any xml:id,
+ // but we are stuck with libxml's XInclude 1.0, so we need to fix these
+ // duplications yourselfs.
// Crude and ugly fixup ahead, beware!
+ // The code below removes any XInclude IDs without warnings, as they are
+ // expected to occur, and also remove any duplicated structural IDs while
+ // generating warnings, as they should never happen.
+
+ // See docs/structure.md for details.
+
+ $structural = false;
$list = [];
- $see = false;
$xpath = new DOMXPath( $dom );
$nodes = $xpath->query( "//*[@xml:id]" );
foreach( $nodes as $node )
@@ -895,25 +895,26 @@ function xinclude_residual_fixup( DOMDocument $dom )
{
if ( ! str_contains( $id , '..' ) )
{
- echo " Random removing duplicated xml:id: $id\n";
- $see = true;
+ echo " Deleted duplicated structural xml:id: $id\n";
+ $structural = true;
}
$node->removeAttribute( "xml:id" );
}
$list[ $id ] = $id;
}
- if ( $see )
+ if ( $structural )
{
echo "\n See: https://github.com/php/doc-base/blob/master/docs/structure.md#xmlid-structure";
echo "\n And: https://github.com/php/doc-base/tree/master/scripts/translation";
- echo "\n In special, qaxml-attributes.php and qaxml-entities.php, with and without --urgent.";
+ echo "\n Use: qaxml-attributes.php and qaxml-entities.php, with and without --urgent.";
echo "\n\n";
}
- // Duplicated strucutral xml:ids are fatal on doc-en
+ // Duplicated structural xml:ids are fatal on doc-en
$fatal = $GLOBALS['ac']['LANG'] == 'en';
- if ( $see && $fatal )
+
+ if ( $structural && $fatal )
errors_are_bad( 1 );
}
@@ -926,9 +927,16 @@ function xinclude_residual_list( DOMDocument $dom ) : DOMNodeList
return $nodes;
}
-echo "Validating {$ac["INPUT_FILENAME"]}... ";
+// Last save/reload before libxml's RelaxNG validation,
+// so file positions and errors are reseted.
+
+$idempath = dom_saveload( $dom ); // idempotent path
+$dom->save( $ac["OUTPUT_FILENAME"] ); // historical path
+
+if ($ac['PARTIAL'] != '' && $ac['PARTIAL'] != 'no')
+{
+ echo "Validating partial temp/manual.xml... ";
-if ($ac['PARTIAL'] != '' && $ac['PARTIAL'] != 'no') { // {{{
$dom->relaxNGValidate(RNG_SCHEMA_FILE); // we don't care if the validation works or not
$node = $dom->getElementById($ac['PARTIAL']);
if (!$node) {
@@ -966,50 +974,106 @@ function xinclude_residual_list( DOMDocument $dom ) : DOMNodeList
exit(0);
} // }}}
-// Saves and reload, so libxml's RelaxNG validation to work correctly
+xml_validate( $dom );
-$mxml = $ac["OUTPUT_FILENAME"];
-$dom->save($mxml); // non idempotent, historical path
-dom_saveload( $dom ); // idempotent path
+function xml_validate( $dom )
+{
+ // libxml2's RelaxNG validation shows quadratic to cubic behavior in some cases.
-if ($dom->relaxNGValidate(RNG_SCHEMA_FILE)) {
- echo "done.\n";
-} else {
- echo "failed.\n";
- echo "\nThe document didn't validate.\n";
-
- if ($ac['DETAILED_ERRORMSG'] === 'yes') {
- /**
- * TODO: Integrate jing to explain schema violations as libxml is *useless*
- * And this is not going to change for a while as the maintainer of libxml2 even acknowledges:
- * > As it stands, libxml2's Relax NG validator doesn't seem suitable for production.
- * cf. https://gitlab.gnome.org/GNOME/libxml2/-/issues/448
- */
- $output = shell_exec('java -jar ' . $srcdir . '/docbook/jing.jar ' . RNG_SCHEMA_FILE. ' ' . $acd['OUTPUT_FILENAME']);
- if ($output === null) {
- echo "Command failed do you have Java installed?";
- } else {
- echo $output;
- }
- } else {
- echo "Here are the errors I got:\n";
- echo "(If this isn't enough information, try again with --enable-xml-details)\n";
- print_xml_errors(false);
+ // > As it stands, libxml2's Relax NG validator doesn't seem suitable for production.
+ // -- https://gitlab.gnome.org/GNOME/libxml2/-/issues/448
+
+ // Jing is faster, but depends on Java.
+
+ $out = null;
+ $ret = null;
+ exec( "java -version 2>&1" , $out , $ret );
+
+ if ( $ret == 0 )
+ xml_validate_jing();
+ else
+ xml_validate_libxml( $dom );
+}
+
+function xml_validate_jing()
+{
+ global $srcdir; // TODO, static typed field on Conf class
+ global $idempath; // TODO, static typed field on Conf class
+
+ echo "Validating temp/manual.xml (jing)... ";
+
+ $out = null;
+ $ret = null;
+ $schema = RNG_SCHEMA_FILE;
+ $cmdJing = "java -jar {$srcdir}/docbook/jing.jar {$schema} {$idempath}";
+ exec( $cmdJing , $out , $ret );
+
+ if ( $ret == 0 )
+ {
+ echo "done.\n";
+ return;
+ }
+ else
+ {
+ echo "failed.\n";
+ if ( is_array( $out ) )
+ foreach ( $out as $line )
+ echo "$line\n";
+ errors_are_bad( 1 );
}
+}
- errors_are_bad(1); // Tell the shell that this script finished with an error.
+function xml_validate_libxml( $dom )
+{
+ echo "Validating temp/manual.xml (libxml)... ";
+
+ if ( $dom->relaxNGValidate( RNG_SCHEMA_FILE ) )
+ {
+ echo "done.\n";
+ }
+ else
+ {
+ echo "failed.\n";
+ print_xml_errors();
+ errors_are_bad( 1 );
+ }
}
-// All PhD stuff, after XML validation
+echo "\nAll good. Saved temp/manual.xml\n";
+echo "All you have to do now is run 'phd -d {$idempath}'\n";
+echo "If the script hangs here, you can abort with ^C.\n";
+echo <<<CAT
+ _ _..._ __
+ \)` (` /
+ / `\
+ | d b |
+ =\ Y =/--..-="````"-.
+ '.=__.-' `\
+ o/ /\ \
+ | | \ \ / )
+ \ .--""`\ < \ '-' /
+ // | || \ '---'
+ jgs ((,,_/ ((,,___/
+
+
+CAT;
+
+// All PhD stuff, after XML validation.
phd_acronym();
php_history();
phd_sources();
phd_version();
+exit(0); // Finished successfully.
+
+
+
+// TODO: Should this moved to github/php/phd?
+// Any input/state can be serialized into doc-base/temp/phd-conf.json.
+
function phd_acronym()
{
- //TODO: Move acronym.xml code here
}
function php_history()
@@ -1145,25 +1209,3 @@ function phd_version()
echo " fail!\n";
}
}
-
-
-printf("\nAll good. Saved %s\n", basename($ac["OUTPUT_FILENAME"]));
-echo "All you have to do now is run 'phd -d {$mxml}'\n";
-echo "If the script hangs here, you can abort with ^C.\n";
-echo <<<CAT
- _ _..._ __
- \)` (` /
- / `\
- | d b |
- =\ Y =/--..-="````"-.
- '.=__.-' `\
- o/ /\ \
- | | \ \ / )
- \ .--""`\ < \ '-' /
- // | || \ '---'
- jgs ((,,_/ ((,,___/
-
-
-CAT;
-
-exit(0); // Finished successfully.
diff --git a/docs/structure.md b/docs/structure.md
index 327374268c..b92755df27 100644
--- a/docs/structure.md
+++ b/docs/structure.md
@@ -50,18 +50,18 @@ The PHP Manual is complex, and uses `xml:id`s extensively, for various
purposes. So some care is necessary to avoid failures.
There are two types of `xml:id`s used in manuals.
-* **Structural IDs:** IDs that are present on structural elements of
-DocBook XML (like `<chapter>`, `<section>` and so on), that are used for
+* **Structural IDs:** Single dotted IDs that are present on structural elements
+of DocBook XML (like `<chapter>`, `<section>` and so on), that are used for
linking and chunking;
-* **XInclude IDs:** IDs that are used as target of XIncludes.
+* **XInclude IDs:** Double dotted IDs that are used as targets of XIncludes.
-Structural IDs are in the pattern `id.id` (always one dot as separator),
-while XInclude IDs use the pattern `structural.id..local.name`. That is,
-for Structural IDs the name parts are separated with a *single* dot, while
+Structural IDs are in the pattern `folder.id` (always one dot as separator),
+while XInclude IDs use the pattern `folder.id..local`. That is,
+in Structural IDs the name parts are separated with a *single* dot, while
XInclude IDs are composed of an Structural ID prefix, a *double* dot separator,
and a named suffix.
The `configure.php` script will remove any duplicated IDs found. Without
-warnings in the case of XInclude IDs, so it is possible to use XInclude
-IDs elsewhere, and will warn about duplicate Structural IDs.
+warnings in the case of XInclude IDs, but will generate warnings while
+removing duplicated Structural IDs.
diff --git a/scripts/file-entities.php b/scripts/file-entities.php
index adf1494744..9229b0b130 100644
--- a/scripts/file-entities.php
+++ b/scripts/file-entities.php
@@ -83,7 +83,7 @@
// Main
-echo "Creating file-entities.ent... ";
+echo "Running file-entities.php... ";
$entities = [];
$mixedCase = [];
diff --git a/scripts/translation/libqa/QaxmlPairList.php b/scripts/translation/libqa/QaxmlPairList.php
index 165ab5b30a..676af80b6c 100644
--- a/scripts/translation/libqa/QaxmlPairList.php
+++ b/scripts/translation/libqa/QaxmlPairList.php
@@ -28,7 +28,7 @@ static function load( ?string $lang = null , array $filterFiles = [] )
$file = __DIR__ . "/../../../temp/lang";
if ( ! file_exists( $file ) )
{
- fwrite( STDERR , "Language to process. Run 'doc-base/configure.php' or use '--lang='.\n" );
+ fwrite( STDERR , "No language to process. Run 'doc-base/configure.php' or use '--lang='.\n" );
exit();
}
$lang = trim( file_get_contents( $file ) );