You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by an...@apache.org on 2013/01/18 18:42:53 UTC

svn commit: r1435266 - in /jena/Experimental/riot-output: ./ src/ src/main/ src/main/java/ src/main/java/dev/ src/main/java/out/ src/main/resources/ src/test/ src/test/java/ src/test/resources/

Author: andy
Date: Fri Jan 18 17:42:53 2013
New Revision: 1435266

URL: http://svn.apache.org/viewvc?rev=1435266&view=rev
Log:
Experimentation for better output routines.
Part 1 : a streaming writer that is eaier to read than the usual N-triples/N-quads.

Added:
    jena/Experimental/riot-output/.classpath
    jena/Experimental/riot-output/.project
    jena/Experimental/riot-output/D.ttl
    jena/Experimental/riot-output/src/
    jena/Experimental/riot-output/src/main/
    jena/Experimental/riot-output/src/main/java/
    jena/Experimental/riot-output/src/main/java/dev/
    jena/Experimental/riot-output/src/main/java/dev/OutputMain.java
    jena/Experimental/riot-output/src/main/java/dev/PrintTurtleStreamRDF.java
    jena/Experimental/riot-output/src/main/java/out/
    jena/Experimental/riot-output/src/main/java/out/EscapeStr.java
    jena/Experimental/riot-output/src/main/java/out/NodeFmtLib.java
    jena/Experimental/riot-output/src/main/java/out/NodeFormatter.java
    jena/Experimental/riot-output/src/main/java/out/NodeFormatterBase.java
    jena/Experimental/riot-output/src/main/java/out/NodeFormatterNT.java
    jena/Experimental/riot-output/src/main/java/out/NodeFormatterTTL.java
    jena/Experimental/riot-output/src/main/java/out/OutputUtils.java
    jena/Experimental/riot-output/src/main/resources/
    jena/Experimental/riot-output/src/test/
    jena/Experimental/riot-output/src/test/java/
    jena/Experimental/riot-output/src/test/resources/

Added: jena/Experimental/riot-output/.classpath
URL: http://svn.apache.org/viewvc/jena/Experimental/riot-output/.classpath?rev=1435266&view=auto
==============================================================================
--- jena/Experimental/riot-output/.classpath (added)
+++ jena/Experimental/riot-output/.classpath Fri Jan 18 17:42:53 2013
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+  <classpathentry including="**/*.java" kind="src" path="src/main/java"/>
+  <classpathentry including="**/*.java" kind="src" path="src/test/java"/>
+  <classpathentry excluding="**/*.java" kind="src" path="src/main/resources"/>
+  <classpathentry excluding="**/*.java" kind="src" path="src/test/resources"/>
+
+  <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
+  <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
+
+  <classpathentry kind="output" path="classes"/>
+
+  <classpathentry combineaccessrules="false" kind="src" path="/jena-arq"/>
+  <classpathentry combineaccessrules="false" kind="src" path="/jena-core"/>
+
+  <classpathentry kind="lib" path="/home/afs/.m2/repo/org/apache/jena/jena-iri/0.9.5-SNAPSHOT/jena-iri-0.9.5-SNAPSHOT.jar"/>
+  <classpathentry combineaccessrules="false" kind="src" path="/jena-tdb"/>
+  <classpathentry kind="var" path="M2_REPO/org/slf4j/slf4j-api/1.6.4/slf4j-api-1.6.4.jar" sourcepath="M2_REPO/org/slf4j/slf4j-api/1.6.4/slf4j-api-1.6.4-sources.jar"/>
+  <classpathentry kind="var" path="M2_REPO/org/slf4j/slf4j-log4j12/1.6.4/slf4j-log4j12-1.6.4.jar" sourcepath="M2_REPO/org/slf4j/slf4j-log4j12/1.6.4/slf4j-log4j12-1.6.4-sources.jar"/>
+  <classpathentry kind="var" path="M2_REPO/log4j/log4j/1.2.16/log4j-1.2.16.jar" sourcepath="M2_REPO/log4j/log4j/1.2.16/log4j-1.2.16-sources.jar"/>
+  <classpathentry kind="var" path="M2_REPO/org/slf4j/jcl-over-slf4j/1.6.4/jcl-over-slf4j-1.6.4.jar" sourcepath="M2_REPO/org/slf4j/jcl-over-slf4j/1.6.4/jcl-over-slf4j-1.6.4-sources.jar"/>
+</classpath>

Added: jena/Experimental/riot-output/.project
URL: http://svn.apache.org/viewvc/jena/Experimental/riot-output/.project?rev=1435266&view=auto
==============================================================================
--- jena/Experimental/riot-output/.project (added)
+++ jena/Experimental/riot-output/.project Fri Jan 18 17:42:53 2013
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>riot-output</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>

Added: jena/Experimental/riot-output/D.ttl
URL: http://svn.apache.org/viewvc/jena/Experimental/riot-output/D.ttl?rev=1435266&view=auto
==============================================================================
--- jena/Experimental/riot-output/D.ttl (added)
+++ jena/Experimental/riot-output/D.ttl Fri Jan 18 17:42:53 2013
@@ -0,0 +1,6 @@
+@prefix : <http://example/> .
+@prefix rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs:    <http://www.w3.org/2000/01/rdf-schema#> .
+
+:s :p :o .
+:s :p (1 2) .

