Skip to content

Commit 460329b

Browse files
authored
merge main (#4959)
2 parents 641d65c + f873bc2 commit 460329b

12 files changed

Lines changed: 187 additions & 19 deletions

File tree

core/model/src/main/java/org/eclipse/rdf4j/model/impl/SimpleIRI.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,28 @@ protected SimpleIRI(String iriString) {
6767
setIRIString(iriString);
6868
}
6969

70+
protected SimpleIRI(String namespace, String localname) {
71+
setIRIString(namespace, localname);
72+
}
73+
7074
/*---------*
7175
* Methods *
7276
*---------*/
7377

78+
protected void setIRIString(String namespace, String localname) {
79+
Objects.requireNonNull(namespace, "namespace must not be null");
80+
Objects.requireNonNull(localname, "localname must not be null");
81+
82+
var joinedIriString = namespace + localname;
83+
84+
if (joinedIriString.indexOf(':') < 0) {
85+
throw new IllegalArgumentException("Not a valid (absolute) IRI: " + joinedIriString);
86+
}
87+
88+
this.iriString = joinedIriString;
89+
this.localNameIdx = namespace.length();
90+
}
91+
7492
protected void setIRIString(String iriString) {
7593
Objects.requireNonNull(iriString, "iriString must not be null");
7694

core/model/src/main/java/org/eclipse/rdf4j/model/impl/SimpleValueFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public IRI createIRI(String iri) {
8585

8686
@Override
8787
public IRI createIRI(String namespace, String localName) {
88-
return createIRI(namespace + localName);
88+
return new SimpleIRI(namespace, localName);
8989
}
9090

9191
@Override

core/rio/rdfxml/src/main/java/org/eclipse/rdf4j/rio/rdfxml/RDFXMLWriter.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ public Writer getWriter() {
106106
return writer;
107107
}
108108

109+
@Override
109110
public Collection<RioSetting<?>> getSupportedSettings() {
110111
final Collection<RioSetting<?>> settings = new HashSet<>(super.getSupportedSettings());
111112
settings.add(BasicWriterSettings.BASE_DIRECTIVE);

core/rio/rdfxml/src/main/java/org/eclipse/rdf4j/rio/rdfxml/util/RDFXMLPrettyWriter.java

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818
import java.util.Collection;
1919
import java.util.HashSet;
2020
import java.util.Stack;
21+
import java.util.regex.Pattern;
2122

2223
import org.eclipse.rdf4j.common.net.ParsedIRI;
24+
import org.eclipse.rdf4j.common.xml.XMLUtil;
2325
import org.eclipse.rdf4j.model.BNode;
2426
import org.eclipse.rdf4j.model.IRI;
2527
import org.eclipse.rdf4j.model.Literal;
@@ -260,8 +262,9 @@ private void popStacks(Resource newSubject) throws IOException, RDFHandlerExcept
260262
writeIndents(i * 2 - 1);
261263

262264
IRI predicate = predicateStack.get(i - 1);
265+
var predicateQName = new QName(predicate);
263266

264-
writeStartTag(predicate.getNamespace(), predicate.getLocalName());
267+
writeStartTag(predicateQName.getNamespace(), predicateQName.getLocalName());
265268
writeNewLine();
266269
}
267270

@@ -281,6 +284,7 @@ private void popStacks(Resource newSubject) throws IOException, RDFHandlerExcept
281284
writeNewLine();
282285
} else {
283286
IRI topPredicate = predicateStack.pop();
287+
var topPredicateQName = new QName(topPredicate);
284288

285289
if (!topNode.hasType()) {
286290
// we can use an abbreviated predicate
@@ -291,7 +295,7 @@ private void popStacks(Resource newSubject) throws IOException, RDFHandlerExcept
291295
// written out as well
292296

293297
writeIndents(nodeStack.size() * 2 - 1);
294-
writeStartTag(topPredicate.getNamespace(), topPredicate.getLocalName());
298+
writeStartTag(topPredicateQName.getNamespace(), topPredicateQName.getLocalName());
295299
writeNewLine();
296300

297301
// write out an empty subject
@@ -300,7 +304,7 @@ private void popStacks(Resource newSubject) throws IOException, RDFHandlerExcept
300304
writeNewLine();
301305

302306
writeIndents(nodeStack.size() * 2 - 1);
303-
writeEndTag(topPredicate.getNamespace(), topPredicate.getLocalName());
307+
writeEndTag(topPredicateQName.getNamespace(), topPredicateQName.getLocalName());
304308
writeNewLine();
305309
}
306310
}
@@ -322,10 +326,11 @@ private void popStacks(Resource newSubject) throws IOException, RDFHandlerExcept
322326

323327
if (!predicateStack.isEmpty()) {
324328
IRI nextPredicate = predicateStack.pop();
329+
var nextPredicateQName = new QName(nextPredicate);
325330

326331
writeIndents(predicateStack.size() + nodeStack.size());
327332

328-
writeEndTag(nextPredicate.getNamespace(), nextPredicate.getLocalName());
333+
writeEndTag(nextPredicateQName.getNamespace(), nextPredicateQName.getLocalName());
329334

330335
writeNewLine();
331336
}
@@ -392,7 +397,8 @@ private void writeNodeStartOfStartTag(Node node) throws IOException, RDFHandlerE
392397

393398
if (node.hasType()) {
394399
// We can use abbreviated syntax
395-
writeStartOfStartTag(node.getType().getNamespace(), node.getType().getLocalName());
400+
var nodeTypeQName = new QName(node.getType());
401+
writeStartOfStartTag(nodeTypeQName.getNamespace(), nodeTypeQName.getLocalName());
396402
} else {
397403
// We cannot use abbreviated syntax
398404
writeStartOfStartTag(RDF.NAMESPACE, "Description");
@@ -423,7 +429,8 @@ private void writeNodeStartTag(Node node) throws IOException, RDFHandlerExceptio
423429
*/
424430
private void writeNodeEndTag(Node node) throws IOException {
425431
if (node.getType() != null) {
426-
writeEndTag(node.getType().getNamespace(), node.getType().getLocalName());
432+
var nodeTypeQName = new QName(node.getType());
433+
writeEndTag(nodeTypeQName.getNamespace(), nodeTypeQName.getLocalName());
427434
} else {
428435
writeEndTag(RDF.NAMESPACE, "Description");
429436
}
@@ -442,7 +449,8 @@ private void writeNodeEmptyTag(Node node) throws IOException, RDFHandlerExceptio
442449
* Write out an empty property element.
443450
*/
444451
private void writeAbbreviatedPredicate(IRI pred, Value obj) throws IOException, RDFHandlerException {
445-
writeStartOfStartTag(pred.getNamespace(), pred.getLocalName());
452+
var predQName = new QName(pred);
453+
writeStartOfStartTag(predQName.getNamespace(), predQName.getLocalName());
446454

447455
if (obj instanceof Resource) {
448456
Resource objRes = (Resource) obj;
@@ -484,7 +492,7 @@ private void writeAbbreviatedPredicate(IRI pred, Value obj) throws IOException,
484492
writeCharacterData(objLit.getLabel());
485493
}
486494

487-
writeEndTag(pred.getNamespace(), pred.getLocalName());
495+
writeEndTag(predQName.getNamespace(), predQName.getLocalName());
488496
}
489497

490498
writeNewLine();
@@ -565,4 +573,31 @@ public boolean isWritten() {
565573
return isWritten;
566574
}
567575
}
576+
577+
private static class QName {
578+
private static final Pattern VALID_XML_ELEMENT_NAME = Pattern.compile("[a-zA-Z_][a-zA-Z0-9_\\-\\.]*");
579+
580+
private final String namespace;
581+
private final String localName;
582+
583+
public QName(IRI resource) {
584+
if (!VALID_XML_ELEMENT_NAME.matcher(resource.getLocalName()).matches()) {
585+
var iriString = resource.getNamespace() + resource.getLocalName();
586+
var sep = XMLUtil.findURISplitIndex(iriString);
587+
namespace = iriString.substring(0, sep);
588+
localName = iriString.substring(sep);
589+
} else {
590+
localName = resource.getLocalName();
591+
namespace = resource.getNamespace();
592+
}
593+
}
594+
595+
public String getLocalName() {
596+
return localName;
597+
}
598+
599+
public String getNamespace() {
600+
return namespace;
601+
}
602+
}
568603
}

core/rio/turtle/src/main/java/org/eclipse/rdf4j/rio/turtle/TurtleUtil.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,20 @@ public static String encodeURIString(String s) {
458458
return s;
459459
}
460460

461+
public static boolean isValidPrefixedName(String s) {
462+
if (s == null || s.isEmpty()) {
463+
return false;
464+
}
465+
466+
if (!isPN_CHARS_BASE(s.codePointAt(0))) {
467+
return false;
468+
}
469+
470+
return s.codePoints() //
471+
.skip(1) // Skip the first code point
472+
.allMatch(TurtleUtil::isPN_CHARS);
473+
}
474+
461475
/**
462476
* Decodes an encoded Turtle string. Any \-escape sequences are substituted with their decoded value.
463477
*

core/rio/turtle/src/main/java/org/eclipse/rdf4j/rio/turtle/TurtleWriter.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -537,11 +537,20 @@ protected void writeResource(Resource res, boolean canShorten) throws IOExceptio
537537
}
538538

539539
protected void writeURI(IRI uri) throws IOException {
540-
String uriString = uri.toString();
541-
542-
// Try to find a prefix for the URI's namespace
543540
String prefix = null;
541+
if (TurtleUtil.isValidPrefixedName(uri.getLocalName())) {
542+
prefix = namespaceTable.get(uri.getNamespace());
543+
if (prefix != null) {
544+
// Namespace is mapped to a prefix; write abbreviated URI
545+
writer.write(prefix);
546+
writer.write(":");
547+
writer.write(uri.getLocalName());
548+
return;
549+
}
550+
}
544551

552+
// Try to find a prefix for the URI's namespace
553+
String uriString = uri.toString();
545554
int splitIdx = TurtleUtil.findURISplitIndex(uriString);
546555
if (splitIdx > 0) {
547556
String namespace = uriString.substring(0, splitIdx);

core/rio/turtle/src/test/java/org/eclipse/rdf4j/rio/turtle/TurtleWriterTest.java

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,17 @@
1010
*******************************************************************************/
1111
package org.eclipse.rdf4j.rio.turtle;
1212

13+
import static org.assertj.core.api.Assertions.assertThat;
1314
import static org.junit.jupiter.api.Assertions.assertTrue;
1415

1516
import java.io.StringReader;
1617
import java.io.StringWriter;
1718

1819
import org.eclipse.rdf4j.model.IRI;
1920
import org.eclipse.rdf4j.model.Model;
21+
import org.eclipse.rdf4j.model.impl.DynamicModelFactory;
2022
import org.eclipse.rdf4j.model.util.Models;
23+
import org.eclipse.rdf4j.model.vocabulary.RDFS;
2124
import org.eclipse.rdf4j.rio.RDFFormat;
2225
import org.eclipse.rdf4j.rio.Rio;
2326
import org.eclipse.rdf4j.rio.WriterConfig;
@@ -95,7 +98,6 @@ public void testBlankNodeInlining1() throws Exception {
9598
Model actual = Rio.parse(new StringReader(stringWriter.toString()), "", RDFFormat.TURTLE);
9699

97100
assertTrue(Models.isomorphic(expected, actual));
98-
99101
}
100102

101103
@Test
@@ -192,6 +194,56 @@ public void testNoBuffering() throws Exception {
192194
assertTrue(Models.isomorphic(expected, actual));
193195
}
194196

197+
@Test
198+
public void testUnusualIrisAndPrefixesParseWriteCompare() throws Exception {
199+
String data = "@prefix server-news: <news:comp.infosystems.www.servers.> .\n" +
200+
"@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n" +
201+
"server-news:unix rdfs:label \"News on Unix\" .\n" +
202+
"server-news:windows rdfs:label \"News on Windows\" .\n";
203+
204+
var expected = Rio.parse(new StringReader(data), "", RDFFormat.TURTLE);
205+
206+
var stringWriter = new StringWriter();
207+
var config = new WriterConfig();
208+
config.set(BasicWriterSettings.INLINE_BLANK_NODES, false);
209+
config.set(BasicWriterSettings.PRETTY_PRINT, false);
210+
Rio.write(expected, stringWriter, RDFFormat.TURTLE, config);
211+
212+
var actual = Rio.parse(new StringReader(stringWriter.toString()), "", RDFFormat.TURTLE);
213+
assertThat(Models.isomorphic(expected, actual)).as("isomorphic").isTrue();
214+
215+
// Requires https://github.com/eclipse-rdf4j/rdf4j/issues/4929 to be fixed
216+
// assertThat(stringWriter.toString()).isEqualTo(data);
217+
}
218+
219+
@Test
220+
public void testUnusualIrisAndPrefixesWriteParserWriteCompare() throws Exception {
221+
var prefix = "server-news";
222+
var ns = "news:comp.infosystems.www.servers.";
223+
224+
var config = new WriterConfig();
225+
config.set(BasicWriterSettings.INLINE_BLANK_NODES, false);
226+
config.set(BasicWriterSettings.PRETTY_PRINT, false);
227+
228+
var expectedModel = new DynamicModelFactory().createEmptyModel();
229+
expectedModel.setNamespace(prefix, ns);
230+
expectedModel.setNamespace(RDFS.PREFIX, RDFS.NAMESPACE);
231+
expectedModel.add(vf.createIRI(ns, "unix"), RDFS.LABEL, vf.createLiteral("News on Unix"));
232+
expectedModel.add(vf.createIRI(ns, "windows"), RDFS.LABEL, vf.createLiteral("News on Windows"));
233+
234+
var turtle1 = new StringWriter();
235+
Rio.write(expectedModel, turtle1, RDFFormat.TURTLE, config);
236+
237+
var actualModel = Rio.parse(new StringReader(turtle1.toString()), "", RDFFormat.TURTLE);
238+
assertThat(Models.isomorphic(expectedModel, actualModel)).as("isomorphic").isTrue();
239+
240+
var turtle2 = new StringWriter();
241+
Rio.write(actualModel, turtle2, RDFFormat.TURTLE, config);
242+
243+
// Requires https://github.com/eclipse-rdf4j/rdf4j/issues/4929 to be fixed
244+
// assertThat(turtle2.toString()).isEqualTo(turtle1.toString());
245+
}
246+
195247
@Test
196248
@Disabled
197249
public void anotherBnodeTest() throws Exception {
@@ -672,6 +724,20 @@ public void testBlankNodeInlining_indirectCircularReferenceWithIRI() throws Exce
672724
assertTrue(Models.isomorphic(expected, actual));
673725
}
674726

727+
@Test
728+
public void testIriNamespace() {
729+
Model model = new DynamicModelFactory().createEmptyModel();
730+
String prefix = "foo-bar";
731+
String ns = "foo:this.is.my.bar.";
732+
model.setNamespace(prefix, ns);
733+
model.add(vf.createIRI(ns, "lala"), vf.createIRI(ns, "lulu"), vf.createIRI(ns, "lolo"));
734+
735+
StringWriter stringWriter = new StringWriter();
736+
Rio.write(model, stringWriter, RDFFormat.TURTLE);
737+
738+
assertThat(stringWriter.toString()).contains("foo-bar:lala foo-bar:lulu foo-bar:lolo .");
739+
}
740+
675741
@Test
676742
public void testIgnoreAbbreviateNumbers() {
677743
StringWriter sw = new StringWriter();

site/content/download.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ toc: true
55

66
You can either retrieve RDF4J via Apache Maven, or download the SDK or onejar directly.
77

8-
## RDF4J 4.3.10 (latest)
8+
## RDF4J 4.3.11 (latest)
99

10-
RDF4J 4.3.10 is our latest stable release. It requires Java 11 minimally.
11-
For details on what’s new and how to upgrade, see the [release and upgrade notes](/release-notes/4.3.10).
10+
RDF4J 4.3.11 is our latest stable release. It requires Java 11 minimally.
11+
For details on what’s new and how to upgrade, see the [release and upgrade notes](/release-notes/4.3.11).
1212

13-
- [RDF4J 4.3.10 SDK (zip)](http://www.eclipse.org/downloads/download.php?file=/rdf4j/eclipse-rdf4j-4.3.10-sdk.zip)<br/>
13+
- [RDF4J 4.3.11 SDK (zip)](http://www.eclipse.org/downloads/download.php?file=/rdf4j/eclipse-rdf4j-4.3.11-sdk.zip)<br/>
1414
Full Eclipse RDF4J SDK, containing all libraries, RDF4J Server, Workbench, and Console applications, and Javadoc API.
1515

16-
- [RDF4J 4.3.10 onejar](http://www.eclipse.org/downloads/download.php?file=/rdf4j/eclipse-rdf4j-4.3.10-onejar.jar)<br/>
16+
- [RDF4J 4.3.11 onejar](http://www.eclipse.org/downloads/download.php?file=/rdf4j/eclipse-rdf4j-4.3.11-onejar.jar)<br/>
1717
Single jar file for easy inclusion of the full RDF4J toolkit in your Java project.
1818

1919
- [RDF4J artifacts](https://search.maven.org/search?q=org.eclipse.rdf4j) on the [Maven Central Repository](http://search.maven.org/)
@@ -28,7 +28,7 @@ You can include RDF4J as a Maven dependency in your Java project by including th
2828
<dependency>
2929
<groupId>org.eclipse.rdf4j</groupId>
3030
<artifactId>rdf4j-bom</artifactId>
31-
<version>4.3.10</version>
31+
<version>4.3.11</version>
3232
<type>pom</type>
3333
<scope>import</scope>
3434
</dependency>

site/content/news/rdf4j-4311.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
title: "RDF4J 4.3.11 released"
3+
date: 2024-04-01T11:25:33+0200
4+
layout: "single"
5+
categories: ["news"]
6+
---
7+
RDF4J 4.3.11 is now available. This is a patch release fixing 2 bugs.
8+
9+
For more details, have a look at the [release notes](/release-notes/4.3.11).
10+
<!--more-->
11+
### Links
12+
13+
- [Download RDF4J](/download/)
14+
- [release notes](/release-notes/4.3.11).
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
title: "4.3.11"
3+
toc: true
4+
---
5+
RDF4J 4.3.11 is a patch release that fixes 2 issues.
6+
7+
For a complete overview, see [all issues fixed in 4.3.11](https://github.com/eclipse/rdf4j/milestone/104?closed=1).
8+
9+
### Acknowledgements
10+
11+
This release was made possible by contributions from Richard Eckart de Castilho.

0 commit comments

Comments
 (0)