Parser::validateOpf()   B
last analyzed

Complexity

Conditions 7
Paths 8

Size

Total Lines 31
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 7.0671

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 17
c 1
b 0
f 0
dl 0
loc 31
ccs 16
cts 18
cp 0.8889
rs 8.8333
cc 7
nc 8
nop 1
crap 7.0671
1
<?php
2
3
declare(strict_types=1);
4
5
namespace PhpEpub;
6
7
class Parser
8
{
9 33
    public function __construct(private readonly XmlParser $xmlParser = new XmlParser())
10
    {
11 33
    }
12
13
    /**
14
     * Parse the EPUB file structure.
15
     *
16
     * @param string $directory The directory containing the extracted EPUB contents.
17
     */
18 26
    public function parse(string $directory): string
19
    {
20
        // Validate mimetype
21 26
        $this->validateMimetype($directory);
22
23 24
        $containerPath = $directory . DIRECTORY_SEPARATOR . 'META-INF' . DIRECTORY_SEPARATOR . 'container.xml';
24
25 24
        $opfPath = $this->extractOpfPath($containerPath);
26
27 23
        $this->validateOpf($directory . DIRECTORY_SEPARATOR . $opfPath);
28
29 22
        return $opfPath;
30
    }
31
32
    /**
33
     * Validates the mimetype file.
34
     */
35 26
    private function validateMimetype(string $directory): void
36
    {
37 26
        $mimetypePath = $directory . DIRECTORY_SEPARATOR . 'mimetype';
38
39 26
        if (! file_exists($mimetypePath)) {
40 1
            throw new Exception('Missing mimetype file: ' . $mimetypePath);
41
        }
42
43 25
        $mimetype = file_get_contents($mimetypePath);
44
45 25
        if ($mimetype === false || trim($mimetype) !== 'application/epub+zip') {
46 1
            throw new Exception('Invalid mimetype content: ' . $mimetypePath);
47
        }
48
    }
49
50
    /**
51
     * Extract the OPF path from container
52
     */
53 24
    private function extractOpfPath(string $containerPath): string
54
    {
55 24
        $xml = $this->xmlParser->parse($containerPath);
56
57 23
        $namespaces = $xml->getNamespaces(true);
58
59 23
        $containerNamespace = $namespaces[''] ?? null;
60
61 23
        if ($containerNamespace === null) {
62
            throw new Exception('No container namespace found in container.xml');
63
        }
64
65 23
        $xml->registerXPathNamespace('ns', $containerNamespace);
66
67 23
        $rootfiles = $xml->xpath('//ns:rootfile');
68
69 23
        if ($rootfiles === false || $rootfiles === null) {
70
            throw new Exception('No rootfile found in container.xml');
71
        }
72
73 23
        $rootfile = $rootfiles[0]; // Get the first rootfile node
74
75 23
        $opfPath = (string) $rootfile['full-path'];
76
77 23
        if ($opfPath === '') {
78
            throw new Exception('Missing full-path attribute in rootfile element');
79
        }
80
81 23
        return $opfPath;
82
    }
83
84
    /**
85
     * Validates the OPF file and checks for the presence of the NCX file.
86
     */
87 23
    private function validateOpf(string $opfPath): void
88
    {
89 23
        $xml = $this->xmlParser->parse($opfPath);
90
91 22
        $namespaces = $xml->getNamespaces(true);
92
93 22
        $containerNamespace = $namespaces[''] ?? null;
94
95 22
        if ($containerNamespace === null) {
96
            throw new Exception('No container namespace found in container.xml');
97
        }
98
99 22
        $xml->registerXPathNamespace('opf', $containerNamespace);
100
101 22
        $items = $xml->xpath('//opf:manifest/opf:item');
102
103 22
        if ($items === false || $items === null) {
104
            throw new Exception('Missing manifest in OPF file');
105
        }
106
107 22
        $ncxItem = null;
108 22
        foreach ($items as $item) {
109 22
            if ((string) $item['media-type'] === 'application/x-dtbncx+xml') {
110 15
                $ncxItem = (string) $item['href'];
111 15
                break;
112
            }
113
        }
114
115 22
        if ($ncxItem !== null) {
116 15
            $ncxPath = dirname($opfPath) . DIRECTORY_SEPARATOR . $ncxItem;
117 15
            $this->validateNcx($ncxPath);
118
        }
119
    }
120
121
    /**
122
     * Validates the NCX file.
123
     */
124 15
    private function validateNcx(string $ncxPath): void
125
    {
126 15
        $xml = $this->xmlParser->parse($ncxPath);
127
128 15
        $namespaces = $xml->getNamespaces(true);
129
130 15
        $navMap = $xml->children($namespaces[''])->navMap;
131
132 15
        if (! $navMap) {
133
            throw new Exception('Missing navMap in NCX file');
134
        }
135
    }
136
}
137