Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions php-transformer/src/AssetAnalysis/ReferenceAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public function referenceReports(array $files, ?callable $isLinkableDocument = n
if ( is_array($target) && ! $this->isLinkableDocument($target, $isLinkableDocument) ) {
unset($reference['target']);
$assetReferences[] = $reference;
if ( 'img' === $candidate['element'] && 'src' === $candidate['attribute'] ) {
if ( str_starts_with((string) ($target['mime_type'] ?? ''), 'image/') ) {
$imageReferences[] = $this->legacyImageReference($reference, count($imageReferences));
}
}
Expand All @@ -59,6 +59,9 @@ public function referenceReports(array $files, ?callable $isLinkableDocument = n
if ( is_array($target) && ! $this->isLinkableDocument($target, $isLinkableDocument) ) {
unset($reference['target']);
$assetReferences[] = $reference;
if ( str_starts_with((string) ($target['mime_type'] ?? ''), 'image/') ) {
$imageReferences[] = $this->legacyImageReference($reference, count($imageReferences));
}
}
}
}
Expand All @@ -76,7 +79,7 @@ public function referenceReports(array $files, ?callable $isLinkableDocument = n
*/
public function htmlReferenceCandidates(string $html, string $sourcePath): array
{
if ( '' === trim($html) || ! preg_match_all('/<\s*(a|audio|img|script|link|source|video)\b([^>]*)>/i', $html, $matches, PREG_SET_ORDER) ) {
if ( '' === trim($html) || ! preg_match_all('/<\s*([a-z][a-z0-9:-]*)\b([^>]*)>/i', $html, $matches, PREG_SET_ORDER) ) {
return array();
}

Expand All @@ -101,6 +104,21 @@ public function htmlReferenceCandidates(string $html, string $sourcePath): array
);
}
}

if ( isset($attributes['style']) ) {
$value = (string) $attributes['style'];
foreach ( $this->cssReferenceCandidates($value, $sourcePath) as $styleCandidate ) {
$candidates[] = array(
'source_path' => $sourcePath,
'selector' => $selector,
'element' => $element,
'attribute' => 'style',
'value' => $value,
'url' => $styleCandidate['url'],
'context' => 'inline-style',
);
}
}
}

return $candidates;
Expand Down Expand Up @@ -229,6 +247,7 @@ private function referenceAttributesForElement(string $element, array $attribute
'link' => array('href'),
'source' => array('src', 'srcset'),
'video' => array('src', 'poster'),
'image' => array('href', 'xlink:href'),
);

