From d787f301885315fa8ae35ffb1b1c591bd8145ba0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20L=20F=20S=20Bacci?= Date: Fri, 10 Jul 2026 12:25:17 -0300 Subject: [PATCH 1/6] stash --- docbook/gentrimlist.php | 114 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 docbook/gentrimlist.php diff --git a/docbook/gentrimlist.php b/docbook/gentrimlist.php new file mode 100644 index 0000000000..059f753242 --- /dev/null +++ b/docbook/gentrimlist.php @@ -0,0 +1,114 @@ + +/* + +This script reads a RelaxNG XML file, and calculates all elements +that _not_ contain contents, in all alternatives. That is, +the list shows all elements that can have all inter-element +whitespace removed, without affecting the expected parsing of the +XML document that follows the RelaxNG specification. + +If informed a second XML file, the script will calculate all savings +that can be done by stripping these insignificant whitespace between +elements. + +*/ + +$arg0 = array_shift( $argv ) ?? null; +$rngFile = array_shift( $argv ) ?? null; +$xmlFile = array_shift( $argv ) ?? null; + +if ( $rngFile == null ) +{ + print "Usage: '$argv0' rngFile [xmlFile]\n\n"; + return; +} + +$doc = new DOMDocument(); +if ( ! $doc->load( $rngFile , LIBXML_NOBLANKS ) ) + return; + +$rng = rng_load_file( $doc ); +$wsi = rng_list_elements_trim( $rng ); +xml_trim_file( $xmlFile , $wsi ); + +exit ( 0 ); + +class RngMaps +{ + private array $defRef = []; // s to s + private array $defTxt = []; // s with + private array $tagDef = []; // s to s + + public function addDefToRef( string $defName , string $refName ) + { + $this->defRef[ $defName ][ $refName ] = true; + } + + public function addDefHasTxt( string $defName ) + { + $this->defTxt[ $defName ] = true; + } + + public function addTagToDef( string $defName , string $tagName ) + { + $this->tagDef[ $defName ][ $tagName ] = true; + } +} + +function rng_load_file( DOMDocument $doc ) : RngMaps +{ + $rng = new RngMaps; + rng_load_recurse( $rng , $doc->documentElement , "" ); + return $rng; +} + +function rng_load_recurse( RngMaps $rng , DOMNode $node , string $defName , int $level = 1 ) +{ + $pad = str_repeat( ' ' , $level ); + //echo "{$pad}{$node->nodeName}\n"; + + switch( $node->nodeName ) + { + case '#text'; + return; + + case 'attribute'; + return; + + case 'define': + $defName = $node->getAttribute( 'name' ); + break; + + case 'element': + $name = $node->getAttribute( 'name' ); + $rng->addTagToDef( $defName , $name ); + echo "{$pad} def {$defName} tag {$name}\n"; + break; + + case 'ref': + $name = $node->getAttribute( 'name' ); + $rng->addDefToRef( $defName , $name ); + echo "{$pad} def {$defName} ref {$name}\n"; + break; + + case 'text': + $rng->addDefHasTxt( $defName ); + echo "{$pad} def {$defName} TEXT\n"; + break; + } + + foreach( $node->childNodes as $child ) + rng_load_recurse( $rng , $child , $defName , $level + 1 ); +} + +function rng_list_elements_trim( RngMaps $rng ) +{ + // For each , expand all s, to mark + // any that indirectly refers to . + + + // For each check it it's refers + // to . +} From bfef55ddc38626faf2f34db8021e95a723abdc36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20L=20F=20S=20Bacci?= Date: Tue, 14 Jul 2026 14:31:22 -0300 Subject: [PATCH 2/6] elemtrimlist --- docbook/elemtrimlist.php | 190 +++++++++++++++++++++++++++++++++++++++ docbook/gentrimlist.php | 114 ----------------------- 2 files changed, 190 insertions(+), 114 deletions(-) create mode 100644 docbook/elemtrimlist.php delete mode 100644 docbook/gentrimlist.php diff --git a/docbook/elemtrimlist.php b/docbook/elemtrimlist.php new file mode 100644 index 0000000000..e9bf48d469 --- /dev/null +++ b/docbook/elemtrimlist.php @@ -0,0 +1,190 @@ + + +This script reads a RelaxNG XML file, and calculates all elements +that _not_ contain contents, in all alternatives. That is, +the list shows all elements that can have all inter-element +whitespace removed, without affecting the expected parsing of the +XML document that follows the RelaxNG specification. + +If informed a second XML file, the script will calculate all savings +that can be done by stripping these insignificant whitespace between +elements. */ + +$arg0 = array_shift( $argv ) ?? null; +$rngFile = array_shift( $argv ) ?? null; +$xmlFile = array_shift( $argv ) ?? null; + +if ( $rngFile == null ) +{ + print "Usage: '$argv0' rngFile [xmlFile]\n\n"; + return; +} + +$list = generate_element_trim_list( $rngFile ); + +if ( $xmlFile == null ) +{ + foreach( $list as $elem => $hasText ) + if ( ! $hasText ) + print "$elem\n"; + exit( 0 ); +} +else + xml_trim_stats( $xmlFile , $list ); + +exit( 0 ); + +function generate_element_trim_list( string $rngFilename ) : array +{ + $doc = new DOMDocument(); + if ( ! $doc->load( $rngFilename , LIBXML_NOBLANKS ) ) + throw new Exception( "XML load failed.\n" ); + + // First, we get all elements definitions that directly + // mentions , and also gather all s they refer. + + $elemText = []; + $elemRefs = []; + + $xpath1 = new DOMXpath( $doc ); + $xpath2 = new DOMXpath( $doc ); + $xpath1->registerNamespace ( 'rng' , 'http://relaxng.org/ns/structure/1.0' ); + $xpath2->registerNamespace ( 'rng' , 'http://relaxng.org/ns/structure/1.0' ); + + $list = $xpath1->query( '//rng:element' ); + foreach( $list as $elem ) + { + $name = $elem->getAttribute( 'name' ); + if ( $name == '' ) + continue; + + $text = count ( $xpath2->query( './/rng:text' , $elem ) ); + $refs = $xpath2->query( './/rng:ref' , $elem ); + + $elemText[ $name ] = $text; + $elemRefs[ $name ] = []; + + foreach( $refs as $ref ) + { + $refName = $ref->getAttribute( 'name' ); + $elemRefs[ $name ][] = $refName; + } + } + + unset( $xpath1 ); + unset( $xpath2 ); + + // After all elements are collected, and directly textual elements + // are marked, we can remove all s, as they cannot influence + // if a parent element is trimmable or not, and so that any + // inside of a cannot be found by XPaths, while exploring + // the original 's erences. + + $xpath3 = new DOMXpath( $doc ); + $xpath3->registerNamespace ( 'rng' , 'http://relaxng.org/ns/structure/1.0' ); + + $todoDels = []; + $dels = $xpath3->query( '//rng:element' ); + foreach( $dels as $del ) + array_push( $todoDels , $del ); + foreach( $todoDels as $del ) + $del->parentNode->removeChild( $del ); + + // Then, we explore all references of all elements, for + // indirect mentions of s. + + foreach( $elemText as $name => $text ) + { + $text = element_references_contains_text( $doc , $name , $elemRefs[ $name ] ); + $elemText[ $name ] |= $text; + } + + return $elemText; +} + +function element_references_contains_text( DOMDocument $doc , string $elemName , array $refs ) : bool +{ + $ret = false; + $doneRefs = []; + $todoRefs = array_unique( $refs ); + + $xpath = new DOMXpath( $doc ); + $xpath->registerNamespace ( 'rng' , 'http://relaxng.org/ns/structure/1.0' ); + + while ( ( $refName = array_pop( $todoRefs ) ) != null ) + { + $doneRefs[ $refName ] = true; + + $defs = $xpath->query( "//rng:define[@name='$refName']" ); + if ( $defs->count() != 1 ) + throw new Exception( "Unique define search failed for '$refName'." ); + $def = $defs[0]; + + $text = count ( $xpath->query( './/rng:text' , $def ) ); + if ( $text ) + return true; + + $subRefs = $xpath->query( './/rng:ref' , $def ); + foreach( $subRefs as $subRef ) + { + $subRefName = $subRef->getAttribute( 'name' ); + if ( isset( $doneRefs[ $subRefName ] ) ) + continue; + $todoRefs[] = $subRefName; + } + } + + return false; +} + +function xml_trim_stats( string $xmlFilename , array $elemText ) +{ + $doc = new DOMDocument(); + if ( ! $doc->load( $xmlFilename ) ) + throw new Exception( "XML load failed.\n" ); + + $stats = []; + xml_trim_stats_enter( $doc->documentElement , $elemText, $stats ); + arsort( $stats ); + + $total = 0; + foreach( $stats as $elem => $trimSize ) + { + print "$trimSize $elem\n"; + $total += $trimSize; + } + print "\ntotal $total\n"; +} + +function xml_trim_stats_enter( DOMNode $node , array $elemText , array & $stats , int $level = 0 ) +{ + $name = $node->nodeName; + $text = $elemText[ $name ] ?? true; + + if ( ! $text ) + { + $size = 0; + $dels = []; + + foreach( $node->childNodes as $child ) + if ( $child->nodeType == XML_TEXT_NODE ) + if ( trim( $child->nodeValue ) == '' ) + $dels[] = $child; + + foreach( $dels as $del ) + { + $size += strlen( $del->nodeValue ); + $del->parentNode->removeChild( $del ); + } + + if ( isset( $stats[ $name ] ) ) + $stats[ $name ] += $size; + else + $stats[ $name ] = $size; + } + + foreach( $node->childNodes as $child ) + xml_trim_stats_enter( $child , $elemText , $stats , $level + 1 ); +} \ No newline at end of file diff --git a/docbook/gentrimlist.php b/docbook/gentrimlist.php deleted file mode 100644 index 059f753242..0000000000 --- a/docbook/gentrimlist.php +++ /dev/null @@ -1,114 +0,0 @@ - -/* - -This script reads a RelaxNG XML file, and calculates all elements -that _not_ contain contents, in all alternatives. That is, -the list shows all elements that can have all inter-element -whitespace removed, without affecting the expected parsing of the -XML document that follows the RelaxNG specification. - -If informed a second XML file, the script will calculate all savings -that can be done by stripping these insignificant whitespace between -elements. - -*/ - -$arg0 = array_shift( $argv ) ?? null; -$rngFile = array_shift( $argv ) ?? null; -$xmlFile = array_shift( $argv ) ?? null; - -if ( $rngFile == null ) -{ - print "Usage: '$argv0' rngFile [xmlFile]\n\n"; - return; -} - -$doc = new DOMDocument(); -if ( ! $doc->load( $rngFile , LIBXML_NOBLANKS ) ) - return; - -$rng = rng_load_file( $doc ); -$wsi = rng_list_elements_trim( $rng ); -xml_trim_file( $xmlFile , $wsi ); - -exit ( 0 ); - -class RngMaps -{ - private array $defRef = []; // s to s - private array $defTxt = []; // s with - private array $tagDef = []; // s to s - - public function addDefToRef( string $defName , string $refName ) - { - $this->defRef[ $defName ][ $refName ] = true; - } - - public function addDefHasTxt( string $defName ) - { - $this->defTxt[ $defName ] = true; - } - - public function addTagToDef( string $defName , string $tagName ) - { - $this->tagDef[ $defName ][ $tagName ] = true; - } -} - -function rng_load_file( DOMDocument $doc ) : RngMaps -{ - $rng = new RngMaps; - rng_load_recurse( $rng , $doc->documentElement , "" ); - return $rng; -} - -function rng_load_recurse( RngMaps $rng , DOMNode $node , string $defName , int $level = 1 ) -{ - $pad = str_repeat( ' ' , $level ); - //echo "{$pad}{$node->nodeName}\n"; - - switch( $node->nodeName ) - { - case '#text'; - return; - - case 'attribute'; - return; - - case 'define': - $defName = $node->getAttribute( 'name' ); - break; - - case 'element': - $name = $node->getAttribute( 'name' ); - $rng->addTagToDef( $defName , $name ); - echo "{$pad} def {$defName} tag {$name}\n"; - break; - - case 'ref': - $name = $node->getAttribute( 'name' ); - $rng->addDefToRef( $defName , $name ); - echo "{$pad} def {$defName} ref {$name}\n"; - break; - - case 'text': - $rng->addDefHasTxt( $defName ); - echo "{$pad} def {$defName} TEXT\n"; - break; - } - - foreach( $node->childNodes as $child ) - rng_load_recurse( $rng , $child , $defName , $level + 1 ); -} - -function rng_list_elements_trim( RngMaps $rng ) -{ - // For each , expand all s, to mark - // any that indirectly refers to . - - - // For each check it it's refers - // to . -} From b56937ede440a32540c581e48547f963209c9f09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20L=20F=20S=20Bacci?= Date: Tue, 14 Jul 2026 16:28:37 -0300 Subject: [PATCH 3/6] Fast trims. --- configure.php | 76 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 58 insertions(+), 18 deletions(-) diff --git a/configure.php b/configure.php index 1a07591639..0b35ad1a6c 100755 --- a/configure.php +++ b/configure.php @@ -652,11 +652,43 @@ function dtd_text_entities() } checkvalue($ac["GENERATE"]); -function dom_load( DOMDocument $dom , string $filename , string $baseURI = "" ) : bool +echo "Creating monolithic temp/manual.xml... "; +$dom = new DOMDocument(); + +if ( dom_load( $dom , "{$ac['srcdir']}/{$ac["INPUT_FILENAME"]}" ) ) +{ + dom_saveload( $dom ); // correct file/line/column on error messages + echo " done.\n"; +} +else +{ + echo "failed.\n"; + print_xml_errors(); + xml_broken_file_check(); + errors_are_bad(1); +} + +function dom_load( DOMDocument $dom , string $filename , bool $firstLoad = true ) : bool { $filename = realpath( $filename ); - $options = LIBXML_NOENT | LIBXML_COMPACT | LIBXML_BIGLINES | LIBXML_PARSEHUGE; - return $dom->load( $filename , $options ); + + // On the first load we cannot use LIBXML_NSCLEAN, because + // libxml drops all namespaces inside DTD entities. + + $options = LIBXML_NOENT + | LIBXML_COMPACT + | LIBXML_BIGLINES + | LIBXML_PARSEHUGE; + + if ( ! $firstLoad ) + $options |= LIBXML_NSCLEAN; + + $ret = $dom->load( $filename , $options ); + + if ( $ret && $firstLoad ) + xml_trim_first( $dom ); + + return $ret; } function dom_saveload( DOMDocument $dom , string $filename = "" ) : string @@ -666,28 +698,36 @@ function dom_saveload( DOMDocument $dom , string $filename = "" ) : string libxml_clear_errors(); $dom->save( $filename ); - dom_load( $dom , $filename ); + dom_load( $dom , $filename , false ); return $filename; } -echo "Creating monolithic temp/manual.xml... "; -$dom = new DOMDocument(); - -if ( dom_load( $dom , "{$ac['srcdir']}/{$ac["INPUT_FILENAME"]}" ) ) +function xml_trim_first( DOMDocument $doc ) { - dom_saveload( $dom ); // correct file/line/column on error messages - echo " done.\n"; -} -else -{ - echo "failed.\n"; - print_xml_errors(); - individual_xml_broken_check(); - errors_are_bad(1); + $xpath = new DOMXPath( $doc ); + $dtdNode = null; + $dels = []; + + // Remove DTD Document Type, as all entity references + // are already expanded at this point. + + foreach( $doc->childNodes as $node ) + if ( $node->nodeType == XML_DOCUMENT_TYPE_NODE ) + $dtdNode = $node; + if ( $dtdNode != null ) + $node->parentNode->removeChild( $dtdNode ); + + // Remove all XML comments, as they are distracting, + // in reverse order, outside enumerations. + + foreach( $xpath->query( "//comment()" ) as $node ) + $dels[] = $node; + while( ( $del = array_pop( $dels ) ) != null ) + $del->parentNode->removeChild( $del ); } -function individual_xml_broken_check() +function xml_broken_file_check() { $cmd = array(); $cmd[] = $GLOBALS['ac']['PHP']; From ee9fe424050aaa1fde2d57147a7e5180e5050a80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20L=20F=20S=20Bacci?= Date: Tue, 14 Jul 2026 19:16:30 -0300 Subject: [PATCH 4/6] Trim Docbook insignificant whitespace. --- configure.php | 60 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/configure.php b/configure.php index 0b35ad1a6c..4dc7f191db 100755 --- a/configure.php +++ b/configure.php @@ -721,10 +721,62 @@ function xml_trim_first( DOMDocument $doc ) // Remove all XML comments, as they are distracting, // in reverse order, outside enumerations. - foreach( $xpath->query( "//comment()" ) as $node ) - $dels[] = $node; - while( ( $del = array_pop( $dels ) ) != null ) - $del->parentNode->removeChild( $del ); + $comments = $xpath->query( "//comment()" ); + for ( $idx = $comments->length - 1 ; $idx >= 0 ; $idx-- ) + { + $node = $comments[ $idx ]; + $node->parentNode->removeChild( $node ); + } + + // These first five elements account for almost 50% + // of all insignificant Docbook whitespace in manual: + + $trimList[] = 'refsect1'; + $trimList[] = 'varlistentry'; + $trimList[] = 'listitem'; + $trimList[] = 'row'; + $trimList[] = 'methodsynopsis'; + + // The first twenty elements account for almost 95% + // of all insignificant Docbook whitespace in manual. + + $trimList[] = 'refentry'; + $trimList[] = 'fieldsynopsis'; + $trimList[] = 'variablelist'; + $trimList[] = 'example'; + $trimList[] = 'simplelist'; + $trimList[] = 'refnamediv'; + $trimList[] = 'tbody'; + $trimList[] = 'reference'; + $trimList[] = 'classsynopsis'; + $trimList[] = 'section'; + $trimList[] = 'tgroup'; + $trimList[] = 'thead'; + $trimList[] = 'itemizedlist'; + $trimList[] = 'note'; + $trimList[] = 'sect2'; + + xml_trim_docbook_wsi( $doc->documentElement , $trimList ); +} + +function xml_trim_docbook_wsi( DOMNode $parent , array $trimList ) +{ + $nodeList = $parent->childNodes; + for ( $idx = $nodeList->length - 1 ; $idx >= 0 ; $idx-- ) + { + $node = $nodeList[ $idx ]; + $type = $node->nodeType; + + if ( $type == 1 ) // XML_ELEMENT_NODE + xml_trim_docbook_wsi( $node , $trimList ); + + if ( $type == 3 ) // XML_TEXT_NODE + { + if ( in_array( $parent->nodeName , $trimList ) ) + $parent->removeChild( $node ); + continue; + } + } } function xml_broken_file_check() From a47ef7dd1028c5e69ca156bc07885bb5c05639f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20L=20F=20S=20Bacci?= Date: Tue, 14 Jul 2026 20:06:57 -0300 Subject: [PATCH 5/6] Strip only whitespace text nodes. --- configure.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/configure.php b/configure.php index 4dc7f191db..d3029f4226 100755 --- a/configure.php +++ b/configure.php @@ -771,11 +771,9 @@ function xml_trim_docbook_wsi( DOMNode $parent , array $trimList ) xml_trim_docbook_wsi( $node , $trimList ); if ( $type == 3 ) // XML_TEXT_NODE - { if ( in_array( $parent->nodeName , $trimList ) ) - $parent->removeChild( $node ); - continue; - } + if ( trim( $node->nodeValue ) == '' ) + $parent->removeChild( $node ); } } From 5f82f3a07d09f8af05ea90838d73308dd1e4be14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20L=20F=20S=20Bacci?= Date: Thu, 16 Jul 2026 09:41:23 -0300 Subject: [PATCH 6/6] References in source code. --- configure.php | 4 +++- docbook/{elemtrimlist.php => docbookwsi.php} | 11 +++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) rename docbook/{elemtrimlist.php => docbookwsi.php} (95%) diff --git a/configure.php b/configure.php index d3029f4226..85c09b77cd 100755 --- a/configure.php +++ b/configure.php @@ -728,8 +728,10 @@ function xml_trim_first( DOMDocument $doc ) $node->parentNode->removeChild( $node ); } + // See docbook/docbookwsi.php. + // These first five elements account for almost 50% - // of all insignificant Docbook whitespace in manual: + // of all insignificant Docbook whitespace in manual. $trimList[] = 'refsect1'; $trimList[] = 'varlistentry'; diff --git a/docbook/elemtrimlist.php b/docbook/docbookwsi.php similarity index 95% rename from docbook/elemtrimlist.php rename to docbook/docbookwsi.php index e9bf48d469..58a20ef002 100644 --- a/docbook/elemtrimlist.php +++ b/docbook/docbookwsi.php @@ -1,16 +1,19 @@ - - +/* This script reads a RelaxNG XML file, and calculates all elements that _not_ contain contents, in all alternatives. That is, the list shows all elements that can have all inter-element whitespace removed, without affecting the expected parsing of the XML document that follows the RelaxNG specification. -If informed a second XML file, the script will calculate all savings +If run with a second XML argument, the script will calculate all savings that can be done by stripping these insignificant whitespace between -elements. */ +elements. + +See mentions of 'docbookwsi' on source code for parts that need to be +updated if/when a new version of Docbook is used. */ $arg0 = array_shift( $argv ) ?? null; $rngFile = array_shift( $argv ) ?? null;