Added: jena/Experimental/riot-output/src/main/java/dev/OutputMain.java
URL: http://svn.apache.org/viewvc/jena/Experimental/riot-output/src/main/java/dev/OutputMain.java?rev=1435266&view=auto
==============================================================================
--- jena/Experimental/riot-output/src/main/java/dev/OutputMain.java (added)
+++ jena/Experimental/riot-output/src/main/java/dev/OutputMain.java Fri Jan 18 17:42:53 2013
@@ -0,0 +1,38 @@
+/*
+ * 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
+ *
+ *     http://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.
+ */
+
+package dev;
+
+
+import org.apache.jena.riot.RDFDataMgr ;
+import org.apache.jena.riot.system.StreamRDF ;
+
+public class OutputMain
+{
+    public static void main(String[] args)
+    {
+        StreamRDF dst = new PrintTurtleStreamRDF(System.out);
+        RDFDataMgr.parse(dst, "D.ttl") ;
+        System.out.println("--------") ;
+        dst = new PrintTurtleStreamRDF(System.out);
+        RDFDataMgr.parse(dst, "D.trig") ;
+        
+        System.out.println("DONE") ;
+        System.exit(0) ;
+    }
+}

Added: jena/Experimental/riot-output/src/main/java/dev/PrintTurtleStreamRDF.java
URL: http://svn.apache.org/viewvc/jena/Experimental/riot-output/src/main/java/dev/PrintTurtleStreamRDF.java?rev=1435266&view=auto
==============================================================================
--- jena/Experimental/riot-output/src/main/java/dev/PrintTurtleStreamRDF.java (added)
+++ jena/Experimental/riot-output/src/main/java/dev/PrintTurtleStreamRDF.java Fri Jan 18 17:42:53 2013
@@ -0,0 +1,236 @@
+/**
+ * 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
+ *
+ *     http://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.
+ */
+
+package dev;
+
+import java.io.OutputStream ;
+
+import org.apache.jena.atlas.io.IndentedWriter ;
+import static org.apache.jena.atlas.lib.Lib.equal ;
+import org.apache.jena.atlas.lib.Tuple ;
+import org.apache.jena.riot.out.NodeToLabel ;
+import org.apache.jena.riot.system.PrefixMap ;
+import org.apache.jena.riot.system.StreamRDF ;
+import out.NodeFormatterTTL ;
+
+import com.hp.hpl.jena.graph.Node ;
+import com.hp.hpl.jena.graph.Triple ;
+import com.hp.hpl.jena.sparql.core.Quad ;
+
+class PrintTurtleStreamRDF implements StreamRDF
+{
+    private final PrefixMap pMap ;
+    private final NodeFormatterTTL fmt ;
+    private final IndentedWriter out ;
+    
+    private static final int INDENT_G = 2 ;
+    private static final int INDENT_S = 4 ;
+    
+    public PrintTurtleStreamRDF(OutputStream output)
+    { 
+        this.out = new IndentedWriter(output) ;
+        out.setFlushOnNewline(true) ;        // Debugging
+        this.pMap = new PrefixMap() ;
+        this.fmt = new NodeFormatterTTL(null, pMap, NodeToLabel.createScopeByDocument()) ; 
+    }
+
+
+    @Override
+    public void start()
+    {}
+
+    private boolean lastWasTripleData = false ;
+    private boolean lastWasQuadData = false ;
+    private boolean lastWasDirective = false ;
+    
+    private Node lastSubject = null ;
+    private Node lastGraph = null ;
+    
+    private void out(Node n)
+    {
+        fmt.format(out, n) ;
+    }
+    
+    private void reset() 
+    {
+        lastWasTripleData = false ;
+        lastWasQuadData = false ;
+        lastWasDirective = false ;
+        lastSubject = null ;
+        lastGraph = null ;
+    }
+    
+    private void printPre()
+    {
+        // Directive -> data transition.
+        if ( lastWasDirective )
+        {
+            reset() ;
+            out.print('\n') ;
+        }
+    }
+    
+    private void print(Node g, Node s, Node p, Node o)
+    {
+        printPre() ;
+
+        // End of graph
+        if ( lastSubject != null && ! equal(lastGraph, g) )
+        {
+            //out.println() ;
+            out.print(" ") ;
+            out.decIndent(INDENT_G) ;
+            out.decIndent(INDENT_S) ;
+            out.println("}") ;
+            lastSubject = null ;
+            lastGraph = null ;
+            
+        }
+        // Start graph
+        if ( lastGraph == null )
+        {
+            if ( lastSubject != null )
+            {
+                out.decIndent(INDENT_S) ;
+                out.println(" .") ;
+            }
+            lastSubject = null ;
+            if ( g != null )
+            {
+                fmt.format(out, g) ;
+                out.print(" ") ;
+            }
+            out.println("{") ;
+            out.incIndent(INDENT_G) ;
+        }
+        print$(s,p,o) ;
+        lastGraph = g ;
+        lastWasQuadData = true ;
+    }
+    
+    private void print(Node s, Node p, Node o)
+    {
+        printPre() ;
+        print$(s, p, o) ;
+        lastWasTripleData = true ;
+    }
+    
+    private void print$(Node s, Node p, Node o)
+    {
+        if ( equal(lastSubject, s) ) 
+        {
+            out.println(" ;") ;
+            fmt.format(out, p) ;
+            out.print(' ') ;
+            fmt.format(out, o) ;
+        } else {
+            // Change subject, same graph. 
+            if ( lastSubject != null )
+            {
+                // DRY
+                out.print(" .") ;
+                out.decIndent(INDENT_S) ;
+                out.println() ;
+            }
+            
+            fmt.format(out, s) ;
+            out.print(' ') ;
+            fmt.format(out, p) ;
+            out.print(' ') ;
+            fmt.format(out, o) ;
+            out.incIndent(INDENT_S) ;
+
+        }
+        lastSubject = s ;
+    }
+    
+    @Override
+    public void triple(Triple triple)
+    {
+        Node s = triple.getSubject() ;
+        Node p = triple.getPredicate() ;
+        Node o = triple.getObject() ;
+        print(s,p,o) ;
+    }
+
+    @Override
+    public void quad(Quad quad)
+    {
+        Node g = quad.getGraph() ;
+        Node s = quad.getSubject() ;
+        Node p = quad.getPredicate() ;
+        Node o = quad.getObject() ;
+        print(g, s, p, o) ;
+    }
+
+    @Override
+    public void tuple(Tuple<Node> tuple)
+    {}
+
+    @Override
+    public void base(String base)
+    {
+        
+    }
+
+    private void endData()
+    {
+        if ( lastWasTripleData )
+        {
+            out.println(" .") ;
+            out.decIndent(INDENT_S) ;
+        }
+        
+        if ( lastWasQuadData )
+        {
+            // DRY
+            out.print(" ") ;
+            out.decIndent(INDENT_G) ;
+            out.decIndent(INDENT_S) ;
+            out.println("}") ;
+        }
+        
+        reset() ;
+    }
+    
+    @Override
+    public void prefix(String prefix, String iri)
+    {
+        boolean b = lastWasTripleData || lastWasQuadData ;
+        endData() ;
+        if ( b ) 
+            out.println() ;
+        
+        lastWasDirective = true ;
+
+        out.print("@prefix ") ;
+        out.print(prefix) ;
+        out.print(":  <") ;
+        out.print(iri) ;        // Don't let it be abbreviated!
+        out.println("> .") ; 
+        pMap.add(prefix, iri) ;
+    }
+
+    @Override
+    public void finish()
+    {
+        endData() ;
+        out.flush() ;
+    }
+
+}

Added: jena/Experimental/riot-output/src/main/java/out/EscapeStr.java
URL: http://svn.apache.org/viewvc/jena/Experimental/riot-output/src/main/java/out/EscapeStr.java?rev=1435266&view=auto
==============================================================================
--- jena/Experimental/riot-output/src/main/java/out/EscapeStr.java (added)
+++ jena/Experimental/riot-output/src/main/java/out/EscapeStr.java Fri Jan 18 17:42:53 2013
@@ -0,0 +1,117 @@
+/*
+ * 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
+ *
+ *     http://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.
+ */
+
+package out;
+
+import org.apache.jena.atlas.io.IndentedLineBuffer ;
+import org.apache.jena.atlas.io.IndentedWriter ;
+
+import com.hp.hpl.jena.sparql.lang.ParserBase ;
+
+public class EscapeStr
+{
+    // Tests: TestOutput
+    // See also OutputLangUtils.outputEsc.
+    private final boolean ascii ;
+
+    public EscapeStr(boolean asciiOnly) { this.ascii = asciiOnly ; }
+
+    public void writeURI(IndentedWriter w, String s)
+    {
+        if ( ascii )
+            stringEsc(w, s, true, ascii) ;
+        else
+            // It's a URI - assume legal.
+            w.print(s) ;
+    }
+
+    public void writeStr(IndentedWriter w, String s) 
+    {
+        stringEsc(w, s, true, ascii) ;
+    }
+
+    public void writeStrMultiLine(IndentedWriter w, String s) 
+    {
+        // N-Triples does not have """
+        stringEsc(w, s, false, ascii) ;
+    }
+
+    // Utility
+    /*
+     * Escape characters in a string according to Turtle rules. 
+     */
+    public static String stringEsc(String s)
+    { return stringEsc(s, true, false) ; }
+
+    private static String stringEsc(String s, boolean singleLineString, boolean asciiOnly)
+    {
+        IndentedLineBuffer sb = new IndentedLineBuffer() ;
+        stringEsc(sb, s, singleLineString, asciiOnly) ;
+        return sb.toString() ;
+    }
+
+    public static void stringEsc(IndentedWriter out, String s, boolean singleLineString, boolean asciiOnly)
+    {
+        int len = s.length() ;
+        for (int i = 0; i < len; i++) {
+            char c = s.charAt(i);
+
+            // \\ Escape always possible.
+            if (c == '\\') 
+            {
+                out.print('\\') ;
+                out.print(c) ;
+                continue ;
+            }
+            if ( singleLineString )
+            {
+                if ( c == '"' )         { out.print("\\\""); continue ; }
+                else if (c == '\n')     { out.print("\\n");  continue ; }
+                else if (c == '\t')     { out.print("\\t");  continue ; }
+                else if (c == '\r')     { out.print("\\r");  continue ; }
+                else if (c == '\f')     { out.print("\\f");  continue ; }
+            }
+            // Not \-style esacpe. 
+            if ( c >= 32 && c < 127 )
+                out.print(c);
+            else if ( !asciiOnly )
+                out.print(c);
+            else
+            {
+                // Outside the charset range.
+                // Does not cover beyond 16 bits codepoints directly
+                // (i.e. \U escapes) but Java keeps these as surrogate
+                // pairs and will print as characters
+                out.print( "\\u") ;
+                OutputUtils.printHex(out, c, 4) ;
+            }
+        }
+    }
+
+    // Utilities to remove escapes
+
+    public static String unescapeStr(String s)
+    { return unescape(s, '\\') ; }
+    
+    // Worker function
+    public static String unescape(String s, char escape)
+    {
+        return ParserBase.unescape(s, escape, false,  -1, -1) ;
+        
+    }
+}

