From 0e5bb81991941c812acfe329dbc0d539443a637b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20L=20F=20S=20Bacci?= Date: Sat, 23 Nov 2024 18:08:31 -0300 Subject: [PATCH 1/5] XInclude from xml:id fixup --- configure.php | 78 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 63 insertions(+), 15 deletions(-) diff --git a/configure.php b/configure.php index 35b34eba4..06f389338 100755 --- a/configure.php +++ b/configure.php @@ -774,29 +774,77 @@ function getFileModificationHistory(): array { } echo "done.\n"; -echo "Running XInclude/XPointer... "; -$status = $dom->xinclude(); -if ($status === -1) { - echo "failed.\n"; -} else { - /* For some dumb reason when no substitution are made it returns false instead of 0... */ - $status = (int) $status; - echo "done. Performed $status XIncludes\n"; -} -flush(); -if ( $ac['XPOINTER_REPORTING'] == 'yes' || $ac['LANG'] == 'en' ) -{ +{ // XInclide/XPointer + + echo "Running XInclude/XPointer... "; + + // xinclides 1.1 capture + + $beforeIdPaths = []; + + $xpath = new DOMXPath( $dom ); + $nodes = $xpath->query( "//*[@xml:id]" ); + + foreach( $nodes as $node ) + { + $id = $node->getAttribute( "xml:id" ); + $path = $node->getNodePath(); + + if ( isset( $beforeIdPaths[ $id ] ) ) + { + echo "failed.\nDuplicated xml:id '$id' before XInclude!\n"; + errors_are_bad( 1 ); + } + $beforeIdPaths[ $id ] = $path; + } + + // main + + $ret = $dom->xinclude(); + $errors = libxml_get_errors(); - $output = ( $ac['STDERR_TO_STDOUT'] == 'yes' ) ? STDOUT : STDERR; - if ( count( $errors ) > 0 ) + libxml_clear_errors(); + + if ( $ret === -1 ) + echo "failed.\n"; + else { + $countOk = (int) $ret; + $countErr = count( $errors ); + echo "done. Performed $countOk XIncludes, $countErr failures.\n"; + } + + // failures + + if ( $countErr && ( $ac['XPOINTER_REPORTING'] == 'yes' || $ac['LANG'] == 'en' ) ) + { + // Always report failures on doc-en. + // Failures on doc-en are fatal. + + $output = $ac['STDERR_TO_STDOUT'] == 'yes' ? STDOUT : STDERR; fprintf( $output , "\n"); + foreach( $errors as $error ) fprintf( $output , "{$error->message}\n"); + if ( $ac['LANG'] == 'en' ) - errors_are_bad(1); + errors_are_bad( 1 ); } + + // xinclides 1.1 fix up + + $nodes = $xpath->query( "//*[@xml:id]" ); + foreach( $nodes as $node ) + { + $id = $node->getAttribute( "xml:id" ); + $path = $node->getNodePath(); + + if ( $beforeIdPaths[ $id ] != $path ) + $node->removeAttribute( "xml:id" ); + } + + flush(); } echo "Validating {$ac["INPUT_FILENAME"]}... "; From 723f0ccd7e1acd6437400184a54d6fc1199649a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20L=20F=20S=20Bacci?= Date: Sun, 24 Nov 2024 11:41:45 -0300 Subject: [PATCH 2/5] XInclude fixup to enable using xml:id targets --- configure.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/configure.php b/configure.php index 06f389338..7e938713f 100755 --- a/configure.php +++ b/configure.php @@ -775,11 +775,11 @@ function getFileModificationHistory(): array { echo "done.\n"; -{ // XInclide/XPointer +{ // XInclude/XPointer echo "Running XInclude/XPointer... "; - // xinclides 1.1 capture + // XInclude 1.1 capture $beforeIdPaths = []; @@ -817,9 +817,9 @@ function getFileModificationHistory(): array { // failures - if ( $countErr && ( $ac['XPOINTER_REPORTING'] == 'yes' || $ac['LANG'] == 'en' ) ) + if ( count( $errors ) && ( $ac['LANG'] == 'en' || $ac['XPOINTER_REPORTING'] == 'yes' ) ) { - // Always report failures on doc-en. + // Always report failures on doc-en, translations can --disable-xpointer-reporting // Failures on doc-en are fatal. $output = $ac['STDERR_TO_STDOUT'] == 'yes' ? STDOUT : STDERR; @@ -832,7 +832,7 @@ function getFileModificationHistory(): array { errors_are_bad( 1 ); } - // xinclides 1.1 fix up + // XInclude 1.1 fixup $nodes = $xpath->query( "//*[@xml:id]" ); foreach( $nodes as $node ) From dc20b4f12827805d40a756bda1c573eee377c9d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20L=20F=20S=20Bacci?= Date: Sun, 24 Nov 2024 15:26:43 -0300 Subject: [PATCH 3/5] Automatic xi:fallback --- configure.php | 125 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 96 insertions(+), 29 deletions(-) diff --git a/configure.php b/configure.php index 7e938713f..c32feec8b 100755 --- a/configure.php +++ b/configure.php @@ -19,9 +19,11 @@ +----------------------------------------------------------------------+ */ -error_reporting(-1); -$cvs_id = '$Id$'; +ini_set( 'display_errors' , 1 ); +ini_set( 'display_startup_errors' , 1 ); +error_reporting( E_ALL ); +$cvs_id = '$Id$'; echo "configure.php: $cvs_id\n"; const RNG_SCHEMA_DIR = __DIR__ . DIRECTORY_SEPARATOR . 'docbook' . DIRECTORY_SEPARATOR . 'docbook-v5.2-os' . DIRECTORY_SEPARATOR . 'rng' . DIRECTORY_SEPARATOR; @@ -779,74 +781,139 @@ function getFileModificationHistory(): array { echo "Running XInclude/XPointer... "; - // XInclude 1.1 capture - - $beforeIdPaths = []; + // Always report failures on doc-en. + // Translations can --disable-xpointer-reporting. + // Failures on doc-en are fatal. + $report = $ac['XPOINTER_REPORTING'] == 'yes' || $ac['LANG'] == 'en'; + $print = $ac['STDERR_TO_STDOUT'] == 'yes' ? STDOUT : STDERR; + $fatal = $ac['LANG'] == 'en'; $xpath = new DOMXPath( $dom ); - $nodes = $xpath->query( "//*[@xml:id]" ); + // pre run id capture + + $prevIdPath = []; + + $nodes = $xpath->query( "//*[@xml:id]" ); foreach( $nodes as $node ) { $id = $node->getAttribute( "xml:id" ); $path = $node->getNodePath(); - - if ( isset( $beforeIdPaths[ $id ] ) ) + if ( isset( $prevIdPath[ $id ] ) ) { echo "failed.\nDuplicated xml:id '$id' before XInclude!\n"; errors_are_bad( 1 ); } - $beforeIdPaths[ $id ] = $path; + $prevIdPath[ $id ] = $path; + } + + // automatic xi:fallback + + $nodes = $xpath->query( "//*[local-name()='include']" ); + foreach( $nodes as $node ) + { + $target = $node->getAttribute( "xpointer" ); + $failback = null; + + $elements = $node->getElementsByTagName( "fallback" ); + if ( count( $elements ) > 0 ) + { + $failback = $elements[0]; + } + else + { + $failback = $node->ownerDocument->createElementNS( "http://www.w3.org/2001/XInclude" , "fallback" ); + //$failback = $node->ownerDocument->createElement( "xi:fallback" ); + $node->append( $failback ); + } + + $comment = $failback->ownerDocument->createComment( "xi:fallback $target" ); + $failback->append( $comment ); + + unset( $failback ); // otherwise core dumps on PHP 8.1.2-1ubuntu2.19 + unset( $comment ); // otherwise core dumps on PHP 8.1.2-1ubuntu2.19 } - // main + // run $ret = $dom->xinclude(); $errors = libxml_get_errors(); libxml_clear_errors(); - if ( $ret === -1 ) - echo "failed.\n"; - else - { - $countOk = (int) $ret; - $countErr = count( $errors ); - echo "done. Performed $countOk XIncludes, $countErr failures.\n"; - } + $status = $ret === -1 ? "failed" : "done"; + $countOk = $ret === -1 ? 0 : (int) $ret; + $countErr = count( $errors ); + echo "$status. Performed $countOk XIncludes, $countErr failures.\n"; // failures - if ( count( $errors ) && ( $ac['LANG'] == 'en' || $ac['XPOINTER_REPORTING'] == 'yes' ) ) + if ( $report ) { - // Always report failures on doc-en, translations can --disable-xpointer-reporting - // Failures on doc-en are fatal. - - $output = $ac['STDERR_TO_STDOUT'] == 'yes' ? STDOUT : STDERR; - fprintf( $output , "\n"); + // xi:include failures foreach( $errors as $error ) - fprintf( $output , "{$error->message}\n"); + fprintf( $print , "\n" . rtrim( $error->message ) . "\n"); - if ( $ac['LANG'] == 'en' ) + if ( $fatal && count( $errors ) ) errors_are_bad( 1 ); + + // xi:failback failures + + $count = 0; + $nodes = $xpath->query( "//comment()" ); + + foreach( $nodes as $node ) + { + $payload = $node->data; + if ( str_starts_with( $payload , "xi:fallback" ) ) + { + $count++; + $path = getNodePathWithIds( $node->parentNode ); + fprintf( $print , "\nFailed xi:include at: $path\n"); + + if ( trim( $payload) != "xi:fallback" ) + { + $payload = trim( substr( $payload , 12 ) ); + fprintf( $print , "Failed xi:include target is: $payload\n"); + } + } + } + + if ( $count > 0 ) + { + fprintf( $print , "\n"); + if ( $fatal ) + errors_are_bad( 1 ); + } } - // XInclude 1.1 fixup + // post run id fixup $nodes = $xpath->query( "//*[@xml:id]" ); foreach( $nodes as $node ) { $id = $node->getAttribute( "xml:id" ); $path = $node->getNodePath(); - - if ( $beforeIdPaths[ $id ] != $path ) + if ( $prevIdPath[ $id ] != $path ) $node->removeAttribute( "xml:id" ); } flush(); } +function getNodePathWithIds( DOMNode|null $node ) : string +{ + if ( $node == null || $node->nodeType == XML_DOCUMENT_NODE ) + return ""; + $path = getNodePathWithIds( $node->parentNode ); + $path .= "/{$node->nodeName}"; + $id = $node->getAttribute( "xml:id" ); + if ( $id != "" ) + $path .= "($id)"; + return $path; +} + echo "Validating {$ac["INPUT_FILENAME"]}... "; flush(); if ($ac['PARTIAL'] != '' && $ac['PARTIAL'] != 'no') { // {{{ From 2fec2ba9b8922377accc8f65bc9c05757b6b6adb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20L=20F=20S=20Bacci?= Date: Wed, 27 Nov 2024 11:29:41 -0300 Subject: [PATCH 4/5] Do not overdelete if all node paths changed --- configure.php | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/configure.php b/configure.php index c32feec8b..c55ca940f 100755 --- a/configure.php +++ b/configure.php @@ -790,21 +790,21 @@ function getFileModificationHistory(): array { $fatal = $ac['LANG'] == 'en'; $xpath = new DOMXPath( $dom ); - // pre run id capture + // pre xinclude() run id capture - $prevIdPath = []; + $listIdPath = []; $nodes = $xpath->query( "//*[@xml:id]" ); foreach( $nodes as $node ) { $id = $node->getAttribute( "xml:id" ); $path = $node->getNodePath(); - if ( isset( $prevIdPath[ $id ] ) ) + if ( isset( $listIdPath[ $id ] ) ) { echo "failed.\nDuplicated xml:id '$id' before XInclude!\n"; errors_are_bad( 1 ); } - $prevIdPath[ $id ] = $path; + $listIdPath[ $id ] = $path; } // automatic xi:fallback @@ -870,13 +870,10 @@ function getFileModificationHistory(): array { { $count++; $path = getNodePathWithIds( $node->parentNode ); - fprintf( $print , "\nFailed xi:include at: $path\n"); + fprintf( $print , "\nFailed xi:include location: $path\n"); - if ( trim( $payload) != "xi:fallback" ) - { - $payload = trim( substr( $payload , 12 ) ); - fprintf( $print , "Failed xi:include target is: $payload\n"); - } + $payload = trim( substr( $payload , 12 ) ); + fprintf( $print , "Failed xi:include target is: $payload\n"); } } @@ -891,12 +888,27 @@ function getFileModificationHistory(): array { // post run id fixup $nodes = $xpath->query( "//*[@xml:id]" ); + + $count = []; + foreach( $nodes as $node ) + { + $id = $node->getAttribute( "xml:id" ); + isset( $count[$id] ) ? $count[$id]++ : $count[$id] = 1; + } + foreach( $nodes as $node ) { $id = $node->getAttribute( "xml:id" ); $path = $node->getNodePath(); - if ( $prevIdPath[ $id ] != $path ) + + // Delete ids with unknown (newer?) paths, + // but do not overdelete if all paths changed. + + if ( $listIdPath[ $id ] != $path && $count[ $id ] > 1 ) + { $node->removeAttribute( "xml:id" ); + $count[ $id ]--; + } } flush(); From 0b15c3c3bcb0b951581d1af6fe9bd4ee85d7797b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20L=20F=20S=20Bacci?= Date: Mon, 2 Dec 2024 07:26:18 -0300 Subject: [PATCH 5/5] Use target's parent as anchors to (not) delete duplicated ids --- configure.php | 113 +++++++++++++++++++++++++++++--------------------- 1 file changed, 66 insertions(+), 47 deletions(-) diff --git a/configure.php b/configure.php index c55ca940f..86f0dc32e 100755 --- a/configure.php +++ b/configure.php @@ -790,21 +790,33 @@ function getFileModificationHistory(): array { $fatal = $ac['LANG'] == 'en'; $xpath = new DOMXPath( $dom ); - // pre xinclude() run id capture + // xinclude by xml:id pre run - $listIdPath = []; + $nodes = $xpath->query( "//*[xiparent]" ); + if ( $nodes->length > 0 ) + { + echo "XML contains elements with 'xiparent' attribute. XInclude by xml:id will fail.\n"; + exit(-1); + } - $nodes = $xpath->query( "//*[@xml:id]" ); - foreach( $nodes as $node ) + $stx = chr( 1 ); // invalid attribute chars + $etx = chr( 2 ); // cannot be used in text + + $nodes = $xpath->query( "//*[@xpointer]" ); + foreach( $nodes as $xinclude ) { - $id = $node->getAttribute( "xml:id" ); - $path = $node->getNodePath(); - if ( isset( $listIdPath[ $id ] ) ) - { - echo "failed.\nDuplicated xml:id '$id' before XInclude!\n"; - errors_are_bad( 1 ); - } - $listIdPath[ $id ] = $path; + $id = $xinclude->getAttribute( "xpointer" ); + $target = $dom->getElementById( $id ); + if ( $target == null ) + continue; + $parent = $target->parentNode; + if ( $parent->nodeType != XML_ELEMENT_NODE ) + continue; + // creates an 'xiparent' attribute on parent of xi:include xpointer target, + // so these targered elements are not to be changed on later fixup. + $mark = $parent->getAttribute( "xiparent" ); + $mark .= "{$stx}{$id}{$etx}"; + $parent->setAttribute( "xiparent" , $mark ); } // automatic xi:fallback @@ -816,16 +828,13 @@ function getFileModificationHistory(): array { $failback = null; $elements = $node->getElementsByTagName( "fallback" ); - if ( count( $elements ) > 0 ) - { - $failback = $elements[0]; - } - else + if ( count( $elements ) == 0 ) { $failback = $node->ownerDocument->createElementNS( "http://www.w3.org/2001/XInclude" , "fallback" ); - //$failback = $node->ownerDocument->createElement( "xi:fallback" ); $node->append( $failback ); } + else + $failback = $elements[0]; $comment = $failback->ownerDocument->createComment( "xi:fallback $target" ); $failback->append( $comment ); @@ -842,54 +851,40 @@ function getFileModificationHistory(): array { libxml_clear_errors(); $status = $ret === -1 ? "failed" : "done"; - $countOk = $ret === -1 ? 0 : (int) $ret; - $countErr = count( $errors ); - echo "$status. Performed $countOk XIncludes, $countErr failures.\n"; + $intret = $ret === -1 ? 0 : (int) $ret; + echo "$status. Performed $intret XIncludes.\n"; // failures if ( $report ) { - // xi:include failures - foreach( $errors as $error ) - fprintf( $print , "\n" . rtrim( $error->message ) . "\n"); - - if ( $fatal && count( $errors ) ) - errors_are_bad( 1 ); + fprintf( $print , " Failed xi:include at line: %s\n" , $error->line ); - // xi:failback failures - - $count = 0; $nodes = $xpath->query( "//comment()" ); - foreach( $nodes as $node ) { $payload = $node->data; - if ( str_starts_with( $payload , "xi:fallback" ) ) + if ( str_starts_with( $payload , "xi:fallback " ) ) { - $count++; - $path = getNodePathWithIds( $node->parentNode ); - fprintf( $print , "\nFailed xi:include location: $path\n"); - $payload = trim( substr( $payload , 12 ) ); - fprintf( $print , "Failed xi:include target is: $payload\n"); + fprintf( $print , " Used xi:fallback for target: $payload\n" ); } } - if ( $count > 0 ) + if ( count( $errors ) > 0 ) { - fprintf( $print , "\n"); + fprintf( $print , "\n\n"); if ( $fatal ) errors_are_bad( 1 ); } } - // post run id fixup + // xinclude by xml:id post run fixup $nodes = $xpath->query( "//*[@xml:id]" ); + $count = []; // id : count - $count = []; foreach( $nodes as $node ) { $id = $node->getAttribute( "xml:id" ); @@ -898,19 +893,43 @@ function getFileModificationHistory(): array { foreach( $nodes as $node ) { - $id = $node->getAttribute( "xml:id" ); - $path = $node->getNodePath(); + $parent = $node->parentNode; + if ( $parent->nodeType != XML_ELEMENT_NODE ) + continue; - // Delete ids with unknown (newer?) paths, - // but do not overdelete if all paths changed. + $id = $node->getAttribute( "xml:id" ); + $mark = "$stx" . $id . $etx; + $list = $parent->getAttribute( "xiparent" ); + if ( str_contains( $list , $mark ) ) + { + if ( $count[ $id ] > 1 ) + { + $node->removeAttribute( "xml:id" ); + $count[ $id ]--; + } + else + echo "\tStrange, xml:id was to be over deleted: $id\n"; + } + } - if ( $listIdPath[ $id ] != $path && $count[ $id ] > 1 ) + $list = array(); + $nodes = $xpath->query( "//*[@xml:id]" ); + foreach( $nodes as $node ) + { + $id = $node->getAttribute( "xml:id" ); + if ( in_array( $id , $list ) ) { + // may happen while migratating xpointers from xpaths to xml:ids + echo " Random removing duplicated xml:id: $id\n"; $node->removeAttribute( "xml:id" ); - $count[ $id ]--; } + $list[] = $id; } + $nodes = $xpath->query( "//*[@xiparent]" ); + foreach( $nodes as $node ) + $node->removeAttribute( "xiparent" ); + flush(); }