/*
* 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 org.apache.jena.riot.system;
import static org.apache.jena.riot.RDFLanguages.NQUADS ;
import static org.apache.jena.riot.RDFLanguages.NTRIPLES ;
import static org.apache.jena.riot.RDFLanguages.RDFJSON ;
import static org.apache.jena.riot.RDFLanguages.sameLang ;
import static org.apache.jena.riot.writer.WriterConst.PREFIX_IRI ;
import static org.apache.jena.riot.writer.WriterConst.RDF_type ;
import static org.apache.jena.riot.writer.WriterConst.rdfNS ;
import java.io.OutputStream ;
import java.io.Writer ;
import java.util.* ;
import java.util.function.Function ;
import java.util.function.Predicate ;
import org.apache.jena.atlas.io.IndentedWriter ;
import org.apache.jena.atlas.iterator.Iter ;
import org.apache.jena.atlas.logging.Log ;
import org.apache.jena.graph.Graph ;
import org.apache.jena.graph.Node ;
import org.apache.jena.graph.NodeFactory ;
import org.apache.jena.graph.Triple ;
import org.apache.jena.query.ARQ ;
import org.apache.jena.riot.Lang ;
import org.apache.jena.riot.RDFLanguages ;
import org.apache.jena.riot.SysRIOT ;
import org.apache.jena.riot.WriterDatasetRIOT ;
import org.apache.jena.riot.lang.LabelToNode ;
import org.apache.jena.riot.tokens.Token ;
import org.apache.jena.riot.tokens.Tokenizer ;
import org.apache.jena.riot.tokens.TokenizerFactory ;
import org.apache.jena.riot.writer.WriterGraphRIOTBase ;
import org.apache.jena.sparql.ARQConstants ;
import org.apache.jena.sparql.core.DatasetGraph ;
import org.apache.jena.sparql.core.DatasetGraphFactory ;
import org.apache.jena.sparql.core.Quad ;
import org.apache.jena.sparql.util.Context ;
import org.apache.jena.util.iterator.ExtendedIterator ;
/** Misc RIOT code */
public class RiotLib
{
private final static String bNodeLabelStart = "_:" ;
private final static boolean skolomizedBNodes = ARQ.isTrue(ARQ.constantBNodeLabels) ;
/** Implement <_:....> as a "Node IRI"
* that is, use the given label as the BNode internal label.
* Use with care.
*/
public static Node createIRIorBNode(String iri)
{
// Is it a bNode label? i.e. <_:xyz>
if ( isBNodeIRI(iri) )
{
String s = iri.substring(bNodeLabelStart.length()) ;
Node n = NodeFactory.createBlankNode(s) ;
return n ;
}
return NodeFactory.createURI(iri) ;
}
/** Test whether a IRI is a ARQ-encoded blank node. */
public static boolean isBNodeIRI(String iri) {
return skolomizedBNodes && iri.startsWith(bNodeLabelStart) ;
}
private static final String URI_PREFIX_FIXUP = "::";
// These two must be in-step.
/** Function applied to undefined prefixes to convert to a URI string */
public static final Function<String,String> fixupPrefixes = (x) -> URI_PREFIX_FIXUP.concat(x) ;
/** Function to test for undefined prefix URIs*/
public static final Predicate<String> testFixupedPrefixURI = (x) -> x.startsWith(URI_PREFIX_FIXUP) ;
/** Test whether a IRI is a ARQ-encoded blank node. */
public static boolean isPrefixIRI(String iri) {
return testFixupedPrefixURI.test(iri) ;
}
/** Convert an prefix name (qname) to an IRI, for when the prerix is nor defined.
* @see ARQ#fixupUndefinedPrefixes
*/
public static String fixupPrefixIRI(String prefix, String localPart) {
return fixupPrefixIRI(prefix+":"+localPart) ;
}
/** Convert an prefix name (qname) to an IRI, for when the prerix is nor defined.
* @see ARQ#fixupUndefinedPrefixes
*/
public static String fixupPrefixIRI(String prefixedName) {
return fixupPrefixes.apply(prefixedName) ;
}
private static ParserProfile profile = profile(RDFLanguages.TURTLE, null, ErrorHandlerFactory.errorHandlerStd) ;
static {
PrefixMap pmap = profile.getPrologue().getPrefixMap() ;
pmap.add("rdf", ARQConstants.rdfPrefix) ;
pmap.add("rdfs", ARQConstants.rdfsPrefix) ;
pmap.add("xsd", ARQConstants.xsdPrefix) ;
pmap.add("owl" , ARQConstants.owlPrefix) ;
pmap.add("fn" , ARQConstants.fnPrefix) ;
pmap.add("op" , ARQConstants.fnPrefix) ;
pmap.add("ex" , "http://example/ns#") ;
pmap.add("" , "http://example/") ;
}
/** Parse a string to get one Node (the first token in the string) */
public static Node parse(String string)
{
Tokenizer tokenizer = TokenizerFactory.makeTokenizerString(string) ;
if ( ! tokenizer.hasNext() )
return null ;
Token t = tokenizer.next();
Node n = profile.create(null, t) ;
if ( tokenizer.hasNext() )
Log.warn(RiotLib.class, "String has more than one token in it: "+string) ;
return n ;
}
public static ParserProfile profile(Lang lang, String baseIRI)
{
return profile(lang, baseIRI, ErrorHandlerFactory.getDefaultErrorHandler()) ;
}
public static ParserProfile profile(Lang lang, String baseIRI, ErrorHandler handler)
{
if ( sameLang(NTRIPLES, lang) || sameLang(NQUADS, lang) )
{
boolean checking = SysRIOT.isStrictMode() ;
// If strict mode, do checking e.g. URIs
return profile(baseIRI, false, checking, handler) ;
}
if ( sameLang(RDFJSON, lang) )
return profile(baseIRI, false, true, handler) ;
return profile(baseIRI, true, true, handler) ;
}
/** Create a parser profile for the given setup
* @param baseIRI Base IRI
* @param resolveIRIs Whether to resolve IRIs
* @param checking Whether to check
* @param handler Error handler
* @return ParserProfile
* @see #profile for per-language setup
*/
public static ParserProfile profile(String baseIRI, boolean resolveIRIs, boolean checking, ErrorHandler handler)
{
LabelToNode labelToNode = true
? SyntaxLabels.createLabelToNode()
: LabelToNode.createUseLabelEncoded() ;
Prologue prologue ;
if ( resolveIRIs )
prologue = new Prologue(PrefixMapFactory.createForInput(), IRIResolver.create(baseIRI)) ;
else
prologue = new Prologue(PrefixMapFactory.createForInput(), IRIResolver.createNoResolve()) ;
if ( checking )
return new ParserProfileChecker(prologue, handler, factoryRDF(labelToNode)) ;
else
return new ParserProfileBase(prologue, handler, factoryRDF(labelToNode)) ;
}
/** Create a new (notinfluenced by anything else) FactoryRDF
* using the label to blank node scheme provided.
*/
public static FactoryRDF factoryRDF(LabelToNode labelMapping) {
return new FactoryRDFCaching(FactoryRDFCaching.DftNodeCacheSize, labelMapping);
}
/** Create a new (not influenced by anything else) FactoryRDF
* using the label to blank node scheme scope by this FactoryRDF.
*/
public static FactoryRDF factoryRDF() {
return factoryRDF(SyntaxLabels.createLabelToNode());
}
/** Get triples with the same subject */
public static Collection<Triple> triplesOfSubject(Graph graph, Node subj) {
return triples(graph, subj, Node.ANY, Node.ANY) ;
}
/** Get all the triples for the graph.find */
public static List<Triple> triples(Graph graph, Node s, Node p, Node o) {
List<Triple> acc = new ArrayList<>() ;
accTriples(acc, graph, s, p, o) ;
return acc ;
}
/* Count the triples for the graph.find */
public static long countTriples(Graph graph, Node s, Node p, Node o) {
ExtendedIterator<Triple> iter = graph.find(s, p, o) ;
try { return Iter.count(iter) ; }
finally { iter.close() ; }
}
/* Count the matches to a pattern across the dataset */
public static long countTriples(DatasetGraph dsg, Node s, Node p, Node o) {
Iterator<Quad> iter = dsg.find(Node.ANY, s, p, o) ;
return Iter.count(iter) ;
}
/** Collect all the matching triples */
public static void accTriples(Collection<Triple> acc, Graph graph, Node s, Node p, Node o) {
ExtendedIterator<Triple> iter = graph.find(s, p, o) ;
for ( ; iter.hasNext() ; )
acc.add(iter.next()) ;
iter.close() ;
}
/** Get exactly one triple or null for none or more than one. */
public static Triple triple1(Graph graph, Node s, Node p, Node o) {
ExtendedIterator<Triple> iter = graph.find(s, p, o) ;
try {
if ( !iter.hasNext() )
return null ;
Triple t = iter.next() ;
if ( iter.hasNext() )
return null ;
return t ;
}
finally {
iter.close() ;
}
}
/** Get exactly one triple, or null for none or more than one. */
public static Triple triple1(DatasetGraph dsg, Node s, Node p, Node o) {
Iterator<Quad> iter = dsg.find(Node.ANY, s, p, o) ;
if ( !iter.hasNext() )
return null ;
Quad q = iter.next() ;
if ( iter.hasNext() )
return null ;
return q.asTriple() ;
}
public static boolean strSafeFor(String str, char ch) {
return str.indexOf(ch) == -1 ;
}
public static void writeBase(IndentedWriter out, String base) {
if ( base != null ) {
out.print("@base ") ;
out.pad(PREFIX_IRI) ;
out.print("<") ;
out.print(base) ;
out.print(">") ;
out.print(" .") ;
out.println() ;
}
}
public static void writePrefixes(IndentedWriter out, PrefixMap prefixMap) {
if ( prefixMap != null && !prefixMap.isEmpty() ) {
for ( Map.Entry<String, String> e : prefixMap.getMappingCopyStr().entrySet() ) {
out.print("@prefix ") ;
out.print(e.getKey()) ;
out.print(": ") ;
out.pad(PREFIX_IRI) ;
out.print("<") ;
out.print(e.getValue()) ;
out.print(">") ;
out.print(" .") ;
out.println() ;
}
}
}
/** Returns dataset that wraps a graph */
public static DatasetGraph dataset(Graph graph)
{
return DatasetGraphFactory.createOneGraph(graph) ;
}
public static PrefixMap prefixMap(DatasetGraph dsg)
{
return PrefixMapFactory.create(dsg.getDefaultGraph().getPrefixMapping()) ;
}
public static int calcWidth(PrefixMap prefixMap, String baseURI, Node p)
{
if ( ! prefixMap.contains(rdfNS) && RDF_type.equals(p) )
return 1 ;
String x = prefixMap.abbreviate(p.getURI()) ;
if ( x == null )
return p.getURI().length()+2 ;
return x.length() ;
}
public static int calcWidth(PrefixMap prefixMap, String baseURI, Collection<Node> nodes, int minWidth, int maxWidth)
{
Node prev = null ;
int nodeMaxWidth = minWidth ;
for ( Node n : nodes )
{
if ( prev != null && prev.equals(n) )
continue ;
int len = calcWidth(prefixMap, baseURI, n) ;
if ( len > maxWidth )
continue ;
if ( nodeMaxWidth < len )
nodeMaxWidth = len ;
prev = n ;
}
return nodeMaxWidth ;
}
public static int calcWidthTriples(PrefixMap prefixMap, String baseURI, Collection<Triple> triples, int minWidth, int maxWidth)
{
Node prev = null ;
int nodeMaxWidth = minWidth ;
for ( Triple triple : triples )
{
Node n = triple.getPredicate() ;
if ( prev != null && prev.equals(n) )
continue ;
int len = calcWidth(prefixMap, baseURI, n) ;
if ( len > maxWidth )
continue ;
if ( nodeMaxWidth < len )
nodeMaxWidth = len ;
prev = n ;
}
return nodeMaxWidth ;
}
/** IndentedWriter over a jaav.io.Writer (better to use an IndentedWriter over an OutputStream) */
public static IndentedWriter create(Writer writer) { return new IndentedWriterWriter(writer) ; }
public static PrefixMap prefixMap(Graph graph) { return PrefixMapFactory.create(graph.getPrefixMapping()) ; }
public static WriterGraphRIOTBase adapter(WriterDatasetRIOT writer)
{ return new WriterAdapter(writer) ; }
/** Hidden to direct program to using OutputStreams (for RDF, that gets the charset right) */
private static class IndentedWriterWriter extends IndentedWriter
{
IndentedWriterWriter(Writer w) { super(w) ; }
}
private static class WriterAdapter extends WriterGraphRIOTBase
{
private WriterDatasetRIOT writer ;
WriterAdapter(WriterDatasetRIOT writer) { this.writer = writer ; }
@Override
public Lang getLang()
{ return writer.getLang() ; }
@Override
public void write(OutputStream out, Graph graph, PrefixMap prefixMap, String baseURI, Context context)
{ writer.write(out, RiotLib.dataset(graph), prefixMap, baseURI, context) ; }
@Override
public void write(Writer out, Graph graph, PrefixMap prefixMap, String baseURI, Context context)
{ writer.write(out, RiotLib.dataset(graph), prefixMap, baseURI, context) ; }
}
}