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 ;
5353const LIBXML_LIMITS_HACK = true ;
5454
5555// Setup
8282$ entities = [];
8383
8484foreach ( $ langs as $ lang )
85- load_all_files ( $ langBase , $ lang , $ allFiles );
85+ scan_files ( $ langBase , $ lang , $ allFiles );
8686check_case_conflict ( $ allFiles );
87+
8788generate_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
177181function 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
291307function 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
355377function realpain ( string $ path , bool $ touch = false , bool $ mkdir = false ) : string
0 commit comments