Added: jena/Experimental/riot-output/src/main/java/out/NodeFmtLib.java
URL: http://svn.apache.org/viewvc/jena/Experimental/riot-output/src/main/java/out/NodeFmtLib.java?rev=1435266&view=auto
==============================================================================
--- jena/Experimental/riot-output/src/main/java/out/NodeFmtLib.java (added)
+++ jena/Experimental/riot-output/src/main/java/out/NodeFmtLib.java Fri Jan 18 17:42:53 2013
@@ -0,0 +1,205 @@
+/*
+ * 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
+ *
+ *     http://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.
+ */
+
+package out;
+
+import java.net.MalformedURLException ;
+import java.util.Map ;
+
+
+import com.hp.hpl.jena.graph.Node ;
+import com.hp.hpl.jena.graph.Triple ;
+
+import org.apache.jena.atlas.io.IndentedLineBuffer ;
+import org.apache.jena.atlas.io.IndentedWriter ;
+import org.apache.jena.atlas.lib.Bytes ;
+import org.apache.jena.atlas.lib.Chars ;
+import org.apache.jena.iri.IRI ;
+import org.apache.jena.iri.IRIFactory ;
+import org.apache.jena.iri.IRIRelativize ;
+import org.apache.jena.riot.system.PrefixMap ;
+import org.apache.jena.riot.system.Prologue ;
+import org.apache.jena.riot.system.RiotChars ;
+
+import com.hp.hpl.jena.shared.PrefixMapping ;
+import com.hp.hpl.jena.sparql.ARQConstants ;
+import com.hp.hpl.jena.sparql.core.Quad ;
+
+/** Presentation utilitiles for Nodes, Triples, Quads and more */ 
+public class NodeFmtLib
+{
+    // See OutputLangUtils.
+    // See and use EscapeStr
+    
+    static PrefixMap dftPrefixMap = new PrefixMap() ;
+    static {
+        PrefixMapping pm = ARQConstants.getGlobalPrefixMap() ;
+        Map<String, String> map = pm.getNsPrefixMap() ;
+        for ( Map.Entry<String, String> e : map.entrySet() )
+            dftPrefixMap.add(e.getKey(), e.getValue() ) ;
+    }
+
+    public static String str(Triple t)
+    {
+        return strNodes(t.getSubject(), t.getPredicate(),t.getObject()) ;
+    }
+
+    public static String str(Quad q)
+    {
+        return strNodes(q.getGraph(), q.getSubject(), q.getPredicate(), q.getObject()) ;
+    }
+    
+
+    // Worker
+    public static String strNodes(Node ... nodes)
+    {
+        IndentedLineBuffer sw = new IndentedLineBuffer() ;
+        boolean first = true ;
+        for ( Node n : nodes ) 
+        {
+            if ( ! first )
+            {
+                sw.append(" ") ;
+                first = false ;
+            }
+            str(sw, n) ;
+        }
+        return sw.toString() ; 
+    }
+
+    public static String str(Node n)
+    {
+        IndentedLineBuffer sw = new IndentedLineBuffer() ;
+        str(sw, n) ;
+        return sw.toString() ; 
+    }
+
+    private static final boolean onlySafeBNodeLabels = true ;
+
+    //public static String displayStr(Node n) { return serialize(n) ; }
+
+    public static void str(IndentedWriter w, Node n)
+    { serialize(w, n, null, null) ; }
+
+    public static void serialize(IndentedWriter w, Node n, Prologue prologue)
+    { serialize(w, n, prologue.getBaseURI(), prologue.getPrefixMap()) ; }
+
+    
+    public static void serialize(IndentedWriter w, Node n, String base, PrefixMap prefixMap)
+    {
+        if ( prefixMap == null )
+            prefixMap = dftPrefixMap ;
+        NodeFormatter formatter = new NodeFormatterTTL(base, prefixMap) ;
+        formatter.format(w, n) ;
+    }
+    
+    // ---- Blank node labels.
+    
+    // Strict N-triples only allows [A-Za-z][A-Za-z0-9]
+    static char encodeMarkerChar = 'X' ;
+
+    // These two form a pair to convert bNode labels to a safe (i.e. legal N-triples form) and back agains. 
+    
+    // Encoding is:
+    // 1 - Add a Letter 
+    // 2 - Hexify, as Xnn, anything outside ASCII A-Za-z0-9
+    // 3 - X is encoded as XX
+    
+    private static char LabelLeadingLetter = 'B' ; 
+    
+    public static String encodeBNodeLabel(String label)
+    {
+        StringBuilder buff = new StringBuilder() ;
+        // Must be at least one char and not a digit.
+        buff.append(LabelLeadingLetter) ;
+        
+        for ( int i = 0 ; i < label.length() ; i++ )
+        {
+            char ch = label.charAt(i) ;
+            if ( ch == encodeMarkerChar )
+            {
+                buff.append(ch) ;
+                buff.append(ch) ;
+            }
+            else if ( RiotChars.isA2ZN(ch) )
+                buff.append(ch) ;
+            else
+                Chars.encodeAsHex(buff, encodeMarkerChar, ch) ;
+        }
+        return buff.toString() ;
+    }
+
+    // Assumes that blank nodes only have characters in the range of 0-255
+    public static String decodeBNodeLabel(String label)
+    {
+        StringBuilder buffer = new StringBuilder() ;
+
+        if ( label.charAt(0) != LabelLeadingLetter )
+            return label ;
+        
+        // Skip first.
+        for ( int i = 1; i < label.length(); i++ )
+        {
+            char ch = label.charAt(i) ;
+            
+            if ( ch != encodeMarkerChar )
+            {
+                buffer.append(ch) ;
+            }
+            else
+            {
+                // Maybe XX or Xnn.
+                char ch2 = label.charAt(i+1) ;
+                if ( ch2 == encodeMarkerChar )
+                {
+                    i++ ;
+                    buffer.append(ch) ;
+                    continue ;
+                }
+                
+                // Xnn
+                i++ ;
+                char hiC = label.charAt(i) ;
+                int hi = Bytes.hexCharToInt(hiC) ;
+                i++ ;
+                char loC = label.charAt(i) ;
+                int lo = Bytes.hexCharToInt(loC) ;
+
+                int combined = ((hi << 4) | lo) ;
+                buffer.append((char) combined) ;
+            }
+        }
+
+        return buffer.toString() ;
+    }
+    
+    // ---- Relative URIs.
+    
+    static private int relFlags = IRIRelativize.SAMEDOCUMENT | IRIRelativize.CHILD ;
+    static public String abbrevByBase(String uri, String base)
+    {
+        if ( base == null )
+            return null ;
+        IRI baseIRI = IRIFactory.jenaImplementation().construct(base) ;
+        IRI rel = baseIRI.relativize(uri, relFlags) ;
+        String r = null ;
+        try { r = rel.toASCIIString() ; }
+        catch (MalformedURLException  ex) { r = rel.toString() ; }
+        return r ;
+    }
+}

