| /* |
| * 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.n3; |
| |
| import java.io.*; |
| import java.math.BigDecimal; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.nio.charset.StandardCharsets; |
| import java.text.CharacterIterator; |
| import java.text.StringCharacterIterator; |
| import java.util.*; |
| import java.util.Map.Entry; |
| |
| import org.apache.jena.JenaRuntime ; |
| import org.apache.jena.rdf.model.* ; |
| import org.apache.jena.rdf.model.impl.Util ; |
| import org.apache.jena.shared.JenaException ; |
| import org.apache.jena.util.iterator.ClosableIterator ; |
| import org.apache.jena.util.iterator.WrappedIterator ; |
| import org.apache.jena.vocabulary.OWL ; |
| import org.apache.jena.vocabulary.RDF ; |
| import org.apache.jena.vocabulary.XSD ; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** Common framework for implementing N3 writers. |
| */ |
| |
| public class N3JenaWriterCommon implements RDFWriter |
| { |
| static Logger logger = LoggerFactory.getLogger(N3JenaWriterCommon.class) ; |
| |
| // N3 writing proceeds in 2 stages. |
| // First, it analysis the model to be written to extract information |
| // that is going to be specially formatted (RDF lists, one ref anon nodes) |
| // Second do the writing walk. |
| |
| // The simple N3 writer does nothing during preparation. |
| |
| protected Map<String, Object> writerPropertyMap = null ; |
| |
| // BaseURI - <#> |
| // final boolean doAbbreviatedBaseURIref = getBooleanValue("abbrevBaseURI", true) ; |
| protected boolean alwaysAllocateBNodeLabel = false ; |
| |
| // Common variables |
| protected RDFErrorHandler errorHandler = null; |
| |
| protected Map<String, String> prefixMap = new HashMap<>() ; // Prefixes to actually use |
| protected Map<String, String> reversePrefixMap = new HashMap<>() ; // URI->prefix |
| protected Map<Resource, String> bNodesMap = null ; // BNodes seen. |
| protected int bNodeCounter = 0 ; |
| |
| // Specific properties that have a short form. |
| // Not Turtle. |
| protected static final String NS_W3_log = "http://www.w3.org/2000/10/swap/log#" ; |
| |
| protected static Map<String, String> wellKnownPropsMapN3 = new HashMap<>() ; |
| static { |
| wellKnownPropsMapN3.put(NS_W3_log+"implies", "=>" ) ; |
| wellKnownPropsMapN3.put(OWL.sameAs.getURI(), "=" ) ; |
| wellKnownPropsMapN3.put(RDF.type.getURI(), "a" ) ; |
| } |
| |
| protected static Map<String, String> wellKnownPropsMapTurtle = new HashMap<>() ; |
| static { |
| //wellKnownPropsMapTurtle.put(OWL.sameAs.getURI(), "=" ) ; |
| wellKnownPropsMapTurtle.put(RDF.type.getURI(), "a" ) ; |
| } |
| |
| protected Map<String, String> wellKnownPropsMap = wellKnownPropsMapN3 ; |
| |
| // Work variables controlling the output |
| protected N3IndentedWriter out = null ; |
| //Removed base URI specials - look for "// BaseURI - <#>" & doAbbreviatedBaseURIref |
| //String baseURIref = null ; |
| //String baseURIrefHash = null ; |
| |
| // Min spacing of items |
| protected int minGap = getIntValue("minGap", 1) ; |
| protected String minGapStr = pad(minGap) ; |
| |
| // Gap from subject to property |
| protected int indentProperty = getIntValue("indentProperty", 6) ; |
| |
| // Width of property before wrapping. |
| // This is not necessarily a control of total width |
| // e.g. the pretty writer may be writing properties inside indented one ref bNodes |
| protected int widePropertyLen = getIntValue("widePropertyLen", 20) ; |
| |
| // Column for property when an object follows a property on the same line |
| protected int propertyCol = getIntValue("propertyColumn", 8) ; |
| |
| // Minimum gap from property to object when object on a new line. |
| protected int indentObject = propertyCol ; |
| |
| // If a subject is shorter than this, the first property may go on same line. |
| protected int subjectColumn = getIntValue("subjectColumn", indentProperty) ; |
| // Require shortSubject < subjectCol (strict less than) |
| protected int shortSubject = subjectColumn-minGap; |
| |
| protected boolean useWellKnownPropertySymbols = getBooleanValue("usePropertySymbols", true) ; |
| |
| protected boolean allowTripleQuotedStrings = getBooleanValue("useTripleQuotedStrings", true) ; |
| protected boolean allowDoubles = getBooleanValue("useDoubles", true) ; |
| protected boolean allowDecimals = getBooleanValue("useDecimals", true) ; |
| |
| // ---------------------------------------------------- |
| // Jena RDFWriter interface |
| |
| @Override |
| public RDFErrorHandler setErrorHandler(RDFErrorHandler errHandler) |
| { |
| RDFErrorHandler old = errorHandler; |
| errorHandler = errHandler; |
| return old; |
| } |
| |
| @Override |
| public Object setProperty(String propName, Object propValue) |
| { |
| if ( ! ( propValue instanceof String ) ) |
| { |
| logger.warn("N3.setProperty: Property for '"+propName+"' is not a string") ; |
| propValue = propValue.toString() ; |
| } |
| |
| // Store absolute name of property |
| propName = absolutePropName(propName) ; |
| if ( writerPropertyMap == null ) |
| writerPropertyMap = new HashMap<>() ; |
| Object oldValue = writerPropertyMap.get(propName); |
| writerPropertyMap.put(propName, propValue); |
| return oldValue; |
| } |
| |
| /** Write the model out in N3. The writer should be one suitable for UTF-8 which |
| * excludes a PrintWriter or a FileWriter which use default character set. |
| * |
| * Examples: |
| * <pre> |
| * try { |
| * Writer w = new BufferedWriter(new OutputStreamWriter(output, "UTF-8")) ; |
| * model.write(w, base) ; |
| * try { w.flush() ; } catch (IOException ioEx) {...} |
| * } catch (java.io.UnsupportedEncodingException ex) {} //UTF-8 is required so can't happen |
| * </pre> |
| * or |
| * <pre> |
| * try { |
| * OutputStream out = new FileOutputStream(file) ; |
| * Writer w = new BufferedWriter(new OutputStreamWriter(out, "UTF-8")) ; |
| * model.write(w, base) ; |
| * } |
| * catch (java.io.UnsupportedEncodingException ex) {} |
| * catch (java.io.FileNotFoundException noFileEx) { ... } |
| * </pre> |
| * @see #write(Model,Writer,String) |
| */ |
| |
| @Override |
| public void write(Model baseModel, Writer _out, String base) |
| { |
| if (!(_out instanceof BufferedWriter)) |
| _out = new BufferedWriter(_out); |
| out = new N3IndentedWriter(_out); |
| |
| // BaseURI - <#> |
| // if ( base != null ) |
| // { |
| // baseURIref = base ; |
| // if ( !base.endsWith("#") &&! isOpaque(base) ) |
| // baseURIrefHash = baseURIref+"#" ; |
| // } |
| |
| processModel(baseModel) ; |
| } |
| |
| /** Write the model out in N3, encoded in in UTF-8 |
| * @see #write(Model,Writer,String) |
| */ |
| |
| @Override |
| public synchronized void write(Model model, OutputStream output, String base) |
| { |
| Writer w = new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8)) ; |
| write(model, w, base) ; |
| try { w.flush() ; } catch (IOException ioEx) { throw new JenaException(ioEx) ; } |
| } |
| |
| // ---------------------------------------------------- |
| // The assumed processing model is: |
| // Writing N3 involves ordering the graph into: |
| // -- Subjects |
| // -- Property lists within subjects |
| // -- Object lists with in properties |
| |
| // A derived class may choose to intercept and implement at any of these levels. |
| |
| // Standard layout is: |
| // subject |
| // property1 value1 ; |
| // property2 value2 ; |
| // property3 value3 . |
| |
| // Normal hook points for subclasses. |
| |
| protected void startWriting() {} |
| protected void finishWriting() {} |
| protected void prepare(Model model) {} |
| |
| protected void processModel(Model model) |
| { |
| prefixMap = model.getNsPrefixMap() ; |
| bNodesMap = new HashMap<>() ; |
| |
| for ( Iterator<Entry<String, String>> iter = prefixMap.entrySet().iterator() ; iter.hasNext() ; ) |
| { |
| Entry<String, String> e = iter.next() ; |
| String prefix = e.getKey() ; |
| String uri = e.getValue(); |
| |
| // XML namespaces name can include '.' |
| // Turtle prefixed names can't. |
| if ( ! checkPrefixPart(prefix) ) |
| iter.remove() ; |
| else |
| { |
| if ( checkPrefixPart(prefix) ) |
| // Build acceptable reverse mapping |
| reversePrefixMap.put(uri, prefix) ; |
| } |
| } |
| |
| startWriting() ; |
| prepare(model) ; |
| |
| writeHeader(model) ; |
| writePrefixes(model) ; |
| |
| if (prefixMap.size() != 0) |
| out.println(); |
| |
| // Do the output. |
| writeModel(model) ; |
| |
| // Release intermediate memory - allows reuse of a writer |
| finishWriting() ; |
| bNodesMap = null ; |
| } |
| |
| protected void writeModel(Model model) |
| { |
| // Needed only for no prefixes, no blank first line. |
| boolean doingFirst = true; |
| ResIterator rIter = listSubjects(model); |
| for (; rIter.hasNext();) |
| { |
| // Subject: |
| // First - it is something we will write out as a structure in an object field? |
| // That is, a RDF list or the object of exactly one statement. |
| Resource subject = rIter.nextResource(); |
| if ( skipThisSubject(subject) ) |
| { |
| if (N3JenaWriter.DEBUG) |
| out.println("# Skipping: " + formatResource(subject)); |
| continue; |
| } |
| |
| // We really are going to print something via writeTriples |
| if (doingFirst) |
| doingFirst = false; |
| else |
| out.println(); |
| |
| writeOneGraphNode(subject) ; |
| |
| |
| } |
| rIter.close(); |
| } |
| |
| protected ResIterator listSubjects(Model model) { return model.listSubjects(); } |
| |
| protected void writeOneGraphNode(Resource subject) |
| { |
| // New top level item. |
| // Does not take effect until newline. |
| out.incIndent(indentProperty) ; |
| writeSubject(subject); |
| ClosableIterator<Property> iter = preparePropertiesForSubject(subject); |
| writePropertiesForSubject(subject, iter) ; |
| out.decIndent(indentProperty) ; |
| out.println(" ."); |
| } |
| |
| protected void writePropertiesForSubject(Resource subj, ClosableIterator<Property> iter) |
| { |
| // For each property. |
| for (; iter.hasNext();) |
| { |
| Property property = iter.next(); |
| |
| // Object list |
| writeObjectList(subj, property); |
| |
| if (iter.hasNext()) |
| out.println(" ;"); |
| } |
| iter.close(); |
| } |
| |
| // Hook called on every resource. |
| // Since there is spacing bewteen resource frames, need to know |
| // whether an item will cause any output. |
| protected boolean skipThisSubject(Resource r) { return false ; } |
| |
| |
| // This is the hook called within writeModel. |
| // NB May not be at the top level (indent = 0) |
| |
| protected void writeSubject(Resource subject) |
| { |
| String subjStr = formatResource(subject); |
| out.print(subjStr); |
| // May be very short : if so, stay on this line. |
| |
| // Currently at end of subject. |
| // NB shortSubject is (subjectColumn-minGap) so there is a gap. |
| |
| if (subjStr.length() < shortSubject ) |
| { |
| out.print(pad(subjectColumn - subjStr.length()) ); |
| } |
| else |
| // Does not fit this line. |
| out.println(); |
| } |
| |
| protected void writeHeader(Model model) |
| { |
| // BaseURI - <#> |
| // if (baseURIref != null && !baseURIref.equals("") ) |
| // out.println("# Base: " + baseURIref); |
| } |
| |
| protected N3IndentedWriter getOutput() { return out ; } |
| protected Map<String, String> getPrefixes() { return prefixMap ; } |
| |
| protected void writePrefixes(Model model) |
| { |
| for ( Entry<String, String> entry : prefixMap.entrySet() ) |
| { |
| String u = entry.getValue(); |
| String p = entry.getKey(); |
| |
| // BaseURI - <#> |
| // // Special cases: N3 handling of base names. |
| // if (doAbbreviatedBaseURIref && p.equals("")) |
| // { |
| // if (baseURIrefHash != null && u.equals(baseURIrefHash)) |
| // u = "#"; |
| // if (baseURIref != null && u.equals(baseURIref)) |
| // u = ""; |
| // } |
| |
| String tmp = "@prefix " + p + ": "; |
| out.print( tmp ); |
| out.print( pad( 16 - tmp.length() ) ); |
| // NB Starts with a space to ensure a gap. |
| out.println( " <" + u + "> ." ); |
| } |
| |
| } |
| |
| protected void writeObjectList(Resource subject, Property property) |
| { |
| String propStr = formatProperty(property) ; |
| |
| // if (wellKnownPropsMap.containsKey(property.getURI())) |
| // propStr = (String) wellKnownPropsMap.get(property.getURI()); |
| // else |
| // propStr = formatResource(property); |
| |
| // Write with object lists as clusters of statements with the same property |
| // Looks more like a machine did it but fewer bad cases. |
| |
| StmtIterator sIter = subject.listProperties(property); |
| for (; sIter.hasNext();) |
| { |
| Statement stmt = sIter.nextStatement() ; |
| String objStr = formatNode(stmt.getObject()) ; |
| |
| out.print(propStr); |
| out.incIndent(indentObject); |
| |
| if ( (propStr.length()+minGap) <= widePropertyLen ) |
| { |
| // Property col allows for min gap but widePropertyLen > propertyCol |
| // (which looses alignment - this is intentional. |
| // Ensure there is at least min gap. |
| |
| int padding = calcPropertyPadding(propStr) ; |
| out.print(pad(padding)) ; |
| |
| // if ( propStr.length() < propertyWidth ) |
| // out.print( pad(propertyCol-minGap-propStr.length()) ) ; |
| // out.print(minGapStr) ; |
| } |
| else |
| // Does not fit this line. |
| out.println(); |
| |
| // Write one object - simple writing. |
| |
| out.print(objStr) ; |
| out.decIndent(indentObject); |
| |
| if ( sIter.hasNext() ) |
| { |
| out.println(" ;") ; |
| } |
| } |
| sIter.close() ; |
| |
| } |
| |
| protected String formatNode(RDFNode node) |
| { |
| if (node instanceof Literal) |
| return formatLiteral((Literal) node); |
| else |
| return formatResource((Resource)node) ; |
| } |
| |
| protected void writeObject(RDFNode node) |
| { |
| if (node instanceof Literal) |
| { |
| writeLiteral((Literal) node); |
| return; |
| } |
| |
| Resource rObj = (Resource) node; |
| |
| out.print(formatResource(rObj)); |
| } |
| |
| protected void writeLiteral(Literal literal) |
| { |
| out.print(formatLiteral(literal)) ; |
| } |
| |
| protected ClosableIterator<Property> preparePropertiesForSubject(Resource r) |
| { |
| // Properties to do. |
| Set<Property> properties = new HashSet<>() ; |
| |
| StmtIterator sIter = r.listProperties(); |
| for ( ; sIter.hasNext() ; ) |
| properties.add(sIter.nextStatement().getPredicate()) ; |
| sIter.close() ; |
| return WrappedIterator.create(properties.iterator()) ; |
| } |
| |
| |
| // Utility operations |
| protected String formatResource(Resource r) |
| { |
| if ( r.isAnon() ) |
| { |
| if ( ! alwaysAllocateBNodeLabel ) |
| { |
| // Does anything point to it? |
| StmtIterator sIter = r.getModel().listStatements(null, null, r) ; |
| |
| if ( ! sIter.hasNext() ) |
| { |
| sIter.close() ; |
| // This bNode is not referenced so don't need the bNode Id. |
| // Must be a subject - indent better be zero! |
| // This only happens for subjects because object bNodes |
| // referred to once (the other case for [] syntax) |
| // are handled elsewhere (by oneRef set) |
| |
| // Later: use [ prop value ] for this. |
| return "[]" ; |
| } |
| sIter.close() ; |
| } |
| if ( ! bNodesMap.containsKey(r) ) |
| bNodesMap.put(r, "_:b"+(++bNodeCounter)) ; |
| return bNodesMap.get(r) ; |
| |
| } |
| |
| // It has a URI. |
| |
| if ( r.equals(RDF.nil) ) |
| return "()" ; |
| |
| return formatURI(r.getURI()) ; |
| } |
| |
| protected String formatLiteral(Literal literal) |
| { |
| String datatype = literal.getDatatypeURI() ; |
| String lang = literal.getLanguage() ; |
| String s = literal.getLexicalForm() ; |
| |
| if ( datatype != null ) |
| { |
| // Special form we know how to handle? |
| // Assume valid text |
| if ( datatype.equals(XSD.integer.getURI()) ) |
| { |
| try { |
| new java.math.BigInteger(s) ; |
| return s ; |
| } catch (NumberFormatException nfe) {} |
| // No luck. Continue. |
| // Continuing is always safe. |
| } |
| |
| if ( datatype.equals(XSD.decimal.getURI()) ) |
| { |
| // Must have ., can't have e or E |
| if ( s.indexOf('.') >= 0 && |
| s.indexOf('e') == -1 && s.indexOf('E') == -1 ) |
| { |
| // Turtle - N3 does not allow .3 +.3 or -.3 |
| // See if parsable. |
| try { |
| new BigDecimal(s); |
| return s ; |
| } catch (NumberFormatException nfe) {} |
| } |
| } |
| |
| if ( this.allowDoubles && datatype.equals(XSD.xdouble.getURI()) ) |
| { |
| // Must have 'e' or 'E' (N3 and Turtle now read 2.3 as a decimal). |
| if ( s.indexOf('e') >= 0 || |
| s.indexOf('E') >= 0 ) |
| { |
| try { |
| // Validate it. |
| Double.parseDouble(s) ; |
| return s ; |
| } catch (NumberFormatException nfe) {} |
| // No luck. Continue. |
| } |
| } |
| } |
| // Format the text - with escaping. |
| StringBuffer sbuff = new StringBuffer() ; |
| boolean singleQuoteLiteral = true ; |
| |
| String quoteMarks = "\"" ; |
| |
| // Things that force the use of """ strings |
| if ( this.allowTripleQuotedStrings && |
| ( s.indexOf("\n") != -1 || |
| s.indexOf("\r") != -1 || |
| s.indexOf("\f") != -1 ) ) |
| { |
| quoteMarks = "\"\"\"" ; |
| singleQuoteLiteral = false ; |
| } |
| |
| sbuff.append(quoteMarks); |
| string(sbuff, s, singleQuoteLiteral) ; |
| sbuff.append(quoteMarks); |
| |
| if ( Util.isLangString(literal) ) { |
| sbuff.append("@") ; |
| sbuff.append(lang) ; |
| } else if ( ! Util.isSimpleString(literal) ) { |
| sbuff.append("^^") ; |
| sbuff.append(formatURI(datatype)) ; |
| } |
| return sbuff.toString() ; |
| } |
| |
| protected String formatProperty(Property p) |
| { |
| String prop = p.getURI() ; |
| if ( this.useWellKnownPropertySymbols && wellKnownPropsMap.containsKey(prop) ) |
| return wellKnownPropsMap.get(prop); |
| |
| return formatURI(prop) ; |
| } |
| |
| protected String formatURI(String uriStr) |
| { |
| |
| |
| // BaseURI - <#> |
| // if ( doAbbreviatedBaseURIref && uriStr.equals(baseURIref) ) |
| // return "<>" ; |
| |
| // Try for a prefix and write as prefixed name. |
| // 1/ Try splitting as a prefixed name |
| // 2/ Search for possibilities |
| |
| // Stage 1. |
| int idx = splitIdx(uriStr) ; |
| // Depends on legal URIs. |
| if ( idx >= 0 ) |
| { |
| // Include the # itself. |
| String x = uriStr.substring(0,idx+1) ; |
| String prefix = reversePrefixMap.get(x) ; |
| if ( prefix != null ) |
| { |
| String localPart = uriStr.substring(idx+1) ; |
| if ( checkNamePart(localPart) ) |
| return prefix+':'+localPart ; |
| } |
| } |
| |
| // Unsplit. Could just return here. |
| // // Find the longest if several. |
| // // Possible optimization: split URI and have URI=> ns: map. |
| // // Ordering prefixes by length, then first hit is better. |
| // // |
| // // Also: could just assume that the split is on / or # |
| // // Means we need to find a prefix just once. |
| // for ( Iterator<String> pIter = prefixMap.keySet().iterator() ; pIter.hasNext() ; ) |
| // { |
| // String p = pIter.next() ; |
| // String u = prefixMap.get(p) ; |
| // if ( uriStr.startsWith(u) ) |
| // if ( matchURI.length() < u.length() ) |
| // { |
| // matchPrefix = p ; |
| // matchURI = u ; |
| // } |
| // } |
| // if ( matchPrefix != null ) |
| // { |
| // String localname = uriStr.substring(matchURI.length()) ; |
| // |
| // if ( checkPrefixedName(matchPrefix, localname) ) |
| // return matchPrefix+":"+localname ; |
| // |
| // // Continue and return quoted URIref |
| // } |
| |
| // Not as a prefixed name - write as a quoted URIref |
| // It should be right - the writer should be UTF-8 on output. |
| return "<"+uriStr+">" ; |
| } |
| |
| protected static int splitIdx(String uriStr) |
| { |
| int idx = uriStr.lastIndexOf('#') ; |
| if ( idx >= 0 ) |
| return idx ; |
| // No # - try for / |
| idx = uriStr.lastIndexOf('/') ; |
| return idx ; |
| } |
| |
| // Checks of prefixed names |
| // These tests must agree, or be more restrictive, than the parser. |
| protected static boolean checkPrefixedName(String ns, String local) |
| { |
| return checkPrefixPart(ns) && checkNamePart(local) ; |
| } |
| |
| /* http://www.w3.org/TeamSubmission/turtle/#sec-grammar-grammar |
| * [27] qname ::= prefixName? ':' name? |
| * [30] nameStartChar ::= [A-Z] | "_" | [a-z] | [#x00C0-#x00D6] | [#x00D8-#x00F6] | [#x00F8-#x02FF] | [#x0370-#x037D] | [#x037F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF] |
| * [31] nameChar ::= nameStartChar | '-' | [0-9] | #x00B7 | [#x0300-#x036F] | [#x203F-#x2040] |
| * [32] name ::= nameStartChar nameChar* |
| * [33] prefixName ::= ( nameStartChar - '_' ) nameChar* |
| */ |
| |
| protected static boolean checkPrefixPart(String s) |
| { |
| if ( s.length() == 0 ) |
| return true; |
| CharacterIterator cIter = new StringCharacterIterator(s) ; |
| char ch = cIter.first() ; |
| if ( ! checkNameStartChar(ch) ) |
| return false ; |
| if ( ch == '_' ) // Can't start with _ (bnodes labels handled separately) |
| return false ; |
| return checkNameTail(cIter) ; |
| } |
| |
| protected static boolean checkNamePart(String s) |
| { |
| if ( s.length() == 0 ) |
| return true; |
| CharacterIterator cIter = new StringCharacterIterator(s) ; |
| char ch = cIter.first() ; |
| if ( ! checkNameStartChar(ch) ) |
| return false ; |
| return checkNameTail(cIter) ; |
| } |
| |
| private static boolean checkNameTail(CharacterIterator cIter) |
| { |
| // Assumes cIter.first already called but nothing else. |
| // Skip first char. |
| char ch = cIter.next() ; |
| for ( ; ch != java.text.CharacterIterator.DONE ; ch = cIter.next() ) |
| { |
| if ( ! checkNameChar(ch) ) |
| return false ; |
| } |
| return true ; |
| } |
| |
| protected static boolean checkNameStartChar(char ch) |
| { |
| if ( Character.isLetter(ch) ) |
| return true ; |
| if ( ch == '_' ) |
| return true ; |
| return false ; |
| } |
| |
| protected static boolean checkNameChar(char ch) |
| { |
| if ( Character.isLetterOrDigit(ch) ) |
| return true ; |
| if ( ch == '_' ) |
| return true ; |
| if ( ch == '-' ) |
| return true ; |
| return false ; |
| } |
| |
| |
| protected final static String WS = "\n\r\t" ; |
| |
| protected static void string(StringBuffer sbuff, String s, boolean singleQuoteLiteral) |
| { |
| for (int i = 0; i < s.length(); i++) { |
| char c = s.charAt(i); |
| |
| // Escape escapes and quotes |
| if (c == '\\' || c == '"' ) |
| { |
| sbuff.append('\\') ; |
| sbuff.append(c) ; |
| continue ; |
| } |
| |
| // Characters to literally output. |
| // This would generate 7-bit safe files |
| // if (c >= 32 && c < 127) |
| // { |
| // sbuff.append(c) ; |
| // continue; |
| // } |
| |
| // Whitespace |
| if ( singleQuoteLiteral && ( c == '\n' || c == '\r' || c == '\f' ) ) |
| { |
| if (c == '\n') sbuff.append("\\n"); |
| if (c == '\t') sbuff.append("\\t"); |
| if (c == '\r') sbuff.append("\\r"); |
| if (c == '\f') sbuff.append("\\f"); |
| continue ; |
| } |
| |
| // Output as is (subject to UTF-8 encoding on output that is) |
| sbuff.append(c) ; |
| |
| // // Unicode escapes |
| // // c < 32, c >= 127, not whitespace or other specials |
| // String hexstr = Integer.toHexString(c).toUpperCase(); |
| // int pad = 4 - hexstr.length(); |
| // sbuff.append("\\u"); |
| // for (; pad > 0; pad--) |
| // sbuff.append("0"); |
| // sbuff.append(hexstr); |
| } |
| } |
| |
| protected int calcPropertyPadding(String propStr) |
| { |
| int padding = propertyCol - propStr.length(); |
| if (padding < minGap) |
| padding = minGap; |
| return padding ; |
| } |
| |
| protected static String pad(int cols) |
| { |
| StringBuffer sb = new StringBuffer() ; |
| for ( int i = 0 ; i < cols ; i++ ) |
| sb.append(' ') ; |
| return sb.toString() ; |
| } |
| |
| // Utilities |
| |
| protected int countProperties(Resource r) |
| { |
| int numProp = 0 ; |
| StmtIterator sIter = r.listProperties() ; |
| for ( ; sIter.hasNext() ; ) |
| { |
| sIter.nextStatement() ; |
| numProp++ ; |
| } |
| sIter.close() ; |
| return numProp ; |
| } |
| |
| protected int countProperties(Resource r, Property p) |
| { |
| int numProp = 0 ; |
| StmtIterator sIter = r.listProperties(p) ; |
| for ( ; sIter.hasNext() ; ) |
| { |
| sIter.nextStatement() ; |
| numProp++ ; |
| } |
| sIter.close() ; |
| return numProp ; |
| } |
| |
| |
| protected int countArcsTo(Resource resource) |
| { |
| return countArcsTo(null, resource) ; |
| } |
| |
| protected int countArcsTo(Property prop, Resource resource) |
| { |
| int numArcs = 0 ; |
| StmtIterator sIter = resource.getModel().listStatements(null, prop, resource) ; |
| for ( ; sIter.hasNext() ; ) |
| { |
| sIter.nextStatement() ; |
| numArcs++ ; |
| } |
| sIter.close() ; |
| return numArcs ; |
| } |
| |
| |
| protected Iterator<RDFNode> rdfListIterator(Resource r) |
| { |
| List<RDFNode> list = new ArrayList<>() ; |
| |
| for ( ; ! r.equals(RDF.nil); ) |
| { |
| StmtIterator sIter = r.getModel().listStatements(r, RDF.first, (RDFNode)null) ; |
| list.add(sIter.nextStatement().getObject()) ; |
| if ( sIter.hasNext() ) |
| // @@ need to cope with this (unusual) case |
| throw new JenaException("N3: Multi valued list item") ; |
| sIter = r.getModel().listStatements(r, RDF.rest, (RDFNode)null) ; |
| r = (Resource)sIter.nextStatement().getObject() ; |
| if ( sIter.hasNext() ) |
| throw new JenaException("N3: List has two tails") ; |
| } |
| return list.iterator() ; |
| } |
| |
| // Convenience operations for accessing system properties. |
| |
| protected String getStringValue(String prop, String defaultValue) |
| { |
| String p = getPropValue(prop) ; |
| |
| if ( p == null ) |
| return defaultValue ; |
| return p ; |
| } |
| |
| protected boolean getBooleanValue(String prop, boolean defaultValue) |
| { |
| String p = getPropValue(prop) ; |
| |
| if ( p == null ) |
| return defaultValue ; |
| |
| if ( p.equalsIgnoreCase("true") ) |
| return true ; |
| |
| if ( p.equals("1") ) |
| return true ; |
| |
| return false ; |
| } |
| |
| protected int getIntValue(String prop, int defaultValue) |
| { |
| String p = getPropValue(prop) ; |
| if ( p == null ) |
| return defaultValue ; |
| try { |
| return Integer.parseInt(p) ; |
| } catch (NumberFormatException ex) |
| { |
| logger.warn("Format error for property: "+prop) ; |
| return defaultValue ; |
| } |
| } |
| |
| // May be the absolute or local form of the property name |
| |
| protected String getPropValue(String prop) |
| { |
| prop = absolutePropName(prop) ; |
| if ( writerPropertyMap != null && writerPropertyMap.containsKey(prop) ) |
| { |
| Object obj = writerPropertyMap.get(prop) ; |
| if ( ! ( obj instanceof String ) ) |
| logger.warn("getPropValue: N3 Property for '"+prop+"' is not a string") ; |
| return (String)obj ; |
| } |
| String s = JenaRuntime.getSystemProperty(prop) ; |
| if ( s == null ) |
| s = JenaRuntime.getSystemProperty(localPropName(prop)) ; |
| return s ; |
| } |
| |
| protected String absolutePropName(String propName) |
| { |
| if ( propName.indexOf(':') == -1 ) |
| return N3JenaWriter.propBase + propName ; |
| return propName ; |
| } |
| |
| protected String localPropName(String propName) |
| { |
| if ( propName.startsWith(N3JenaWriter.propBase) ) |
| propName = propName.substring(N3JenaWriter.propBase.length()) ; |
| return propName ; |
| } |
| |
| private boolean isOpaque(String uri) |
| { |
| try { |
| return new URI(uri).isOpaque() ; |
| } catch (URISyntaxException ex) { return true ; } |
| } |
| } |