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 66aafa89280d26fec0af51eda4a1275f9a6581a6
Author: Andy Seaborne <[email protected]>
AuthorDate: Sun Mar 8 21:32:18 2026 +0000

    GH-3790: Pretty RDF/XML output: write triple terms and text dir
---
 .../java/org/apache/jena/system/TS_System.java     |   1 +
 .../org/apache/jena/system/TestFindNamespaces.java |  68 +++++
 .../jena/rdfxml/xmloutput/impl/BaseXMLWriter.java  |  37 +--
 .../xmloutput/impl/FindNamespacesRDFXML.java       |  75 +++++
 .../jena/rdfxml/xmloutput/impl/Unparser.java       | 311 ++++++++++++++++++---
 .../main/java/org/apache/jena/vocabulary/ITS.java  |  32 +--
 .../jena/rdfxml/xmloutput/TestXMLAbbrev.java       |   2 +-
 .../jena/rdfxml/xmloutput/TestXMLFeatures.java     |   2 +-
 8 files changed, 448 insertions(+), 80 deletions(-)

diff --git a/jena-arq/src/test/java/org/apache/jena/system/TS_System.java 
b/jena-arq/src/test/java/org/apache/jena/system/TS_System.java
index cb22b2c717..292ea12e20 100644
--- a/jena-arq/src/test/java/org/apache/jena/system/TS_System.java
+++ b/jena-arq/src/test/java/org/apache/jena/system/TS_System.java
@@ -36,6 +36,7 @@ import org.junit.platform.suite.api.Suite;
     , TestPrefixes.class
     , TestPrefixLib.class
     , TestRDFStarTranslation.class
+    , TestFindNamespaces.class
 })
 
 public class TS_System {}
diff --git 
a/jena-arq/src/test/java/org/apache/jena/system/TestFindNamespaces.java 
b/jena-arq/src/test/java/org/apache/jena/system/TestFindNamespaces.java
new file mode 100644
index 0000000000..ac25f02d55
--- /dev/null
+++ b/jena-arq/src/test/java/org/apache/jena/system/TestFindNamespaces.java
@@ -0,0 +1,68 @@
+/*
+ * 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.system;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import org.apache.jena.graph.Graph;
+import org.apache.jena.rdfxml.xmloutput.impl.FindNamespacesRDFXML;
+import org.apache.jena.riot.Lang;
+import org.apache.jena.riot.RDFParser;
+
+/** Test FindNamespacesRDFXML  - easier heer se we can use full turtle */
+public class TestFindNamespaces {
+    private static String testStr = """
+        PREFIX : <http://example/>
+
+        <http://x-subj/s> <http://x-pred/p1> 'abc'^^<http://ns-dt/dType> .
+
+        :s <http://x-pred/p1> <<( :s1 <http://x-pred-tt/p1>   
'abc'^^<http://ns-dt-tt/dType> )>> .
+
+        :s <http://x-pred/p1> <<(
+                                <http://x-subj/s1>
+                                <http://x-pred-tt/p1>
+                                <<( :s1 <http://x-pred-tt2/p1> 
'abc'^^<http://ns-dt-tt2/dType> )>>
+                              )>> .
+         """;
+
+    @Test
+    public void findNamespace() {
+        Graph graph = RDFParser.fromString(testStr, Lang.TURTLE).toGraph();
+        Set<String> ns = FindNamespacesRDFXML.namespacesForRDFXML(graph);
+
+        // Not http://x-subj/s, not http://example/
+        Set<String> expected = Set.of("http://x-pred/";, "http://x-pred-tt/";, 
"http://x-pred-tt2/";
+                                      // datatypes.
+                                      //, "http://ns-dt-tt2/";, 
"http://ns-dt-tt/";
+                                      );
+        if ( ! expected.equals(ns) ) {
+            System.err.println("== TestFindNamespaces");
+            System.err.println("Expected: "+expected);
+            System.err.println("Actual:   "+ns);
+        }
+        assertEquals(expected, ns);
+    }
+}
diff --git 
a/jena-core/src/main/java/org/apache/jena/rdfxml/xmloutput/impl/BaseXMLWriter.java
 
b/jena-core/src/main/java/org/apache/jena/rdfxml/xmloutput/impl/BaseXMLWriter.java
index 996a140ae5..94c745ff0c 100644
--- 
a/jena-core/src/main/java/org/apache/jena/rdfxml/xmloutput/impl/BaseXMLWriter.java
+++ 
b/jena-core/src/main/java/org/apache/jena/rdfxml/xmloutput/impl/BaseXMLWriter.java
@@ -119,12 +119,12 @@ abstract public class BaseXMLWriter implements 
RDFXMLWriterI {
     /**
         Counter used for allocating Jena transient namespace declarations.
     */
-       private int jenaPrefixCount;
+       private int jenaPrefixCount = 0;
 
        static String RDFNS = RDF.getURI();
 
 
-       static private final Pattern jenaNamespace = 
Pattern.compile("j\\.([1-9][0-9]*|cook\\.up)");
+       static private final Pattern jenaNamespace = 
Pattern.compile("j\\.([1-9][0-9]*|cook\\.up|fixup)");
 
        String xmlBase = null;
 
@@ -213,8 +213,10 @@ abstract public class BaseXMLWriter implements 
RDFXMLWriterI {
     }
 
     private void addNameSpaces( Model model )  {
-        NsIterator nsIter = model.listNameSpaces();
-        while (nsIter.hasNext()) this.addNameSpace( nsIter.nextNs() );
+        Set<String> ns = FindNamespacesRDFXML.namespacesForRDFXML(model);
+        ns.forEach(u->addNameSpace(u));
+//        NsIterator nsIter = model.listNameSpaces();
+//        while (nsIter.hasNext()) this.addNameSpace( nsIter.nextNs() );
     }
 
     private void primeNamespace(Model model) {
@@ -300,9 +302,8 @@ abstract public class BaseXMLWriter implements 
RDFXMLWriterI {
             }
             if ( val == null ) {
                 // just in case the prefix has already been used, look for a 
free
-                // one.
-                // (the usual source of such prefixes is reading in a model we 
wrote
-                // out earlier)
+                // one (the usual source of such prefixes is reading in a 
model we
+                // wrote out earlier)
                 do {
                     val = "j." + (jenaPrefixCount++);
                 } while (prefixesUsed.contains(val));
@@ -312,7 +313,7 @@ abstract public class BaseXMLWriter implements 
RDFXMLWriterI {
         }
     }
 
-       final synchronized public void setNsPrefix(String prefix, String ns) {
+    final public void setNsPrefix(String prefix, String ns) {
         if (checkLegalPrefix(prefix)) {
             nameSpaces.set11(ns, prefix);
         }
@@ -400,24 +401,26 @@ abstract public class BaseXMLWriter implements 
RDFXMLWriterI {
                        }
                }
         String prefix = ns.get( namespace );
-               boolean cookUp = false;
+               boolean synthNS = false;
                if (prefix == null) {
             checkURI( namespace );
-            logger.warn("Internal error: unexpected QName URI: <" + namespace 
+ ">.  Fixing up.",
-                                       new BrokenException( "unexpected QName 
URI " + namespace ));
-                       cookUp = true;
+            logger.warn("Internal error: QNamed needed but no namespace for 
URI: <" + namespace + ">. Fixing up."
+                        //Development: , new BrokenException( "unexpected 
QName URI " + namespace )
+                        );
+                       synthNS = true;
                } else if (prefix.length() == 0) {
                        if (type == ATTR || type == FASTATTR)
-                               cookUp = true;
+                               synthNS = true;
                        else
                                return local;
                }
-               if (cookUp) return cookUpAttribution( type, namespace, local );
+               if (synthNS) return fixupQName( type, namespace, local );
                return prefix + ":" + local;
        }
 
-    private String cookUpAttribution( int type, String namespace, String local 
) {
-        String prefix = "j.cook.up";
+    private String fixupQName( int type, String namespace, String local ) {
+        //String prefix = "j.cook.up";  // "Traditional" name.
+        String prefix = "j.fixup";
         switch (type) {
             case FASTATTR :
             case ATTR :
@@ -429,7 +432,7 @@ abstract public class BaseXMLWriter implements 
RDFXMLWriterI {
                 return prefix + ":" + local;
             case FAST :
               //  logger.error("Unreachable code - reached.");
-                throw new BrokenException( "cookup reached final FAST" );
+                throw new BrokenException( "NS generation reached final FAST" 
);
         }
     }
 
diff --git 
a/jena-core/src/main/java/org/apache/jena/rdfxml/xmloutput/impl/FindNamespacesRDFXML.java
 
b/jena-core/src/main/java/org/apache/jena/rdfxml/xmloutput/impl/FindNamespacesRDFXML.java
new file mode 100644
index 0000000000..442ff4dd12
--- /dev/null
+++ 
b/jena-core/src/main/java/org/apache/jena/rdfxml/xmloutput/impl/FindNamespacesRDFXML.java
@@ -0,0 +1,75 @@
+/*
+ * 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.rdfxml.xmloutput.impl;
+
+import java.util.Set;
+
+import org.apache.jena.graph.Graph;
+import org.apache.jena.graph.Triple;
+import org.apache.jena.rdf.model.Model;
+import org.apache.jena.util.CollectionFactory;
+import org.apache.jena.util.SplitIRI;
+import org.apache.jena.util.iterator.ExtendedIterator;
+
+/**
+ * Find all the namespaces needed to print RDF/XML, including inside triple 
terms.
+ * Namespaces are needed for predicates.
+ */
+public class FindNamespacesRDFXML {
+
+    public static Set<String> namespacesForRDFXML(Model model) {
+        return namespacesForRDFXML(model.getGraph());
+    }
+
+    public static Set<String> namespacesForRDFXML(Graph graph) {
+        Set<String> namespaces = CollectionFactory.createHashedSet();
+        ExtendedIterator<Triple> iter = graph.find();
+        try {
+            iter.forEachRemaining(triple->{
+                processTriple(namespaces, triple);
+            });
+        } finally { iter.close(); }
+        return namespaces;
+    }
+
+    private static void processTriple(Set<String> namespaces, Triple triple) {
+        String predicateURI = triple.getPredicate().getURI();
+        accNamespace(namespaces, predicateURI);
+
+        // Nor necessary for RDF/XML but Jena used to include them.
+//        if ( triple.getObject().isLiteral() ) {
+//            String dtURI = triple.getObject().getLiteralDatatypeURI();
+//            accNamepsace(namespaces, dtURI);
+//            return;
+//        }
+
+        if ( triple.getObject().isTripleTerm() ) {
+            processTriple(namespaces, triple.getObject().getTriple());
+        }
+    }
+
+    private static void accNamespace(Set<String> namespaces, String uri) {
+        String ns = uri.substring(0,  SplitIRI.splitXML(uri));
+        namespaces.add(ns);
+    }
+
+}
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 274ce91472..4d1bdb7404 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
@@ -21,6 +21,8 @@
 
 package org.apache.jena.rdfxml.xmloutput.impl;
 
+import static java.lang.String.format;
+
 /*
  * Want todo List - easy efficiency gains in listSubjects() and
  * modelListSubjects() by removing those subjects that we have already
@@ -137,6 +139,7 @@ import org.apache.jena.shared.JenaException ;
 import org.apache.jena.shared.PropertyNotFoundException ;
 import org.apache.jena.util.XML10Char;
 import org.apache.jena.util.iterator.* ;
+import org.apache.jena.vocabulary.ITS;
 import org.apache.jena.vocabulary.RDF ;
 import org.slf4j.Logger ;
 import org.slf4j.LoggerFactory ;
@@ -173,12 +176,12 @@ class Unparser {
         objectTable = new HashMap<>();
         StmtIterator ss = m.listStatements();
         try {
+            // Track number of time a (blank) node will be used
+            // Recurse into triple terms.
             while (ss.hasNext()) {
                 Statement s = ss.nextStatement();
-                RDFNode rn = s.getObject();
-                if (rn instanceof Resource) {
-                    increaseObjectCount((Resource) rn);
-                }
+                RDFNode obj = s.getObject();
+                processRDFNode(obj, false);
             }
         } finally {
             ss.close();
@@ -214,8 +217,7 @@ class Unparser {
 
                     Property ppred = model.createProperty(rpred.getURI());
 
-                    Statement statement = model.createStatement(rsubj, ppred,
-                            nobj);
+                    Statement statement = model.createStatement(rsubj, ppred, 
nobj);
                     res2statement.put(r, statement);
                     statement2res.put(statement, r);
                 } catch (Exception ignored) {
@@ -226,6 +228,56 @@ class Unparser {
         }
     }
 
+    private void processRDFNode(RDFNode obj, boolean inTripleTerm) {
+        if ( obj.isResource() ) {
+            Resource r = obj.asResource();
+            increaseObjectCount(r);
+            if ( inTripleTerm && r.isAnon() )
+                // Force it to be printed
+                increaseObjectCount(r);
+        }
+        if ( obj.isStatementTerm() ) {
+            processTripleTerm(obj.asStatementTerm());
+            return;
+        }
+        if ( obj.isLiteral() ) {
+            if ( obj.asLiteral().getBaseDirection() != null ) {
+                hasTextDirectionLiterals = true;
+                // We will need a namespace prefix.
+                // Ideally, there is one in the model can be added to rdf:RDF 
as usual.
+                itsPrefix = model.getNsURIPrefix(ITS.uri);
+                if ( itsPrefix == null ) {
+                    // Not in model.
+                    // This is added to rdf:RDF, along with [its]:version
+                    itsInsertNs = true;
+                    itsPrefix = syntheticNamespaceForITS();
+                }
+            }
+        }
+    }
+
+    private void processTripleTerm(StatementTerm sTerm) {
+        Statement tripleTerm = sTerm.asStatementTerm().getStatement();
+        processTripleTermsOneLevel(tripleTerm);
+
+        if ( tripleTerm.getObject().isStatementTerm() ) {
+            StatementTerm sTerm2 = tripleTerm.getObject().asStatementTerm();
+            processTripleTerm(sTerm2);
+        }
+    }
+
+    private void processTripleTermsOneLevel(Statement stmt) {
+        RDFNode ttSubj = stmt.getSubject();
+        if ( ttSubj.isAnon() ) {
+            Resource r = ttSubj.asResource();
+                // XXX !!! twice to make sure it is printed and not compacted.
+            if ( r.isAnon() )
+                increaseObjectCount(r);
+            increaseObjectCount(r);
+        }
+        processRDFNode(stmt.getObject(), true);
+    }
+
     /**
      * Note: must work with uri being null.
      */
@@ -242,6 +294,25 @@ class Unparser {
         outputName = u.str();
     }
 
+    // Determine a prefix for the ITS
+    private String syntheticNamespaceForITS() {
+        if ( itsPrefix != null )
+            return itsPrefix;
+        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");
+        }
+        prettyWriter.setNsPrefix(nsPrefix, ITS.uri);
+        return nsPrefix;
+    }
+
     /**
      * Should be called exactly once for each Unparser. Calling it a second 
time
      * will have undesired results.
@@ -249,20 +320,13 @@ class Unparser {
     void write() {
         prettyWriter.workOutNamespaces();
         wRDF();
-        /*
-         * System.out.print("Coverage = "); for (int 
i=0;i<codeCoverage.length;i++)
-         * System.out.print(" c[" + i + "] = " + codeCoverage[i]+ ";");
-         * System.out.println();
-         */
     }
 
     /**
      * Set a list of types of objects that will be expanded at the top-level of
      * the file.
      *
-     * @param types
-     *            An array of rdf:Class'es.
-     *
+     * @param types An array of rdf:Class'es.
      */
     void setTopLevelTypes(Resource types[]) {
         pleasingTypes = types;
@@ -316,9 +380,17 @@ class Unparser {
 
     // Reification stuff.
 
-    Map<Resource, Statement> res2statement;
+    private Map<Resource, Statement> res2statement;
+
+    private Map<Statement, Resource> statement2res;
 
-    Map<Statement, Resource> statement2res;
+    // ITS (text direction)
+    // The data has text direction literals.
+    private boolean hasTextDirectionLiterals = false;
+    // The model does not have an its: namespace so we need to add it.
+    private boolean itsInsertNs = false;
+    // The prefix decided on (its:: may be already used by the model)
+    private String itsPrefix = null;
 
     /*
      * The top-down recursive descent unparser. The methods starting in w all
@@ -338,6 +410,20 @@ class Unparser {
         print(prettyWriter.rdfEl("RDF"));
         indentPlus();
         printNameSpaceDefn();
+        if ( hasTextDirectionLiterals ) {
+            indentPlus();
+            if ( itsInsertNs ) {
+                tab();
+                print("xmlns:" );
+                print(itsPrefix);
+                print("=");
+                print(q(ITS.uri));
+            }
+            // Put the version in.
+            tab();
+            print(format("%s:%s=%s", itsPrefix, ITS.version, q("2.0")));
+            indentMinus();
+        }
         if (xmlBase != null) {
             setOutputName(xmlBase);
             tab();
@@ -384,16 +470,141 @@ class Unparser {
      * others (e.g. choice 1). For embedded XML choice 2 is obligatory. For
      * untyped, anonymous resource valued items choice 3 is used. Choice 1 is
      * the fall back.
+     *
+     * Do triple term first to protect the later (original) functions that may 
not expect triple terms
      */
-    private boolean wPropertyElt(WType wt, Property prop, Statement s,
-            RDFNode val) {
-        return wPropertyEltCompact(wt, prop, s, val) || // choice 4
+    private boolean wPropertyElt(WType wt, Property prop, Statement s, RDFNode 
val) {
+        return wPropertyEltTripleTerm(wt, prop, s, val)  ||  // RDF Triple term
+               wPropertyEltCompact(wt, prop, s, val) || // choice 4
                wPropertyEltCollection(wt, prop, s, val) || // choice RDF 
collections
-                wPropertyEltLiteral(wt, prop, s, val) || // choice 2
-                wPropertyEltResource(wt, prop, s, val) || // choice 3
-                wPropertyEltDatatype(wt, prop, s, val) ||
-                wPropertyEltValue(wt, prop, s, val);
-        // choice 1.
+               wPropertyEltLiteral(wt, prop, s, val) || // choice 2
+               wPropertyEltResource(wt, prop, s, val) || // choice 3
+               wPropertyEltDatatype(wt, prop, s, val) ||
+               wPropertyEltValue(wt, prop, s, val);    // choice 1.
+
+    }
+
+    private boolean wPropertyEltTripleTerm(WType wt, Property prop, Statement 
statement, RDFNode val) {
+        if ( ! ( val instanceof StatementTerm sTerm ) )
+            return false;
+        done(statement);
+        wPropertyTripleTerm(wt, prop, sTerm.getStatement(), statement);
+        return true;
+    }
+
+    private void wPropertyTripleTerm(WType wt, Property prop, Statement 
triple, Statement containingStatement) {
+        // parseType="Triple"
+        tab();
+        print("<");
+        wt.wTypeStart(prop);
+        if ( containingStatement != null )
+            wIdAttrReified(containingStatement);
+        maybeNewline();
+        print(" ");
+        printRdfAt("parseType");
+        print("=" + q("Triple"));
+        print(">");
+        indentPlus();
+        tab();
+        // Print the triple term
+        wTripleTerm(wt, triple);
+
+        // End property.
+        indentMinus();
+        tab();
+
+        print("</");
+        wt.wTypeEnd(prop);
+        print(">");
+    }
+
+    private void wTripleTerm(WType wt, Statement triple) {
+        // Don't use the asserted triple printing mechanism.
+        // - it tracks asserted triples that have been printed
+        //   the triple of a triple term may also be asserted and need to be 
printed twice.
+        // - it looks in the (asserted) model for properties and objects
+
+        Resource subject = triple.getSubject();
+
+        print("<");
+        printRdfAt("Description");
+        // rdf:NodeID= or rdf:about= , not wIdAttrOpt ID= (check)
+        boolean x = wNodeIDAttr(subject) || wAboutAttr(subject);
+        print(">");
+        indentPlus();
+
+        Property property = triple.getPredicate();
+        RDFNode object = triple.getObject();
+        wTripleTermPropertyObject(property, object);
+
+        indentMinus();
+        tab();
+        print("</");
+        printRdfAt("Description");
+        print(">");
+    }
+
+    private void wTripleTermPropertyObject(Property property, RDFNode object) {
+        tab();
+
+        if ( object.isStatementTerm() ) {
+            indentPlus();
+            wPropertyTripleTerm(wtype, property, 
object.asStatementTerm().getStatement(), null);
+            //wTripleTerm(wtype, object.asStatementTerm().getStatement());
+            indentMinus();
+            return;
+        }
+
+        print("<");
+        wtype.wTypeStart(property);
+
+        // Could split up!
+
+        // "/>" forms.
+        if ( object.isURIResource() ) {
+            // parse type resource
+            //wResourceAttr
+            print(" ");
+            printRdfAt("resource");
+            print("=");
+            String uri = object.asResource().getURI();
+            wURIreference(uri);
+            print("/>");
+            return;
+        }
+        if ( object.isAnon() ) {
+            print(" ");
+            printRdfAt("nodeID");
+            print("=");
+            // Corner(?) case
+            // Could use <property rdf:parseType="Resource"></property>
+            print(q(prettyWriter.anonId(object.asResource())));
+            print("/>");
+            return;
+        }
+        // Forms that have text.
+        if ( object.isLiteral() ) {
+            Literal literal = object.asLiteral() ;
+            String lang = literal.getLanguage();
+            if ( Util.isLangString(literal) || Util.isDirLangString(literal) ) 
{
+                // has language tag
+                // And don't print datatype;
+                wLangAndBaseDirection(literal);
+            } else if ( !Util.isSimpleString(literal) ) {
+                maybeNewline();
+                wDatatype(literal.getDatatypeURI());
+            }
+            print(">");
+            String escLex = 
Util.substituteEntitiesInElementContent(literal.getLexicalForm());
+            print(escLex);
+        } else {
+            throw new JenaException("Triple terms not supported in RDF/XML");
+        }
+
+        // End property.
+        print("</");
+        wtype.wTypeEnd(property);
+        print(">");
     }
 
     /*
@@ -403,9 +614,10 @@ class Unparser {
         // Conditions
         if (!(val instanceof Resource))
             return false;
+
         Resource r = (Resource) val;
         if ( r.isStatementTerm() )
-            throw new JenaException("Triple terms not supported in RDF/XML");
+            return false;
 
         if (!(allPropsAreAttr(r) || doing.contains(r)))
             return false;
@@ -497,8 +709,7 @@ class Unparser {
     /*
      * [6.12.3] propertyElt ::= '<' propName idAttr? parseResource '>' 
propertyElt* '</' propName '>'
      */
-    private boolean wPropertyEltResource(WType wt, Property prop, Statement s,
-            RDFNode r) {
+    private boolean wPropertyEltResource(WType wt, Property prop, Statement s, 
RDFNode r) {
         if (prettyWriter.sParseTypeResourcePropertyElt)
             return false;
         if (r instanceof Literal)
@@ -529,8 +740,7 @@ class Unparser {
     /*
      * [6.12] propertyElt ::= '<' propName idAttr? '>' value '</' propName '>'
      */
-    private boolean wPropertyEltValue(WType wt, Property prop, Statement s,
-            RDFNode r) {
+    private boolean wPropertyEltValue(WType wt, Property prop, Statement s, 
RDFNode r) {
         return wPropertyEltValueString(wt, prop, s, r)
                 || wPropertyEltValueObj(wt, prop, s, r);
     }
@@ -538,19 +748,16 @@ class Unparser {
     /*
      * [6.12] propertyElt ::= '<' propName idAttr? '>' value '</' propName '>'
      */
-    private boolean wPropertyEltValueString(WType wt, Property prop,
-            Statement s, RDFNode r) {
+    private boolean wPropertyEltValueString(WType wt, Property prop, Statement 
s, RDFNode r) {
         if (r instanceof Literal) {
             done(s);
             Literal lt = (Literal) r;
-            String lang = lt.getLanguage();
             tab();
             print("<");
             wt.wTypeStart(prop);
             wIdAttrReified(s);
             maybeNewline();
-            if (lang != null && lang.length() > 0)
-                print(" xml:lang=" + q(lang));
+            wLangAndBaseDirection(lt);
             maybeNewline();
             print(">");
             wValueString(lt);
@@ -563,6 +770,23 @@ class Unparser {
 
     }
 
+    private void wLangAndBaseDirection(Literal literal) {
+        String lang = literal.getLanguage();
+        if ( lang == null || lang.isEmpty() )
+            return;
+        String baseDir = literal.getBaseDirection();
+//        if ( baseDir != null ) {
+//            if  ( itsInsertNs )
+//                print(format(" xmlns:%s=\"%s\"", itsPrefix, ITS.uri));
+//        }
+        print(" xml:lang=" + q(lang));
+        if ( baseDir != null ) {
+            // done in rdf:RDFs
+            //print(format(" %s:%s=%s", itsPrefix, ITS.version, q("2.0")));
+            print(format(" %s:%s=%s", itsPrefix, ITS.dir, q(baseDir)));
+        }
+    }
+
     /*
      * [6.17.2] value ::= string
      */
@@ -575,8 +799,7 @@ class Unparser {
      * [6.12] propertyElt ::= '<' propName idAttr? '>' value '</' propName '>'
      * [6.17.1] value ::= obj
      */
-    private boolean wPropertyEltValueObj(WType wt, Property prop, Statement s,
-            RDFNode r) {
+    private boolean wPropertyEltValueObj(WType wt, Property prop, Statement s, 
RDFNode r) {
         if (r instanceof Resource && !prettyWriter.sResourcePropertyElt) {
             Resource res = (Resource) r;
             done(s);
@@ -603,8 +826,7 @@ class Unparser {
     /*
      *  '<' propName idAttr? parseCollection '>' obj* '</' propName '>'
      */
-    private boolean wPropertyEltCollection(WType wt, Property prop,
-            Statement s, RDFNode r) {
+    private boolean wPropertyEltCollection(WType wt, Property prop, Statement 
s, RDFNode r) {
         Statement list[][] = getRDFList(r);
         if (list == null)
             return false;
@@ -837,8 +1059,7 @@ class Unparser {
      * [6.13.2] typedNode ::=
      *       '<' typeName idAboutAttr? bagIdAttr? propAttr*'>' propertyElt* 
'</' typeName '>'
      */
-    private boolean wTypedNodeOrDescriptionLong(WType wt, Resource ty,
-            Resource r, List<Statement> li) {
+    private boolean wTypedNodeOrDescriptionLong(WType wt, Resource ty, 
Resource r, List<Statement> li) {
         Iterator<Statement> it = li.iterator();
         while (it.hasNext()) {
             done(it.next());
@@ -924,7 +1145,6 @@ class Unparser {
 
         }
         return false;
-
     }
 
     /*
@@ -1211,6 +1431,11 @@ class Unparser {
         if (!r.isAnon())
             return false;
         Integer v = objectTable.get(r);
+//        if ( v == null )
+//            return true;
+//        return !prettyWriter.sResourcePropertyElt &&
+//               v.intValue() <= 1 &&
+//               !haveReified.contains(r);
         return v == null
                 || ((!prettyWriter.sResourcePropertyElt) && v.intValue() <= 1 
&& (!haveReified
                         .contains(r)));
@@ -1501,11 +1726,9 @@ class Unparser {
     }
 
     /**
-     * @param n
-     *            The value of some rdf:type (precondition).
+     * @param n The value of some rdf:type (precondition).
      * @return The split point or -1.
      */
-
     private int isOKType(RDFNode n) {
 
         if (!(n instanceof Resource))
diff --git a/jena-arq/src/test/java/org/apache/jena/system/TS_System.java 
b/jena-core/src/main/java/org/apache/jena/vocabulary/ITS.java
similarity index 62%
copy from jena-arq/src/test/java/org/apache/jena/system/TS_System.java
copy to jena-core/src/main/java/org/apache/jena/vocabulary/ITS.java
index cb22b2c717..72e7f5ffc3 100644
--- a/jena-arq/src/test/java/org/apache/jena/system/TS_System.java
+++ b/jena-core/src/main/java/org/apache/jena/vocabulary/ITS.java
@@ -19,23 +19,21 @@
  *   SPDX-License-Identifier: Apache-2.0
  */
 
-package org.apache.jena.system;
+package org.apache.jena.vocabulary;
 
-import org.junit.platform.suite.api.SelectClasses;
-import org.junit.platform.suite.api.Suite;
+/**
+ * See <a href="https://www.w3.org/TR/its20/";>Internationalization Tag Set 
(ITS) Version 2.0</a>
+ */
+public class ITS {
+    /**
+     * The namespace as a string
+     * NB the namespace does not end in '/' or '#'.
+     */
+    public static final String uri = "http://www.w3.org/2005/11/its";;
 
-@Suite
-@SelectClasses({
-    TestCounter.class
-    , TestThreadAction.class
-    , TestTxnLifecycle.class
-    , TestTxnOp.class
-    , TestTxn.class
-    , TestTxnThread.class
-    , TestReadXML.class
-    , TestPrefixes.class
-    , TestPrefixLib.class
-    , TestRDFStarTranslation.class
-})
+    // There are no resources in this vocabulary.
+    // ITS = Internationalization Tag Set
 
-public class TS_System {}
+    public static final String version = "version";
+    public static final String dir = "dir";
+}
diff --git 
a/jena-core/src/test/java/org/apache/jena/rdfxml/xmloutput/TestXMLAbbrev.java 
b/jena-core/src/test/java/org/apache/jena/rdfxml/xmloutput/TestXMLAbbrev.java
index f45b865896..ea96b64ca6 100644
--- 
a/jena-core/src/test/java/org/apache/jena/rdfxml/xmloutput/TestXMLAbbrev.java
+++ 
b/jena-core/src/test/java/org/apache/jena/rdfxml/xmloutput/TestXMLAbbrev.java
@@ -140,7 +140,7 @@ public class TestXMLAbbrev extends XMLOutputTestBase
     {
         check("testing/abbreviated/cookup.rdf",
               null,
-              "j.cook.up",
+              "(j\\.fixup|j\\.cook\\.up)",
               Change.blockRules( "" )
                 );
     }
diff --git 
a/jena-core/src/test/java/org/apache/jena/rdfxml/xmloutput/TestXMLFeatures.java 
b/jena-core/src/test/java/org/apache/jena/rdfxml/xmloutput/TestXMLFeatures.java
index 02d749e2e1..f1c8a7450c 100644
--- 
a/jena-core/src/test/java/org/apache/jena/rdfxml/xmloutput/TestXMLFeatures.java
+++ 
b/jena-core/src/test/java/org/apache/jena/rdfxml/xmloutput/TestXMLFeatures.java
@@ -188,7 +188,7 @@ public class TestXMLFeatures extends XMLOutputTestBase {
 
        public void testRDFDefaultNamespace() throws IOException {
                check(file1, "xmlns=['\"]" + RDF.getURI() + "['\"].*"
-                               + "xmlns:j.cook.up=['\"]" + RDF.getURI() + 
"['\"]", Change
+                               + "xmlns:(j\\.cook.up|j\\.fixup)=['\"]" + 
RDF.getURI() + "['\"]", Change
                                .setPrefix("", RDF.getURI()));
        }
 

Reply via email to