diff --git a/composer.json b/composer.json
index 85746ce..eae6d8c 100644
--- a/composer.json
+++ b/composer.json
@@ -1,7 +1,7 @@
{
"name": "kiwilan/php-ebook",
"description": "PHP package to read metadata and extract covers from eBooks, comics and audiobooks.",
- "version": "2.5.0",
+ "version": "2.5.10",
"keywords": [
"php",
"ebook",
diff --git a/src/Formats/Audio/AudiobookModule.php b/src/Formats/Audio/AudiobookModule.php
index 881c0a2..c5f21c1 100644
--- a/src/Formats/Audio/AudiobookModule.php
+++ b/src/Formats/Audio/AudiobookModule.php
@@ -37,7 +37,10 @@ private function create(): self
$audio = $this->ebook->getAudio();
$authors = $audio->getArtist() ?? $audio->getAlbumArtist();
- $genres = $this->parseGenres($audio->getGenre());
+
+ $genres = EbookUtils::parseStringWithSeperator($audio->getGenre());
+ $genres = array_map('ucfirst', $genres);
+
$series = $audio->getTag('series') ?? $audio->getTag('mvnm');
$series_part = $audio->getTag('series-part') ?? $audio->getTag('mvin');
$series_part = $this->parseTag($series_part);
@@ -51,12 +54,12 @@ private function create(): self
}
$this->audio = [
- 'authors' => $this->parseAuthors($authors),
+ 'authors' => EbookUtils::parseStringWithSeperator($authors),
'title' => $audio->getAlbum() ?? $audio->getTitle(),
'subtitle' => $this->parseTag($audio->getTag('subtitle'), false),
'publisher' => $audio->getTag('encoded_by'),
'publish_year' => $audio->getYear(),
- 'narrators' => $this->parseAuthors($narrators),
+ 'narrators' => EbookUtils::parseStringWithSeperator($narrators),
'description' => $this->parseTag($audio->getDescription(), false),
'lyrics' => $this->parseTag($audio->getLyrics()),
'comment' => $this->parseTag($audio->getComment()),
@@ -186,59 +189,6 @@ public function __toString(): string
return $this->toJson();
}
- /**
- * @return string[]
- */
- private function parseGenres(?string $genres): array
- {
- if (! $genres) {
- return [];
- }
-
- $items = [];
- if (str_contains($genres, ';')) {
- $items = explode(';', $genres);
- } elseif (str_contains($genres, '/')) {
- $items = explode('/', $genres);
- } elseif (str_contains($genres, '//')) {
- $items = explode('//', $genres);
- } elseif (str_contains($genres, ',')) {
- $items = explode(',', $genres);
- } else {
- $items = [$genres];
- }
-
- $items = array_map('trim', $items);
- $items = array_map('ucfirst', $items);
-
- return $items;
- }
-
- /**
- * @return string[]
- */
- private function parseAuthors(?string $authors): array
- {
- if (! $authors) {
- return [];
- }
-
- $items = [];
- if (str_contains($authors, ',')) {
- $items = explode(',', $authors);
- } elseif (str_contains($authors, ';')) {
- $items = explode(';', $authors);
- } elseif (str_contains($authors, '&')) {
- $items = explode('&', $authors);
- } elseif (str_contains($authors, 'and')) {
- $items = explode('and', $authors);
- } else {
- $items = [$authors];
- }
-
- return array_map('trim', $items);
- }
-
private function parseTag(?string $tag, bool $flat = true): ?string
{
if (! $tag) {
diff --git a/src/Formats/Epub/Parser/OpfItem.php b/src/Formats/Epub/Parser/OpfItem.php
index b60bb81..12cfb77 100644
--- a/src/Formats/Epub/Parser/OpfItem.php
+++ b/src/Formats/Epub/Parser/OpfItem.php
@@ -8,6 +8,7 @@
use Kiwilan\Ebook\Models\BookContributor;
use Kiwilan\Ebook\Models\BookIdentifier;
use Kiwilan\Ebook\Models\BookMeta;
+use Kiwilan\Ebook\Utils\EbookUtils;
use Kiwilan\XmlReader\XmlReader;
/**
@@ -64,7 +65,7 @@ protected function __construct(
) {
}
- public static function make(string $content, string $filename): self
+ public static function make(string $content, ?string $filename = null): self
{
$xml = XmlReader::make($content);
$self = new self($xml);
@@ -364,6 +365,8 @@ private function setDcSubjects(): array
$items = $core;
}
+ $items = EbookUtils::parseStringWithSeperator($items);
+
return $items;
}
@@ -411,6 +414,10 @@ private function setDcCreators(): array
continue;
}
$attributes = XmlReader::parseAttributes($item);
+ // remove `\n` and `\r` from the name
+ $name = preg_replace('/\s+/', ' ', $name);
+ $name = trim($name);
+
$items[$name] = new BookAuthor(
name: $name,
role: $attributes['role'] ?? null,
@@ -549,10 +556,7 @@ private function multipleItems(mixed $items): array
$attr = XmlReader::parseAttributes($items);
// Check if bad multiple creators `Jean M. Auel, Philippe Rouard` exists
- if (is_string($content) && str_contains($content, ',')) {
- $content = explode(',', $content);
- $content = array_map('trim', $content);
- }
+ $content = EbookUtils::parseStringWithSeperator($content);
$temp = [];
// If bad multiple creators exists
diff --git a/src/Formats/Mobi/MobiModule.php b/src/Formats/Mobi/MobiModule.php
index 425da2e..c1968aa 100644
--- a/src/Formats/Mobi/MobiModule.php
+++ b/src/Formats/Mobi/MobiModule.php
@@ -10,6 +10,7 @@
use Kiwilan\Ebook\Formats\Mobi\Parser\MobiReader;
use Kiwilan\Ebook\Models\BookAuthor;
use Kiwilan\Ebook\Models\BookIdentifier;
+use Kiwilan\Ebook\Utils\EbookUtils;
/**
* @docs https://stackoverflow.com/questions/11817047/php-library-to-parse-mobi
@@ -33,7 +34,13 @@ public function toEbook(): Ebook
return $this->ebook;
}
+ $authors = [];
foreach ($this->parser->get(MobiReader::AUTHOR_100, true) as $author) {
+ $authors[] = $author;
+ }
+
+ $authors = EbookUtils::parseStringWithSeperator($authors);
+ foreach ($authors as $author) {
$this->ebook->setAuthor(new BookAuthor($author));
}
diff --git a/src/Formats/Pdf/PdfModule.php b/src/Formats/Pdf/PdfModule.php
index adae17d..70401c9 100644
--- a/src/Formats/Pdf/PdfModule.php
+++ b/src/Formats/Pdf/PdfModule.php
@@ -7,6 +7,7 @@
use Kiwilan\Ebook\EbookCover;
use Kiwilan\Ebook\Formats\EbookModule;
use Kiwilan\Ebook\Models\BookAuthor;
+use Kiwilan\Ebook\Utils\EbookUtils;
class PdfModule extends EbookModule
{
@@ -27,16 +28,7 @@ public function toEbook(): Ebook
$author = $this->meta?->getAuthor();
if ($author !== null) {
- $authors = [];
- if (str_contains($author, ',')) {
- $authors = explode(',', $author);
- } elseif (str_contains($author, '&')) {
- $authors = explode(',', $author);
- } elseif (str_contains($author, 'and')) {
- $authors = explode(',', $author);
- } else {
- $authors[] = $author;
- }
+ $authors = EbookUtils::parseStringWithSeperator($author);
$creators = [];
foreach ($authors as $author) {
@@ -49,9 +41,9 @@ public function toEbook(): Ebook
}
$this->ebook->setDescription($this->meta?->getSubject());
$this->ebook->setPublisher($this->meta?->getCreator());
- $this->ebook->setTags($this->meta?->getKeywords());
+ $keywords = EbookUtils::parseStringWithSeperator($this->meta?->getKeywords());
+ $this->ebook->setTags($keywords);
$this->ebook->setPublishDate($this->meta?->getCreationDate());
-
$this->ebook->setHasParser(true);
return $this->ebook;
diff --git a/src/Models/MetaTitle.php b/src/Models/MetaTitle.php
index df105f1..43c4a01 100644
--- a/src/Models/MetaTitle.php
+++ b/src/Models/MetaTitle.php
@@ -40,7 +40,7 @@ class MetaTitle
],
'fr' => [
'les ',
- 'l\' ',
+ 'l\'',
'le ',
'la ',
'du ',
@@ -296,13 +296,17 @@ protected function __construct(
protected ?string $slugSimple = null,
protected ?string $seriesSlug = null,
protected ?string $seriesSlugSimple = null,
+
+ protected bool $useIntl = true,
) {
}
/**
* Create a new MetaTitle instance from an Ebook.
+ *
+ * @param bool $useIntl Use intl extension for slugify.
*/
- public static function fromEbook(Ebook $ebook): ?self
+ public static function fromEbook(Ebook $ebook, bool $useIntl = true): ?self
{
if (! $ebook->getTitle()) {
return null;
@@ -317,6 +321,7 @@ public static function fromEbook(Ebook $ebook): ?self
year: $ebook->getPublishDate()?->format('Y'),
extension: $ebook->getExtension(),
);
+ $self->useIntl = $useIntl;
$self->parse();
return $self;
@@ -324,6 +329,8 @@ public static function fromEbook(Ebook $ebook): ?self
/**
* Create a new MetaTitle instance from data.
+ *
+ * @param bool $useIntl Use intl extension for slugify.
*/
public static function fromData(
string $title,
@@ -333,6 +340,7 @@ public static function fromData(
?string $author = null,
string|int|null $year = null,
?string $extension = null,
+ bool $useIntl = true,
): self {
$self = new self(
title: $title,
@@ -343,6 +351,7 @@ public static function fromData(
year: (string) $year,
extension: $extension,
);
+ $self->useIntl = $useIntl;
$self->parse();
return $self;
@@ -353,18 +362,24 @@ private function parse(): static
$title = $this->generateSlug($this->title);
$language = $this->language ? $this->generateSlug($this->language) : null;
$series = $this->series ? $this->generateSlug($this->series) : null;
- $volume = $this->volume ? str_pad((string) $this->volume, 2, '0', STR_PAD_LEFT) : null;
+ $volume = $this->parseVolume($this->volume);
$author = $this->author ? $this->generateSlug($this->author) : null;
$year = $this->year ? $this->generateSlug($this->year) : null;
- $extension = strtolower($this->extension);
-
- $titleDeterminer = $this->removeDeterminers($this->title, $this->language);
- $seriesDeterminer = $this->removeDeterminers($this->series, $this->language);
+ $extension = $this->extension ? strtolower($this->extension) : null;
if (! $title) {
return $this;
}
+ $title = $this->removeDots($title);
+ $language = $this->removeDots($language);
+ $series = $this->removeDots($series);
+ $author = $this->removeDots($author);
+ $year = $this->removeDots($year);
+
+ $titleDeterminer = $this->removeDeterminers($this->title, $this->language);
+ $seriesDeterminer = $this->removeDeterminers($this->series, $this->language);
+
if ($this->series) {
$this->slug = $this->generateSlug([
$seriesDeterminer,
@@ -502,6 +517,40 @@ public function getUniqueFilename(): string
return $this->slug;
}
+ private function parseVolume(?string $volume): ?string
+ {
+ if ($volume === null) {
+ return null;
+ }
+
+ if ($volume == '0') {
+ return '000';
+ }
+
+ $decimals = null;
+
+ if (str_contains($volume, '.')) {
+ $explode = explode('.', $volume);
+ $volume = $explode[0];
+ $decimals = $explode[1];
+ }
+
+ if (str_contains($volume, ',')) {
+ $explode = explode(',', $volume);
+ $volume = $explode[0];
+ $decimals = $explode[1];
+ }
+
+ // add `0` before volume number to get `000` format
+ $volume = str_pad($volume, 3, '0', STR_PAD_LEFT);
+
+ if ($decimals) {
+ $volume .= '.'.$decimals;
+ }
+
+ return $volume;
+ }
+
private function removeDeterminers(?string $string, ?string $language): ?string
{
if (! $string) {
@@ -516,6 +565,10 @@ private function removeDeterminers(?string $string, ?string $language): ?string
$articlesLang = $articles[$language];
}
+ $uppercaseArticles = array_map('ucfirst', $articlesLang);
+ $lowercaseArticles = array_map('lcfirst', $articlesLang);
+ $articlesLang = array_merge($uppercaseArticles, $lowercaseArticles);
+
foreach ($articlesLang as $key => $value) {
$string = preg_replace('/^'.preg_quote($value, '/').'/i', '', $string);
}
@@ -574,32 +627,41 @@ private function slugifier(?string $title, string $separator = '-', array $dicti
return null;
}
- if (! extension_loaded('intl')) {
- return $this->slugifierNative($title, $separator);
+ if (extension_loaded('intl') && $this->useIntl) {
+ return $this->slugifierIntl($title, $separator, $dictionary);
+ }
+
+ return $this->slugifierNative($title, $separator);
+ }
+
+ private function slugifierIntl(?string $text, string $divider = '-', array $dictionary = ['@' => 'at']): ?string
+ {
+ if (! $text) {
+ return null;
}
$transliterator = Transliterator::createFromRules(':: Any-Latin; :: Latin-ASCII; :: NFD; :: [:Nonspacing Mark:] Remove; :: Lower(); :: NFC;', Transliterator::FORWARD);
- $title = $transliterator->transliterate($title);
+ $text = $transliterator->transliterate($text);
// Convert all dashes/underscores into separator
- $flip = $separator === '-' ? '_' : '-';
+ $flip = $divider === '-' ? '_' : '-';
- $title = preg_replace('!['.preg_quote($flip).']+!u', $separator, $title);
+ $text = preg_replace('!['.preg_quote($flip).']+!u', $divider, $text);
// Replace dictionary words
foreach ($dictionary as $key => $value) {
- $dictionary[$key] = $separator.$value.$separator;
+ $dictionary[$key] = $divider.$value.$divider;
}
- $title = str_replace(array_keys($dictionary), array_values($dictionary), $title);
+ $text = str_replace(array_keys($dictionary), array_values($dictionary), $text);
// Remove all characters that are not the separator, letters, numbers, or whitespace
- $title = preg_replace('![^'.preg_quote($separator).'\pL\pN\s]+!u', '', strtolower($title));
+ $text = preg_replace('![^'.preg_quote($divider).'\pL\pN\s\.]+!u', '', strtolower($text));
// Replace all separator characters and whitespace by a single separator
- $title = preg_replace('!['.preg_quote($separator).'\s]+!u', $separator, $title);
+ $text = preg_replace('!['.preg_quote($divider).'\s]+!u', $divider, $text);
- return trim($title, $separator);
+ return trim($text, $divider);
}
private function slugifierNative(?string $text, string $divider = '-'): ?string
@@ -608,14 +670,17 @@ private function slugifierNative(?string $text, string $divider = '-'): ?string
return null;
}
+ // remove `'` and `"` characters
+ $text = str_replace(["'"], '', $text);
+
// replace non letter or digits by divider
- $text = preg_replace('~[^\pL\d]+~u', $divider, $text);
+ $text = preg_replace('~[^\pL\d.]+~u', $divider, $text);
// transliterate
- $text = iconv('utf-8', 'us-ascii//TRANSLIT', $text);
+ $text = $this->removeAccents($text);
// remove unwanted characters
- $text = preg_replace('~[^-\w]+~', '', $text);
+ $text = preg_replace('~[^-\w.]+~', '', $text);
// trim
$text = trim($text, $divider);
@@ -632,4 +697,25 @@ private function slugifierNative(?string $text, string $divider = '-'): ?string
return $text;
}
+
+ private function removeDots(?string $string): ?string
+ {
+ if (! $string) {
+ return null;
+ }
+
+ return str_replace('.', ' ', $string);
+ }
+
+ public function removeAccents(?string $string): ?string
+ {
+ if (! $string) {
+ return null;
+ }
+
+ $string = htmlentities($string, ENT_COMPAT, 'UTF-8');
+ $string = preg_replace('/&([a-zA-Z])(uml|acute|grave|circ|tilde|ring);/', '$1', $string);
+
+ return html_entity_decode($string);
+ }
}
diff --git a/src/Utils/EbookUtils.php b/src/Utils/EbookUtils.php
index 9651b43..c8fc572 100644
--- a/src/Utils/EbookUtils.php
+++ b/src/Utils/EbookUtils.php
@@ -4,6 +4,42 @@
class EbookUtils
{
+ /**
+ * @return string[]|null|string|int
+ */
+ public static function parseStringWithSeperator(mixed $content): mixed
+ {
+ if (! $content) {
+ return null;
+ }
+
+ if (! is_string($content)) {
+ return $content;
+ }
+
+ if (str_contains($content, ',')) {
+ $content = explode(',', $content);
+ } elseif (str_contains($content, ';')) {
+ $content = explode(';', $content);
+ } elseif (str_contains($content, '&')) {
+ $content = explode('&', $content);
+ } elseif (str_contains($content, 'and')) {
+ $content = explode('and', $content);
+ } elseif (str_contains($content, '/')) {
+ $content = explode('/', $content);
+ } elseif (str_contains($content, '//')) {
+ $content = explode('//', $content);
+ } else {
+ $content = [$content];
+ }
+
+ if (is_array($content)) {
+ $content = array_map('trim', $content);
+ }
+
+ return $content;
+ }
+
public static function parseNumber(mixed $number): int|float|null
{
if (EbookUtils::isFloat($number)) {
diff --git a/tests/EpubTest.php b/tests/EpubTest.php
index 3df9ce1..4e5f121 100644
--- a/tests/EpubTest.php
+++ b/tests/EpubTest.php
@@ -71,7 +71,7 @@
$ebook = Ebook::read(EPUB);
$meta = $ebook->getMetaTitle();
- expect($meta->getSlug())->toBe('earths-children-en-01-clan-of-the-cave-bear-jean-m-auel-1980-epub');
+ expect($meta->getSlug())->toBe('earths-children-en-001-clan-of-the-cave-bear-jean-m-auel-1980-epub');
expect($meta->getSeriesSlug())->toBe('earths-children-en-jean-m-auel-epub');
expect($meta->toArray())->toBeArray();
@@ -194,6 +194,7 @@
$ebook = Ebook::read($path);
expect($ebook->getVolume())->toBe(1.5);
+ expect($ebook->getMetaTitle()?->getSlug())->toBe('enfants-de-la-terre-fr-001.5-clan-de-lours-des-cavernes-jean-m-auel-1980-epub');
})->with([EPUB_VOLFLOAT]);
it('can parse epub with bad summary', function (string $path) {
diff --git a/tests/MetaTitleTest.php b/tests/MetaTitleTest.php
index 09b39c8..5cbeff3 100644
--- a/tests/MetaTitleTest.php
+++ b/tests/MetaTitleTest.php
@@ -14,7 +14,7 @@
$ebook->setAuthorMain(new BookAuthor('Pierre Bottero'));
$meta = MetaTitle::fromEbook($ebook);
- expect($meta->getSlug())->toBe('a-comme-association-fr-01-pale-lumiere-des-tenebres-pierre-bottero-1980-epub');
+ expect($meta->getSlug())->toBe('a-comme-association-fr-001-pale-lumiere-des-tenebres-pierre-bottero-1980-epub');
expect($meta->getSlugSimple())->toBe('la-pale-lumiere-des-tenebres');
expect($meta->getSeriesSlug())->toBe('a-comme-association-fr-pierre-bottero-epub');
expect($meta->getSeriesSlugSimple())->toBe('a-comme-association');
@@ -26,7 +26,7 @@
$ebook->setAuthorMain(new BookAuthor('J. R. R. Tolkien'));
$meta = MetaTitle::fromEbook($ebook);
- expect($meta->getSlug())->toBe('lord-of-the-rings-en-01-fellowship-of-the-ring-j-r-r-tolkien-1980-epub');
+ expect($meta->getSlug())->toBe('lord-of-the-rings-en-001-fellowship-of-the-ring-j-r-r-tolkien-1980-epub');
expect($meta->getSlugSimple())->toBe('the-fellowship-of-the-ring');
expect($meta->getSeriesSlug())->toBe('lord-of-the-rings-en-j-r-r-tolkien-epub');
expect($meta->getSeriesSlugSimple())->toBe('the-lord-of-the-rings');
@@ -53,8 +53,58 @@
extension: 'epub',
);
- expect($meta->getSlug())->toBe('lord-of-the-rings-en-01-fellowship-of-the-ring-j-r-r-tolkien-1980-epub');
+ expect($meta->getSlug())->toBe('lord-of-the-rings-en-001-fellowship-of-the-ring-j-r-r-tolkien-1980-epub');
expect($meta->getSlugSimple())->toBe('the-fellowship-of-the-ring');
expect($meta->getSeriesSlug())->toBe('lord-of-the-rings-en-j-r-r-tolkien-epub');
expect($meta->getSeriesSlugSimple())->toBe('the-lord-of-the-rings');
});
+
+it('can slug without intl', function () {
+ $meta = MetaTitle::fromData(
+ title: 'La pâle lumière des ténèbres',
+ language: 'fr',
+ series: 'A comme Association',
+ volume: 1.5,
+ author: 'Pierre Bottero',
+ extension: 'epub',
+ useIntl: false,
+ );
+
+ expect($meta->getSlug())->toBe('a-comme-association-fr-001.5-pale-lumiere-des-tenebres-pierre-bottero-epub');
+ expect($meta->getSlugSimple())->toBe('la-pale-lumiere-des-tenebres');
+ expect($meta->getSeriesSlug())->toBe('a-comme-association-fr-pierre-bottero-epub');
+ expect($meta->getSeriesSlugSimple())->toBe('a-comme-association');
+});
+
+it('can use alt volume', function () {
+ $ebook = Ebook::read(EPUB_VOLFLOAT);
+ $meta = $ebook->getMetaTitle();
+
+ expect($meta->getSlug())->toBe('enfants-de-la-terre-fr-001.5-clan-de-lours-des-cavernes-jean-m-auel-1980-epub');
+});
+
+it('can use determiner title', function () {
+ $ebook = Ebook::read(EPUB);
+ $ebook->setSeries("L'Assassin Royal");
+ $ebook->setLanguage('fr');
+ $ebook->setAuthorMain(new BookAuthor('Robin Hobb'));
+
+ $ebook->setTitle("L'apprenti assassin");
+ $ebook->setVolume(1);
+ $meta = MetaTitle::fromEbook($ebook);
+
+ expect($meta->getSlug())->toBe('assassin-royal-fr-001-apprenti-assassin-robin-hobb-1980-epub');
+
+ $ebook->setTitle('Le Prince Bâtard');
+ $ebook->setVolume(0);
+ $meta = MetaTitle::fromEbook($ebook);
+
+ expect($meta->getSlug())->toBe('assassin-royal-fr-000-prince-batard-robin-hobb-1980-epub');
+ ray($ebook->toArray());
+
+ $ebook->setTitle("L'apprenti assassin");
+ $ebook->setVolume(50);
+ $meta = MetaTitle::fromEbook($ebook);
+
+ expect($meta->getSlug())->toBe('assassin-royal-fr-050-apprenti-assassin-robin-hobb-1980-epub');
+});
diff --git a/tests/OpfTest.php b/tests/OpfTest.php
index 2b7b0cf..42afa1b 100644
--- a/tests/OpfTest.php
+++ b/tests/OpfTest.php
@@ -137,6 +137,11 @@
})->with([EPUB_OPF_EMPTY_CREATOR]);
it('can use float volume', function () {
- $opf = OpfItem::make(file_get_contents(EPUB_OPF_EPUB2_VOLUME_FLOAT), EPUB_OPF_EPUB2_VOLUME_FLOAT);
+ $opf = OpfItem::make(file_get_contents(EPUB_OPF_EPUB2_VOLUME_FLOAT));
expect($opf->getMetaItem('calibre:series_index')->getContents())->toBe('1.5');
});
+
+it('can use multiple authors', function (string $path) {
+ $opf = OpfItem::make(file_get_contents($path));
+ expect($opf->getDcCreators())->toHaveCount(2);
+})->with([EPUB_OPF_MULTIPLE_AUTHORS, EPUB_OPF_MULTIPLE_AUTHORS_MERGE]);
diff --git a/tests/Pest.php b/tests/Pest.php
index a07d1fe..b80f57f 100644
--- a/tests/Pest.php
+++ b/tests/Pest.php
@@ -34,6 +34,8 @@
define('EPUB_OPF_NOT_FORMATTED', __DIR__.'/media/opf-not-formatted.opf');
define('EPUB_OPF_EMPTY_CREATOR', __DIR__.'/media/opf-epub2-empty-creator.opf');
define('EPUB_OPF_LA5EVAGUE', __DIR__.'/media/opf-content-la-5e-vague.opf');
+define('EPUB_OPF_MULTIPLE_AUTHORS', __DIR__.'/media/opf-epub2-multiple-authors.opf');
+define('EPUB_OPF_MULTIPLE_AUTHORS_MERGE', __DIR__.'/media/opf-epub2-multiple-authors-merge.opf');
define('EPUB', __DIR__.'/media/test-epub.epub');
define('EPUB_ONE_TAG', __DIR__.'/media/epub-one-tag.epub');
diff --git a/tests/media/opf-epub2-multiple-authors-merge.opf b/tests/media/opf-epub2-multiple-authors-merge.opf
new file mode 100644
index 0000000..84d895d
--- /dev/null
+++ b/tests/media/opf-epub2-multiple-authors-merge.opf
@@ -0,0 +1,84 @@
+
+
+
+ Le clan de l'ours des cavernes
+ Jean M. Auel, Philippe
+ Rouard
+ calibre (6.12.0) [https://calibre-ebook.com]
+ <div>
+ <p>Quelque part en Europe, 35 000 ans avant notre ère. Petite fille Cro-Magnon de cinq
+ ans, Ayla est séparée de ses parents à la suite d'un violent tremblement de terre. Elle est
+ recueillie par le clan de l'ours des cavernes, une tribu Neandertal qui l'adopte, non sans
+ réticence, ayant reconnu en elle la représentante d'une autre espèce, plus évoluée.
+ <br><br>Iza, la guérisseuse, Brun, le chef et Creb, le magicien lui enseignent les
+ règles de la vie communautaire, leurs rites, leurs peurs, leurs audaces. Mais Ayla, la
+ fillette blonde aux yeux bleus les surprend par sa puissance de raisonnement qui lui permet de
+ s'adapter, de réagir rapidement et de ne pas être totalement dépendante de son environnement.
+ Une différence qui ne tarde pas à faire d'elle une menace pour tout le clan, et à attiser la
+ convoitise de Brud, le fils du chef...</p></div>
+ Presses de la cité
+ a2cf2f25-4de2-4f77-82cc-0198352b0851
+ 1980-01-13T21:00:00+00:00
+ Roman Historique
+ Les Enfants de la Terre
+ Fiction
+ fr
+ a2cf2f25-4de2-4f77-82cc-0198352b0851
+ 63CTHAAACAAJ
+ 9782266122122
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/media/opf-epub2-multiple-authors.opf b/tests/media/opf-epub2-multiple-authors.opf
new file mode 100644
index 0000000..7862e78
--- /dev/null
+++ b/tests/media/opf-epub2-multiple-authors.opf
@@ -0,0 +1,84 @@
+
+
+
+ Le clan de l'ours des cavernes
+ Jean M. Auel
+ Philippe Rouard
+ calibre (6.12.0) [https://calibre-ebook.com]
+ <div>
+ <p>Quelque part en Europe, 35 000 ans avant notre ère. Petite fille Cro-Magnon de cinq
+ ans, Ayla est séparée de ses parents à la suite d'un violent tremblement de terre. Elle est
+ recueillie par le clan de l'ours des cavernes, une tribu Neandertal qui l'adopte, non sans
+ réticence, ayant reconnu en elle la représentante d'une autre espèce, plus évoluée.
+ <br><br>Iza, la guérisseuse, Brun, le chef et Creb, le magicien lui enseignent les
+ règles de la vie communautaire, leurs rites, leurs peurs, leurs audaces. Mais Ayla, la
+ fillette blonde aux yeux bleus les surprend par sa puissance de raisonnement qui lui permet de
+ s'adapter, de réagir rapidement et de ne pas être totalement dépendante de son environnement.
+ Une différence qui ne tarde pas à faire d'elle une menace pour tout le clan, et à attiser la
+ convoitise de Brud, le fils du chef...</p></div>
+ Presses de la cité
+ a2cf2f25-4de2-4f77-82cc-0198352b0851
+ 1980-01-13T21:00:00+00:00
+ Roman Historique
+ Les Enfants de la Terre
+ Fiction
+ fr
+ a2cf2f25-4de2-4f77-82cc-0198352b0851
+ 63CTHAAACAAJ
+ 9782266122122
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file