Skip to content

Commit ebf1a0f

Browse files
author
André L F S Bacci
committed
Recreates mixed encoding of dir entities
1 parent 8ac6be8 commit ebf1a0f

1 file changed

Lines changed: 123 additions & 101 deletions

File tree

scripts/file-entities.php

Lines changed: 123 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@
4848
4949
*/
5050

51-
const ENTITY_NAME_MINUS = true;
52-
const ENTITY_NAME_EQUAL = false;
51+
const BACKPORT_MIXED_REPLACE = true;
52+
const ENTITY_NAME_REPLACE = true;
5353
const LIBXML_LIMITS_HACK = true;
5454

5555
// Setup
@@ -82,28 +82,32 @@
8282
$entities = [];
8383

8484
foreach( $langs as $lang )
85-
load_all_files( $langBase , $lang , $allFiles );
85+
scan_files( $langBase , $lang , $allFiles );
8686
check_case_conflict( $allFiles );
87+
8788
generate_entities( $allFiles , $entities );
89+
writeEntities( $entities );
90+
91+
$total = count( $entities );
92+
print "done: $total entities.\n";
93+
94+
exit( 0 );
8895

8996
// old scheme
9097
// file en
9198
// list en
9299
// file? lang
93100

94-
// Fixups
95-
96-
pushEntity( "global.function-index", realpain( __DIR__ . "/../funcindex.xml" ) , $entities ); // TODO move this file from doc-bese to doc-en, with a <?do-not-translate tag
97-
98-
// Output
99-
100-
writeEntities( $entities );
101-
$total = count( $entities );
102-
print "done: $total entities.\n";
103-
104-
exit( 0 );
101+
class Entity
102+
{
103+
public function __construct(
104+
public string $name,
105+
public string $text,
106+
public string $file,
107+
) {}
108+
}
105109

