• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

sweetrdf / quickRdfIo / #112

24 Apr 2025 02:36PM UTC coverage: 90.805%. Remained the same
#112

push

php-coveralls

zozlak
Implement rdf-interface 3.1.0

* Implement $baseUri parameter handling in parse() and parseStream() of
  all parsers.
* Util::parse(): pass document base URI to the parser.

(closes #11)

33 of 34 new or added lines in 7 files covered. (97.06%)

21 existing lines in 4 files now uncovered.

869 of 957 relevant lines covered (90.8%)

6.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.97
/src/quickRdfIo/Util.php
1
<?php
2

3
/*
4
 * The MIT License
5
 *
6
 * Copyright 2022 zozlak.
7
 *
8
 * Permission is hereby granted, free of charge, to any person obtaining a copy
9
 * of this software and associated documentation files (the "Software"), to deal
10
 * in the Software without restriction, including without limitation the rights
11
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
 * copies of the Software, and to permit persons to whom the Software is
13
 * furnished to do so, subject to the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be included in
16
 * all copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
 * THE SOFTWARE.
25
 */
26

27
namespace quickRdfIo;
28

29
use Traversable;
30
use rdfInterface\DataFactoryInterface as iDataFactory;
31
use rdfInterface\ParserInterface as iParser;
32
use rdfInterface\SerializerInterface as iSerializer;
33
use rdfInterface\QuadInterface as iQuad;
34
use rdfInterface\QuadIteratorInterface as iQuadIterator;
35
use rdfInterface\RdfNamespaceInterface as iRdfNamespace;
36
use Psr\Http\Message\ResponseInterface;
37
use Psr\Http\Message\StreamInterface;
38

39
/**
40
 * Provides static factory methods for plug&play parsers/serializers creation.
41
 *
42
 * @author zozlak
43
 */
44
class Util {
45

46
    /**
47
     * Returns a serializer object for a given format or file name (in the latter
48
     * case the match is based on the file name extenstion).
49
     * 
50
     * Use a special value of `jsonld-stream` to get the `JsonLdStreamSerializer`
51
     * serializer.
52
     * @param string $formatOrFilename
53
     * @return iSerializer
54
     */
55
    static public function getSerializer(string $formatOrFilename): iSerializer {
56
        $format = preg_replace('/;[^;]*/', '', $formatOrFilename) ?? ''; // skip accept header additional data
2✔
57
        $format = preg_replace('/^.+[.]/', '', $format) ?? '';
2✔
58
        $format = strtolower($format);
2✔
59
        return match ($format) {
2✔
60
            'ttl',
2✔
61
            'turtle',
2✔
62
            'n3',
2✔
63
            'text/turtle',
2✔
64
            'application/turtle',
2✔
65
            'text/n3',
2✔
66
            'text/rdf+n3',
2✔
67
            'application/rdf+n3' => new TrigSerializer(TrigSerializer::MODE_TURTLE),
1✔
68
            'trig',
2✔
69
            'application/trig' => new TrigSerializer(TrigSerializer::MODE_TRIG),
1✔
70
            'nt',
2✔
71
            'ntriples',
2✔
72
            'ntriplesstar',
2✔
73
            'n-triples',
2✔
74
            'n-triples-star',
2✔
75
            'application/n-triples',
2✔
76
            'text/plain' => new NQuadsSerializer(),
1✔
77
            'nq',
2✔
78
            'nquads',
2✔
79
            'nquadstar',
2✔
80
            'n-quads',
2✔
81
            'n-quads-star',
2✔
82
            'application/n-quads' => new NQuadsSerializer(),
1✔
83
            'xml',
2✔
84
            'rdf',
2✔
85
            'rdfxml', // EasyRdf is using it in Format::guessFormat
2✔
86
            'application/rdf+xml',
2✔
87
            'text/rdf',
2✔
88
            'application/xml',
2✔
89
            'text/xml' => new RdfXmlSerializer(),
2✔
90
            'json',
2✔
91
            'jsonld',
2✔
92
            'application/ld+json',
2✔
93
            'application/json' => new JsonLdSerializer(),
1✔
94
            'jsonld-stream' => new JsonLdStreamSerializer(),
1✔
95
            default => throw new RdfIoException("Unknown format $format ($formatOrFilename)")
2✔
96
        };
2✔
97
    }
98

99
    /**
100
     * Returns a parser object for a given format or file name (in the latter case
101
     * the match is based on the file name extension).
102
     * @param string $formatOrFilename
103
     * @param iDataFactory $dataFactory
104
     * @param string|null $baseUri
105
     * @return iParser
106
     */
107
    static public function getParser(string $formatOrFilename,
108
                                     iDataFactory $dataFactory,
109
                                     ?string $baseUri = null): iParser {
110
        $format = preg_replace('/;[^;]*/', '', $formatOrFilename) ?? ''; // skip content-type header additional data
4✔
111
        $format = preg_replace('/^.+[.]/', '', $format) ?? '';
4✔
112
        $format = strtolower($format);
4✔
113
        return match ($format) {
4✔
114
            'ttl',
4✔
115
            'turtle',
4✔
116
            'n3',
4✔
117
            'trig',
4✔
118
            'text/turtle',
4✔
119
            'application/turtle',
4✔
120
            'text/n3',
4✔
121
            'text/rdf+n3',
4✔
122
            'application/rdf+n3',
4✔
123
            'application/trig' => new TriGParser($dataFactory, ['documentIRI' => $baseUri]),
1✔
124
            'nt',
4✔
125
            'ntriples',
4✔
126
            'n-triples' => new NQuadsParser($dataFactory, false, NQuadsParser::MODE_TRIPLES),
3✔
127
            'ntriplesstar',
4✔
128
            'n-triples-star',
4✔
129
            'application/n-triples',
4✔
130
            'text/plain' => new NQuadsParser($dataFactory, false, NQuadsParser::MODE_TRIPLES_STAR),
1✔
131
            'nq',
4✔
132
            'nquads',
4✔
133
            'n-quads' => new NQuadsParser($dataFactory, false, NQuadsParser::MODE_QUADS),
1✔
134
            'nquadsstar',
4✔
135
            'n-quads-star',
4✔
136
            'application/n-quads' => new NQuadsParser($dataFactory, false, NQuadsParser::MODE_QUADS_STAR),
×
137
            'xml',
4✔
138
            'rdf',
4✔
139
            'rdfxml', // EasyRdf is using it in Format::guessFormat
4✔
140
            'application/rdf+xml',
4✔
141
            'text/rdf',
4✔
142
            'application/xml',
4✔
143
            'text/xml' => new RdfXmlParser($dataFactory, $baseUri ?? ''),
2✔
144
            'json',
4✔
145
            'jsonld',
4✔
146
            'application/ld+json',
4✔
147
            'application/json' => new JsonLdParser($dataFactory, $baseUri),
×
148
            default => throw new RdfIoException("Unknown format $format ($formatOrFilename)")
4✔
149
        };
4✔
150
    }
151

152
    /**
153
     * 
154
     * @param ResponseInterface | StreamInterface | resource | string $input
155
     *   Input to be parsed as RDF. In case of a string value `fopen($input, 'r')`
156
     *   is called first and when it fails, the value is treated as RDF. Format 
157
     *   is detected automatically.
158
     * @param iDataFactory $dataFactory
159
     * @param string $format Allows to explicitly specify format. Required if
160
     *   the $input is a non-seekable stream.
161
     * @param string $baseUri Allows to explicitly specify the baseUri if it's
162
     *   needed and can't be guesed from the $input.
163
     * @return iQuadIterator
164
     * @throws RdfIoException
165
     */
166
    static public function parse(mixed $input, iDataFactory $dataFactory,
167
                                 ?string $format = null, ?string $baseUri = null): iQuadIterator {
168
        $parser = null;
3✔
169

170
        if ($input instanceof ResponseInterface) {
3✔
171
            // ResponseInterface
172
            // - try to use HTTP Location header as base URI
173
            // - try to use HTTP Content-Type header as a format
174
            // - convert $input to StreamInterface
175
            $baseUri     ??= $input->getHeader('Location')[0] ?? null;
1✔
176
            $contentType = $input->getHeader('Content-Type')[0] ?? '';
1✔
177
            if (empty($format) && !empty($contentType)) {
1✔
178
                try {
179
                    $parser = self::getParser($contentType, $dataFactory, $baseUri);
1✔
180
                } catch (RdfIoException $ex) {
×
181
                    
182
                }
183
            }
184
            $input = $input->getBody();
1✔
185
        }
186
        if (is_string($input)) {
3✔
187
            // string
188
            // - if it can't be fopen()-ed, treat it as a string containing RDF
189
            //   and turn it into temp stream
190
            // - if it can be fopen()-ed and format is empty, take $input as $format
191
            $stream = @fopen($input, 'r');
3✔
192
            if ($stream === false) {
3✔
193
                $stream = fopen('php://memory', 'r+') ?: throw new RdfIoException('Failed to convert input to a stream');
1✔
194
                fwrite($stream, $input);
1✔
195
                rewind($stream);
1✔
196
            } else {
197
                if (empty($format)) {
3✔
198
                    $format  = $input;
3✔
199
                }
200
                $baseUri ??= 'file://' . $input;
3✔
201
            }
202
            $input = $stream;
3✔
203
        }
204
        if (is_resource($input)) {
3✔
205
            // turn resource input into StreamInterface for uniform read API
206
            $input = new ResourceWrapper($input);
3✔
207
            if ($baseUri === null && $input->getMetadata('stream_type') === 'http') {
3✔
UNCOV
208
                $baseUri = $input->getMetadata('uri');
×
209
            }
210
        }
211
        if ($parser === null && empty($format)) {
3✔
212
            // format autodetection
213
            $format = match ($input->read(1)) {
1✔
UNCOV
214
                '[' => 'application/ld+json',
×
215
                '<' => 'application/rdf+xml',
1✔
216
                default => 'application/trig'
1✔
217
            };
1✔
218
            $input->rewind();
1✔
219
        }
220
        if ($parser === null) {
3✔
221
            $parser = self::getParser($format ?? '', $dataFactory, $baseUri);
3✔
222
        }
223
        return $parser->parseStream($input, $baseUri ?? '');
3✔
224
    }
225

226
    /**
227
     * 
228
     * @param Traversable<iQuad>|array<iQuad> $data
229
     * @param string $format A mime type, file extension HTTP Accept header value
230
     *   or file name indicating the output format.
231
     * @param resource | StreamInterface | string | null $output Output to write 
232
     *   to. String value is taken as a path passed to `fopen($output, 'wb')`.
233
     *   If null, output as string is provided as function return value.
234
     * @param iRdfNamespace | null $nmsp An optional RdfNamespace object driving 
235
     *   namespace aliases creation. It can be used for compacting for the JsonLD
236
     *   serialization, just in such a case remember to register aliases for
237
     *   full URIs instead of for namespaces.
238
     * @return string | null
239
     * @throws RdfIoException
240
     */
241
    static public function serialize(Traversable | array $data, string $format,
242
                                     mixed $output = null,
243
                                     ?iRdfNamespace $nmsp = null): ?string {
244
        $serializer = self::getSerializer($format);
1✔
245
        $close      = false;
1✔
246
        if (is_string($output)) {
1✔
247
            $output = fopen($output, 'wb') ?: throw new RdfIoException("Can't open $output for writing");
1✔
248
            $close  = true;
1✔
249
        }
250
        if (is_resource($output) || $output instanceof StreamInterface) {
1✔
251
            $serializer->serializeStream($output, $data, $nmsp);
1✔
252
            if ($close && is_resource($output)) {
1✔
253
                fclose($output) ?: throw new RdfIoException("Failed to close the output file");
1✔
254
            }
255
            return null;
1✔
256
        } else {
257
            return $serializer->serialize($data, $nmsp);
1✔
258
        }
259
    }
260
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc