This is an automated email from the ASF dual-hosted git repository. andy pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/jena.git
commit 00d1e3cce41d0b257d3986b8f3aec971a9f80b86 Author: Andy Seaborne <[email protected]> AuthorDate: Fri Mar 13 09:47:53 2026 +0000 GH-3790: Output RDF/XML --- .../jena/riot/lang/rdfxml/TC_RIOT_RDFXML.java | 2 + .../jena/riot/lang/rdfxml/TestOutputRDFXML.java | 358 +++++++++++++++++++++ .../java/org/apache/jena/rdf/model/impl/Util.java | 21 +- .../jena/rdfxml/xmloutput/impl/RDFXML_Abbrev.java | 123 ++++--- .../jena/rdfxml/xmloutput/impl/RDFXML_Basic.java | 300 +++++++++-------- .../jena/rdfxml/xmloutput/impl/Unparser.java | 10 +- 6 files changed, 607 insertions(+), 207 deletions(-) diff --git a/jena-arq/src/test/java/org/apache/jena/riot/lang/rdfxml/TC_RIOT_RDFXML.java b/jena-arq/src/test/java/org/apache/jena/riot/lang/rdfxml/TC_RIOT_RDFXML.java index 33b4d4e5dd..6a7b4c9572 100644 --- a/jena-arq/src/test/java/org/apache/jena/riot/lang/rdfxml/TC_RIOT_RDFXML.java +++ b/jena-arq/src/test/java/org/apache/jena/riot/lang/rdfxml/TC_RIOT_RDFXML.java @@ -31,6 +31,8 @@ import org.apache.jena.riot.lang.rdfxml.rrx.TS_RRX; @SelectClasses({ // Local tests, extensions and error reports. TS_RRX.class, + // Additional writer tests. RDF 1.2 features. Setup using Turtle. + TestOutputRDFXML.class, // jena-core legacy test (RDF 1.0) TS_ConvertedARP1.class diff --git a/jena-arq/src/test/java/org/apache/jena/riot/lang/rdfxml/TestOutputRDFXML.java b/jena-arq/src/test/java/org/apache/jena/riot/lang/rdfxml/TestOutputRDFXML.java new file mode 100644 index 0000000000..b4eed94978 --- /dev/null +++ b/jena-arq/src/test/java/org/apache/jena/riot/lang/rdfxml/TestOutputRDFXML.java @@ -0,0 +1,358 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.apache.jena.riot.lang.rdfxml; + +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import org.apache.jena.graph.Graph; +import org.apache.jena.riot.*; +import org.apache.jena.sparql.util.IsoMatcher; +import org.apache.jena.sys.JenaSystem; + +/** RDFXML writer tests using Turtle to setup the test case. */ +public class TestOutputRDFXML { + + static { JenaSystem.init(); } + + enum Setup { + PLAIN(RDFFormat.RDFXML_PLAIN), + PRETTY(RDFFormat.RDFXML_PRETTY) ; + + private RDFFormat fmt; + Setup(RDFFormat fmt) { this.fmt = fmt; } + } + + // No ITS + static String PREFIXES = """ +PREFIX : <http://example/> +PREFIX ex: <http://example.org/> +PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> +PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> +PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> + """; + + // Text direction. + + @ParameterizedTest + @EnumSource(Setup.class) + public void textDirNoITS(Setup setup) { + runTest(":s :p 'abc'@en--ltr .", setup.fmt); + } + + @ParameterizedTest + @EnumSource(Setup.class) + public void textDirWithITS(Setup setup) { + // With its: + runTest("PREFIX its: <http://www.w3.org/2005/11/its> :s :p 'abc'@en--ltr .", setup.fmt); + } + + @ParameterizedTest + @EnumSource(Setup.class) + public void textDirOtherITS(Setup setup) { + // Someother use of "its:" + runTest("PREFIX its: <http://example/ITS/> :s :p 'abc'@en--ltr .", setup.fmt); + } + + @ParameterizedTest + @EnumSource(Setup.class) + public void textDirTripleTermNoITS(Setup setup) { + runTest(":s :p <<( :x :y 'abc'@en--ltr)>> .", setup.fmt); + } + + @ParameterizedTest + @EnumSource(Setup.class) + public void textDirTripleTermWithITS(Setup setup) { + runTest("PREFIX its: <http://www.w3.org/2005/11/its> :s :p <<( :x :y 'abc'@en--ltr)>> .", setup.fmt); + } + + @ParameterizedTest + @EnumSource(Setup.class) + public void textDirTripleTermOtherITS(Setup setup) { + runTest("PREFIX its: <http://example/ITS/> :s :p <<( :x :y 'abc'@en--ltr)>> .", setup.fmt); + } + + // ---- Triple Term Basics + + @ParameterizedTest + @EnumSource(Setup.class) + public void ttBasicObjIRI(Setup setup) { + runTest(":s :p <<( :x :y :z )>> .", setup.fmt); + } + + @ParameterizedTest + @EnumSource(Setup.class) + public void ttBasicObjNumber(Setup setup) { + runTest(":s :p <<( :x :y 123 )>> .", setup.fmt); + } + + @ParameterizedTest + @EnumSource(Setup.class) + public void ttBasicObjBNode(Setup setup) { + runTest(":s :p <<( :x :y _:x )>> .", setup.fmt); + } + + @ParameterizedTest + @EnumSource(Setup.class) + public void ttBasicObjString(Setup setup) { + runTest(":s :p <<( :x :y 'ABC' )>>", setup.fmt); + } + + @ParameterizedTest + @EnumSource(Setup.class) + public void ttBasicObjLangString(Setup setup) { + runTest(":s :p <<( :x :y 'ABC'@en )>> .", setup.fmt); + } + + @ParameterizedTest + @EnumSource(Setup.class) + public void ttBasicObjDirLangString(Setup setup) { + runTest(":s :p <<( :x :y 'ABC'@en-ltr )>> .", setup.fmt); + } + + @ParameterizedTest + @EnumSource(Setup.class) + public void ttBasicObjTripleterm(Setup setup) { + runTest(":s :p <<( _:x :y <<( :a :b :c )>> )>> .", setup.fmt); + } + + @ParameterizedTest + @EnumSource(Setup.class) + public void ttBasicSubjBNode(Setup setup) { + runTest(":s :p <<( _:x :y :z )>>", setup.fmt); + } + + // BNode connectivity tests (not triple terms) + + @ParameterizedTest + @EnumSource(Setup.class) + public void bnodePlain01(Setup setup) { + runTest("_:b :pp :bb .", setup.fmt); + } + + @ParameterizedTest + @EnumSource(Setup.class) + public void bnodePlain02(Setup setup) { + runTest(":ss :pp _:b .", setup.fmt); + } + + @ParameterizedTest + @EnumSource(Setup.class) + public void bnodePlain03(Setup setup) { + runTest("_:b :pp _:b .", setup.fmt); + } + + // ---- Triple terms and blank node sharing. + + @ParameterizedTest + @EnumSource(Setup.class) + public void bnodeShape11(Setup setup) { + String bnodeShape11 = """ + _:a :pp _:a . + :xx :qq <<( :s :pp _:b )>> . + """; + runTest(bnodeShape11, setup.fmt); + } + + @ParameterizedTest + @EnumSource(Setup.class) + public void bnodeShape12(Setup setup) { + // Share in triple term + String bnodeShape12 = """ + _:a :pp :bb . + :xx :qq <<( _:b :pp _:b )>> . + """; + runTest(bnodeShape12, setup.fmt); + } + + // ---- + @ParameterizedTest + @EnumSource(Setup.class) + public void bnodeShape20(Setup setup) { + // Share subjects + String bnodeShape20 = """ + _:b :pp :bb . + :xx :qq <<( _:b :pp "ABC" )>> . + """; + + runTest(bnodeShape20, setup.fmt); + } + + @ParameterizedTest + @EnumSource(Setup.class) + public void bnodeShape21(Setup setup) { + String bnodeShape21 = """ + _:b :pp :bb . + :xx :qq <<( :s :pp _:b )>> . + """; + runTest(bnodeShape21, setup.fmt); + } + + @ParameterizedTest + @EnumSource(Setup.class) + public void bnodeShape22(Setup setup) { + String bnodeShape22 = """ + :ss :pp _:b . + :xx :qq <<( _:b :pp "ABC" )>> . + """; + runTest(bnodeShape22, setup.fmt); + } + + @ParameterizedTest + @EnumSource(Setup.class) + public void bnodeShape23(Setup setup) { + // Shared objects + String bnodeShape23 = """ + :ss :pp _:b . + :xx :qq <<( :s :pp _:b )>> . + """; + + runTest(bnodeShape23, setup.fmt); + } + + @ParameterizedTest + @EnumSource(Setup.class) + public void bnodeShape24(Setup setup) { + String bnodeShape24 = """ + _:b :pp _:b . + :xx :qq <<( _:b :pp _:b )>> . + """; + runTest(bnodeShape24, setup.fmt); + } + + @ParameterizedTest + @EnumSource(Setup.class) + public void bnodeShape25(Setup setup) { + // Shared, common subject + String bnodeShape25 = """ + :xx :dd _:b . + :xx :qq <<( _:b :pp _:b )>> . + """; + runTest(bnodeShape25, setup.fmt); + } + + //---- Namespaces + + @ParameterizedTest + @EnumSource(Setup.class) + public void ttNamespace1(Setup setup) { + // Different namespace inside, but in the prefix map. + String ttNamespace1 = """ + :xx :dd _:b . + :xx :qq <<( _:b ex:pp _:b )>> . + """; + runTest(ttNamespace1, setup.fmt); + } + + @ParameterizedTest + @EnumSource(Setup.class) + public void ttNamespace2(Setup setup) { + // Unmentioned namespace both outside and inside + String ttNamespace2 = """ + :xx <http://ex/p> _:b . + :xx :qq <<( _:b <http://ex/pp> _:b )>> . + """; + runTest(ttNamespace2, setup.fmt); + } + + @ParameterizedTest + @EnumSource(Setup.class) + public void ttNamespace3(Setup setup) { + // Unmentioned namespace inside only + String ttNamespace3 = """ + :xx :qq <<( _:b <http://ex/pp> _:b )>> . + """; + runTest(ttNamespace3, setup.fmt); + } + + @ParameterizedTest + @EnumSource(Setup.class) + public void ttNamespace4(Setup setup) { + // Unmentioned namespaces inside only + String ttNamespace4 = """ + :xx :qq <<( :ss <http://ns1/p1> _:b )>> . + :xx :qq <<( :ss <http://ns2/p2> _:b )>> . + """; + runTest(ttNamespace4, setup.fmt); + } + + private static void runTest(String str, RDFFormat format) { + Graph graph1; + + String parseString = PREFIXES+str; + try { + graph1 = RDFParser.fromString(parseString, Lang.TTL).toGraph(); + } catch (RiotException ex) { + printString(str); + fail("Can't parse input"); + return; + } + + String outString; + try { + outString = RDFWriter.source(graph1).format(format).asString(); + } catch (Throwable ex) { + ex.printStackTrace(); + RDFWriter.source(graph1).format(RDFFormat.TTL).output(System.out); + System.out.println(); + fail("Can't write -- "+ex.getMessage()); + return; + } + + // And parse the output + Graph graph2; + try { + graph2 = RDFParser.fromString(outString, format.getLang()).toGraph(); + } catch (RiotException ex) { + ex.printStackTrace(); + fail("Can't parse writtern graph -- "+ex.getMessage()); + return; + } + + boolean same = IsoMatcher.isomorphic(graph1, graph2); + //System.out.printf("[%s] Same = %s\n", format, same); + if ( !same ) { + System.out.println("==== Expected"); + //RDFWriter.source(graph1).format(format).output(System.out); + RDFWriter.source(graph1).format(RDFFormat.TURTLE).output(System.out); + System.out.println("==== Actual"); + RDFWriter.source(graph2).format(RDFFormat.TURTLE).output(System.out); + System.out.println("== RDF/XML"); + System.out.print(outString); + fail("Graph not isomorphic"); + return; + } + } + + /** Print a multiline string, with line numbers. */ + private static void printString(String str) { + System.out.printf("==== 1 2 3 4\n"); + System.out.printf("==== 12345789_123456789_123456789_123456789_\n"); + String[] x = str.split("\n"); + for ( int i = 0 ; i < x.length ; i++ ) { + System.out.printf("%2d -- %s\n", i+1, x[i]); + } + System.out.println("===="); + } +} diff --git a/jena-core/src/main/java/org/apache/jena/rdf/model/impl/Util.java b/jena-core/src/main/java/org/apache/jena/rdf/model/impl/Util.java index d17ca3c8b9..851bff469e 100644 --- a/jena-core/src/main/java/org/apache/jena/rdf/model/impl/Util.java +++ b/jena-core/src/main/java/org/apache/jena/rdf/model/impl/Util.java @@ -191,6 +191,18 @@ public class Util extends Object { return ! Lib.isEmpty(lang); } + /** Test whether this literal has a language (rdf:langString or rdf:dirLangString) */ + public static boolean hasLang(Literal n) { + String lang = n.getLanguage(); + return ! Lib.isEmpty(lang); + } + + /** Test whether this Literal has a text direction (rdf:dirLangString) */ + public static boolean hasTextDirection(Literal n) { + String textDir = n.getBaseDirection(); + return ! Lib.isEmpty(textDir); + } + /** Return true if the literal is a simple string. * <p>RDF 1.0 {@literal =>} it is a plain literal, with no language tag * <p>RDF 1.1 {@literal =>} it has datatype xsd:string @@ -199,17 +211,18 @@ public class Util extends Object { Objects.requireNonNull(lit); RDFDatatype dt = lit.getDatatype(); if ( dt == null ) - return ! isLangString(lit); + return ! hasLang(lit); return dt.equals(XSDDatatype.XSDstring); } - /** Return true if the literal has a language tag. */ + /** Return true if the literal is a rdf:langString (not rdf:dirLangString) */ public static boolean isLangString(Literal lit) { Objects.requireNonNull(lit); String lang = lit.getLanguage(); - if ( lang == null ) + if ( lang == null || lang.isEmpty() ) return false; - return ! lang.equals(""); + String textDir = lit.getBaseDirection(); + return ! hasTextDirection(lit); } /** Return true if the literal is well-formed, has a language tag and a base direction. */ diff --git a/jena-core/src/main/java/org/apache/jena/rdfxml/xmloutput/impl/RDFXML_Abbrev.java b/jena-core/src/main/java/org/apache/jena/rdfxml/xmloutput/impl/RDFXML_Abbrev.java index a1e6f0182a..2928da7537 100644 --- a/jena-core/src/main/java/org/apache/jena/rdfxml/xmloutput/impl/RDFXML_Abbrev.java +++ b/jena-core/src/main/java/org/apache/jena/rdfxml/xmloutput/impl/RDFXML_Abbrev.java @@ -43,26 +43,26 @@ import java.io.PrintWriter; */ public class RDFXML_Abbrev extends BaseXMLWriter implements RDFErrorHandler { - private Resource types[] = - new Resource[] { - OWL.Ontology, - //OWL.DataRange, named or orphaned dataranges unusual. - RDFS.Datatype, - RDFS.Class, - OWL.Class, - OWL.ObjectProperty, - RDF.Property, - OWL.DatatypeProperty, - OWL.TransitiveProperty, - OWL.SymmetricProperty, - OWL.FunctionalProperty, - OWL.InverseFunctionalProperty, - }; - - boolean sReification; - - - boolean sIdAttr; + private Resource types[] = + new Resource[] { + OWL.Ontology, + //OWL.DataRange, named or orphaned dataranges unusual. + RDFS.Datatype, + RDFS.Class, + OWL.Class, + OWL.ObjectProperty, + RDF.Property, + OWL.DatatypeProperty, + OWL.TransitiveProperty, + OWL.SymmetricProperty, + OWL.FunctionalProperty, + OWL.InverseFunctionalProperty, + }; + + boolean sReification; + + + boolean sIdAttr; boolean sDamlCollection; boolean sParseTypeCollectionPropertyElt; boolean sListExpand; @@ -79,18 +79,18 @@ public class RDFXML_Abbrev extends BaseXMLWriter implements RDFErrorHandler { @Deprecated public RDFXML_Abbrev() {} - @Override + @Override protected void unblockAll() { - sDamlCollection = false; - sReification = false; - sResourcePropertyElt = false; - sParseTypeLiteralPropertyElt = false; - sParseTypeResourcePropertyElt = false; - sParseTypeCollectionPropertyElt = false; - sIdAttr = false; - sPropertyAttr = false; + sDamlCollection = false; + sReification = false; + sResourcePropertyElt = false; + sParseTypeLiteralPropertyElt = false; + sParseTypeResourcePropertyElt = false; + sParseTypeCollectionPropertyElt = false; + sIdAttr = false; + sPropertyAttr = false; sListExpand = false; - } + } { unblockAll(); @@ -100,7 +100,7 @@ public class RDFXML_Abbrev extends BaseXMLWriter implements RDFErrorHandler { @Override protected void blockRule(Resource r) { if (r.equals(RDFSyntax.sectionReification)) sReification=true; - // else if (r.equals(RDFSyntax.resourcePropertyElt)) sResourcePropertyElt=true; + // else if (r.equals(RDFSyntax.resourcePropertyElt)) sResourcePropertyElt=true; else if (r.equals(RDFSyntax.sectionListExpand)) sListExpand=true; else if (r.equals(RDFSyntax.parseTypeLiteralPropertyElt)) sParseTypeLiteralPropertyElt=true; else if (r.equals(RDFSyntax.parseTypeResourcePropertyElt)) sParseTypeResourcePropertyElt=true; @@ -115,44 +115,37 @@ public class RDFXML_Abbrev extends BaseXMLWriter implements RDFErrorHandler { logger.warn("Cannot block rule <"+r.getURI()+">"); } } - @Override + @Override Resource[] setTypes(Resource[] propValue) { - Resource[] rslt = types; - types = propValue; - return rslt; - } - - @Override - protected void writeBody( - Model model, - PrintWriter pw, - String base, - boolean useXMLBase) { - Unparser unp = new Unparser(this, base, model, pw); - - unp.setTopLevelTypes(types); - //unp.useNameSpaceDecl(nameSpacePrefices); - if (useXMLBase) - unp.setXMLBase(base); - unp.write(); - } - - // Implemenatation of RDFErrorHandler - @Override + Resource[] rslt = types; + types = propValue; + return rslt; + } + + @Override + protected void writeBody(Model model, PrintWriter pw, String base, boolean useXMLBase) { + Unparser unp = new Unparser(this, base, model, pw); + + unp.setTopLevelTypes(types); + //unp.useNameSpaceDecl(nameSpacePrefices); + if (useXMLBase) + unp.setXMLBase(base); + unp.write(); + } + + // Implementation of RDFErrorHandler + @Override public void error(Exception e) { - errorHandler.error(e); - } + errorHandler.error(e); + } - @Override + @Override public void warning(Exception e) { - errorHandler.warning(e); - } + errorHandler.warning(e); + } - @Override + @Override public void fatalError(Exception e) { - errorHandler.fatalError(e); - } - - - + errorHandler.fatalError(e); + } } diff --git a/jena-core/src/main/java/org/apache/jena/rdfxml/xmloutput/impl/RDFXML_Basic.java b/jena-core/src/main/java/org/apache/jena/rdfxml/xmloutput/impl/RDFXML_Basic.java index 27da13563e..f0dfea40ff 100644 --- a/jena-core/src/main/java/org/apache/jena/rdfxml/xmloutput/impl/RDFXML_Basic.java +++ b/jena-core/src/main/java/org/apache/jena/rdfxml/xmloutput/impl/RDFXML_Basic.java @@ -27,6 +27,7 @@ import org.apache.jena.datatypes.xsd.impl.XMLLiteralType; import org.apache.jena.rdf.model.* ; import org.apache.jena.rdf.model.impl.Util ; import org.apache.jena.shared.JenaException; +import org.apache.jena.vocabulary.ITS; import org.apache.jena.vocabulary.RDFSyntax ; /** Writes out an XML serialization of a model. @@ -35,101 +36,113 @@ public class RDFXML_Basic extends BaseXMLWriter { /** * Do not create directly. - * @deprecated The RDFWriter may be replaced, + * @deprecated The RDFWriter may be replaced. */ @Deprecated public RDFXML_Basic() {} private String space; - @Override protected void writeBody - ( Model model, PrintWriter pw, String base, boolean inclXMLBase ) - { + // Move to BaseXMLWriter? + private String itsPrefix = null; + // Is there an ITS namespace declaration in the model prefixes? + private boolean itsModelNamespace = false; + + @Override + protected void writeBody(Model model, PrintWriter pw, String base, boolean inclXMLBase) { setSpaceFromTabCount(); - writeRDFHeader( model, pw ); - writeRDFStatements( model, pw ); - writeRDFTrailer( pw, base ); - pw.flush(); - } + setupITS(model); + writeRDFHeader(model, pw); + writeRDFStatements(model, pw); + writeRDFTrailer(pw, base); + pw.flush(); + } + + private void setupITS(Model model) { + itsPrefix = model.getNsURIPrefix(ITS.uri); + // If its is in the namespaces, we use that else print on each literal with a base direction + // This enables streaming. + itsModelNamespace = (itsPrefix != null); + } - private void setSpaceFromTabCount() - { + private void setSpaceFromTabCount() { space = ""; - for (int i=0; i < tabSize; i += 1) space += " "; - } + for ( int i = 0 ; i < tabSize ; i += 1 ) + space += " "; + } - protected void writeSpace( PrintWriter writer ) - { writer.print( space ); } + protected void writeSpace(PrintWriter writer) { + writer.print(space); + } - private void writeRDFHeader(Model model, PrintWriter writer) - { - String xmlns = xmlnsDecl(); - writer.print( "<" + rdfEl( "RDF" ) + xmlns ); - if (null != xmlBase && xmlBase.length() > 0) - writer.print( "\n xml:base=" + substitutedAttribute( xmlBase ) ); - writer.println( " > " ); - } + private void writeRDFHeader(Model model, PrintWriter writer) { + String xmlns = xmlnsDecl(); + writer.print("<" + rdfEl("RDF") + xmlns); + if ( null != xmlBase && xmlBase.length() > 0 ) + writer.print("\n xml:base=" + substitutedAttribute(xmlBase)); + if ( itsModelNamespace ) + writer.printf("\n %s:version=%s", itsPrefix, attributeQuoted("2.0")); + writer.println(" > "); + } - protected void writeRDFStatements( Model model, PrintWriter writer ) - { - ResIterator rIter = model.listSubjects(); - while (rIter.hasNext()) writeRDFStatements( model, rIter.nextResource(), writer ); - } - - protected void writeRDFTrailer( PrintWriter writer, String base ) - { writer.println( "</" + rdfEl( "RDF" ) + ">" ); } - - protected void writeRDFStatements - ( Model model, Resource subject, PrintWriter writer ) - { - StmtIterator sIter = model.listStatements( subject, null, (RDFNode) null ); - writeDescriptionHeader( subject, writer ); - while (sIter.hasNext()) writePredicate( sIter.nextStatement(), writer ); - writeDescriptionTrailer( subject, writer ); - } - - protected void writeDescriptionHeader( Resource subject, PrintWriter writer) - { - writer.print( space + "<" + rdfEl( "Description" ) + " " ); - writeResourceId( subject, writer ); - writer.println( ">" ); - } + protected void writeRDFStatements(Model model, PrintWriter writer) { + ResIterator rIter = model.listSubjects(); + while (rIter.hasNext()) + writeRDFStatements(model, rIter.nextResource(), writer); + } - protected void writePredicate(Statement stmt, final PrintWriter writer) - { - final Property predicate = stmt.getPredicate(); - final RDFNode object = stmt.getObject(); - - writer.print(space+space+ - "<" - + startElementTag( - SplitRDFXML.namespace(predicate), - SplitRDFXML.localname(predicate))); - - switch(object) { - case Resource resource ->{ - writer.print(" "); - writeResourceReference(resource, writer); - writer.println("/>"); - } - case Literal literal ->{ - writeLiteral(literal, writer); - writer.println("</"+ endElementTag(SplitRDFXML.namespace(predicate), SplitRDFXML.localname(predicate)) + ">"); - } - case StatementTerm triple ->{ - writeTripleTerm(triple, writer); - writer.print(space); - writer.print(space); - writer.println("</"+ endElementTag(SplitRDFXML.namespace(predicate), SplitRDFXML.localname(predicate)) + ">"); - } - default->{ - throw new JenaException("Bad object: "+object); - } - } - } + protected void writeRDFTrailer(PrintWriter writer, String base) { + writer.println("</" + rdfEl("RDF") + ">"); + } + + protected void writeRDFStatements(Model model, Resource subject, PrintWriter writer) { + StmtIterator sIter = model.listStatements(subject, null, (RDFNode)null); + writeDescriptionHeader(subject, writer); + while (sIter.hasNext()) + writePredicate(sIter.nextStatement(), writer); + writeDescriptionTrailer(subject, writer); + } + + protected void writeDescriptionHeader(Resource subject, PrintWriter writer) { + writer.print(space + "<" + rdfEl("Description") + " "); + writeResourceId(subject, writer); + writer.println(">"); + } + + protected void writePredicate(Statement stmt, final PrintWriter writer) { + final Property predicate = stmt.getPredicate(); + final RDFNode object = stmt.getObject(); + + writer.print(space+space+ + "<" + + startElementTag( + SplitRDFXML.namespace(predicate), + SplitRDFXML.localname(predicate))); + + switch(object) { + case Resource resource ->{ + writer.print(" "); + writeResourceReference(resource, writer); + writer.println("/>"); + } + case Literal literal ->{ + writeLiteral(literal, writer); + writer.println("</"+ endElementTag(SplitRDFXML.namespace(predicate), SplitRDFXML.localname(predicate)) + ">"); + } + case StatementTerm triple ->{ + writeTripleTerm(triple, writer); + writer.print(space); + writer.print(space); + writer.println("</"+ endElementTag(SplitRDFXML.namespace(predicate), SplitRDFXML.localname(predicate)) + ">"); + } + default->{ + throw new JenaException("Bad object: "+object); + } + } + } @Override protected void unblockAll() - { blockLiterals = false; } + { blockLiterals = false; } private boolean blockLiterals = false; @@ -137,61 +150,85 @@ public class RDFXML_Basic extends BaseXMLWriter { if (r.equals( RDFSyntax.parseTypeLiteralPropertyElt )) { blockLiterals = true; } else - logger.warn("Cannot block rule <"+r.getURI()+">"); - } - - protected void writeDescriptionTrailer( Resource subject, PrintWriter writer ) - { writer.println( space + "</" + rdfEl( "Description" ) + ">" ); } - - - protected void writeResourceId( Resource r, PrintWriter writer ) - { - if (r.isAnon()) { - writer.print(rdfAt("nodeID") + "=" + attributeQuoted(anonId(r))); - } else { - writer.print( - rdfAt("about") - + "=" - + substitutedAttribute(relativize(r.getURI()))); - } - } - - protected void writeResourceReference( Resource r, PrintWriter writer ) - { - if (r.isAnon()) { - writer.print(rdfAt("nodeID") + "=" + attributeQuoted(anonId(r))); - } else { - if ( r.isStatementTerm() ) - throw new JenaException("Triple terms not supported in RDF/XML"); - - writer.print( - rdfAt("resource") - + "=" - + substitutedAttribute(relativize(r.getURI()))); - } - } + logger.warn("Cannot block rule <"+r.getURI()+">"); + } + + protected void writeDescriptionTrailer( Resource subject, PrintWriter writer ) + { writer.println( space + "</" + rdfEl( "Description" ) + ">" ); } + + + protected void writeResourceId( Resource r, PrintWriter writer ) { + if (r.isAnon()) { + writer.print(rdfAt("nodeID") + "=" + attributeQuoted(anonId(r))); + } else { + writer.print( + rdfAt("about") + + "=" + + substitutedAttribute(relativize(r.getURI()))); + } + } + + protected void writeResourceReference( Resource r, PrintWriter writer ) { + if (r.isAnon()) { + writer.print(rdfAt("nodeID") + "=" + attributeQuoted(anonId(r))); + } else { + if ( r.isStatementTerm() ) + throw new JenaException("Triple terms not supported in RDF/XML"); + + writer.print( + rdfAt("resource") + + "=" + + substitutedAttribute(relativize(r.getURI()))); + } + } protected void writeLiteral( Literal literal, PrintWriter writer ) { - String lang = literal.getLanguage(); + String lang = literal.getLanguage(); String form = literal.getLexicalForm(); boolean isXML = XMLLiteralType.isXMLLiteral(literal.getDatatype()); - if (Util.isLangString(literal)) { - writer.print(" xml:lang=" + attributeQuoted( lang )); - } else if ( isXML && !blockLiterals) { - // RDF XML Literals inline. - writer.print(" " + rdfAt("parseType") + "=" + attributeQuoted( "Literal" )+">"); - writer.print( form ); - return ; - } else { - // Datatype (if not xsd:string and RDF 1.1) - String dt = literal.getDatatypeURI(); - if ( ! Util.isSimpleString(literal) ) - writer.print( " " + rdfAt( "datatype" ) + "=" + substitutedAttribute( dt ) ); - } - // Content. - writer.print(">"); - writer.print( Util.substituteEntitiesInElementContent( form ) ); - } + if (Util.isLangString(literal)) { + writer.printf(" xml:lang=%s", attributeQuoted( lang )); + } else if ( Util.isDirLangString(literal)) { + if ( itsPrefix == null ) + itsPrefix = syntheticNamespaceForITS(literal.getModel());; + if ( ! itsModelNamespace ) { + writer.printf(" xmlns:%s=%s",itsPrefix, attributeQuoted(ITS.uri)); + writer.printf(" %s:version=%s", itsPrefix, attributeQuoted("2.0")); + } + writer.printf(" xml:lang=%s", attributeQuoted( lang )); + writer.printf(" %s:dir=%s", itsPrefix, attributeQuoted(literal.getBaseDirection())); + } else if ( isXML && !blockLiterals) { + // RDF XML Literals inline. + writer.print(" " + rdfAt("parseType") + "=" + attributeQuoted( "Literal" )+">"); + writer.print( form ); + return ; + } else { + // Datatype (if not xsd:string and RDF 1.1) + String dt = literal.getDatatypeURI(); + if ( ! Util.isSimpleString(literal) ) + writer.print( " " + rdfAt( "datatype" ) + "=" + substitutedAttribute( dt ) ); + } + // Content. + writer.print(">"); + writer.print( Util.substituteEntitiesInElementContent( form ) ); + } + + // Determine a prefix for the ITS + private static String syntheticNamespaceForITS(Model model) { + int count = 0; + String nsPrefix = "its"; + // Find an unused prefix. + for(;;) { + if ( model.getNsPrefixURI(nsPrefix) == null) + break; + nsPrefix = "its." + (count++); + if ( count > 10_000 ) + // Safety + throw new JenaException("Can't determine an XML namepsace prefix for ITS"); + } + return nsPrefix; + } + protected void writeTripleTerm(StatementTerm triple, PrintWriter writer) { writer.print(" " + rdfAt("parseType") + "=" + attributeQuoted( "Triple" )+">"); @@ -210,5 +247,4 @@ public class RDFXML_Basic extends BaseXMLWriter { writer.print(space); writeDescriptionTrailer( subject, writer ); } - } diff --git a/jena-core/src/main/java/org/apache/jena/rdfxml/xmloutput/impl/Unparser.java b/jena-core/src/main/java/org/apache/jena/rdfxml/xmloutput/impl/Unparser.java index 4d1bdb7404..f2e4879b3c 100644 --- a/jena-core/src/main/java/org/apache/jena/rdfxml/xmloutput/impl/Unparser.java +++ b/jena-core/src/main/java/org/apache/jena/rdfxml/xmloutput/impl/Unparser.java @@ -250,7 +250,8 @@ class Unparser { // Not in model. // This is added to rdf:RDF, along with [its]:version itsInsertNs = true; - itsPrefix = syntheticNamespaceForITS(); + itsPrefix = syntheticNamespaceForITS(model); + prettyWriter.setNsPrefix(itsPrefix, ITS.uri); } } } @@ -295,9 +296,7 @@ class Unparser { } // Determine a prefix for the ITS - private String syntheticNamespaceForITS() { - if ( itsPrefix != null ) - return itsPrefix; + private static String syntheticNamespaceForITS(Model model) { int count = 0; String nsPrefix = "its"; // Find an unused prefix. @@ -309,7 +308,6 @@ class Unparser { // Safety throw new JenaException("Can't determine an XML namepsace prefix for ITS"); } - prettyWriter.setNsPrefix(nsPrefix, ITS.uri); return nsPrefix; } @@ -685,7 +683,7 @@ class Unparser { Literal lit = ((Literal) r) ; if ( Util.isSimpleString(lit) ) return false; - if ( Util.isLangString(lit) ) + if ( Util.hasLang(lit) ) // rdf:langStrign and rdf:dirLangString return false; // print out with "datatype="
