11package eu .neverblink .jelly .cli .command .sparql
22
33import caseapp .*
4- import com .google .protobuf .InvalidProtocolBufferException
4+ import com .google .protobuf .{ InvalidProtocolBufferException , TextFormat }
55import eu .neverblink .jelly .cli .*
66import eu .neverblink .jelly .cli .command .sparql .util .SparqlFormat
7+ import eu .neverblink .jelly .cli .util .io .ProtoText
78import eu .neverblink .jelly .core .{RdfProtoDeserializationError , RdfProtoSerializationError }
9+ import eu .neverblink .jelly .core .proto .v1 .sparql .SparqlResultsFrame
10+ import eu .neverblink .jelly .core .sparql .JellySparqlIoUtils
811import org .apache .jena .riot .{RIOT , RiotException }
912import org .apache .jena .riot .resultset .{ResultSetReaderRegistry , ResultSetWriterRegistry }
1013
11- import java .io .{InputStream , OutputStream }
14+ import java .io .{BufferedReader , InputStream , InputStreamReader , OutputStream }
15+ import java .nio .charset .StandardCharsets .UTF_8
16+ import scala .util .Using
1217
1318/** Common logic for the two SPARQL result set conversion commands.
1419 */
@@ -51,13 +56,15 @@ abstract class SparqlSerDesCommand[T <: HasJellyCommandOptions: {Parser, Help}]
5156 outputStream : OutputStream ,
5257 ): Unit =
5358 try {
54- val context = RIOT .getContext.copy()
55- val reader = ResultSetReaderRegistry .getFactory(from.jenaLang).create(from.jenaLang)
56- val writer = ResultSetWriterRegistry .getFactory(to.jenaLang).create(to.jenaLang)
57- val result = reader.readAny(inputStream, context)
58- if result.isBoolean then
59- writer.write(outputStream, result.getBooleanResult.booleanValue, context)
60- else writer.write(outputStream, result.getResultSet, context)
59+ (from, to) match
60+ case (SparqlFormat .JellySparql , SparqlFormat .JellySparqlText ) =>
61+ jellyBinaryToText(inputStream, outputStream)
62+ case (SparqlFormat .JellySparqlText , SparqlFormat .JellySparql ) =>
63+ jellyTextToBinary(inputStream, outputStream)
64+ case (f : SparqlFormat .Jena , t : SparqlFormat .Jena ) =>
65+ jenaConvert(f, t, inputStream, outputStream)
66+ case _ =>
67+ throw CriticalException (f " Conversion from $from to $to is not supported. " )
6168 outputStream.flush()
6269 } catch
6370 // The Jelly RowSet reader wraps I/O errors (including protobuf ones) in a RiotException,
@@ -72,3 +79,61 @@ abstract class SparqlSerDesCommand[T <: HasJellyCommandOptions: {Parser, Help}]
7279 throw JellyDeserializationError (e.getMessage)
7380 case e : RdfProtoSerializationError =>
7481 throw JellySerializationError (e.getMessage)
82+ case e : TextFormat .ParseException =>
83+ throw InvalidJellyFile (e)
84+
85+ private def jenaConvert (
86+ from : SparqlFormat .Jena ,
87+ to : SparqlFormat .Jena ,
88+ inputStream : InputStream ,
89+ outputStream : OutputStream ,
90+ ): Unit =
91+ val context = RIOT .getContext.copy()
92+ val reader = ResultSetReaderRegistry .getFactory(from.jenaLang).create(from.jenaLang)
93+ val writer = ResultSetWriterRegistry .getFactory(to.jenaLang).create(to.jenaLang)
94+ val result = reader.readAny(inputStream, context)
95+ if result.isBoolean then
96+ writer.write(outputStream, result.getBooleanResult.booleanValue, context)
97+ else writer.write(outputStream, result.getResultSet, context)
98+
99+ private val frameCommentPrefix = " # Frame"
100+
101+ private def jellyBinaryToText (inputStream : InputStream , outputStream : OutputStream ): Unit =
102+ val response = JellySparqlIoUtils .autodetectDelimiting(inputStream)
103+ val input = response.newInput()
104+ val frames =
105+ if response.isDelimited then
106+ Iterator .continually(SparqlResultsFrame .parseDelimitedFrom(input)).takeWhile(_ != null )
107+ else Iterator (SparqlResultsFrame .parseFrom(input))
108+ for (frame, i) <- frames.zipWithIndex do
109+ outputStream.write(f " $frameCommentPrefix $i\n " .getBytes(UTF_8 ))
110+ val text = ProtoText .print(SparqlResultsFrame .getDescriptor, frame.toByteArray)
111+ outputStream.write(text.getBytes(UTF_8 ))
112+
113+ /** Frames are split on the "# Frame" comments. Without them, the whole input is one frame. */
114+ private def jellyTextToBinary (inputStream : InputStream , outputStream : OutputStream ): Unit =
115+ if ! isQuietMode then
116+ printLine(
117+ " WARNING: The Jelly-SPARQL text format is not stable and may change in incompatible " +
118+ " ways in the future.\n It's only intended for testing and development.\n " +
119+ " NEVER use it in production.\n Use --quiet to silence this warning." ,
120+ true ,
121+ )
122+ def writeFrame (text : String ): Unit =
123+ ProtoText .parse(SparqlResultsFrame .getDescriptor, text).writeDelimitedTo(outputStream)
124+
125+ Using .resource(BufferedReader (InputStreamReader (inputStream, UTF_8 ))) { reader =>
126+ val buffer = StringBuilder ()
127+ var hasContent = false
128+ Iterator .continually(reader.readLine()).takeWhile(_ != null ).foreach { line =>
129+ if line.startsWith(frameCommentPrefix) then
130+ if hasContent then writeFrame(buffer.toString)
131+ buffer.clear()
132+ hasContent = false
133+ else
134+ buffer.append(line).append('\n ' )
135+ val trimmed = line.trim
136+ if trimmed.nonEmpty && ! trimmed.startsWith(" #" ) then hasContent = true
137+ }
138+ if hasContent then writeFrame(buffer.toString)
139+ }
0 commit comments