Skip to content

Commit 3050cd1

Browse files
committed
Try using PHP to parse PHP instead of strings and RegExp
1 parent fbb1c58 commit 3050cd1

1 file changed

Lines changed: 39 additions & 194 deletions

File tree

tools/gutenberg/copy.js

Lines changed: 39 additions & 194 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
* @package WordPress
1010
*/
1111

12+
const child_process = require( 'child_process' );
1213
const fs = require( 'fs' );
1314
const path = require( 'path' );
1415
const json2php = require( 'json2php' );
@@ -353,18 +354,9 @@ function generateScriptModulesPackages() {
353354
const jsPathRegular = jsPathMin.replace( /\.min\.js$/, '.js' );
354355

355356
try {
356-
// Read and parse the PHP asset file.
357-
const phpContent = fs.readFileSync( fullPath, 'utf8' );
358-
// Extract the array from PHP: <?php return array(...);
359-
const match = phpContent.match(
360-
/return\s+array\(([\s\S]*?)\);/
361-
);
362-
if ( match ) {
363-
// Parse PHP array to JavaScript object.
364-
const assetData = parsePHPArray( match[ 1 ] );
365-
assetsMin[ jsPathMin ] = assetData;
366-
assetsRegular[ jsPathRegular ] = assetData;
367-
}
357+
const assetData = readReturnedValueFromPHPFile( fullPath );
358+
assetsMin[ jsPathMin ] = assetData;
359+
assetsRegular[ jsPathRegular ] = assetData;
368360
} catch ( error ) {
369361
console.error(
370362
` ⚠️ Error reading ${ relativePath }:`,
@@ -446,26 +438,19 @@ function generateScriptLoaderPackages() {
446438
}
447439

448440
try {
449-
// Read and parse the PHP asset file.
450-
const phpContent = fs.readFileSync( assetFile, 'utf8' );
451-
// Extract the array from PHP: <?php return array(...);
452-
const match = phpContent.match( /return\s+array\(([\s\S]*?)\);/ );
453-
if ( match ) {
454-
// Parse PHP array to JavaScript object.
455-
const assetData = parsePHPArray( match[ 1 ] );
456-
457-
// For regular scripts, use dependencies as-is.
458-
if ( ! assetData.dependencies ) {
459-
assetData.dependencies = [];
460-
}
441+
const assetData = readReturnedValueFromPHPFile( assetFile );
461442

462-
// Create entries for both minified and non-minified versions.
463-
const jsPathMin = `${ entry.name }.min.js`;
464-
const jsPathRegular = `${ entry.name }.js`;
465-
466-
assetsMin[ jsPathMin ] = assetData;
467-
assetsRegular[ jsPathRegular ] = assetData;
443+
// For regular scripts, use dependencies as-is.
444+
if ( ! assetData.dependencies ) {
445+
assetData.dependencies = [];
468446
}
447+
448+
// Create entries for both minified and non-minified versions.
449+
const jsPathMin = `${ entry.name }.min.js`;
450+
const jsPathRegular = `${ entry.name }.js`;
451+
452+
assetsMin[ jsPathMin ] = assetData;
453+
assetsRegular[ jsPathRegular ] = assetData;
469454
} catch ( error ) {
470455
console.error(
471456
` ⚠️ Error reading ${ entry.name }/index.min.asset.php:`,
@@ -663,179 +648,39 @@ function generateBlocksJson() {
663648
}
664649

665650
/**
666-
* Parse PHP array syntax to JavaScript object.
667-
* Uses a simple but effective approach for the specific format in asset files.
651+
* Given a path to a PHP file which returns a single value, converts that
652+
* value into a native JavaScript value (limited by JSON serialization).
668653
*
669-
* @param {string} phpArrayContent - PHP array content (without outer 'array(' and ')').
670-
* @return {Object|Array} Parsed JavaScript object or array.
654+
* @throws Error when PHP source file unable to be read, or PHP is unavailable.
655+
*
656+
* @param {string} phpFilepath Absolute path of PHP file returning a single value.
657+
* @return {Object|Array} JavaScript representation of value from input file.
671658
*/
672-
function parsePHPArray( phpArrayContent ) {
673-
phpArrayContent = phpArrayContent.trim();
674-
675-
// First, extract all nested array() blocks and replace with placeholders.
676-
const nestedArrays = [];
677-
let content = phpArrayContent;
678-
let depth = 0;
679-
let inString = false;
680-
let stringChar = '';
681-
let currentArray = '';
682-
let arrayStart = -1;
683-
684-
for ( let i = 0; i < content.length; i++ ) {
685-
const char = content[ i ];
686-
687-
// Track strings.
688-
if (
689-
( char === "'" || char === '"' ) &&
690-
( i === 0 || content[ i - 1 ] !== '\\' )
691-
) {
692-
if ( ! inString ) {
693-
inString = true;
694-
stringChar = char;
695-
} else if ( char === stringChar ) {
696-
inString = false;
697-
}
698-
}
699-
700-
if ( ! inString ) {
701-
// Look for array( keyword.
702-
if ( content.substring( i, i + 6 ) === 'array(' ) {
703-
if ( depth === 0 ) {
704-
arrayStart = i;
705-
currentArray = '';
706-
}
707-
depth++;
708-
if ( depth > 1 ) {
709-
currentArray += 'array(';
710-
}
711-
i += 5; // Skip 'array('.
712-
continue;
713-
}
714-
715-
if ( depth > 0 ) {
716-
if ( char === '(' ) {
717-
depth++;
718-
currentArray += char;
719-
} else if ( char === ')' ) {
720-
depth--;
721-
if ( depth === 0 ) {
722-
// Found complete nested array.
723-
const placeholder = `__ARRAY_${ nestedArrays.length }__`;
724-
nestedArrays.push( currentArray );
725-
content =
726-
content.substring( 0, arrayStart ) +
727-
placeholder +
728-
content.substring( i + 1 );
729-
i = arrayStart + placeholder.length - 1;
730-
currentArray = '';
731-
} else {
732-
currentArray += char;
733-
}
734-
} else {
735-
currentArray += char;
736-
}
737-
}
738-
} else if ( depth > 0 ) {
739-
currentArray += char;
740-
}
741-
}
742-
743-
// Now parse the simplified content.
744-
const result = {};
745-
const values = [];
746-
let isAssociative = false;
747-
748-
// Split by top-level commas.
749-
const parts = [];
750-
depth = 0;
751-
inString = false;
752-
let currentPart = '';
753-
754-
for ( let i = 0; i < content.length; i++ ) {
755-
const char = content[ i ];
756-
757-
if (
758-
( char === "'" || char === '"' ) &&
759-
( i === 0 || content[ i - 1 ] !== '\\' )
760-
) {
761-
inString = ! inString;
762-
}
763-
764-
if ( ! inString && char === ',' && depth === 0 ) {
765-
parts.push( currentPart.trim() );
766-
currentPart = '';
767-
} else {
768-
currentPart += char;
769-
if ( ! inString ) {
770-
if ( char === '(' ) {
771-
depth++;
772-
}
773-
if ( char === ')' ) {
774-
depth--;
775-
}
776-
}
659+
function readReturnedValueFromPHPFile( phpFilepath ) {
660+
const results = child_process.spawnSync(
661+
'php',
662+
[ '-r', '$path = file_get_contents( "php://stdin" ); if ( ! is_file( $path ) ) { die( 1 ); } try { $data = require $path; } catch ( \\Throwable $e ) { die( 2 ); } $json = json_encode( $data ); if ( ! is_string( $json ) ) { die( 3 ); } echo $json;' ],
663+
{
664+
encoding: 'utf8',
665+
input: phpFilepath,
777666
}
778-
}
779-
if ( currentPart.trim() ) {
780-
parts.push( currentPart.trim() );
781-
}
782-
783-
// Parse each part.
784-
for ( const part of parts ) {
785-
const arrowMatch = part.match( /^(.+?)\s*=>\s*(.+)$/ );
786-
787-
if ( arrowMatch ) {
788-
isAssociative = true;
789-
let key = arrowMatch[ 1 ].trim().replace( /^['"]|['"]$/g, '' );
790-
let value = arrowMatch[ 2 ].trim();
667+
);
791668

792-
// Replace placeholders.
793-
while ( value.match( /__ARRAY_(\d+)__/ ) ) {
794-
value = value.replace( /__ARRAY_(\d+)__/, ( match, index ) => {
795-
return 'array(' + nestedArrays[ parseInt( index ) ] + ')';
796-
} );
797-
}
669+
switch ( results.status ) {
670+
case 0:
671+
return JSON.parse( results.stdout );
798672

799-
result[ key ] = parseValue( value );
800-
} else {
801-
// No arrow, indexed array.
802-
let value = part;
673+
case 1:
674+
throw new Error( `Could not read PHP source file: '${ phpFilepath }'` );
803675

804-
// Replace placeholders.
805-
while ( value.match( /__ARRAY_(\d+)__/ ) ) {
806-
value = value.replace( /__ARRAY_(\d+)__/, ( match, index ) => {
807-
return 'array(' + nestedArrays[ parseInt( index ) ] + ')';
808-
} );
809-
}
676+
case 2:
677+
throw new Error( `PHP source file did not return value when imported: '${ phpFilepath }'` );
810678

811-
values.push( parseValue( value ) );
812-
}
679+
case 3:
680+
throw new Error( `Could not serialize PHP source value into JSON: '${ phpFilepath }'` );
813681
}
814682

815-
return isAssociative ? result : values;
816-
817-
/**
818-
* Parse a single value.
819-
*
820-
* @param {string} value - The value string to parse.
821-
* @return {*} Parsed value.
822-
*/
823-
function parseValue( value ) {
824-
value = value.trim();
825-
826-
if ( value.startsWith( 'array(' ) && value.endsWith( ')' ) ) {
827-
return parsePHPArray( value.substring( 6, value.length - 1 ) );
828-
} else if ( value.match( /^['"].*['"]$/ ) ) {
829-
return value.substring( 1, value.length - 1 );
830-
} else if ( value === 'true' ) {
831-
return true;
832-
} else if ( value === 'false' ) {
833-
return false;
834-
} else if ( ! isNaN( value ) && value !== '' ) {
835-
return parseInt( value, 10 );
836-
}
837-
return value;
838-
}
683+
throw new Error( `Unknown error while reading PHP source file: '${ phpFilepath }'` );
839684
}
840685

841686
/**

0 commit comments

Comments
 (0)