-
Notifications
You must be signed in to change notification settings - Fork 129
Expand file tree
/
Copy pathdocbookwsi.php
More file actions
193 lines (155 loc) · 5.54 KB
/
Copy pathdocbookwsi.php
File metadata and controls
193 lines (155 loc) · 5.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
<?php
// SPDX-License-Identifier: 0BSD
// © André L F S Bacci <ae#php.net>
/*
This script reads a RelaxNG XML file, and calculates all elements
that _not_ contain <text/> 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 run with a second XML argument, the script will calculate all savings
that can be done by stripping these insignificant whitespace between
elements.
See mentions of 'docbookwsi' on source code for parts that need to be
updated if/when a new version of Docbook is used. */
$argv0 = 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 <text/>, and also gather all <ref>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 <element>s, as they cannot influence
// if a parent element is trimmable or not, and so that any <text/>
// inside of a <element> cannot be found by XPaths, while exploring
// the original <element>'s <ref>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 <text/>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 );
}