Added: jena/Experimental/riot-output/src/main/java/out/NodeFormatter.java
URL: http://svn.apache.org/viewvc/jena/Experimental/riot-output/src/main/java/out/NodeFormatter.java?rev=1435266&view=auto
==============================================================================
--- jena/Experimental/riot-output/src/main/java/out/NodeFormatter.java (added)
+++ jena/Experimental/riot-output/src/main/java/out/NodeFormatter.java Fri Jan 18 17:42:53 2013
@@ -0,0 +1,53 @@
+/*
+ * 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
+ *
+ *     http://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.
+ */
+
+package out;
+
+
+import org.apache.jena.atlas.io.IndentedWriter ;
+
+import com.hp.hpl.jena.graph.Node ;
+
+public interface NodeFormatter
+{
+    public void format(IndentedWriter w, Node n) ;
+
+    /** Node is guaranteed to be a URI node */
+    public void formatURI(IndentedWriter w, Node n) ;
+    public void formatURI(IndentedWriter w, String uriStr) ;
+    
+    public void formatVar(IndentedWriter w, Node n) ;
+    public void formatVar(IndentedWriter w, String name) ;
+    
+    /** Node is guaranteed to be a blank node */
+    public void formatBNode(IndentedWriter w, Node n) ;
+    public void formatBNode(IndentedWriter w, String label) ;
+    
+    /** Node is guaranteed to be a literal */
+    public void formatLiteral(IndentedWriter w, Node n) ;
+    
+    /** Plain string / xsd:string (RDF 1.1) */
+    public void formatLitString(IndentedWriter w, String lex) ;
+    
+    /** String with language tag */
+    public void formatLitLang(IndentedWriter w, String lex, String langTag) ;
+
+    /** Literal with datatype, not a simple literal, not an xsd:string (RDF 1.1), no language tag. */
+    public void formatLitDT(IndentedWriter w, String lex, String datatypeURI) ;
+    
+}

Added: jena/Experimental/riot-output/src/main/java/out/NodeFormatterBase.java
URL: http://svn.apache.org/viewvc/jena/Experimental/riot-output/src/main/java/out/NodeFormatterBase.java?rev=1435266&view=auto
==============================================================================
--- jena/Experimental/riot-output/src/main/java/out/NodeFormatterBase.java (added)
+++ jena/Experimental/riot-output/src/main/java/out/NodeFormatterBase.java Fri Jan 18 17:42:53 2013
@@ -0,0 +1,74 @@
+/*
+ * 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
+ *
+ *     http://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.
+ */
+
+package out;
+
+import org.apache.jena.atlas.io.IndentedWriter ;
+
+import com.hp.hpl.jena.graph.Node ;
+import com.hp.hpl.jena.sparql.ARQInternalErrorException ;
+
+/** Provide implementations of the operations of NodeFormatter in terms
+ * of core operations for each node type.
+ */
+public abstract class NodeFormatterBase implements NodeFormatter
+{
+    @Override
+    public void format(IndentedWriter w, Node n)
+    {
+        if ( n.isBlank() )
+            formatBNode(w, n) ;
+        else if ( n.isURI() )
+            formatURI(w, n) ;
+        else if ( n.isLiteral() )
+            formatLiteral(w, n) ;
+        else if ( n.isVariable() )
+            formatVar(w, n) ;
+        else if ( Node.ANY.equals(n) )
+            w.print("ANY") ;
+        else
+            throw new ARQInternalErrorException("Unknow node type: "+n) ;
+    }
+    
+    @Override
+    public void formatURI(IndentedWriter w, Node n)         { formatURI(w, n.getURI()) ; }
+
+    @Override
+    public void formatBNode(IndentedWriter w, Node n)       { formatBNode(w, n.getBlankNodeLabel()) ; }
+
+    @Override
+    public void formatLiteral(IndentedWriter w, Node n)
+    {
+        String dt = n.getLiteralDatatypeURI() ;
+        String lang = n.getLiteralLanguage() ;
+        String lex = n.getLiteralLexicalForm() ;
+        
+        if ( dt == null )
+        {
+            if ( lang == null || lang.equals("") )
+                formatLitString(w, lex) ;
+            else
+                formatLitLang(w, lex,lang) ;
+        }
+        else
+            formatLitDT(w, lex, dt) ;
+    }
+
+    @Override
+    public void formatVar(IndentedWriter w, Node n)         { formatVar(w, n.getName()) ; }
+}

