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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,12 @@ The parser distinguishes the internal project format from the detected eXeLearni

Use `getFormatVersion()` for the ODE format version, `getApplicationVersion()` for the declared eXeLearning version, and `getVersionInfo()` when the distinction between declared and inferred versions matters.

## Performance characteristics

Parsed projects are indexed by page, block and iDevice ID for constant-time lookup. Aggregate collections and diagnostics are cached because parser instances are immutable after construction. Asset-reference resolution also caches normalized archive lookups.

The upstream compatibility corpus records per-project timings, total elapsed time, peak memory and the five slowest projects. These measurements are informational and do not impose brittle timing thresholds in CI.

## Compatibility regression testing

The regular test suite includes a deterministic corpus for malformed XML, encoded and Unicode asset paths, malformed iDevice state and cyclic page hierarchies.
Expand Down
31 changes: 30 additions & 1 deletion src/Asset/AssetReferenceExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ class AssetReferenceExtractor
/** @var array<string, string> */
private array $archiveLookup = [];

/** @var array<string, string|null> */
private array $resolutionCache = [];

/**
* @param array<int, string> $archiveEntries Archive entry names.
*/
Expand Down Expand Up @@ -69,7 +72,10 @@ public function extract(array $pages): array
}

$this->collectStringValues($idevice['jsonProperties'] ?? [], $sources);
$this->collectStringValues($idevice['data'] ?? [], $sources);

if (($idevice['storagePattern'] ?? '') !== 'standard-json') {
$this->collectStringValues($idevice['data'] ?? [], $sources);
}

foreach ($sources as $source) {
foreach ($this->extractPathsFromString($source) as $path) {
Expand Down Expand Up @@ -137,6 +143,10 @@ public function findBrokenReferences(array $pages): array

$this->collectStringValues($idevice['jsonProperties'] ?? [], $sources);

if (($idevice['storagePattern'] ?? '') !== 'standard-json') {
$this->collectStringValues($idevice['data'] ?? [], $sources);
}

foreach ($sources as $source) {
foreach ($this->extractCandidatesFromString($source) as $candidate) {
if ($this->resolveArchivePath($candidate) !== null) {
Expand Down Expand Up @@ -271,6 +281,25 @@ private function collectStringValues(mixed $value, array &$strings): void
* @return string|null
*/
private function resolveArchivePath(string $candidate): ?string
{
if (array_key_exists($candidate, $this->resolutionCache)) {
return $this->resolutionCache[$candidate];
}

$resolved = $this->resolveArchivePathUncached($candidate);
$this->resolutionCache[$candidate] = $resolved;

return $resolved;
}

/**
* Resolve an uncached asset reference against archive entries.
*
* @param string $candidate Raw asset reference.
*
* @return string|null
*/
private function resolveArchivePathUncached(string $candidate): ?string
{
$candidate = trim($candidate, " \t\n\r\0\x0B\"'");
$candidate = str_replace('{{context_path}}/', '', $candidate);
Expand Down
Loading
Loading