106-
function load_all_files( string $langBase , string $lang , array & $allFIles )
110+
function scan_files( string $langBase , string $lang , array & $allFIles )
107111
{
108112
$todo = [ "" ];
109113
while ( count( $todo ) > 0 )
@@ -176,160 +180,180 @@ function check_case_conflict( array $allFIles )
176180

177181
function generate_entities( array $allFiles , array & $entities )
178182
{
179-
// Direct file inclusion is easy. It is just a
180-
// DTD entity that points to a filename, without
181-
// the extension
183+
// Ugly, but necessary
184+
// TODO move this file from doc-bese to doc-en, with a do-not-translate PI
185+
186+
$name = 'global.function-index';
187+
$file = realpain( __DIR__ . "/../funcindex.xml" );
188+
$text = "<!ENTITY $name SYSTEM '$file'>";
189+
pushEntity( $entities , $name , $text );
190+
191+
// Inclusion of a single file is easy. The entity name is the
192+
// relative path without the .xml extension (sadly), and the text
193+
// is complete DTD entity with a SYSTEM pointing to the real path
194+
// of the included file.
182195

183-
foreach( $allFiles as $name => $file )
196+
foreach( $allFiles as $path => $file )
184197
{
185-
$name = substr( $name , 0 , -4 );
186-
$name = normalizeEntityName( $name );
198+
$name = pathToEntityName( $path );
187199
$text = "<!ENTITY $name SYSTEM '$file'>";
188-
pushEntity( $name , $text , $entities );
200+
pushEntity( $entities , $name , $text );
189201
}
190202

191203
// Inclusion of reference/ directories is a little more involved.
192-
// From the entity name of the file, is calculated list name and
193-
// one list item. The list items are then grouped, and a virtual
194-
// DTD entity for the directory is created with these components.
195-
196-
// Note that these "list" entities do not contain the final
197-
// filenames, as there is only a SYSTEM attribute per DTD entity.
198-
// The contents of list entities are the concatenated list of
199-
// entity references of the final files.
200-
201-
$mapNameList = [];
202-
203-
foreach( $allFiles as $name => $file )
204+
// The entity name is calculated from the relative path, but with
205+
// an 'entities' component added in penultimae position. The
206+
// contents are concatened DTD entities references, as above.
207+
208+
// LIBXML_LIMITS_HACK - Unfortunatlly, we nedd to put these entities
209+
// that expand in another DTD entities as separate files, to bypass
210+
// some hardcoded limits of libxml2. This is slow, more so on HDDs.
211+
212+
// BACKPORT_MIXED_REPLACE - Anoying enought, the previous script
213+
// normalized the entity name, but not the file name of the extra file
214+
// file. So indirect file entities ends having a surprising convention:
215+
//
216+
// <!ENTITY name-dir SYSTEM 'name_dir.ent'>
217+
//
218+
// Mind the distinction between _ and - above. In the future, let's
219+
// remove this, to make debugging easier.
220+
221+
$groupFilename = []; // LIBXML_LIMITS_HACK
222+
$groupContents = [];
223+
224+
foreach( $allFiles as $path => $null )
204225
{
205226
// Only generate directory inclusions for reference/
206227

207-
if ( ! str_starts_with ( $name , 'reference' ) )
228+
if ( ! str_starts_with ( $path , 'reference' ) )
208229
continue;
209230

210-
// List name
231+
// Entity name
232+
//
233+
// Discard the file part, 'entities' in the
234+
// second-to-last position.
211235
//
212-
// Discard the file part, and reform the name from
213-
// dir.dir.dir
214-
// to
215-
// dir.dir.entities.dir
236+
// dir/dir/dir/file.xml -> dir.dir.entities.dir
216237

217-
$parts = explode( '/' , $name );
238+
$parts = explode( '/' , $path );
218239
array_pop( $parts );
219240
$last = array_pop( $parts );
220241
$parts[] = 'entities';
221242
$parts[] = $last;
222-
$listName = implode( '.' , $parts );
243+
$entName = implode( '.' , $parts );
244+
$entName = str_replace( '_' , '-' , $entName ); // BACKPORT_MIXED_REPLACE
223245

224-
// List item
246+
// Entity fila
247+
//
248+
// dir/dir/dir/file.xml -> dir.dir.dir.ent
225249

226-
$listItem = "&{$name};";
250+
$parts = explode( '/' , $path );
251+
array_pop( $parts );
252+
array_push( $parts , 'ent');
253+
$entFile = implode( '.' , $parts );
254+
255+
$groupFilename[ $entName ] = $entFile;
256+
257+
// Contents
227258

228-
// Collect
259+
$name = pathToEntityName( $path );
260+
$entRef = "&{$name};";
229261

230-
iF ( ! isset( $mapNameList[$listName] ) )
231-
$mapNameList[$listName] = [];
232-
$mapNameList[$listName][] = $listItem;
262+
$groupContents[ $entName ][ $name ] = $entRef;
233263
}
234264

235-
// List emit
265+
// Merge
236266

237-
foreach( $mapNameList as $name => $list )
267+
foreach( $groupContents as $name => $list )
238268
{
239-
sort( $list );
269+
ksort( $list );
240270
$text = implode ( "\n" , $list );
241-
pushEntity( $name , $text , $entities );
271+
$file = $groupFilename[ $name ];
272+
pushEntity( $entities , $name , $text , $file );
242273
}
243274
}
244275

245-
function normalizeEntityName( string $name ) : string
276+
function pathToEntityName( string $name , string $removeSuffix = "" ) : string
246277
{
278+
if ( str_ends_with( $name , ".xml" ) )
279+
$name = substr( $name , 0 , -4 );
280+
else
281+
throw new Exception( "Expected extension .xml" );
282+
247283
$name = str_replace( '\\' , '/' , $name );
284+
$name = str_replace( '_' , '-' , $name ); // ENTITY_NAME_REPLACE
248285
$name = str_replace( '/' , '.' , $name );
249286
$name = trim( $name , '.' );
250287
return $name;
288+
289+
// ENTITY_NAME_REPLACE, or a TODO to a far future
290+
// - Replace all name replaced entities from doc en
291+
// - Add the removed entities on doc-en/entities/remove.ent
292+
// - Remove all codepaths related to ENTITY_NAME_REPLACE constant
251293
}
252294

253-
function pushEntity( string $name , string $text , array & $entities )
295+
function pushEntity( array & $entities , string $name , string $text , string $file = "" )
254296
{
255-
$debug = false;
256-
if ( str_contains( $name , "apache" ) )
257-
$debug = true;
258-
259297
if ( $name == "" || $text == "" )
260298
{
261299
print "Something went very wrong on file-entities.php.\n";
262300
exit( 1 );
263301
}
264302

265-
$nameEqual = normalizeEntityName( $name ); // Almost same as on disk
266-
$nameMinus = str_replace( '_' , '-' , $nameEqual ); // Historical behaviour
267-
268-
if ( $nameEqual == $nameMinus )
269-
{
270-
$entities[ $nameEqual ] = $text;
271-
return;
272-
}
273-
274-
// This is the historical behaviour. Why?
275-
276-
if ( ENTITY_NAME_MINUS )
277-
$entities[ $nameMinus ] = $text;
278-
279-
// To make manual writing easier, as file entity names
280-
// always match file system names.
281-
282-
if ( ENTITY_NAME_EQUAL )
283-
$entities[ $nameEqual ] = $text;
284-
285-
// TODO for the far future
286-
// - Replace all MINUS entities from doc en
287-
// - Add the MINUS entities on doc-en/entities/remove.ent
288-
// - Remove all codepaths related to MINUS constant
303+
$entity = new Entity( $name , $text , $file );
304+
$entities[ $name ] = $entity;
289305
}
290306

291307
function writeEntities( array $entities )
292308
{
293-
ksort( $entities );
309+
// Output a single temp/file-entities.ent file for single file inclusion.
310+
311+
// Output separate files for file list inclusions, at
312+
// temp/file-entities/dir.dir.dir.ent
313+
// LIBXML_LIMITS_HACK
294314

295-
// Output a single temp/file-entities.ent file for direct file inclusion.
296-
// Output individual files for indirect file inclusions on
297-
// temp/file-entities/dir.dir.entities.dir.ent
298-
// See LIBXML_LIMITS_HACK below.
315+
ksort( $entities );
299316

300317
$outFile = realpain( __DIR__ . "/../temp/file-entities.ent" , touch: true );
301318
$lstFile = realpain( __DIR__ . "/../temp/file-entities.txt" , touch: true );
302319
$sepPath = realpain( __DIR__ . "/../temp/file-entities" , mkdir: true );
303320

304-
$file = fopen( $outFile , "w" );
305-
if ( ! $file )
321+
$singleFile = fopen( $outFile , "w" );
322+
if ( ! $singleFile )
306323
{
307324
print "Failed to open $outFile\n.";
308325
exit( 1 );
309326
}
310-
fputs( $file , "<!-- DON'T TOUCH - AUTOGENERATED BY file-entities.php -->\n\n" );
327+
fputs( $singleFile , "<!-- DON'T TOUCH - AUTOGENERATED BY file-entities.php -->\n\n" );
311328

312329
// Life could be simpler, but the building of PHP Manual is already
313330
// triping some hardcoded limits of bundled libxml2.
314331

315-
// Off loading file entities that expand to more file entities,
332+
// Off loading DTD entities that expand to more DTD entities,
316333
// as external files, somehow avoid these limits.
317334

318335
if ( LIBXML_LIMITS_HACK )
319336
{
320-
foreach ( $entities as $name => $text )
337+
foreach ( $entities as $entity )
338+
{
339+
$name = $entity->name;
340+
$text = $entity->text;
341+
$file = $entity->file;
342+
$extraFile = "{$sepPath}/{$file}";
343+
321344
if ( $text[0] == '&' )
322-
writeEntityIndirectSlow( $file , $name , $text , $sepPath );
345+
writeEntityIndirectSlow( $singleFile , $extraFile , $name , $text );
323346
else
324-
fputs( $file , "$text\n" );
347+
fputs( $singleFile , "$text\n" );
348+
}
325349
}
326350
else
327351
{
328352
foreach ( $entities as $name => $text )
329-
fputs( $file , "$text\n" );
353+
fputs( $singleFile , "$text\n" );
330354
}
331355

332-
fclose( $file );
356+
fclose( $singleFile );
333357

334358
// After everything is said and done, also output a listing file, so
335359
// it is possible to analyse collisions between 'text' and 'file'
@@ -339,17 +363,15 @@ function writeEntities( array $entities )
339363
file_put_contents( $lstFile , $contents );
340364
}
341365

342-
function writeEntityIndirectSlow( $file , string $name , string $text , string $baseDir )
366+
function writeEntityIndirectSlow( $singleFile , string $extraFile , string $name , string $text )
343367
{
344-
$newFilename = "{$baseDir}/{$name}.ent";
345-
346368
// The entity will point to to a new, individual filename
347369

348-
fputs( $file , "<!ENTITY $name SYSTEM '$newFilename'>\n" );
370+
fputs( $singleFile , "<!ENTITY $name SYSTEM '$extraFile'>\n" );
349371

350372
// And the new individual file will hold the final text
351373

352-
file_put_contents( $newFilename , $text );
374+
file_put_contents( $extraFile , $text );
353375
}
354376

355377
function realpain( string $path , bool $touch = false , bool $mkdir = false ) : string

0 commit comments

Comments
 (0)