Added: jena/Experimental/riot-output/src/main/java/out/NodeFormatterNT.java
URL: http://svn.apache.org/viewvc/jena/Experimental/riot-output/src/main/java/out/NodeFormatterNT.java?rev=1435266&view=auto
==============================================================================
--- jena/Experimental/riot-output/src/main/java/out/NodeFormatterNT.java (added)
+++ jena/Experimental/riot-output/src/main/java/out/NodeFormatterNT.java Fri Jan 18 17:42:53 2013
@@ -0,0 +1,86 @@
+/*
+ * 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
+ *
+ *     http://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.
+ */
+
+package out;
+
+
+import org.apache.jena.atlas.io.IndentedWriter ;
+
+public class NodeFormatterNT extends NodeFormatterBase
+{
+    // Formatting for NTriples 
+    // Turtles extends this class to intercept forms it can do better.
+
+    private final EscapeStr escapeProc ; 
+
+    public NodeFormatterNT() { this(true) ; }
+
+    protected NodeFormatterNT(boolean asciiOnly) { escapeProc = new EscapeStr(asciiOnly) ;}
+
+    @Override
+    public void formatURI(IndentedWriter w, String uriStr)
+    {
+        w.print('<') ;
+        w.print(uriStr) ;
+        w.print('>') ;
+    }
+
+    @Override
+    public void formatVar(IndentedWriter w, String name)
+    {
+        w.print('?') ;
+        w.print(name) ;
+    }
+
+    @Override
+    public void formatBNode(IndentedWriter w, String label)
+    {
+        w.print("_:") ;
+        String lab = NodeFmtLib.encodeBNodeLabel(label) ;
+        w.print(lab) ;
+    }
+
+    @Override
+    public void formatLitString(IndentedWriter w, String lex)
+    {
+        writeEscaped(w, lex) ;
+    }
+
+    private void writeEscaped(IndentedWriter w, String lex)
+    {
+        w.print('"') ;
+        escapeProc.writeStr(w, lex) ;
+        w.print('"') ;
+    }
+
+    @Override
+    public void formatLitLang(IndentedWriter w, String lex, String langTag)
+    {
+        writeEscaped(w, lex) ;
+        w.print('@') ;
+        w.print(langTag) ;
+    }
+
+    @Override
+    public void formatLitDT(IndentedWriter w, String lex, String datatypeURI)
+    {
+        writeEscaped(w, lex) ;
+        w.print("^^") ;
+        formatURI(w, datatypeURI) ;
+    }
+}

