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="


Reply via email to