| /* |
| * 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 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.graph.Graph; |
| import org.apache.jena.graph.Node; |
| import org.apache.jena.graph.NodeFactory; |
| import org.apache.jena.graph.Triple; |
| import org.apache.jena.irix.IRIs; |
| import org.apache.jena.irix.IRIx; |
| import org.apache.jena.irix.IRIxResolver; |
| import org.apache.jena.query.ARQ; |
| import org.apache.jena.riot.*; |
| import org.apache.jena.riot.lang.LabelToNode; |
| import org.apache.jena.riot.writer.WriterGraphRIOTBase; |
| import org.apache.jena.shared.PrefixMapping; |
| 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.sparql.util.NodeFactoryExtra; |
| import org.apache.jena.util.iterator.ExtendedIterator; |
| |
| /** |
| * Misc RIOT code |
| */ |
| public class RiotLib { |
| // ---- BlankNode skolemization as IRIs |
| private final static boolean skolomizedBNodes = ARQ.isTrueOrUndef(ARQ.constantBNodeLabels); |
| /** |
| * BNodes as pseudo-IRIs |
| */ |
| private final static String bNodeLabelStart = "_:"; |
| |
| /** |
| * "Skolemize" to a node. |
| * Returns a Node_URI. |
| */ |
| public static Node blankNodeToIri(Node node) { |
| if ( node.isBlank() ) |
| return NodeFactory.createURI(blankNodeToIriString(node)); |
| return node; |
| } |
| |
| /** |
| * "Skolemize" to a string. |
| */ |
| public static String blankNodeToIriString(Node node) { |
| if ( node.isBlank() ) { |
| String x = node.getBlankNodeLabel(); |
| return bNodeLabelStart + x; |
| } |
| if ( node.isURI() ) |
| return node.getURI(); |
| throw new RiotException("Not a blank node or URI"); |
| } |
| |
| /** |
| * Convert an ARQ-encoded blank node URI to a blank node, otherwise return the argument node unchanged. |
| */ |
| public static Node fromIRIorBNode(Node node) { |
| if ( ! node.isURI() ) |
| return node; |
| String uristr = node.getURI(); |
| return createIRIorBNode(uristr); |
| } |
| |
| /** |
| * Implement {@code <_:....>} as a "Node IRI" |
| * that is, use the given label as the BNode internal label. |
| * Use with care. |
| * Returns a Node_URI. |
| */ |
| public static Node createIRIorBNode(String str) { |
| // Is it a bNode label? i.e. <_:xyz> |
| if ( skolomizedBNodes && isBNodeIRI(str) ) { |
| String s = str.substring(bNodeLabelStart.length()); |
| return NodeFactory.createBlankNode(s); |
| } |
| return NodeFactory.createURI(str); |
| } |
| |
| /** |
| * Test whether a IRI is an ARQ-encoded blank node. |
| */ |
| public static boolean isBNodeIRI(String iri) { |
| return iri.startsWith(bNodeLabelStart); |
| } |
| |
| /** |
| * Test whether a node is an ARQ-encoded blank node IRI. |
| */ |
| public static boolean isBNodeIRI(Node node) { |
| if ( ! node.isURI() ) |
| return false; |
| return isBNodeIRI(node.getURI()); |
| } |
| |
| private static final String URI_PREFIX_FIXUP = "local:"; |
| |
| // These two must be in-step. |
| /** |
| * Function applied to undefined prefixes to convert to a URI string |
| */ |
| public static final Function<String, String> fixupPrefixes = URI_PREFIX_FIXUP::concat; |
| |
| /** |
| * Function to test for undefined prefix URIs |
| */ |
| public static final Predicate<String> testFixupedPrefixURI = (x) -> x.startsWith(URI_PREFIX_FIXUP); |
| |
| /** |
| * Test whether a IRI is an 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 prefix is not 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 prefix is not defined. |
| * |
| * @see ARQ#fixupUndefinedPrefixes |
| */ |
| public static String fixupPrefixIRI(String prefixedName) { |
| return fixupPrefixes.apply(prefixedName); |
| } |
| |
| /** |
| * Parse a string to get one Node (the first token in the string) |
| */ |
| public static Node parse(String string) { |
| return NodeFactoryExtra.parseNode(string, null); |
| } |
| |
| 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 |
| */ |
| private static ParserProfile profile(String baseIRI, boolean resolveIRIs, boolean checking, ErrorHandler handler) { |
| LabelToNode labelToNode = SyntaxLabels.createLabelToNode(); |
| IRIx base = resolveIRIs |
| ? IRIs.resolveIRI(baseIRI) |
| : IRIx.create(baseIRI); |
| IRIxResolver resolver = IRIxResolver |
| .create(base) |
| .resolve(resolveIRIs) |
| .allowRelative(false) |
| .build(); |
| return RiotLib.createParserProfile(factoryRDF(labelToNode), handler, resolver, checking); |
| } |
| |
| /** |
| * Create a new (not influenced by anything else) {@code 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) {@code FactoryRDF} |
| * using the default label to blank node scheme. |
| */ |
| public static FactoryRDF factoryRDF() { |
| return factoryRDF(SyntaxLabels.createLabelToNode()); |
| } |
| |
| /** |
| * Create a {@link ParserProfile} with default settings. |
| */ |
| public static ParserProfile dftProfile() { |
| return createParserProfile(RiotLib.factoryRDF(), ErrorHandlerFactory.errorHandlerStd, true); |
| } |
| |
| /** |
| * Create a {@link ParserProfile} with default settings, and a specific error handler. |
| */ |
| public static ParserProfile createParserProfile(FactoryRDF factory, ErrorHandler errorHandler, boolean checking) { |
| return new ParserProfileStd(factory, |
| errorHandler, |
| IRIxResolver.create(IRIs.getSystemBase()).build(), |
| PrefixMapFactory.create(), |
| RIOT.getContext().copy(), |
| checking, |
| false); |
| } |
| |
| /** |
| * Create a {@link ParserProfile}. |
| */ |
| public static ParserProfile createParserProfile(FactoryRDF factory, ErrorHandler errorHandler, |
| IRIxResolver resolver, boolean checking) { |
| return new ParserProfileStd(factory, |
| errorHandler, |
| resolver, |
| PrefixMapFactory.create(), |
| RIOT.getContext().copy(), |
| checking, false); |
| } |
| |
| /** |
| * 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) { |
| graph.find(s, p, o).forEach(acc::add); |
| } |
| |
| public static void writeBase(IndentedWriter out, String base, boolean newStyle) { |
| if (newStyle) |
| writeBaseNewStyle(out, base); |
| else |
| writeBaseOldStyle(out, base); |
| } |
| |
| private static void writeBaseNewStyle(IndentedWriter out, String base) { |
| if (base != null) { |
| out.print("BASE "); |
| out.pad(PREFIX_IRI); |
| out.print("<"); |
| out.print(base); |
| out.print(">"); |
| out.println(); |
| } |
| } |
| |
| private static void writeBaseOldStyle(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(); |
| } |
| } |
| |
| /** |
| * Write prefixes |
| */ |
| public static void writePrefixes(IndentedWriter out, PrefixMap prefixMap, boolean newStyle) { |
| if ( prefixMap != null && !prefixMap.isEmpty() ) { |
| int maxPrefixLength = prefixMap.getMapping().keySet().stream() |
| .map(String::length) |
| .max(Comparator.naturalOrder()) |
| .orElse(0); |
| for (Map.Entry<String, String> e : sortPrefixes(prefixMap)) { |
| writePrefix(out, e.getKey(), e.getValue(), newStyle, maxPrefixLength); |
| } |
| } |
| } |
| |
| /** |
| * Sort the entries of a prefix map by their key |
| */ |
| private static List<Map.Entry<String, String>> sortPrefixes(PrefixMap prefixMap) { |
| List<Map.Entry<String, String>> prefixesMappings = new ArrayList<>(prefixMap.getMapping().entrySet()); |
| prefixesMappings.sort(Map.Entry.comparingByKey()); |
| return prefixesMappings; |
| } |
| |
| /** |
| * Write a prefix. |
| * Write using {@code @prefix} or {@code PREFIX}. |
| */ |
| public static void writePrefix(IndentedWriter out, String prefix, String uri, boolean newStyle) { |
| writePrefix(out, prefix, uri, newStyle, 0); |
| } |
| |
| private static void writePrefix(IndentedWriter out, String prefix, String uri, boolean newStyle, int maxPrefixLenght) { |
| if (newStyle) |
| writePrefixNewStyle(out, prefix, uri, maxPrefixLenght); |
| else |
| writePrefixOldStyle(out, prefix, uri, maxPrefixLenght); |
| } |
| |
| /** |
| * Write prefix, using {@code PREFIX} |
| */ |
| private static void writePrefixNewStyle(IndentedWriter out, String prefix, String uri, int intent) { |
| out.print("PREFIX "); |
| out.print(prefix); |
| out.print(": "); |
| out.pad(9 + intent); // 9 e.q. length of "PREFIX" plus ": " |
| out.print("<"); |
| out.print(uri); |
| out.print(">"); |
| out.println(); |
| } |
| |
| /** |
| * Write prefixes, using {@code @prefix} |
| */ |
| private static void writePrefixOldStyle(IndentedWriter out, String prefix, String uri, int intent) { |
| out.print("@prefix "); |
| out.print(prefix); |
| out.print(": "); |
| out.pad(10 + intent); // 10 e.q. length of "@prefix" plus ": " |
| out.print("<"); |
| out.print(uri); |
| out.print(">"); |
| out.print(" ."); |
| out.println(); |
| } |
| |
| /** @deprecated Use {@link DatasetGraph#prefixes} */ |
| @Deprecated |
| public static PrefixMap prefixMap(DatasetGraph dsg) { |
| return dsg.prefixes(); |
| } |
| |
| /** |
| * IndentedWriter over a java.io.Writer (better to use an IndentedWriter over an OutputStream) |
| */ |
| public static IndentedWriter create(Writer writer) { |
| return new IndentedWriterWriter(writer); |
| } |
| |
| /** @deprecated Use {@link Prefixes#adapt(PrefixMapping)} */ |
| @Deprecated |
| public static PrefixMap prefixMap(Graph graph) { |
| return Prefixes.adapt(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, DatasetGraphFactory.wrap(graph), prefixMap, baseURI, context); |
| } |
| |
| @Override |
| public void write(Writer out, Graph graph, PrefixMap prefixMap, String baseURI, Context context) { |
| writer.write(out, DatasetGraphFactory.wrap(graph), prefixMap, baseURI, context); |
| } |
| } |
| } |