Added: jena/Experimental/riot-output/src/main/java/out/NodeFormatterTTL.java
URL: http://svn.apache.org/viewvc/jena/Experimental/riot-output/src/main/java/out/NodeFormatterTTL.java?rev=1435266&view=auto
==============================================================================
--- jena/Experimental/riot-output/src/main/java/out/NodeFormatterTTL.java (added)
+++ jena/Experimental/riot-output/src/main/java/out/NodeFormatterTTL.java Fri Jan 18 17:42:53 2013
@@ -0,0 +1,340 @@
+/*
+ * 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
+ *
+ *     http://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.
+ */
+
+package out;
+
+import java.net.MalformedURLException ;
+
+import org.apache.jena.atlas.io.IndentedWriter ;
+import org.apache.jena.atlas.lib.Pair ;
+import org.apache.jena.iri.IRI ;
+import org.apache.jena.iri.IRIFactory ;
+import org.apache.jena.iri.IRIRelativize ;
+import org.apache.jena.riot.out.NodeToLabel ;
+import org.apache.jena.riot.system.PrefixMap ;
+import org.apache.jena.riot.system.RiotChars ;
+
+import com.hp.hpl.jena.datatypes.xsd.XSDDatatype ;
+import com.hp.hpl.jena.graph.Node ;
+
+public class NodeFormatterTTL extends NodeFormatterNT
+{
+    private final NodeToLabel nodeToLabel ;
+    private final PrefixMap prefixMap ;
+    private final String baseIRI ; 
+    
+    // Replace with a single "OutputPolicy"
+    public NodeFormatterTTL(String baseIRI , PrefixMap prefixMap) //OutputPolicy outputPolicy)
+    {
+        this(baseIRI, prefixMap, NodeToLabel.createBNodeByLabelEncoded()) ;
+    }
+    
+   public NodeFormatterTTL(String baseIRI , PrefixMap prefixMap, NodeToLabel nodeToLabel)
+   {
+       super(false) ;
+       this.nodeToLabel = nodeToLabel ;
+       if ( prefixMap == null )
+           prefixMap = new PrefixMap() ;
+       this.prefixMap = prefixMap ;
+       this.baseIRI = baseIRI ;
+   }
+    
+    @Override
+    public void formatURI(IndentedWriter w, String uriStr)
+    {
+        Pair<String, String> pName = prefixMap.abbrev(uriStr) ;
+        // Check if legal
+        if ( pName != null )
+        {
+            // Check legal - need to check legal, not for illegal.
+            String pref = pName.getLeft() ;
+            String ln = pName.getRight() ;
+            if ( safeForPrefix(pref) && safeForPrefixLocalname(ln) )
+            {
+                w.print(pName.getLeft()) ;
+                w.print(':') ;
+                w.print(pName.getRight()) ;
+                return ;
+            }
+        }
+
+        // Attempt base abbreviation.
+        if ( baseIRI != null )
+        {
+            String x = abbrevByBase(uriStr, baseIRI) ;
+            if ( x != null )
+            {
+                w.print('<') ;
+                w.print(x) ;
+                w.print('>') ;
+                return ;
+            }
+        }
+
+        // else
+        super.formatURI(w, uriStr) ;
+    }
+    
+    static private int relFlags = IRIRelativize.SAMEDOCUMENT | IRIRelativize.CHILD ;
+    static private String abbrevByBase(String uri, String base)
+    {
+        if ( base == null )
+            return null ;
+        IRI baseIRI = IRIFactory.jenaImplementation().construct(base) ;
+        IRI rel = baseIRI.relativize(uri, relFlags) ;
+        String r = null ;
+        try { r = rel.toASCIIString() ; }
+        catch (MalformedURLException  ex) { r = rel.toString() ; }
+        return r ;
+    }
+
+    /*private-testing*/ 
+    static boolean safeForPrefix(String str)
+    {
+        int N = str.length() ;
+        if ( N == 0 ) return true ;
+        int idx = 0 ;
+        idx = skip1_PN_CHARS_BASE(str, idx) ;
+        if ( idx == -1 ) return false ;
+        idx = skipAny_PN_CHARS_or_DOT(str, idx) ;
+        if ( idx == -1 ) return false ;
+        if ( idx == N ) return true ;
+        idx = skip1_PN_CHARS(str, idx) ;
+        if ( idx == -1 ) return false ;
+        return ( idx == N ) ;
+    }
+
+//    @Override
+//    public void formatVar(IndentedWriter w, String name)
+
+//    @Override
+//    public void formatBNode(IndentedWriter w, String label)
+    
+    @Override
+    public void formatBNode(IndentedWriter w, Node n)
+    {
+        String x = nodeToLabel.get(null, n) ;
+        w.print(x) ; 
+    }
+
+//    @Override
+//    public void formatLitString(IndentedWriter w, String lex)
+
+//    @Override
+//    public void formatLitLang(IndentedWriter w, String lex, String langTag)
+
+    /*private-testing*/ static boolean safeForPrefixLocalname(String str)
+    {
+        int N = str.length() ;
+        if ( N == 0 ) return true ;
+        int idx = 0 ;
+        idx = skip1_PN_CHARS_U_or_029(str, idx) ;
+        if ( idx == -1 ) return false ;
+        idx = skipAny_PN_CHARS_or_DOT(str, idx) ;
+        if ( idx == -1 ) return false ;
+        if ( idx == N ) return true ;
+        idx = skip1_PN_CHARS(str, idx) ;
+        return ( idx == N ) ;
+    }
+
+    private static boolean is_PN_CHARS_BASE(int ch)    { return RiotChars.isAlpha(ch) ; }
+    private static boolean is_PN_CHARS_U(int ch)       { return is_PN_CHARS_BASE(ch) || ch == '_' ; }
+    private static boolean is_PN_CHARS(int ch)         { return is_PN_CHARS_U(ch) || ch == '-' || RiotChars.isDigit(ch) || isCharsExtra(ch) ; }
+    
+    public static boolean isCharsExtra(int ch)
+    {
+        return ch == '\u00B7' || RiotChars.range(ch, '\u0300', '\u036F') || RiotChars.range(ch, '\u203F', '\u2040')  ;  
+    }
+    
+    private static int skip1_PN_CHARS_U_or_029(String str, int idx)
+    {
+        char ch = str.charAt(idx) ;
+        if ( is_PN_CHARS_U(ch) ) return idx+1 ;
+        if ( RiotChars.isDigit(ch) ) return idx+1 ;
+        return -1 ;
+    }
+
+    private static int skip1_PN_CHARS_BASE(String str, int idx)
+    {
+        char ch = str.charAt(idx) ;
+        if ( is_PN_CHARS_BASE(ch) ) return idx+1 ;
+        return -1 ;
+    }
+
+    private static int skipAny_PN_CHARS_or_DOT(String str, int idx)
+    {
+        int N = str.length() ;
+        for ( int i = idx ; i < N ; i++ )
+        {
+            char ch = str.charAt(i) ;
+            if ( ! is_PN_CHARS(ch) && ch != '.' ) return i ;
+        }
+        return N ;
+    }
+
+    private static int skip1_PN_CHARS(String str, int idx)
+    {
+        char ch = str.charAt(idx) ;
+        if ( is_PN_CHARS(ch) ) return idx+1 ;
+        return -1 ;
+    }
+
+    private static final String dtDecimal   = XSDDatatype.XSDdecimal.getURI() ;
+    private static final String dtInteger   = XSDDatatype.XSDinteger.getURI() ;
+    private static final String dtDouble    = XSDDatatype.XSDdouble.getURI() ;
+    private static final String dtBoolean   = XSDDatatype.XSDboolean.getURI() ;
+
+    @Override
+    public void formatLitDT(IndentedWriter w, String lex, String datatypeURI)
+    {
+        if ( dtDecimal.equals(datatypeURI) )
+        {
+            if ( validDecimal(lex) )
+            {
+                w.print(lex) ;
+                return ;
+            }
+        }
+        else if ( dtInteger.equals(datatypeURI) )
+        {
+            if ( validInteger(lex) )
+            {
+                w.print(lex) ;
+                return ;
+            }
+        }
+        if ( dtDouble.equals(datatypeURI) )
+        {
+            if ( validDouble(lex) )
+            {
+                w.print(lex) ;
+                return ; 
+            }
+        }
+        // Boolean
+        if ( dtBoolean.equals(datatypeURI) )
+        {
+            // We leave "0" and "1" as-is assumign that if written like that, there was a reason.
+            if ( lex.equals("true") || lex.equals("false") )
+            {
+                w.print(lex) ;
+                return ; 
+            }
+        }
+        
+        // else.
+        super.formatLitDT(w, lex, datatypeURI) ;
+    }
+
+
+    private static boolean validInteger(String lex)
+    {
+        int N = lex.length() ;
+        if ( N == 0 ) return false ;
+        int idx = 0 ;
+        
+        idx = skipSign(lex, idx) ;
+        idx = skipDigits(lex, idx) ;
+        return ( idx == N ) ;
+    }
+    
+    private static boolean validDecimal(String lex)
+    {
+        // case : In N3, "." illegal, as is "+." and -." but legal in Turtle.
+        int N = lex.length() ;
+        if ( N <= 1 ) return false ;
+        int idx = 0 ;
+        
+        idx = skipSign(lex, idx) ;
+        idx = skipDigits(lex, idx) ;    // Maybe none.
+        
+        // DOT required.
+        if ( idx >= N ) return false ;
+        
+        char ch = lex.charAt(idx) ;
+        if ( ch != '.' ) return false ;
+        idx++ ;
+        // Digit required.
+        if ( idx >= N ) return false ;
+        idx = skipDigits(lex, idx) ;
+        return ( idx == N )  ;
+    }
+    
+    private static boolean validDouble(String lex)
+    {
+        int N = lex.length() ;
+        if ( N == 0 ) return false ;
+        int idx = 0 ;
+        
+        // Decimal part (except 12. is legal)
+        
+        idx = skipSign(lex, idx) ;
+        
+        int idx2 = skipDigits(lex, idx) ;
+        boolean initialDigits = ( idx != idx2) ;
+        idx = idx2 ;
+        // Exponent required.
+        if ( idx >= N ) return false ;
+        char ch = lex.charAt(idx) ;
+        if ( ch == '.' )
+        {
+            idx++ ;
+            if ( idx >= N ) return false ;
+            idx2 = skipDigits(lex, idx) ;
+            boolean trailingDigits = ( idx != idx2 ) ;
+            idx = idx2 ;
+            if ( idx >= N ) return false ;
+            if ( !initialDigits && !trailingDigits ) return false ;
+        }
+        // "e" or "E"
+        ch = lex.charAt(idx) ;
+        if ( ch != 'e' && ch != 'E' ) return false ;
+        idx++ ;
+        if ( idx >= N ) return false ;
+        idx = skipSign(lex, idx) ;
+        if ( idx >= N ) return false ;  // At least one digit.
+        idx = skipDigits(lex, idx) ;
+        return ( idx == N )  ;
+    }
+    
+    /** Skip digits [0-9] and return the index just after the digits,
+     * which may be beyond the length of the string.
+     * May skip zero.
+     */
+    private static int skipDigits(String str, int start)
+    {
+        int N = str.length() ;
+        for ( int i = start ; i < N ; i++ )
+        {
+            char ch = str.charAt(i) ;
+            if ( ! RiotChars.isDigit(ch) )
+                return i ;
+        } 
+        return N ;
+    }
+    
+    /** Skip any plus or minus */
+    private static int skipSign(String str, int idx)
+    {
+        int N = str.length() ;
+        char ch = str.charAt(idx) ;
+        if ( ch == '+' || ch == '-' )
+            return idx + 1 ;
+        return idx ;
+    }
+}

