examples: Querying an RDF triplestore using SPARQL v_0
authorFrantišek Kučera <franta-hg@frantovo.cz>
Mon, 27 Jul 2020 17:51:53 +0200
branchv_0
changeset 310 aeda3cb4528d
parent 309 71a627e72815
child 311 f677eba0c86c
examples: Querying an RDF triplestore using SPARQL
relpipe-data/examples-rdf-sparql.xml
relpipe-data/examples/rdf-blonde-and-brunette.sparql
relpipe-data/examples/rdf-blonde-and-brunette.txt
relpipe-data/examples/rdf-breakfast-club.sparql
relpipe-data/examples/rdf-breakfast-club.txt
relpipe-data/examples/rdf-coreys.sparql
relpipe-data/examples/rdf-coreys.txt
relpipe-data/examples/rdf-heathers-members.sparql
relpipe-data/examples/rdf-heathers-members.txt
relpipe-data/examples/rdf-heathers-much.sparql
relpipe-data/examples/rdf-heathers-much.txt
relpipe-data/examples/rdf-heathers-quotes.sparql
relpipe-data/examples/rdf-heathers-quotes.txt
relpipe-data/examples/rdf-heathers.sparql
relpipe-data/examples/rdf-heathers.ttl
relpipe-data/examples/rdf-heathers.txt
relpipe-data/examples/rdf-return.sparql
relpipe-data/examples/rdf-return.txt
relpipe-data/examples/rdf-sample-triples.sparql
relpipe-data/examples/rdf-sparql-interpreter.sh
relpipe-data/examples/relpipe-in-sparql.sh
relpipe-data/examples/relpipe-in-sparql.xsl
relpipe-data/makra/sparql-endpoint.xsl
relpipe-data/makra/sparql-example.xsl
relpipe-data/principles.xml
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/relpipe-data/examples-rdf-sparql.xml	Mon Jul 27 17:51:53 2020 +0200
@@ -0,0 +1,602 @@
+<stránka
+	xmlns="https://trac.frantovo.cz/xml-web-generator/wiki/xmlns/strana"
+	xmlns:m="https://trac.frantovo.cz/xml-web-generator/wiki/xmlns/makro">
+	
+	<nadpis>Querying an RDF triplestore using SPARQL</nadpis>
+	<perex>use SQL-DK with Jena JDBC driver or a custom script to gather linked data</perex>
+	<m:pořadí-příkladu>04300</m:pořadí-příkladu>
+
+	<text xmlns="http://www.w3.org/1999/xhtml">
+		
+		<p>
+			In the Resource Description Framework (<a href="https://www.w3.org/RDF/">RDF</a>) world, there are no relations.
+			The data model is quite different.
+			It is built on top of triples: subject – predicate – object.
+			Despite there are no tables (compared to relational databases), RDF is not a schema-less clutter – 
+			actually RDF has a schema (ontology, vocabulary), just differently shaped.
+			Subjects and predicates are identified by <a href="https://en.wikipedia.org/wiki/Internationalized_Resource_Identifier">IRI</a>s
+			(or formerly <a href="https://en.wikipedia.org/wiki/Uniform_Resource_Identifier">URI</a>s)
+			that are globally unique (compared to primary keys in relational databases that are almost never globally unique).
+			Objects are also identified by IRIs (and yes, one can be both subject and object) or they can be a primitive values like a text string or a number.
+		</p>
+		
+		<m:diagram orientace="vodorovně">
+			node [fontname = "Latin Modern Sans, sans-serif"];
+			edge [fontname = "Latin Modern Sans, sans-serif"];
+			subject	->	object [ label = "predicate"];
+		</m:diagram>
+		
+		<p>
+			This <em>triple</em> is also called a <em>statement</em>.
+			In the following statement:
+		</p>
+		
+		<blockquote>
+			<m:name/> tools are released under the GNU GPL license.
+		</blockquote>
+		
+		<p>we recognize:</p>
+		
+		<ul>
+			<li>
+				Subject: <i>					
+					<m:name/> tools</i>
+			</li>
+			<li>Predicate: <i>is released under license</i></li>
+			<li>Object: <i>GNU GPL</i></li>
+		</ul>
+		
+		<p>
+			This data model is seemingly simple: just a graph, two kinds of nodes and edges connecting them together.
+			Or a flat list of statements (triples).
+			But it can be also very complicated, depending on how we use it and how rich ontologies we design.
+			RDF can be studied for years and is a great topic for diploma thesis and dissertations,
+			but in this example, we will keep it as simple as possible.
+		</p>
+		
+		<p>
+			Collections of statements are stored in special databases called triplestores.
+			The data inside can be queried using the 
+			<a href="https://www.w3.org/TR/sparql11-overview/">SPARQL</a> language through the endpoint provided by the triplestore.
+			Popular implementations are 
+			<a href="https://jena.apache.org/">Jena</a>,
+			<a href="http://vos.openlinksw.com/owiki/wiki/VOS">Virtuoso</a> and
+			<a href="https://rdf4j.org/about/">RDF4J</a>
+			(all free software).
+		</p>
+		
+		<p>
+			Relational model can be easily mapped to RDF.
+			We can just simply add a prefix to the primary keys to make them globally unique IRIs.
+			The attributes will become predicates (also prefixed).
+			And the values will become objects (either primitive values or IRIs in case of foreign keys).
+			Of course, more complex transformation can be done – this is the most straightforward way.
+		</p>
+		
+		<p>
+			Mapping RDF data to relational model is bit more difficult.
+			Sometimes easy, sometimes very cumbersome.
+			We can always design some kind of EAV (entity – attribute – value) model in the relational database
+			or we can create a relation for each predicate…
+			If we do some universal automatic mapping and retain the flexibility of RDF and richness of the original ontology,
+			we usually lose the performance and simplicity of our relational queries.
+			Good mapping that will feel natural and idiomatic in the relational world and will perform well usually poses some hard work.
+		</p>
+		
+		<p>
+			But mapping mere results of a SPARQL query obtained from an RDF endpoint is a different story.
+			These results can be seen as records and processed using our relational tools,
+			stored, transformed or converted to other formats, displayed in GUI windows or safely passed to shell scripts.
+			This example shows how we can bridge the RDF and relational worlds.
+		</p>
+		
+		
+		<h2>Several ways of connecting to an RDF triplestore</h2>
+		
+		<p>
+			Currently there is no official <code>relpipe-in-rdf</code> or <code>relpipe-in-sparql</code> tool.
+			It will be probably part of some future release of <m:name/>.
+			But until then, despite this lack, we still have several options how to join the RDF world
+			and let the data from an RDF triplestore flow through our relational pipelines:
+		</p>
+		
+		<ul>
+			<li>SQL-DK + Jena JDBC driver + <code>relpipe-in-xml</code></li>
+			<li>ODBC-JDBC bridge + Jena JDBC driver + <code>relpipe-in-sql</code></li>
+			<li>A native SPARQL ODBC driver + <code>relpipe-in-sql</code></li>
+			<li>A shell script + <code>relpipe-in-csv</code> or <code>relpipe-in-xml</code></li>
+		</ul>
+		
+		<p>In this example, we will look at the first and the last option.</p>
+		
+		<h2>SQL-DK + Jena JDBC driver</h2>
+		
+		
+		<p>
+			Apache Jena is not only a triplestore,
+			it is a framework consisting of several parts
+			and provides also a special JDBC driver that is ready to use
+			(despite this <a href="https://issues.apache.org/jira/browse/JENA-1939">small bug</a>).
+			Thanks to this driver, we can use existing Java tools and run SPARQL queries instead of SQL ones.
+		</p>
+		
+		<p>
+			Such a tool that uses this standard API (JDBC)
+			is <a href="https://sql-dk.globalcode.info/">SQL-DK</a>.
+			This tool integrates well with <m:name/> because it can output results in the XML format (or alternatively the Recfile format)
+			that can be directly consumed by <code>relpipe-in-xml</code> (or alternatively <code>relpipe-in-recfile</code>).
+		</p>
+		
+		<p>First we download Jena source codes:</p>
+		
+		<m:pre jazyk="bash"><![CDATA[mkdir -p ~/src; cd ~/src
+git clone https://gitbox.apache.org/repos/asf/jena.git]]></m:pre>
+		
+		<p>
+			and apply the <a href="https://git-zaloha.frantovo.cz/gitbox.apache.org/repos/asf/jena.git/commit/?h=JENA-1939_updateCount&amp;id=bdb5439d22b80b2909258449d82fb7b5003fd64c">patch</a>
+			for abovementioned bug (if not already merged in the upstream).
+		</p>
+		
+		<p>n.b. As always when doing such experiments, we would probably run this under a separate user account or in a virtual machine.</p>
+		
+		<p>Then we will compile the JDBC driver:</p>
+		
+		<m:pre jazyk="bash"><![CDATA[cd ~/src/jena/jena-jdbc/
+mvn clean install]]></m:pre>
+
+		<p>
+			Now we will install SQL-DK (either from sources or from <code>.deb</code> or <code>.rpm</code> package)
+			and run it for the first time (which creates the configuration directory and files):
+		</p>
+		
+		<pre>sql-dk --list-databases</pre>
+		
+		<p>Then we will register the previously compiled Jena JDBC driver in the <code>~/.sql-dk/environment.sh</code></p>
+		
+		<m:pre jazyk="bash"><![CDATA[CUSTOM_JDBC=(
+	~/src/jena/jena-jdbc/jena-jdbc-driver-bundle/target/jena-jdbc-driver-bundle-*.jar
+);]]></m:pre>
+
+		<p>And we should see it among other drivers:</p>
+		
+		<pre><![CDATA[$ sql-dk --list-jdbc-drivers 
+ ╭──────────────────────────────────────────────────┬───────────────────┬─────────────────┬─────────────────┬──────────────────────────╮
+ │ class                                  (VARCHAR) │ version (VARCHAR) │ major (INTEGER) │ minor (INTEGER) │ jdbc_compliant (BOOLEAN) │
+ ├──────────────────────────────────────────────────┼───────────────────┼─────────────────┼─────────────────┼──────────────────────────┤
+ │ org.postgresql.Driver                            │ 9.4               │               9 │               4 │                    false │
+ │ com.mysql.jdbc.Driver                            │ 5.1               │               5 │               1 │                    false │
+ │ org.sqlite.JDBC                                  │ 3.25              │               3 │              25 │                    false │
+ │ org.apache.jena.jdbc.mem.MemDriver               │ 1.0               │               1 │               0 │                    false │
+ │ org.apache.jena.jdbc.remote.RemoteEndpointDriver │ 1.0               │               1 │               0 │                    false │
+ │ org.apache.jena.jdbc.tdb.TDBDriver               │ 1.0               │               1 │               0 │                    false │
+ ╰──────────────────────────────────────────────────┴───────────────────┴─────────────────┴─────────────────┴──────────────────────────╯
+Record count: 6]]></pre>
+
+		<p>The driver seems present so we can configure the connection in the <code>~/.sql-dk/config.xml</code> file:</p>
+		
+		<m:pre jazyk="xml"><![CDATA[<database>
+	<name>rdf-dbpedia</name>
+	<url>jdbc:jena:remote:query=http://dbpedia.org/sparql</url>
+	<userName></userName>
+	<password></password>
+</database>]]></m:pre>
+
+		<p>
+			This will connect us to the DBpedia endpoint (more datasources are mentioned in the chapter below).
+			We can test the connection:
+		</p>
+
+		<pre><![CDATA[$ sql-dk --test-connection rdf-dbpedia 
+ ╭─────────────────────────┬──────────────────────┬─────────────────────┬────────────────────────┬───────────────────────────╮
+ │ database_name (VARCHAR) │ configured (BOOLEAN) │ connected (BOOLEAN) │ product_name (VARCHAR) │ product_version (VARCHAR) │
+ ├─────────────────────────┼──────────────────────┼─────────────────────┼────────────────────────┼───────────────────────────┤
+ │ rdf-dbpedia             │                 true │                true │                        │                           │
+ ╰─────────────────────────┴──────────────────────┴─────────────────────┴────────────────────────┴───────────────────────────╯
+Record count: 1]]></pre>
+
+		<p>and run our first SPARQL query:</p>
+
+		<pre><![CDATA[$ sql-dk --db rdf-dbpedia --formatter tabular-prefetching --sql "SELECT * WHERE { ?subject ?predicate ?object . } LIMIT 8"
+ ╭──────────────────────────────────────────────────────────────────────────────┬─────────────────────────────────────────────────┬─────────────────────────────────────────────────────────╮
+ │ subject                                         (org.apache.jena.graph.Node) │ predicate          (org.apache.jena.graph.Node) │ object                     (org.apache.jena.graph.Node) │
+ ├──────────────────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────┼─────────────────────────────────────────────────────────┤
+ │ http://www.openlinksw.com/virtrdf-data-formats#default-iid                   │ http://www.w3.org/1999/02/22-rdf-syntax-ns#type │ http://www.openlinksw.com/schemas/virtrdf#QuadMapFormat │
+ │ http://www.openlinksw.com/virtrdf-data-formats#default-iid-nullable          │ http://www.w3.org/1999/02/22-rdf-syntax-ns#type │ http://www.openlinksw.com/schemas/virtrdf#QuadMapFormat │
+ │ http://www.openlinksw.com/virtrdf-data-formats#default-iid-nonblank          │ http://www.w3.org/1999/02/22-rdf-syntax-ns#type │ http://www.openlinksw.com/schemas/virtrdf#QuadMapFormat │
+ │ http://www.openlinksw.com/virtrdf-data-formats#default-iid-nonblank-nullable │ http://www.w3.org/1999/02/22-rdf-syntax-ns#type │ http://www.openlinksw.com/schemas/virtrdf#QuadMapFormat │
+ │ http://www.openlinksw.com/virtrdf-data-formats#default                       │ http://www.w3.org/1999/02/22-rdf-syntax-ns#type │ http://www.openlinksw.com/schemas/virtrdf#QuadMapFormat │
+ │ http://www.openlinksw.com/virtrdf-data-formats#default-nullable              │ http://www.w3.org/1999/02/22-rdf-syntax-ns#type │ http://www.openlinksw.com/schemas/virtrdf#QuadMapFormat │
+ │ http://www.openlinksw.com/virtrdf-data-formats#sql-varchar                   │ http://www.w3.org/1999/02/22-rdf-syntax-ns#type │ http://www.openlinksw.com/schemas/virtrdf#QuadMapFormat │
+ │ http://www.openlinksw.com/virtrdf-data-formats#sql-varchar-nullable          │ http://www.w3.org/1999/02/22-rdf-syntax-ns#type │ http://www.openlinksw.com/schemas/virtrdf#QuadMapFormat │
+ ╰──────────────────────────────────────────────────────────────────────────────┴─────────────────────────────────────────────────┴─────────────────────────────────────────────────────────╯
+Record count: 8]]></pre>
+
+		<p>
+			Not a big fun yet, but it proves that the connection is working and we are getting some results from the endpoint.
+			We will run some more interesting queries later.
+		</p>
+
+		<p>
+			When we switch to the <code>--formatter xml</code> we can pipe the stream from SQL-DK
+			to <code>relpipe-in-xml</code> and then process it using relational tools.
+			We can also use the <code>--sql-in</code> option of SQL-DK which reads the query from STDIN (instead of from command line argument)
+			and then wrap it as a reusable script that reads SPARQL and outputs relational data:
+		</p>
+		
+		<m:pre jazyk="bash">sql-dk --db "rdf-dbpedia" --formatter "xml" --sql-in | relpipe-in-xml</m:pre>
+		
+		<p>
+			For accessing remote SPARQL endpoint this is a bit overkill with lot of dependencies (so we will use different approach in the next chapter).
+			But Jena JDBC driver is not only for accessing remote endpoints – we can use it as an embedded database,
+			either an in-memory one or regular DB backed by persistent files.
+		</p>
+		
+		<p>
+			The in-memory database loads some initial data and then operates on them.
+			So we configure such connection:
+		</p>
+		
+		<m:pre jazyk="xml"><![CDATA[<database>
+	<name>rdf-in-memory</name>
+	<url>jdbc:jena:mem:dataset=/tmp/rdf-initial-data.ttl</url>
+	<userName></userName>
+	<password></password>
+</database>]]></m:pre>
+
+		<p>It runs fine, but <a href="https://en.wikipedia.org/wiki/Turtle_(syntax)">turtles</a> are not at home:</p>
+		
+		<pre><![CDATA[$ echo > /tmp/rdf-initial-data.ttl
+$ echo "SELECT * WHERE { ?subject ?predicate ?object . }" | sql-dk --db rdf-in-memory --formatter tabular-prefetching --sql-in 
+ ╭──────────────────────────────────────┬────────────────────────────────────────┬─────────────────────────────────────╮
+ │ subject (org.apache.jena.graph.Node) │ predicate (org.apache.jena.graph.Node) │ object (org.apache.jena.graph.Node) │
+ ├──────────────────────────────────────┼────────────────────────────────────────┼─────────────────────────────────────┤
+ ╰──────────────────────────────────────┴────────────────────────────────────────┴─────────────────────────────────────╯
+Record count: 0]]></pre>
+
+		<p>
+			If we are in a desperate need of turtles and have installed any <a href="https://lv2plug.in/">LV2</a> plugins,
+			we can find some and put them in our initial data file or reconfigure the database connection:
+		</p>
+		
+		<pre><![CDATA[$ find /usr/lib -name '*.ttl' | head
+/usr/lib/lv2/fil4.lv2/manifest.ttl
+/usr/lib/lv2/fil4.lv2/fil4.ttl
+/usr/lib/ardour5/LV2/a-fluidsynth.lv2/manifest.ttl
+/usr/lib/ardour5/LV2/a-fluidsynth.lv2/a-fluidsynth.ttl
+/usr/lib/ardour5/LV2/reasonablesynth.lv2/manifest.ttl
+/usr/lib/ardour5/LV2/reasonablesynth.lv2/reasonablesynth.ttl
+/usr/lib/ardour5/LV2/a-delay.lv2/manifest.ttl
+/usr/lib/ardour5/LV2/a-delay.lv2/presets.ttl
+/usr/lib/ardour5/LV2/a-delay.lv2/a-delay.ttl
+/usr/lib/ardour5/LV2/a-eq.lv2/manifest.ttl
+
+$ cat /usr/lib/lv2/fil4.lv2/manifest.ttl > /tmp/rdf-initial-data.ttl
+$ sed s@/tmp/rdf-initial-data.ttl@/usr/lib/lv2/fil4.lv2/manifest.ttl@g -i ~/.sql-dk/config.xml]]></pre>
+
+		<p>and look through Jena/RDF/SPARQL what is inside:</p>
+			
+		<pre><![CDATA[$ echo "SELECT * WHERE { ?subject ?predicate ?object . }" | sql-dk --db rdf-in-memory --formatter xml --sql-in | relpipe-in-xml | relpipe-out-tabular 
+r1:
+ ╭───────────────────────────────────────┬─────────────────────────────────────────────────┬───────────────────────────────────────────╮
+ │ subject                      (string) │ predicate                              (string) │ object                           (string) │
+ ├───────────────────────────────────────┼─────────────────────────────────────────────────┼───────────────────────────────────────────┤
+ │ http://gareus.org/oss/lv2/fil4#ui_gl  │ http://www.w3.org/2000/01/rdf-schema#seeAlso    │ file:///usr/lib/lv2/fil4.lv2/fil4.ttl     │
+ │ http://gareus.org/oss/lv2/fil4#ui_gl  │ http://lv2plug.in/ns/extensions/ui#binary       │ file:///usr/lib/lv2/fil4.lv2/fil4UI_gl.so │
+ │ http://gareus.org/oss/lv2/fil4#ui_gl  │ http://www.w3.org/1999/02/22-rdf-syntax-ns#type │ http://lv2plug.in/ns/extensions/ui#X11UI  │
+ │ http://gareus.org/oss/lv2/fil4#mono   │ http://www.w3.org/2000/01/rdf-schema#seeAlso    │ file:///usr/lib/lv2/fil4.lv2/fil4.ttl     │
+ │ http://gareus.org/oss/lv2/fil4#mono   │ http://lv2plug.in/ns/lv2core#binary             │ file:///usr/lib/lv2/fil4.lv2/fil4.so      │
+ │ http://gareus.org/oss/lv2/fil4#mono   │ http://www.w3.org/1999/02/22-rdf-syntax-ns#type │ http://lv2plug.in/ns/lv2core#Plugin       │
+ │ http://gareus.org/oss/lv2/fil4#stereo │ http://www.w3.org/2000/01/rdf-schema#seeAlso    │ file:///usr/lib/lv2/fil4.lv2/fil4.ttl     │
+ │ http://gareus.org/oss/lv2/fil4#stereo │ http://lv2plug.in/ns/lv2core#binary             │ file:///usr/lib/lv2/fil4.lv2/fil4.so      │
+ │ http://gareus.org/oss/lv2/fil4#stereo │ http://www.w3.org/1999/02/22-rdf-syntax-ns#type │ http://lv2plug.in/ns/lv2core#Plugin       │
+ ╰───────────────────────────────────────┴─────────────────────────────────────────────────┴───────────────────────────────────────────╯
+Record count: 9]]></pre>
+
+		<p>
+			Now we can be sure that LV2 uses the Turtle format for plugin configurations,
+			which is quite ingenious and inspirational – 
+			such configuration is well structured and its options (predicates in general) have globally unique identifiers (IRIs).
+			Also plugins are identified by IRIs which is great, because it avoids name collisions.
+		</p>
+		
+		<p>
+			Let us make some own turtles.
+			Reconfigure the database connection back:
+		</p>
+
+		<pre>sed s@/usr/lib/lv2/fil4.lv2/manifest.ttl@/tmp/rdf-initial-data.ttl@g -i ~/.sql-dk/config.xml</pre>
+		
+		<p>and fill the <code>/tmp/rdf-initial-data.ttl</code> with some new data:</p>
+		
+		<m:pre jazyk="turtle"><![CDATA[<http://example.org/person/you>
+	<http://example.org/predicate/have>
+	<http://example.org/thing/nice-day> .]]></m:pre>
+	
+		<p>
+			Turtle is a simple format that contains statements.
+			Subjects, predicates and objects are separated by spaces (tabs and line-ends are here just to make it more readable for us).
+			And statements end with <i>full stop</i> like ordinary sentences.
+		</p>
+		
+		<p>
+			To avoid repeating common parts of IRIs we can declare namespace prefixes:
+		</p>
+		
+		<m:pre jazyk="turtle"><![CDATA[@prefix person:     <http://example.org/person/> .
+@prefix predicate:  <http://example.org/predicate/> .
+@prefix thing:      <http://example.org/thing/> .
+
+person:you
+	predicate:have
+		thing:nice-day .]]></m:pre>
+		
+		<p>
+			This format is very concise.
+			If we describe the same subject, we use <i>semicolon</i> to avoid repeating it.
+			And if even the predicate is the same (multiple values), we use <i>comma</i>:
+		</p>
+		
+		<m:pre jazyk="turtle"><![CDATA[@prefix person:     <http://example.org/person/> .
+@prefix predicate:  <http://example.org/predicate/> .
+@prefix thing:      <http://example.org/thing/> .
+
+person:you
+	predicate:have
+		thing:nice-day, thing:much-fun;
+	predicate:read-about
+		thing:relational-pipes .]]></m:pre>
+
+		<p>
+			Jena will parse our file and respond to our basic query with these data:
+		</p>
+
+		<pre><![CDATA[$ echo "SELECT * WHERE { ?subject ?predicate ?object . }" | sql-dk --db rdf-in-memory --formatter xml --sql-in --relation rdf_results | relpipe-in-xml | relpipe-out-tabular 
+rdf_results:
+ ╭───────────────────────────────┬─────────────────────────────────────────┬───────────────────────────────────────────╮
+ │ subject              (string) │ predicate                      (string) │ object                           (string) │
+ ├───────────────────────────────┼─────────────────────────────────────────┼───────────────────────────────────────────┤
+ │ http://example.org/person/you │ http://example.org/predicate/read-about │ http://example.org/thing/relational-pipes │
+ │ http://example.org/person/you │ http://example.org/predicate/have       │ http://example.org/thing/much-fun         │
+ │ http://example.org/person/you │ http://example.org/predicate/have       │ http://example.org/thing/nice-day         │
+ ╰───────────────────────────────┴─────────────────────────────────────────┴───────────────────────────────────────────╯
+Record count: 3]]></pre>
+
+		<p>Or if we prefer more vertical formats like Recfile:</p>
+		
+		<pre><![CDATA[$ echo "SELECT * WHERE { ?subject ?predicate ?object . }" | sql-dk --db rdf-in-memory --formatter xml --sql-in --relation rdf_results | relpipe-in-xml | relpipe-out-recfile 
+%rec: rdf_results
+
+subject: http://example.org/person/you
+predicate: http://example.org/predicate/read-about
+object: http://example.org/thing/relational-pipes
+
+subject: http://example.org/person/you
+predicate: http://example.org/predicate/have
+object: http://example.org/thing/much-fun
+
+subject: http://example.org/person/you
+predicate: http://example.org/predicate/have
+object: http://example.org/thing/nice-day]]></pre>
+
+		<p>Let us create some more data:</p>
+		
+		<m:pre jazyk="turtle" src="examples/rdf-heathers.ttl"/>
+		
+		<p>list them as statements:</p>
+		
+		<m:pre jazyk="text" src="examples/rdf-heathers.txt"/>
+		
+		<p>and run some more SPARQL queries…</p>
+		
+		<p>
+			Note:
+			<em>
+				we use <a href="https://tools.ietf.org/html/rfc4151">The tag: URI scheme</a> for our IRIs.
+				It makes URIs (IRIs) globally unique not only in space but also in time (domain owners change during time).
+				Which is great.
+				In the semantic web and linked data world, it is not common and locators (URLs) are used rather than pure identifiers (URIs, IRIs).
+				But here we want to emphasise that we work strictly with our local data
+				and make it clear that we do not depend on any on-line resources and nothing will be downloaded from remote servers.
+				And in a real project, we should use existing ontologies / vocabularies as much as possible instead of inventing new ones.
+				But we keep this example rather isolated from the complexity of the outer world and bit synthetic.
+			</em>
+		</p>
+		
+		<p>Find all quotes and names of their authors:</p>
+		<m:sparql-example name="examples/rdf-heathers-quotes"/>
+		
+		<p>List groups and counts of their members:</p>
+		<m:sparql-example name="examples/rdf-heathers-members"/>
+		
+		<p>Filter by a regular expression and list actor names rather than characters:</p>
+		<m:sparql-example name="examples/rdf-heathers-much"/>
+		
+		<p>Now imagine semantic model of Twin Peaks… How very!</p>
+		
+		<h2>Improvised relpipe-in-sparql tool</h2>
+		
+		<p>
+			Starting the JVM and creating always a new database from scratch on each query is quite… <i>heavy</i>.
+			We can keep Jena running in the background and connect to its SPARQL endpoint – or connect to any other endpoint on the internet.
+			So we will hack together a light script and name it <code>relpipe-in-sparql</code> (in some future release there will be such official tool).
+		</p>
+		
+		<p>
+			Because SPARQL endpoints accept plain HTTP requests, support besides XML also CSV and we already have <code>relpipe-in-csv</code>
+			the script can be very simple:
+		</p>
+		
+		<m:pre jazyk="bash"><![CDATA[curl \
+	--header "Accept: text/csv" \
+	--data-urlencode query="SELECT * WHERE { ?subject ?predicate ?object . } LIMIT 3" \
+	https://dbpedia.org/sparql | relpipe-in-csv | relpipe-out-tabular]]></m:pre>
+	
+		<p>
+			It becomes bit longer if we add some documentation, argument parsing and configuration:
+		</p>
+
+		
+		<m:pre jazyk="bash" src="examples/relpipe-in-sparql.sh" odkaz="ano"/>
+		
+		<p>
+			Here we have even two implementations that could be switched using the <code>RELPIPE_IN_SPARQL_IMPLEMENTATION</code> environmental variable.
+			The XML one is more powerful and can be customized (e.g. to specifically handle localized strings or add some new attributes to the relational output).
+			On the other hand, the CSV one has fewer dependencies and support streaming of long result sets (XSLT needs to load whole document first).
+		</p>
+		
+		<p>Both implementation should work:</p>
+		
+		<m:pre jazyk="bash"><![CDATA[export RELPIPE_IN_SPARQL_IMPLEMENTATION=xml
+export RELPIPE_IN_SPARQL_IMPLEMENTATION=csv
+echo 'SELECT * WHERE { ?subject ?predicate "Laura Dern"@en . } LIMIT 3' \
+	| relpipe-in-sparql \
+		--relation "jurassic" \
+		--endpoint "https://dbpedia.org/sparql" \
+	| relpipe-out-tabular]]></m:pre>
+
+		<p>and produce the same output:</p>
+
+		<pre><![CDATA[jurassic:
+ ╭────────────────────────────────────────┬────────────────────────────────────────────╮
+ │ subject                       (string) │ predicate                         (string) │
+ ├────────────────────────────────────────┼────────────────────────────────────────────┤
+ │ http://dbpedia.org/resource/Laura_Dern │ http://www.w3.org/2000/01/rdf-schema#label │
+ │ http://www.wikidata.org/entity/Q220901 │ http://www.w3.org/2000/01/rdf-schema#label │
+ │ http://dbpedia.org/resource/Laura_Dern │ http://xmlns.com/foaf/0.1/name             │
+ ╰────────────────────────────────────────┴────────────────────────────────────────────╯
+Record count: 3]]></pre>
+
+		<p>And maybe somewhere nearby in the graph we will find:</p>
+
+		<blockquote>It's a Unix System… I know this!</blockquote>
+		
+		<h2>Sources of RDF data</h2>
+		
+		<p></p>
+		
+		<p>
+			The bad news are that we are not querying the real world. 
+			We are querying an imperfect, incomplete and outdated snapshot of the reality stored in someone's database.
+			The good news are that we can improve the content of certain databases like we improve articles in Wikipedia.
+		</p>
+		
+		<p>
+			Some addresses have already <i>leaked</i> in the <code>relpipe-in-sparql --help</code> above.
+			Here is brief description of some publicly available sources of RDF data
+			that we can play with.
+		</p>
+		
+		
+		<h3>Wikidata</h3>
+		
+		<p>
+			A free and open knowledge base, a sister project of Wikipedia.
+			Anyone can use and even edit its content.
+		</p>
+
+		<m:sparql-endpoint url="https://query.wikidata.org/sparql" website-url="https://www.wikidata.org/" website-title="Wikidata"/>
+		
+		
+		<h3>DBpedia</h3>
+		
+		<p>
+			They extract structured content from the information created in various Wikimedia projects.
+			And publish this knowledge graph for everyone.
+		</p>
+		
+		<m:sparql-endpoint url="https://dbpedia.org/sparql" website-url="https://wiki.dbpedia.org/" website-title="DBpedia"/>
+		
+		<h3>Czech government</h3>
+		<p>
+			Ministries and other institutions publish some data as open data and part of them as linked open data (LOD).
+		</p>
+		
+		<m:sparql-endpoint url="https://data.gov.cz/sparql" website-url="https://data.gov.cz/english/" website-title="Open data portal of the Czech Republic"/>
+		<m:sparql-endpoint url="https://data.cssz.cz/sparql" website-url="https://data.cssz.cz/" website-title="Open data portal of the Czech Social Security Administration"/>
+		<m:sparql-endpoint url="https://cedropendata.mfcr.cz/c3lod/cedr/sparql" website-url="https://cedropendata.mfcr.cz/" website-title="Open Data CEDR III"/>
+		
+		
+		<h2>Running SPARQL queries as scripts</h2>
+		
+		<p>Besides piping SPARQL queries through <code>relpipe-in-sparql</code> like this:</p>
+		<m:pre jazyk="bash"><![CDATA[cat query.sparql | relpipe-in-sparql | relpipe-out-tabular]]></m:pre>
+		
+		<p>we can make them executable and run like a (Bash, Perl, PHP etc.) script:</p>
+		<m:pre jazyk="bash"><![CDATA[chmod +x query.sparql
+./query.sparql | relpipe-out-csv     # output in the CSV format
+./query.sparql | relpipe-out-recfile # output in the Recfile format
+./query.sparql                       # automatically appends relpipe-out-tabular to the pipeline
+]]></m:pre>
+		
+		<p>(see the <m:a href="implementation">Implementation</m:a> page for complete list of available transformations and output filters)</p>
+
+		<p>
+			We need to add the first line comment that points to the interpreter.
+			The <code>endpoint</code> and <code>relation</code> parameters
+			are optional – we can say, where this query will be executed and how the output relation will be named:
+		</p>
+		
+		<m:pre jazyk="sparql" src="examples/rdf-sample-triples.sparql" odkaz="ano"/>
+		
+		<p>
+			Environmental variables <code>RELPIPE_IN_SPARQL_ENDPOINT</code> and <code>RELPIPE_IN_SPARQL_RELATION</code>
+			can be set to override the parameters from the file.
+			All the magic is done by this (bit hackish) helper script:
+		</p>
+		
+		<m:pre jazyk="bash" src="examples/rdf-sparql-interpreter.sh" odkaz="ano"/>
+
+		<p>
+			This script requires the <code>relpipe-in-sparql</code> we put together earlier.
+			Both scripts are just examples (not part of any release yet).
+		</p>
+		
+		
+		
+		<h2>Samples of SPARQL queries</h2>
+		
+		<p>
+			<i>Hey kid, rock and roll,</i>
+			let us list the films where both Coreys starred:
+		</p>
+		
+		<m:sparql-example name="examples/rdf-coreys"/>
+		
+		<p>
+			<i>So Mercedes has scratched our Cadillac, but it was still a great night. </i>
+		</p>
+		
+		<p>Now it is time to visit our friends from the club:</p>
+		
+		<m:sparql-example name="examples/rdf-breakfast-club"/>
+		
+		<p>
+			Not only <i>pretty in pink</i>, this is true <i>wisdom</i> and we could have much fun traversing this part of the graph.
+			But let us turn the globe around… there is also a lot to see in the Eastern Bloc.
+		</p>
+		
+		<m:sparql-example name="examples/rdf-blonde-and-brunette"/>
+		
+		<p>
+			<i>
+				Dad, what is this place?
+				Where are we?
+				Is there anyone here?<br/>
+				No. Just us.
+			</i>
+		</p>
+		
+		<m:sparql-example name="examples/rdf-return"/>
+		
+		<h2>P.S.</h2>
+		<p>
+			<i>
+				If you got an impression that RDF is just a poor relational database with a single table consisting of mere three columns
+				and with freaky SQL dialect, please be assured that this example shows just a small fraction of the wonderful RDF world.
+			</i>
+		</p>
+		
+		
+	</text>
+
+</stránka>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/relpipe-data/examples/rdf-blonde-and-brunette.sparql	Mon Jul 27 17:51:53 2020 +0200
@@ -0,0 +1,29 @@
+#!/usr/bin/env rdf-sparql-interpreter.sh
+# endpoint: https://dbpedia.org/sparql
+# relation: blonde_and_brunette
+
+PREFIX foaf:    <http://xmlns.com/foaf/0.1/>
+PREFIX dbo:     <http://dbpedia.org/ontology/>
+PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX xsd:     <http://www.w3.org/2001/XMLSchema#>
+PREFIX schema:  <http://schema.org/>
+
+SELECT DISTINCT
+	?star_name
+WHERE {
+	{
+		?film rdfs:label "Ирония судьбы, или С лёгким паром!"@ru .
+		?film dbo:starring ?blonde .
+		?blonde dbo:birthYear "1941"^^xsd:gYear .
+		?blonde rdfs:label ?star_name .
+		FILTER (lang(?star_name) = "pl") .
+	}
+	UNION
+	{
+		?brunette ?born "1956-04-27"^^xsd:date .
+		?brunette schema:description "czech actress, presenter and singer"@en .
+		?brunette rdfs:label ?star_name .
+		FILTER (lang(?star_name) = "cs") .
+	}
+}
+ORDER BY ?star_name
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/relpipe-data/examples/rdf-blonde-and-brunette.txt	Mon Jul 27 17:51:53 2020 +0200
@@ -0,0 +1,8 @@
+blonde_and_brunette:
+ ╭────────────────────╮
+ │ star_name (string) │
+ ├────────────────────┤
+ │ Barbara Brylska    │
+ │ Dagmar Patrasová   │
+ ╰────────────────────╯
+Record count: 2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/relpipe-data/examples/rdf-breakfast-club.sparql	Mon Jul 27 17:51:53 2020 +0200
@@ -0,0 +1,22 @@
+#!/usr/bin/env rdf-sparql-interpreter.sh
+# endpoint: https://dbpedia.org/sparql
+# relation: breakfast_club
+
+PREFIX foaf:  <http://xmlns.com/foaf/0.1/>
+PREFIX dbo:   <http://dbpedia.org/ontology/>
+PREFIX rdfs:  <http://www.w3.org/2000/01/rdf-schema#>
+
+SELECT
+	?firstname
+	?surname
+WHERE {
+	?film a dbo:Film .
+	?film rdfs:label "The Breakfast Club"@en .
+	?film dbo:starring ?actor .
+	?actor foaf:givenName ?firstname .
+	?actor foaf:surname ?surname .
+	FILTER (lang(?firstname) = "en" && lang(?surname) = "en")
+	?actor dbo:birthYear ?born .
+	FILTER(?born > "1950"^^xsd:gYear)
+}
+ORDER BY ?firstname ?surname
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/relpipe-data/examples/rdf-breakfast-club.txt	Mon Jul 27 17:51:53 2020 +0200
@@ -0,0 +1,11 @@
+breakfast_club:
+ ╭────────────────────┬──────────────────╮
+ │ firstname (string) │ surname (string) │
+ ├────────────────────┼──────────────────┤
+ │ Ally               │ Sheedy           │
+ │ Anthony            │ Hall             │
+ │ Emilio             │ Estevez          │
+ │ Judd               │ Nelson           │
+ │ Molly              │ Ringwald         │
+ ╰────────────────────┴──────────────────╯
+Record count: 5
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/relpipe-data/examples/rdf-coreys.sparql	Mon Jul 27 17:51:53 2020 +0200
@@ -0,0 +1,18 @@
+#!/usr/bin/env rdf-sparql-interpreter.sh
+# endpoint: https://dbpedia.org/sparql
+# relation: coreys
+
+PREFIX foaf:  <http://xmlns.com/foaf/0.1/>
+PREFIX dbo:   <http://dbpedia.org/ontology/>
+
+SELECT
+	?film_name
+	(?film AS ?film_uri)
+WHERE {
+	?actor1 foaf:name "Corey Haim"@en .
+	?actor2 foaf:name "Corey Feldman"@en .
+	?film dbo:starring ?actor1 .
+	?film dbo:starring ?actor2 .
+	?film foaf:name ?film_name .
+}
+ORDER BY ?film_name
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/relpipe-data/examples/rdf-coreys.txt	Mon Jul 27 17:51:53 2020 +0200
@@ -0,0 +1,13 @@
+coreys:
+ ╭────────────────────────────────┬────────────────────────────────────────────────────────────╮
+ │ film_name             (string) │ film_uri                                          (string) │
+ ├────────────────────────────────┼────────────────────────────────────────────────────────────┤
+ │ Blown Away                     │ http://dbpedia.org/resource/Blown_Away_(1992_film)         │
+ │ Busted                         │ http://dbpedia.org/resource/Busted_(film)                  │
+ │ Dream a Little Dream           │ http://dbpedia.org/resource/Dream_a_Little_Dream_(film)    │
+ │ Dream a Little Dream 2         │ http://dbpedia.org/resource/Dream_a_Little_Dream_2         │
+ │ License to Drive               │ http://dbpedia.org/resource/License_to_Drive               │
+ │ National Lampoon's Last Resort │ http://dbpedia.org/resource/National_Lampoon's_Last_Resort │
+ │ The Two Coreys                 │ http://dbpedia.org/resource/The_Two_Coreys_(TV_series)     │
+ ╰────────────────────────────────┴────────────────────────────────────────────────────────────╯
+Record count: 7
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/relpipe-data/examples/rdf-heathers-members.sparql	Mon Jul 27 17:51:53 2020 +0200
@@ -0,0 +1,13 @@
+PREFIX person:     <tag:heathers.globalcode.info,2020:person:>
+PREFIX predicate:  <tag:heathers.globalcode.info,2020:predicate:>
+PREFIX thing:      <tag:heathers.globalcode.info,2020:thing:>
+
+SELECT
+	?group_name
+	(count(*) AS ?member_count)
+WHERE {
+	?person predicate:is-member-of ?group .
+	?group predicate:has-title ?group_name .
+}
+GROUP BY ?group_name
+ORDER BY DESC(?member_count)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/relpipe-data/examples/rdf-heathers-members.txt	Mon Jul 27 17:51:53 2020 +0200
@@ -0,0 +1,9 @@
+rdf_results:
+ ╭───────────────────────────┬───────────────────────╮
+ │ group_name       (string) │ member_count (string) │
+ ├───────────────────────────┼───────────────────────┤
+ │ Future Leaders of America │ 5                     │
+ │ Croquet Team              │ 4                     │
+ │ Sharp Shooters            │ 2                     │
+ ╰───────────────────────────┴───────────────────────╯
+Record count: 3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/relpipe-data/examples/rdf-heathers-much.sparql	Mon Jul 27 17:51:53 2020 +0200
@@ -0,0 +1,16 @@
+PREFIX person:     <tag:heathers.globalcode.info,2020:person:>
+PREFIX predicate:  <tag:heathers.globalcode.info,2020:predicate:>
+PREFIX thing:      <tag:heathers.globalcode.info,2020:thing:>
+
+SELECT
+	?firstname
+	?surname
+	?quote
+WHERE {
+	?character predicate:says ?quote .
+	?character predicate:is-played-by ?actor .
+	?actor predicate:has-firstname ?firstname .
+	?actor predicate:has-surname ?surname .
+	FILTER (regex(?quote, "much", "i" )) . # i = case-insensitive
+}
+ORDER BY ?firstname ?surname ?quote
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/relpipe-data/examples/rdf-heathers-much.txt	Mon Jul 27 17:51:53 2020 +0200
@@ -0,0 +1,8 @@
+rdf_results:
+ ╭────────────────────┬──────────────────┬────────────────╮
+ │ firstname (string) │ surname (string) │ quote (string) │
+ ├────────────────────┼──────────────────┼────────────────┤
+ │ Lisanne            │ Falk             │ Drool much?    │
+ │ Shannen            │ Doherty          │ Jealous much?  │
+ ╰────────────────────┴──────────────────┴────────────────╯
+Record count: 2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/relpipe-data/examples/rdf-heathers-quotes.sparql	Mon Jul 27 17:51:53 2020 +0200
@@ -0,0 +1,14 @@
+PREFIX person:     <tag:heathers.globalcode.info,2020:person:>
+PREFIX predicate:  <tag:heathers.globalcode.info,2020:predicate:>
+PREFIX thing:      <tag:heathers.globalcode.info,2020:thing:>
+
+SELECT
+	?firstname
+	?surname
+	?quote
+WHERE {
+	?person predicate:says ?quote .
+	?person predicate:has-firstname ?firstname .
+	?person predicate:has-surname ?surname .
+}
+ORDER BY ?firstname ?surname ?quote
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/relpipe-data/examples/rdf-heathers-quotes.txt	Mon Jul 27 17:51:53 2020 +0200
@@ -0,0 +1,21 @@
+rdf_results:
+ ╭────────────────────┬──────────────────┬───────────────────────────────────────────────────────╮
+ │ firstname (string) │ surname (string) │ quote                                        (string) │
+ ├────────────────────┼──────────────────┼───────────────────────────────────────────────────────┤
+ │ Heather            │ Chandler         │ Grow up Heather, bulimia is so '87.                   │
+ │ Heather            │ Chandler         │ What's your damage?                                   │
+ │ Heather            │ Chandler         │ You're such a pillowcase!                             │
+ │ Heather            │ Duke             │ Color me stoked!                                      │
+ │ Heather            │ Duke             │ Jealous much?                                         │
+ │ Heather            │ Duke             │ Veronica, you look like hell.                         │
+ │ Heather            │ McNamara         │ Drool much?                                           │
+ │ Heather            │ McNamara         │ God, aren't they fed, yet?                            │
+ │ Heather            │ McNamara         │ Suicide is a private thing.                           │
+ │ Jason              │ Dean             │ Did you say a cherry or Coke slushie?                 │
+ │ Jason              │ Dean             │ Greetings and salutations!                            │
+ │ Jason              │ Dean             │ The extreme always seems to make an impression.       │
+ │ Veronica           │ Sawyer           │ Are we going to prom or to hell?                      │
+ │ Veronica           │ Sawyer           │ How very!                                             │
+ │ Veronica           │ Sawyer           │ I use my grand IQ to decide what colour gloss to wear │
+ ╰────────────────────┴──────────────────┴───────────────────────────────────────────────────────╯
+Record count: 15
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/relpipe-data/examples/rdf-heathers.sparql	Mon Jul 27 17:51:53 2020 +0200
@@ -0,0 +1,1 @@
+SELECT * WHERE { ?subject ?predicate ?object . }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/relpipe-data/examples/rdf-heathers.ttl	Mon Jul 27 17:51:53 2020 +0200
@@ -0,0 +1,119 @@
+@prefix person:     <tag:heathers.globalcode.info,2020:person:> .
+@prefix predicate:  <tag:heathers.globalcode.info,2020:predicate:> .
+@prefix thing:      <tag:heathers.globalcode.info,2020:thing:> .
+
+person:veronica-sawyer
+	predicate:has-firstname "Veronica";
+	predicate:has-surname   "Sawyer";
+	predicate:says
+		"How very!",
+		"I use my grand IQ to decide what colour gloss to wear",
+		"Are we going to prom or to hell?";
+	predicate:dates person:jason-dean;
+	predicate:is-member-of
+		thing:sharp-shooters,
+	    thing:croquet-team,
+		thing:future-leaders;
+	predicate:is-played-by person:winona-ryder .
+
+person:jason-dean
+	predicate:has-firstname "Jason";
+	predicate:has-surname   "Dean";
+	predicate:says
+		"Greetings and salutations!",
+		"The extreme always seems to make an impression.",
+		"Did you say a cherry or Coke slushie?";
+	predicate:dates person:veronica-sawyer;
+	predicate:is-member-of
+		thing:sharp-shooters,
+		thing:future-leaders;
+	predicate:is-played-by person:christian-slater .
+
+person:heather-duke
+	predicate:has-firstname "Heather";
+	predicate:has-surname   "Duke";
+	predicate:says
+		"Color me stoked!",
+		"Jealous much?",
+		"Veronica, you look like hell.";
+	predicate:is-member-of
+		thing:croquet-team,
+		thing:future-leaders;
+	predicate:is-played-by person:shannen-doherty .
+
+person:heather-mcnamara
+	predicate:has-firstname "Heather";
+	predicate:has-surname   "McNamara";
+	predicate:says
+		"Drool much?",
+		"God, aren't they fed, yet?",
+		"Suicide is a private thing.";
+	predicate:is-member-of
+		thing:croquet-team,
+		thing:future-leaders;
+	predicate:is-played-by person:lisanne-falk .
+
+person:heather-chandler
+	predicate:has-firstname "Heather";
+	predicate:has-surname   "Chandler";
+	predicate:says
+		"What's your damage?",
+		"You're such a pillowcase!",
+		"Grow up Heather, bulimia is so '87.";
+	predicate:is-member-of
+		thing:croquet-team,
+		thing:future-leaders;
+	predicate:is-played-by person:kim-walker .
+
+person:betty-finn
+	predicate:has-firstname "Betty";
+	predicate:has-surname   "Finn";
+	predicate:is-true-friend-of person:veronica-sawyer;
+	predicate:is-played-by person:renee-estevez .
+
+person:martha-dunnstock
+	predicate:has-firstname "Martha";
+	predicate:has-surname   "Dunnstock";
+	predicate:has-nickname  "Martha Dumptruck";
+	predicate:listens-to thing:big-fun;
+	predicate:is-played-by person:carrie-lynn .
+
+thing:sharp-shooters
+	predicate:has-title "Sharp Shooters" .
+
+thing:croquet-team
+	predicate:has-title "Croquet Team" .
+
+thing:future-leaders
+	predicate:has-title "Future Leaders of America" .
+
+thing:big-fun
+	predicate:has-title "Big Fun" .
+
+person:winona-ryder
+	predicate:has-firstname "Winona";
+	predicate:has-surname   "Ryder" .
+
+person:christian-slater
+	predicate:has-firstname "Christian";
+	predicate:has-surname   "Slater" .
+
+person:shannen-doherty
+	predicate:has-firstname "Shannen";
+	predicate:has-surname   "Doherty" .
+
+person:lisanne-falk
+	predicate:has-firstname "Lisanne";
+	predicate:has-surname   "Falk" .
+
+person:kim-walker
+	predicate:has-firstname "Kim";
+	predicate:has-surname   "Walker" .
+
+person:renee-estevez
+	predicate:has-firstname "Renée";
+	predicate:has-surname   "Estevez" .
+
+person:carrie-lynn
+	predicate:has-firstname "Carrie";
+	predicate:has-surname   "Lynn" .
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/relpipe-data/examples/rdf-heathers.txt	Mon Jul 27 17:51:53 2020 +0200
@@ -0,0 +1,76 @@
+rdf_results:
+ ╭───────────────────────────────────────────────────────────┬───────────────────────────────────────────────────────────────┬───────────────────────────────────────────────────────────╮
+ │ subject                                          (string) │ predicate                                            (string) │ object                                           (string) │
+ ├───────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────┤
+ │ tag:heathers.globalcode.info,2020:person:martha-dunnstock │ tag:heathers.globalcode.info,2020:predicate:is-played-by      │ tag:heathers.globalcode.info,2020:person:carrie-lynn      │
+ │ tag:heathers.globalcode.info,2020:person:martha-dunnstock │ tag:heathers.globalcode.info,2020:predicate:listens-to        │ tag:heathers.globalcode.info,2020:thing:big-fun           │
+ │ tag:heathers.globalcode.info,2020:person:martha-dunnstock │ tag:heathers.globalcode.info,2020:predicate:has-nickname      │ Martha Dumptruck                                          │
+ │ tag:heathers.globalcode.info,2020:person:martha-dunnstock │ tag:heathers.globalcode.info,2020:predicate:has-surname       │ Dunnstock                                                 │
+ │ tag:heathers.globalcode.info,2020:person:martha-dunnstock │ tag:heathers.globalcode.info,2020:predicate:has-firstname     │ Martha                                                    │
+ │ tag:heathers.globalcode.info,2020:person:lisanne-falk     │ tag:heathers.globalcode.info,2020:predicate:has-surname       │ Falk                                                      │
+ │ tag:heathers.globalcode.info,2020:person:lisanne-falk     │ tag:heathers.globalcode.info,2020:predicate:has-firstname     │ Lisanne                                                   │
+ │ tag:heathers.globalcode.info,2020:thing:sharp-shooters    │ tag:heathers.globalcode.info,2020:predicate:has-title         │ Sharp Shooters                                            │
+ │ tag:heathers.globalcode.info,2020:person:heather-duke     │ tag:heathers.globalcode.info,2020:predicate:is-played-by      │ tag:heathers.globalcode.info,2020:person:shannen-doherty  │
+ │ tag:heathers.globalcode.info,2020:person:heather-duke     │ tag:heathers.globalcode.info,2020:predicate:is-member-of      │ tag:heathers.globalcode.info,2020:thing:future-leaders    │
+ │ tag:heathers.globalcode.info,2020:person:heather-duke     │ tag:heathers.globalcode.info,2020:predicate:is-member-of      │ tag:heathers.globalcode.info,2020:thing:croquet-team      │
+ │ tag:heathers.globalcode.info,2020:person:heather-duke     │ tag:heathers.globalcode.info,2020:predicate:says              │ Veronica, you look like hell.                             │
+ │ tag:heathers.globalcode.info,2020:person:heather-duke     │ tag:heathers.globalcode.info,2020:predicate:says              │ Jealous much?                                             │
+ │ tag:heathers.globalcode.info,2020:person:heather-duke     │ tag:heathers.globalcode.info,2020:predicate:says              │ Color me stoked!                                          │
+ │ tag:heathers.globalcode.info,2020:person:heather-duke     │ tag:heathers.globalcode.info,2020:predicate:has-surname       │ Duke                                                      │
+ │ tag:heathers.globalcode.info,2020:person:heather-duke     │ tag:heathers.globalcode.info,2020:predicate:has-firstname     │ Heather                                                   │
+ │ tag:heathers.globalcode.info,2020:thing:big-fun           │ tag:heathers.globalcode.info,2020:predicate:has-title         │ Big Fun                                                   │
+ │ tag:heathers.globalcode.info,2020:person:heather-mcnamara │ tag:heathers.globalcode.info,2020:predicate:is-played-by      │ tag:heathers.globalcode.info,2020:person:lisanne-falk     │
+ │ tag:heathers.globalcode.info,2020:person:heather-mcnamara │ tag:heathers.globalcode.info,2020:predicate:is-member-of      │ tag:heathers.globalcode.info,2020:thing:future-leaders    │
+ │ tag:heathers.globalcode.info,2020:person:heather-mcnamara │ tag:heathers.globalcode.info,2020:predicate:is-member-of      │ tag:heathers.globalcode.info,2020:thing:croquet-team      │
+ │ tag:heathers.globalcode.info,2020:person:heather-mcnamara │ tag:heathers.globalcode.info,2020:predicate:says              │ Suicide is a private thing.                               │
+ │ tag:heathers.globalcode.info,2020:person:heather-mcnamara │ tag:heathers.globalcode.info,2020:predicate:says              │ God, aren't they fed, yet?                                │
+ │ tag:heathers.globalcode.info,2020:person:heather-mcnamara │ tag:heathers.globalcode.info,2020:predicate:says              │ Drool much?                                               │
+ │ tag:heathers.globalcode.info,2020:person:heather-mcnamara │ tag:heathers.globalcode.info,2020:predicate:has-surname       │ McNamara                                                  │
+ │ tag:heathers.globalcode.info,2020:person:heather-mcnamara │ tag:heathers.globalcode.info,2020:predicate:has-firstname     │ Heather                                                   │
+ │ tag:heathers.globalcode.info,2020:person:jason-dean       │ tag:heathers.globalcode.info,2020:predicate:is-played-by      │ tag:heathers.globalcode.info,2020:person:christian-slater │
+ │ tag:heathers.globalcode.info,2020:person:jason-dean       │ tag:heathers.globalcode.info,2020:predicate:is-member-of      │ tag:heathers.globalcode.info,2020:thing:future-leaders    │
+ │ tag:heathers.globalcode.info,2020:person:jason-dean       │ tag:heathers.globalcode.info,2020:predicate:is-member-of      │ tag:heathers.globalcode.info,2020:thing:sharp-shooters    │
+ │ tag:heathers.globalcode.info,2020:person:jason-dean       │ tag:heathers.globalcode.info,2020:predicate:dates             │ tag:heathers.globalcode.info,2020:person:veronica-sawyer  │
+ │ tag:heathers.globalcode.info,2020:person:jason-dean       │ tag:heathers.globalcode.info,2020:predicate:says              │ Did you say a cherry or Coke slushie?                     │
+ │ tag:heathers.globalcode.info,2020:person:jason-dean       │ tag:heathers.globalcode.info,2020:predicate:says              │ The extreme always seems to make an impression.           │
+ │ tag:heathers.globalcode.info,2020:person:jason-dean       │ tag:heathers.globalcode.info,2020:predicate:says              │ Greetings and salutations!                                │
+ │ tag:heathers.globalcode.info,2020:person:jason-dean       │ tag:heathers.globalcode.info,2020:predicate:has-surname       │ Dean                                                      │
+ │ tag:heathers.globalcode.info,2020:person:jason-dean       │ tag:heathers.globalcode.info,2020:predicate:has-firstname     │ Jason                                                     │
+ │ tag:heathers.globalcode.info,2020:person:shannen-doherty  │ tag:heathers.globalcode.info,2020:predicate:has-surname       │ Doherty                                                   │
+ │ tag:heathers.globalcode.info,2020:person:shannen-doherty  │ tag:heathers.globalcode.info,2020:predicate:has-firstname     │ Shannen                                                   │
+ │ tag:heathers.globalcode.info,2020:person:heather-chandler │ tag:heathers.globalcode.info,2020:predicate:is-played-by      │ tag:heathers.globalcode.info,2020:person:kim-walker       │
+ │ tag:heathers.globalcode.info,2020:person:heather-chandler │ tag:heathers.globalcode.info,2020:predicate:is-member-of      │ tag:heathers.globalcode.info,2020:thing:future-leaders    │
+ │ tag:heathers.globalcode.info,2020:person:heather-chandler │ tag:heathers.globalcode.info,2020:predicate:is-member-of      │ tag:heathers.globalcode.info,2020:thing:croquet-team      │
+ │ tag:heathers.globalcode.info,2020:person:heather-chandler │ tag:heathers.globalcode.info,2020:predicate:says              │ Grow up Heather, bulimia is so '87.                       │
+ │ tag:heathers.globalcode.info,2020:person:heather-chandler │ tag:heathers.globalcode.info,2020:predicate:says              │ You're such a pillowcase!                                 │
+ │ tag:heathers.globalcode.info,2020:person:heather-chandler │ tag:heathers.globalcode.info,2020:predicate:says              │ What's your damage?                                       │
+ │ tag:heathers.globalcode.info,2020:person:heather-chandler │ tag:heathers.globalcode.info,2020:predicate:has-surname       │ Chandler                                                  │
+ │ tag:heathers.globalcode.info,2020:person:heather-chandler │ tag:heathers.globalcode.info,2020:predicate:has-firstname     │ Heather                                                   │
+ │ tag:heathers.globalcode.info,2020:person:veronica-sawyer  │ tag:heathers.globalcode.info,2020:predicate:is-member-of      │ tag:heathers.globalcode.info,2020:thing:future-leaders    │
+ │ tag:heathers.globalcode.info,2020:person:veronica-sawyer  │ tag:heathers.globalcode.info,2020:predicate:dates             │ tag:heathers.globalcode.info,2020:person:jason-dean       │
+ │ tag:heathers.globalcode.info,2020:person:veronica-sawyer  │ tag:heathers.globalcode.info,2020:predicate:says              │ I use my grand IQ to decide what colour gloss to wear     │
+ │ tag:heathers.globalcode.info,2020:person:veronica-sawyer  │ tag:heathers.globalcode.info,2020:predicate:is-played-by      │ tag:heathers.globalcode.info,2020:person:winona-ryder     │
+ │ tag:heathers.globalcode.info,2020:person:veronica-sawyer  │ tag:heathers.globalcode.info,2020:predicate:says              │ How very!                                                 │
+ │ tag:heathers.globalcode.info,2020:person:veronica-sawyer  │ tag:heathers.globalcode.info,2020:predicate:is-member-of      │ tag:heathers.globalcode.info,2020:thing:sharp-shooters    │
+ │ tag:heathers.globalcode.info,2020:person:veronica-sawyer  │ tag:heathers.globalcode.info,2020:predicate:is-member-of      │ tag:heathers.globalcode.info,2020:thing:croquet-team      │
+ │ tag:heathers.globalcode.info,2020:person:veronica-sawyer  │ tag:heathers.globalcode.info,2020:predicate:has-surname       │ Sawyer                                                    │
+ │ tag:heathers.globalcode.info,2020:person:veronica-sawyer  │ tag:heathers.globalcode.info,2020:predicate:has-firstname     │ Veronica                                                  │
+ │ tag:heathers.globalcode.info,2020:person:veronica-sawyer  │ tag:heathers.globalcode.info,2020:predicate:says              │ Are we going to prom or to hell?                          │
+ │ tag:heathers.globalcode.info,2020:thing:croquet-team      │ tag:heathers.globalcode.info,2020:predicate:has-title         │ Croquet Team                                              │
+ │ tag:heathers.globalcode.info,2020:person:renee-estevez    │ tag:heathers.globalcode.info,2020:predicate:has-surname       │ Estevez                                                   │
+ │ tag:heathers.globalcode.info,2020:person:renee-estevez    │ tag:heathers.globalcode.info,2020:predicate:has-firstname     │ Renée                                                     │
+ │ tag:heathers.globalcode.info,2020:person:kim-walker       │ tag:heathers.globalcode.info,2020:predicate:has-surname       │ Walker                                                    │
+ │ tag:heathers.globalcode.info,2020:person:kim-walker       │ tag:heathers.globalcode.info,2020:predicate:has-firstname     │ Kim                                                       │
+ │ tag:heathers.globalcode.info,2020:thing:future-leaders    │ tag:heathers.globalcode.info,2020:predicate:has-title         │ Future Leaders of America                                 │
+ │ tag:heathers.globalcode.info,2020:person:winona-ryder     │ tag:heathers.globalcode.info,2020:predicate:has-surname       │ Ryder                                                     │
+ │ tag:heathers.globalcode.info,2020:person:winona-ryder     │ tag:heathers.globalcode.info,2020:predicate:has-firstname     │ Winona                                                    │
+ │ tag:heathers.globalcode.info,2020:person:betty-finn       │ tag:heathers.globalcode.info,2020:predicate:is-played-by      │ tag:heathers.globalcode.info,2020:person:renee-estevez    │
+ │ tag:heathers.globalcode.info,2020:person:betty-finn       │ tag:heathers.globalcode.info,2020:predicate:is-true-friend-of │ tag:heathers.globalcode.info,2020:person:veronica-sawyer  │
+ │ tag:heathers.globalcode.info,2020:person:betty-finn       │ tag:heathers.globalcode.info,2020:predicate:has-surname       │ Finn                                                      │
+ │ tag:heathers.globalcode.info,2020:person:betty-finn       │ tag:heathers.globalcode.info,2020:predicate:has-firstname     │ Betty                                                     │
+ │ tag:heathers.globalcode.info,2020:person:carrie-lynn      │ tag:heathers.globalcode.info,2020:predicate:has-surname       │ Lynn                                                      │
+ │ tag:heathers.globalcode.info,2020:person:carrie-lynn      │ tag:heathers.globalcode.info,2020:predicate:has-firstname     │ Carrie                                                    │
+ │ tag:heathers.globalcode.info,2020:person:christian-slater │ tag:heathers.globalcode.info,2020:predicate:has-surname       │ Slater                                                    │
+ │ tag:heathers.globalcode.info,2020:person:christian-slater │ tag:heathers.globalcode.info,2020:predicate:has-firstname     │ Christian                                                 │
+ ╰───────────────────────────────────────────────────────────┴───────────────────────────────────────────────────────────────┴───────────────────────────────────────────────────────────╯
+Record count: 70
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/relpipe-data/examples/rdf-return.sparql	Mon Jul 27 17:51:53 2020 +0200
@@ -0,0 +1,24 @@
+#!/usr/bin/env rdf-sparql-interpreter.sh
+# endpoint: https://dbpedia.org/sparql
+# relation: return
+
+PREFIX foaf:    <http://xmlns.com/foaf/0.1/>
+PREFIX dbo:     <http://dbpedia.org/ontology/>
+PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX xsd:     <http://www.w3.org/2001/XMLSchema#>
+PREFIX schema:  <http://schema.org/>
+
+SELECT DISTINCT
+	?actor_name
+	?film_name
+	?lang
+WHERE {
+	?actor foaf:name "Konstantin Lavronenko"@en .
+	?film dbo:starring ?actor .
+	?film dbo:abstract ?film_abstract .
+	FILTER (regex(?film_abstract, "mythic"))
+	?actor rdfs:label ?actor_name .
+	?film rdfs:label ?film_name .
+	FILTER (lang(?actor_name) = "en" && lang(?film_name) IN ("ru", "en", "nl"))
+	BIND (lang(?film_name) AS ?lang)
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/relpipe-data/examples/rdf-return.txt	Mon Jul 27 17:51:53 2020 +0200
@@ -0,0 +1,9 @@
+return:
+ ╭───────────────────────┬───────────────────────────────────┬───────────────╮
+ │ actor_name   (string) │ film_name                (string) │ lang (string) │
+ ├───────────────────────┼───────────────────────────────────┼───────────────┤
+ │ Konstantin Lavronenko │ Возвращение (фильм, 2003, Россия) │ ru            │
+ │ Konstantin Lavronenko │ The Return (2003 film)            │ en            │
+ │ Konstantin Lavronenko │ Vozvrasjtsjenie                   │ nl            │
+ ╰───────────────────────┴───────────────────────────────────┴───────────────╯
+Record count: 3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/relpipe-data/examples/rdf-sample-triples.sparql	Mon Jul 27 17:51:53 2020 +0200
@@ -0,0 +1,5 @@
+#!/usr/bin/env rdf-sparql-interpreter.sh
+# endpoint: https://query.wikidata.org/sparql
+# relation: few_statements
+
+SELECT * WHERE { ?subject ?predicate ?object } LIMIT 3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/relpipe-data/examples/rdf-sparql-interpreter.sh	Mon Jul 27 17:51:53 2020 +0200
@@ -0,0 +1,65 @@
+#!/bin/bash
+
+# Relational pipes
+# Copyright © 2020 František Kučera (Frantovo.cz, GlobalCode.info)
+# 
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, version 3 of the License.
+# 
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+# This is a helper script that could be used as a shebang interpreter for SPARQL files.
+# Just set the SPARQL file executable and add the first line comment.
+# The endpoint and relation parameters are optional.
+# Parameters must be at the beginning of the file before any empty lines.
+# Only the first occurrence of each parameter is relevant.
+# This interpreter produces a human-readable table if STDOUT is a terminal
+# and machine-readable relational data if STDOUT is not a terminal.
+# So the stream can be piped to any relational transformation (e.g. relpipe-tr-sql)
+# or output filter (e.g. relpipe-out-csv).
+#
+# Example:
+#
+#	#!/usr/bin/env rdf-sparql-interpreter.sh
+#	# endpoint: https://query.wikidata.org/sparql
+#	# relation: few_statements
+#	
+#	SELECT * WHERE { ?subject ?predicate ?object } LIMIT 3
+
+
+# Execute the SPARQL query and send the results to the STDOUT
+# either raw or formatted as a table depending on whether STDOUT is a terminal or not.
+interpret_sparql() {
+	if [ -t 1 ] ; then relpipe-in-sparql "$@" | relpipe-out-tabular;
+	else relpipe-in-sparql "$@"; fi
+}
+
+# Fetches the parameter $2 from file $1.
+# Parameters must be formatted as comments: „# name: value“.
+# Only the first occurrence is relevant. And empty line terminates processing.
+find_parameter() {
+	perl -e 'while(<>) { if (/^\s*$/) { last; } elsif (/^# ($ARGV[0]): (.*)$/) { print "$2"; last; } }' "$1" "$2"
+}
+
+# Appends the "${options[@]}" array with option $2 taken from the $1 file
+# or from environmental variable RELPIPE_IN_SPARQL_${2^^} (the variable has precedence).
+prepare_option() {
+	variableName="RELPIPE_IN_SPARQL_${2^^}"
+	parameterName="$2"
+	[[ -n "${!variableName}" ]] && value="${!variableName}" || value="$(find_parameter "$1" "$parameterName")"
+	[[ -n "$value" ]] && options+=("--$parameterName" "$value")
+}
+
+options=()
+prepare_option "$1" endpoint
+prepare_option "$1" relation
+
+cat "$1" | interpret_sparql "${options[@]}"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/relpipe-data/examples/relpipe-in-sparql.sh	Mon Jul 27 17:51:53 2020 +0200
@@ -0,0 +1,86 @@
+#!/bin/bash
+
+# Relational pipes
+# Copyright © 2020 František Kučera (Frantovo.cz, GlobalCode.info)
+# 
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, version 3 of the License.
+# 
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+relpipe_in_sparql_help() {
+	cat <<-EOF
+		SPARQL query is expected on the STDIN. Exapmple:
+		  echo 'SELECT * WHERE { ?subject ?predicate ?object . } LIMIT 3' \
+| relpipe-in-sparql | relpipe-out-tabular
+		
+		Namespace prefixes are part of the query.
+		But because they are usually constant, they can be set as the 
+		RELPIPE_IN_SPARQL_PREFIXES environmental variable.
+		Then they are prepended to the query.
+		
+		SPARQL endpoint can be set ad-hoc by the --endpoint option.
+		
+		To configure default SPARQL endpoint, add something like this to your environment:
+		  export RELPIPE_IN_SPARQL_ENDPOINT="https://query.wikidata.org/sparql"
+		  export RELPIPE_IN_SPARQL_ENDPOINT="https://dbpedia.org/sparql"
+		  export RELPIPE_IN_SPARQL_ENDPOINT="https://data.gov.cz/sparql"
+		  export RELPIPE_IN_SPARQL_ENDPOINT="https://data.cssz.cz/sparql"
+
+		The relation name defaults to "rdf". Custom name can be set using the --relation option.
+EOF
+}
+
+relation="rdf";
+endpoint="${RELPIPE_IN_SPARQL_ENDPOINT:-https://dbpedia.org/sparql}";
+
+while [[ $# -gt 0 ]]; do
+	argument="$1";
+	case "$argument" in
+		"--relation") relation="$2"; shift; shift; ;;
+		"--endpoint") endpoint="$2"; shift; shift; ;;
+		"--help") relpipe_in_sparql_help; exit; ;;
+	esac
+done
+
+[[ -n "$RELPIPE_IN_SPARQL_PREFIXES" ]] && query="$RELPIPE_IN_SPARQL_PREFIXES"; query+=$'\n\n';
+
+query+="$(</dev/stdin)";
+
+
+# Simple implementation that utilizes the CSV output of the SPARQL endpoint.
+relpipe_in_sparql_implementation_csv() {
+	curl \
+		--header "Accept: text/csv" \
+		--data-urlencode query="$query" \
+		--fail \
+		--silent \
+		--show-error \
+		"$endpoint" \
+		| relpipe-in-csv "$relation"
+}
+
+# More powerful implementation based on XML.
+# Can be customized through XSLT.
+# But: has more dependencies and avoids streaming.
+relpipe_in_sparql_implementation_xml() {
+	DIR="$(dirname $(realpath "$0"))";
+	curl \
+		--header "Accept: application/sparql-results+xml" \
+		--data-urlencode query="$query" \
+		--fail \
+		--silent \
+		--show-error \
+		"$endpoint" \
+		| xsltproc --stringparam "relation" "$relation" "$DIR/relpipe-in-sparql.xsl" - \
+		| relpipe-in-xml
+}
+
+relpipe_in_sparql_implementation_${RELPIPE_IN_SPARQL_IMPLEMENTATION:-csv}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/relpipe-data/examples/relpipe-in-sparql.xsl	Mon Jul 27 17:51:53 2020 +0200
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Relational pipes
+Copyright © 2020 František Kučera (Frantovo.cz, GlobalCode.info)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 3 of the License.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+-->
+<xsl:stylesheet version="1.0"
+				xmlns="tag:globalcode.info,2018:relpipe"
+				xmlns:sparql="http://www.w3.org/2005/sparql-results#"
+				xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+				xmlns:fn="http://www.w3.org/2005/xpath-functions"
+				xmlns:xs="http://www.w3.org/2001/XMLSchema"
+				exclude-result-prefixes="fn sparql xs">
+	<xsl:output
+		method="xml"
+		indent="yes"
+		encoding="UTF-8"/>
+
+	<xsl:param name="relation" select="'rdf'"/>
+
+	<xsl:template match="/sparql:sparql">
+		<relpipe>
+			<relation>
+				<name>
+					<xsl:value-of select="$relation"/>
+				</name>
+				<attributes-metadata>
+					<xsl:for-each select="sparql:head/sparql:variable">
+						<attribute-metadata name="{@name}" type="string"/>
+						<!-- here we can optionally append additional attributes with some metadata -->
+					</xsl:for-each>
+				</attributes-metadata>
+				
+				<xsl:for-each select="sparql:results/sparql:result">
+					<xsl:variable name="record" select="."/>
+					<record>
+						<xsl:for-each select="/sparql:sparql/sparql:head/sparql:variable">
+							<xsl:variable name="attribute" select="@name"/>
+							<xsl:call-template name="record">
+								<xsl:with-param name="record" select="$record"/>
+								<xsl:with-param name="attribute" select="$attribute"/>
+							</xsl:call-template>
+						</xsl:for-each>
+					</record>
+				</xsl:for-each>
+				
+			</relation>
+		</relpipe>
+	</xsl:template>
+	
+	<xsl:template name="record">
+		<xsl:param name="record"/>
+		<xsl:param name="attribute"/>
+		<attribute>
+			<xsl:value-of select="$record/sparql:binding[@name=$attribute]"/>
+			<!-- here we can optionally append additional attributes with some metadata -->
+			<!--
+				<sparql:binding name="…attribute…">
+					<sparql:uri>…value…</sparql:uri>
+					<sparql:literal xml:lang="it">…value…</sparql:literal>
+				</sparql:binding>
+			-->
+		</attribute>
+	</xsl:template>
+
+</xsl:stylesheet>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/relpipe-data/makra/sparql-endpoint.xsl	Mon Jul 27 17:51:53 2020 +0200
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet version="2.0"
+				xmlns="http://www.w3.org/1999/xhtml"
+				xmlns:h="http://www.w3.org/1999/xhtml"
+				xmlns:s="https://trac.frantovo.cz/xml-web-generator/wiki/xmlns/strana"
+				xmlns:k="https://trac.frantovo.cz/xml-web-generator/wiki/xmlns/konfigurace"
+				xmlns:m="https://trac.frantovo.cz/xml-web-generator/wiki/xmlns/makro"
+				xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+				xmlns:fn="http://www.w3.org/2005/xpath-functions"
+				xmlns:svg="http://www.w3.org/2000/svg"
+				xmlns:xs="http://www.w3.org/2001/XMLSchema"
+				exclude-result-prefixes="fn h s k m xs">
+	
+	<!--
+		SPARQL endpoint links
+		*********************
+		Inserts a listing of shell code to set the endpoint + link to the website.
+		Warning:
+			@url is not escaped and just pasted to the shell template; 
+		    it could not break the XHTML output or execute something, but the shell example inside <pre/> might be invalid.
+		*
+		@url URL of the SPARQL endpoint e.g. https://example.com/sparql
+		@website-url URL of the documentation or the organization publishing the data e.g. https://example.com/
+		@website-title human readable name for @website-url
+	-->
+	<xsl:template match="m:sparql-endpoint">
+		
+		<xsl:variable name="source">
+			<m:pre jazyk="shell">
+				<xsl:text>export RELPIPE_IN_SPARQL_ENDPOINT="</xsl:text>
+				<xsl:value-of select="@url"/>
+				<xsl:text>"</xsl:text>
+				<xsl:text>&#10;</xsl:text>
+				<xsl:text># or relpipe-in-sparql --endpoint "</xsl:text>
+				<xsl:value-of select="@url"/>
+				<xsl:text>"</xsl:text>
+			</m:pre>
+			<p>
+				Website:
+				<a href="{@website-url}">					
+					<xsl:value-of select="@website-title"/>
+				</a>
+			</p>
+		</xsl:variable>
+		
+		<xsl:apply-templates select="$source/*"/>
+		
+	</xsl:template>
+
+</xsl:stylesheet>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/relpipe-data/makra/sparql-example.xsl	Mon Jul 27 17:51:53 2020 +0200
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet version="2.0"
+				xmlns="http://www.w3.org/1999/xhtml"
+				xmlns:h="http://www.w3.org/1999/xhtml"
+				xmlns:s="https://trac.frantovo.cz/xml-web-generator/wiki/xmlns/strana"
+				xmlns:k="https://trac.frantovo.cz/xml-web-generator/wiki/xmlns/konfigurace"
+				xmlns:m="https://trac.frantovo.cz/xml-web-generator/wiki/xmlns/makro"
+				xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+				xmlns:fn="http://www.w3.org/2005/xpath-functions"
+				xmlns:svg="http://www.w3.org/2000/svg"
+				xmlns:xs="http://www.w3.org/2001/XMLSchema"
+				exclude-result-prefixes="fn h s k m xs">
+	
+	<!--
+		SPARQL example listing
+		**********************
+		Inserts the listing of a SPARQL query and the listing of result.
+		Both files should already exist – the query is not executed by this macro.
+		*
+		@name base name of the file; .sparql and .txt will be appended
+		@result-description optional text (will replace default „Results:“);
+		there could be also element m:result-description containing formatted text (including the <p>…</p>)
+	-->
+	<xsl:template match="m:sparql-example">
+		
+		<xsl:variable name="source">
+			<m:pre jazyk="sparql" src="{@name}.sparql" odkaz="ano"/>
+			<xsl:choose>
+				<xsl:when test="@result-description">
+					<p>
+						<xsl:value-of select="@result-description"/>
+					</p>
+				</xsl:when>
+				<xsl:when test="m:result-description">
+					<xsl:apply-templates select="m:result-description/*"/>
+				</xsl:when>
+				<xsl:otherwise>
+					<p>Result:</p>
+				</xsl:otherwise>
+			</xsl:choose>
+			<m:pre jazyk="text" src="{@name}.txt"/>
+		</xsl:variable>
+		
+		<xsl:apply-templates select="$source/*"/>
+		
+	</xsl:template>
+
+</xsl:stylesheet>
+
--- a/relpipe-data/principles.xml	Mon Jul 27 16:48:40 2020 +0200
+++ b/relpipe-data/principles.xml	Mon Jul 27 17:51:53 2020 +0200
@@ -106,7 +106,7 @@
 		<h2>Optional complexity</h2>
 		
 		<p>
-			We are not scared by things like XML, SQL, Java or even C++ and we do not hate them.
+			We are not scared by things like XML, SQL, RDF, Java or even C++ and we do not hate them.
 			There are use cases where their complexity is reasonable and makes sense.
 			But on the other hand, there are many scenarios, where such complexity is not necessary or is even harmful.
 			This leads us to the conclusion: <em>the complexity must be optional</em>.