return array_values(array_filter(
Expand Down Expand Up @@ -316,13 +335,16 @@ private function legacyImageReference(array $reference, int $index): array
return array_filter(
array(
'source_path' => $reference['source_path'] ?? '',
'selector' => 'img:nth-of-type(' . ($index + 1) . ')',
'selector' => $reference['selector'] ?? 'image-reference:nth-of-type(' . ($index + 1) . ')',
'src' => $reference['url'] ?? '',
'resolved_path' => $reference['resolved_path'] ?? '',
'asset_path' => $reference['asset_path'] ?? '',
'mime_type' => $reference['mime_type'] ?? '',
'bytes' => $reference['bytes'] ?? 0,
'safe' => $reference['safe'] ?? null,
'element' => $reference['element'] ?? '',
'attribute' => $reference['attribute'] ?? '',
'context' => $reference['context'] ?? '',
),
static fn (mixed $value): bool => null !== $value && '' !== $value
);
Expand Down
42 changes: 42 additions & 0 deletions php-transformer/tests/contract/run.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,27 @@ function serialize_blocks(array $blocks): string
$assert('theme/fonts/fonts.css' === ($referenceReports['asset_references'][1]['asset_path'] ?? ''), 'reference analyzer assembles CSS @import asset reference reports');
$assert('assets/paper.png' === ($referenceReports['asset_references'][2]['asset_path'] ?? ''), 'reference analyzer resolves CSS url() reports relative to source CSS');
$assert('theme/FixtureSans.woff2' === ($referenceReports['asset_references'][3]['asset_path'] ?? ''), 'reference analyzer assembles @font-face local font reference reports');
$assert(2 === count($referenceReports['image_references']), 'reference analyzer projects HTML and CSS image asset references');
$assert('assets/paper.png' === ($referenceReports['image_references'][1]['asset_path'] ?? ''), 'reference analyzer projects CSS background images into image references');
$assert('css-url' === ($referenceReports['image_references'][1]['context'] ?? ''), 'reference analyzer preserves CSS background image context');

$imageReferenceReports = $referenceAnalyzer->referenceReports(array(
array('path' => 'pages/index.html', 'kind' => 'html', 'content' => '<picture><source srcset="../assets/hero-small.png 480w, ../assets/hero-large.png 960w"><img src="../assets/logo.png" srcset="../assets/logo@2x.png 2x" alt="Logo"></picture><section style="background-image:url(../assets/panel.png)"></section><svg><image href="../assets/vector.png"></image></svg>', 'binary' => false),
array('path' => 'assets/hero-small.png', 'kind' => 'image', 'content_base64' => base64_encode('small'), 'binary' => true, 'mime_type' => 'image/png', 'role' => 'asset', 'bytes' => 5),
array('path' => 'assets/hero-large.png', 'kind' => 'image', 'content_base64' => base64_encode('large'), 'binary' => true, 'mime_type' => 'image/png', 'role' => 'asset', 'bytes' => 5),
array('path' => 'assets/logo.png', 'kind' => 'image', 'content_base64' => base64_encode('logo'), 'binary' => true, 'mime_type' => 'image/png', 'role' => 'asset', 'bytes' => 4),
array('path' => 'assets/logo@2x.png', 'kind' => 'image', 'content_base64' => base64_encode('retina'), 'binary' => true, 'mime_type' => 'image/png', 'role' => 'asset', 'bytes' => 6),
array('path' => 'assets/panel.png', 'kind' => 'image', 'content_base64' => base64_encode('panel'), 'binary' => true, 'mime_type' => 'image/png', 'role' => 'asset', 'bytes' => 5),
array('path' => 'assets/vector.png', 'kind' => 'image', 'content_base64' => base64_encode('vector'), 'binary' => true, 'mime_type' => 'image/png', 'role' => 'asset', 'bytes' => 6),
));
$assert(6 === count($imageReferenceReports['image_references']), 'image reference analysis reports src, srcset, inline background, picture source, and SVG image href references');
$assert('source' === ($imageReferenceReports['image_references'][0]['element'] ?? ''), 'image reference analysis reports picture source elements');
$assert('srcset' === ($imageReferenceReports['image_references'][0]['attribute'] ?? ''), 'image reference analysis preserves srcset attributes');
$assert('assets/hero-small.png' === ($imageReferenceReports['image_references'][0]['asset_path'] ?? ''), 'image reference analysis resolves source srcset paths relative to the HTML document');
$assert('inline-style' === ($imageReferenceReports['image_references'][4]['context'] ?? ''), 'image reference analysis reports inline CSS background image references');
$assert('assets/panel.png' === ($imageReferenceReports['image_references'][4]['asset_path'] ?? ''), 'image reference analysis resolves inline style image paths relative to the HTML document');
$assert('image' === ($imageReferenceReports['image_references'][5]['element'] ?? ''), 'image reference analysis reports SVG image href elements');
$assert('assets/vector.png' === ($imageReferenceReports['image_references'][5]['asset_path'] ?? ''), 'image reference analysis resolves SVG image href paths relative to the HTML document');

$assertNormalizedFallbackDiagnostic = static function (array $diagnostic, string $code, string $severity, string $runtimeRequirement, string $suggestedPrimitive) use ($assert): void {
$assert($code === ($diagnostic['diagnostic_code'] ?? ''), "conversion report exposes {$code} diagnostic code");
Expand Down Expand Up @@ -805,6 +826,27 @@ function serialize_blocks(array $blocks): string
$assert('css-font-face' === ($fontCompiledAsset['references'][0]['context'] ?? ''), 'compiled site assets expose structured reference metadata');
$assert('css-font-face' === ($fontPlanAsset['references'][0]['context'] ?? ''), 'materialization plan assets preserve structured reference metadata');

$imageReferenceSite = $compiler->compile(
array(
'entrypoint' => 'pages/index.html',
'files' => array(
'pages/index.html' => '<main><picture><source srcset="../assets/hero-small.png 480w, ../assets/hero-large.png 960w"><img src="../assets/logo.png" alt="Logo"></picture><section style="background-image:url(../assets/panel.png)"></section><svg><image href="../assets/vector.png"></image></svg></main>',
'assets/hero-small.png' => array('content_base64' => base64_encode('small'), 'mime_type' => 'image/png'),
'assets/hero-large.png' => array('content_base64' => base64_encode('large'), 'mime_type' => 'image/png'),
'assets/logo.png' => array('content_base64' => base64_encode('logo'), 'mime_type' => 'image/png'),
'assets/panel.png' => array('content_base64' => base64_encode('panel'), 'mime_type' => 'image/png'),
'assets/vector.png' => array('content_base64' => base64_encode('vector'), 'mime_type' => 'image/png'),
),
)
)->toArray();
$imageReferencePlanAssets = array();
foreach ( $imageReferenceSite['source_reports']['materialization_plan']['assets'] ?? array() as $asset ) {
$imageReferencePlanAssets[$asset['path'] ?? ''] = $asset;
}
$assert('source' === ($imageReferencePlanAssets['assets/hero-small.png']['references'][0]['element'] ?? ''), 'materialization plan image rows preserve picture source references');
$assert('inline-style' === ($imageReferencePlanAssets['assets/panel.png']['references'][0]['context'] ?? ''), 'materialization plan image rows preserve inline background references');
$assert('image' === ($imageReferencePlanAssets['assets/vector.png']['references'][0]['element'] ?? ''), 'materialization plan image rows preserve SVG image href references');

$materializationView = ( new MaterializationView() )->fromResult($staticSite);
$assert(MaterializationView::SCHEMA === ($materializationView['schema'] ?? ''), 'materialization view exposes its own schema');
$assert(TransformerResult::SCHEMA === ($materializationView['result_schema'] ?? ''), 'materialization view exposes transformer result schema');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,12 @@
{ "path": "source_reports.artifact.asset_references.5.asset_path", "assert": "equals", "value": "assets/site.css" },
{ "path": "source_reports.artifact.asset_references.6.element", "assert": "equals", "value": "style" },
{ "path": "source_reports.artifact.asset_references.6.asset_path", "assert": "equals", "value": "assets/bg.png" },
{ "path": "source_reports.artifact.image_references", "assert": "count", "count": 1 },
{ "path": "source_reports.artifact.image_references", "assert": "count", "count": 4 },
{ "path": "source_reports.artifact.image_references.0.asset_path", "assert": "equals", "value": "assets/hero.png" },
{ "path": "source_reports.artifact.image_references.1.asset_path", "assert": "equals", "value": "assets/hero-small.png" },
{ "path": "source_reports.artifact.image_references.1.attribute", "assert": "equals", "value": "srcset" },
{ "path": "source_reports.artifact.image_references.3.asset_path", "assert": "equals", "value": "assets/bg.png" },
{ "path": "source_reports.artifact.image_references.3.context", "assert": "equals", "value": "css-url" },
{ "path": "source_reports.conversion_report.schema", "assert": "equals", "value": "blocks-engine/php-transformer/conversion-report/v1" },
{ "path": "source_reports.conversion_report.source_summary.entry_path", "assert": "equals", "value": "pages/home.html" },
{ "path": "source_reports.conversion_report.source_summary.file_count", "assert": "equals", "value": 8 },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@
"expect": [
{ "path": "status", "assert": "equals", "value": "success" },
{ "path": "source_reports.artifact.asset_references", "assert": "count", "count": 2 },
{ "path": "source_reports.artifact.image_references", "assert": "count", "count": 1 },
{ "path": "source_reports.artifact.image_references", "assert": "count", "count": 2 },
{ "path": "source_reports.artifact.image_references.0.resolved_path", "assert": "equals", "value": "assets/images/hero.png" },
{ "path": "source_reports.artifact.image_references.1.attribute", "assert": "equals", "value": "srcset" },
{ "path": "source_reports.artifact.image_references.1.resolved_path", "assert": "equals", "value": "assets/images/hero-small.png" },
{ "path": "source_reports.materialization_plan.assets", "assert": "count", "count": 2 },
{ "path": "source_reports.materialization_plan.assets.0.path", "assert": "equals", "value": "assets/images/hero.png" },
{ "path": "source_reports.materialization_plan.assets.1.path", "assert": "equals", "value": "assets/images/hero-small.png" },
Expand Down
Loading