Added: jena/Experimental/riot-output/src/main/java/out/OutputUtils.java
URL: http://svn.apache.org/viewvc/jena/Experimental/riot-output/src/main/java/out/OutputUtils.java?rev=1435266&view=auto
==============================================================================
--- jena/Experimental/riot-output/src/main/java/out/OutputUtils.java (added)
+++ jena/Experimental/riot-output/src/main/java/out/OutputUtils.java Fri Jan 18 17:42:53 2013
@@ -0,0 +1,78 @@
+/*
+ * 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
+ *
+ *     http://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.
+ */
+
+package out;
+
+import java.io.IOException ;
+import java.io.Writer ;
+
+import org.apache.jena.atlas.io.IndentedWriter ;
+import org.apache.jena.atlas.lib.BitsInt ;
+import org.apache.jena.atlas.lib.Chars ;
+
+public class OutputUtils
+{
+    /** Print the number x in width hex chars.  x must fit */
+    public static void printHex(StringBuilder out, int x, int width)
+    {
+        for ( int i = width-1 ; i >= 0 ; i-- )
+            x = oneHex(out, x, i) ;
+    }
+
+    /** Print one hex digit of the number */
+    public static int oneHex(StringBuilder out, int x, int i)
+    {
+        int y = BitsInt.unpack(x, 4*i, 4*i+4) ;
+        char charHex = Chars.hexDigitsUC[y] ;
+        out.append(charHex) ; 
+        return BitsInt.clear(x, 4*i, 4*i+4) ;
+    }
+    
+    /** Print the number x in width hex chars.  x must fit */
+    public static void printHex(Writer out, int x, int width)
+    {
+        for ( int i = width-1 ; i >= 0 ; i-- )
+            x = oneHex(out, x, i) ;
+    }
+
+    /** Print one hex digit of the numer */
+    public static int oneHex(Writer out, int x, int i)
+    {
+        int y = BitsInt.unpack(x, 4*i, 4*i+4) ;
+        char charHex = Chars.hexDigitsUC[y] ;
+        try { out.write(charHex) ; } catch (IOException ex) {} 
+        return BitsInt.clear(x, 4*i, 4*i+4) ;
+    }
+
+    /** Print the number x in width hex chars.  x must fit */
+    public static void printHex(IndentedWriter out, int x, int width)
+    {
+        for ( int i = width-1 ; i >= 0 ; i-- )
+            x = oneHex(out, x, i) ;
+    }
+
+    /** Print one hex digit of the number */
+    public static int oneHex(IndentedWriter out, int x, int i)
+    {
+        int y = BitsInt.unpack(x, 4*i, 4*i+4) ;
+        char charHex = Chars.hexDigitsUC[y] ;
+        out.print(charHex) ; 
+        return BitsInt.clear(x, 4*i, 4*i+4) ;
+    }
+    
+}