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
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,19 @@ composer require exelearning/elp-parser

## Usage

### Streams and uploads

Projects can be parsed from PHP streams or in-memory bytes. Stream input is copied in chunks to a bounded temporary file because `ZipArchive` requires a filesystem path:

```php
$stream = fopen($_FILES['project']['tmp_name'], 'rb');
$parser = ELPParser::fromStream($stream, 'elpx');

$parserFromBytes = ELPParser::fromContents($bytes, 'elpx');
```

Temporary files owned by parser instances are removed automatically. `inspectStream()` and `inspectContents()` provide the corresponding lightweight inspection APIs.

### Lightweight inspection

For cataloging or indexing, `inspect()` reads archive metadata and the project XML without normalizing pages, iDevices or assets:
Expand Down
16 changes: 16 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,26 @@ Create and immediately parse an eXeLearning project.

Factory equivalent to the constructor.

#### `fromStream(mixed $stream, string $extension = 'elpx', ?ArchiveLimits $limits = null): ELPParser`

Parse a readable PHP stream using bounded temporary-file spooling.

#### `fromContents(string $contents, string $extension = 'elpx', ?ArchiveLimits $limits = null): ELPParser`

Parse in-memory project bytes.

#### `inspect(string $filePath, ?ArchiveLimits $limits = null): array`

Read core format/version/project metadata without fully normalizing pages, iDevices or assets.

#### `inspectStream(mixed $stream, string $extension = 'elpx', ?ArchiveLimits $limits = null): array`

Lightweight inspection for stream input.

#### `inspectContents(string $contents, string $extension = 'elpx', ?ArchiveLimits $limits = null): array`

Lightweight inspection for in-memory project bytes.

### Version and format

- `getVersion(): int` — detected major version kept for backward compatibility.
Expand Down
133 changes: 133 additions & 0 deletions src/ELPParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use Exelearning\Parser\OdeParser;
use Exelearning\Reference\InternalReferenceExtractor;
use Exelearning\Support\ProjectInspector;
use Exelearning\Support\TemporaryProjectFile;
use Exelearning\Support\VersionDetector;
use Exelearning\Support\XmlLoader;
use Exelearning\Validation\PackageValidator;
Expand Down Expand Up @@ -99,6 +100,7 @@ class ELPParser implements JsonSerializable
private ArchiveReader $archiveReader;
private AssetReferenceExtractor $assetExtractor;
private InternalReferenceExtractor $internalReferenceExtractor;
private ?string $ownedTemporaryFile = null;

/**
* Create a new parser instance.
Expand Down Expand Up @@ -128,6 +130,70 @@ public static function fromFile(string $filePath, ?ArchiveLimits $limits = null)
return new self($filePath, $limits);
}

/**
* Create a parser from a readable PHP stream resource.
*
* @param mixed $stream Readable stream resource.
* @param string $extension Source extension used for format heuristics.
* @param ArchiveLimits|null $limits Optional archive safety limits.
*
* @return self
*/
public static function fromStream(
mixed $stream,
string $extension = 'elpx',
?ArchiveLimits $limits = null
): self {
$limits = $limits ?? new ArchiveLimits();
$path = TemporaryProjectFile::fromStream(
$stream,
$extension,
$limits->maxTotalBytes
);

try {
$parser = new self($path, $limits);
$parser->ownedTemporaryFile = $path;

return $parser;
} catch (\Throwable $exception) {
@unlink($path);
throw $exception;
}
}

/**
* Create a parser from in-memory project bytes.
*
* @param string $contents Project bytes.
* @param string $extension Source extension used for format heuristics.
* @param ArchiveLimits|null $limits Optional archive safety limits.
*
* @return self
*/
public static function fromContents(
string $contents,
string $extension = 'elpx',
?ArchiveLimits $limits = null
): self {
$limits = $limits ?? new ArchiveLimits();
$path = TemporaryProjectFile::fromContents(
$contents,
$extension,
$limits->maxTotalBytes
);

try {
$parser = new self($path, $limits);
$parser->ownedTemporaryFile = $path;

return $parser;
} catch (\Throwable $exception) {
@unlink($path);
throw $exception;
}
}

/**
* Inspect core project metadata without fully normalizing page content.
*
Expand All @@ -141,6 +207,62 @@ public static function inspect(string $filePath, ?ArchiveLimits $limits = null):
return (new ProjectInspector($filePath, $limits))->inspect();
}

/**
* Inspect a project from a readable stream without full normalization.
*
* @param mixed $stream Readable stream resource.
* @param string $extension Source extension used for format heuristics.
* @param ArchiveLimits|null $limits Optional archive safety limits.
*
* @return array<string, mixed>
*/
public static function inspectStream(
mixed $stream,
string $extension = 'elpx',
?ArchiveLimits $limits = null
): array {
$limits = $limits ?? new ArchiveLimits();
$path = TemporaryProjectFile::fromStream(
$stream,
$extension,
$limits->maxTotalBytes
);

try {
return self::inspect($path, $limits);
} finally {
@unlink($path);
}
}

/**
* Inspect in-memory project bytes without full normalization.
*
* @param string $contents Project bytes.
* @param string $extension Source extension used for format heuristics.
* @param ArchiveLimits|null $limits Optional archive safety limits.
*
* @return array<string, mixed>
*/
public static function inspectContents(
string $contents,
string $extension = 'elpx',
?ArchiveLimits $limits = null
): array {
$limits = $limits ?? new ArchiveLimits();
$path = TemporaryProjectFile::fromContents(
$contents,
$extension,
$limits->maxTotalBytes
);

try {
return self::inspect($path, $limits);
} finally {
@unlink($path);
}
}

/**
* Detect the project format and parse its contents.
*
Expand Down Expand Up @@ -1538,4 +1660,15 @@ public function extract(string $destinationPath): void
{
$this->archiveReader->extract($destinationPath);
}

/**
* Remove any temporary project file owned by this parser instance.
*/
public function __destruct()
{
if ($this->ownedTemporaryFile !== null) {
@unlink($this->ownedTemporaryFile);
$this->ownedTemporaryFile = null;
}
}
}
Loading
Loading