Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
 
3
declare(strict_types=1);
4
 
5
namespace OpenSpout\Reader\XLSX\Manager;
6
 
7
use OpenSpout\Common\Exception\IOException;
8
use OpenSpout\Reader\Wrapper\XMLReader;
9
 
10
/**
11
 * @internal
12
 */
13
final class WorkbookRelationshipsManager
14
{
15
    public const BASE_PATH = 'xl/';
16
 
17
    /**
18
     * Path of workbook relationships XML file inside the XLSX file.
19
     */
20
    public const WORKBOOK_RELS_XML_FILE_PATH = 'xl/_rels/workbook.xml.rels';
21
 
22
    /**
23
     * Relationships types - For Transitional and Strict OOXML.
24
     */
25
    public const RELATIONSHIP_TYPE_SHARED_STRINGS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings';
26
    public const RELATIONSHIP_TYPE_STYLES = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles';
27
    public const RELATIONSHIP_TYPE_SHARED_STRINGS_STRICT = 'http://purl.oclc.org/ooxml/officeDocument/relationships/sharedStrings';
28
    public const RELATIONSHIP_TYPE_STYLES_STRICT = 'http://purl.oclc.org/ooxml/officeDocument/relationships/styles';
29
 
30
    /**
31
     * Nodes and attributes used to find relevant information in the workbook relationships XML file.
32
     */
33
    public const XML_NODE_RELATIONSHIP = 'Relationship';
34
    public const XML_ATTRIBUTE_TYPE = 'Type';
35
    public const XML_ATTRIBUTE_TARGET = 'Target';
36
 
37
    /** @var string Path of the XLSX file being read */
38
    private readonly string $filePath;
39
 
40
    /** @var array<string, string> Cache of the already read workbook relationships: [TYPE] => [FILE_NAME] */
41
    private array $cachedWorkbookRelationships;
42
 
43
    /**
44
     * @param string $filePath Path of the XLSX file being read
45
     */
46
    public function __construct(string $filePath)
47
    {
48
        $this->filePath = $filePath;
49
    }
50
 
51
    /**
52
     * @return string The path of the shared string XML file
53
     */
54
    public function getSharedStringsXMLFilePath(): string
55
    {
56
        $workbookRelationships = $this->getWorkbookRelationships();
57
        $sharedStringsXMLFilePath = $workbookRelationships[self::RELATIONSHIP_TYPE_SHARED_STRINGS]
58
            ?? $workbookRelationships[self::RELATIONSHIP_TYPE_SHARED_STRINGS_STRICT];
59
 
60
        // the file path can be relative (e.g. "styles.xml") or absolute (e.g. "/xl/styles.xml")
61
        $doesContainBasePath = str_contains($sharedStringsXMLFilePath, self::BASE_PATH);
62
        if (!$doesContainBasePath) {
63
            // make sure we return an absolute file path
64
            $sharedStringsXMLFilePath = self::BASE_PATH.$sharedStringsXMLFilePath;
65
        }
66
 
67
        return $sharedStringsXMLFilePath;
68
    }
69
 
70
    /**
71
     * @return bool Whether the XLSX file contains a shared string XML file
72
     */
73
    public function hasSharedStringsXMLFile(): bool
74
    {
75
        $workbookRelationships = $this->getWorkbookRelationships();
76
 
77
        return isset($workbookRelationships[self::RELATIONSHIP_TYPE_SHARED_STRINGS])
78
            || isset($workbookRelationships[self::RELATIONSHIP_TYPE_SHARED_STRINGS_STRICT]);
79
    }
80
 
81
    /**
82
     * @return bool Whether the XLSX file contains a styles XML file
83
     */
84
    public function hasStylesXMLFile(): bool
85
    {
86
        $workbookRelationships = $this->getWorkbookRelationships();
87
 
88
        return isset($workbookRelationships[self::RELATIONSHIP_TYPE_STYLES])
89
            || isset($workbookRelationships[self::RELATIONSHIP_TYPE_STYLES_STRICT]);
90
    }
91
 
92
    /**
93
     * @return string The path of the styles XML file
94
     */
95
    public function getStylesXMLFilePath(): string
96
    {
97
        $workbookRelationships = $this->getWorkbookRelationships();
98
        $stylesXMLFilePath = $workbookRelationships[self::RELATIONSHIP_TYPE_STYLES]
99
            ?? $workbookRelationships[self::RELATIONSHIP_TYPE_STYLES_STRICT];
100
 
101
        // the file path can be relative (e.g. "styles.xml") or absolute (e.g. "/xl/styles.xml")
102
        $doesContainBasePath = str_contains($stylesXMLFilePath, self::BASE_PATH);
103
        if (!$doesContainBasePath) {
104
            // make sure we return a full path
105
            $stylesXMLFilePath = self::BASE_PATH.$stylesXMLFilePath;
106
        }
107
 
108
        return $stylesXMLFilePath;
109
    }
110
 
111
    /**
112
     * Reads the workbook.xml.rels and extracts the filename associated to the different types.
113
     * It caches the result so that the file is read only once.
114
     *
115
     * @return array<string, string>
116
     *
117
     * @throws IOException If workbook.xml.rels can't be read
118
     */
119
    private function getWorkbookRelationships(): array
120
    {
121
        if (!isset($this->cachedWorkbookRelationships)) {
122
            $xmlReader = new XMLReader();
123
 
124
            if (false === $xmlReader->openFileInZip($this->filePath, self::WORKBOOK_RELS_XML_FILE_PATH)) {
125
                throw new IOException('Could not open "'.self::WORKBOOK_RELS_XML_FILE_PATH.'".');
126
            }
127
 
128
            $this->cachedWorkbookRelationships = [];
129
 
130
            while ($xmlReader->readUntilNodeFound(self::XML_NODE_RELATIONSHIP)) {
131
                $this->processWorkbookRelationship($xmlReader);
132
            }
133
        }
134
 
135
        return $this->cachedWorkbookRelationships;
136
    }
137
 
138
    /**
139
     * Extracts and store the data of the current workbook relationship.
140
     */
141
    private function processWorkbookRelationship(XMLReader $xmlReader): void
142
    {
143
        $type = $xmlReader->getAttribute(self::XML_ATTRIBUTE_TYPE);
144
        $target = $xmlReader->getAttribute(self::XML_ATTRIBUTE_TARGET);
145
        \assert(null !== $target);
146
 
147
        // @NOTE: if a type is defined more than once, we overwrite the previous value
148
        // To be changed if we want to get the file paths of sheet XML files for instance.
149
        $this->cachedWorkbookRelationships[$type] = $target;
150
    }
151
}