/*
* 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.out ;
import java.net.MalformedURLException ;
import org.apache.jena.atlas.io.AWriter ;
import org.apache.jena.atlas.lib.CharSpace ;
import org.apache.jena.atlas.lib.Pair ;
import org.apache.jena.datatypes.xsd.XSDDatatype ;
import org.apache.jena.graph.Node ;
import org.apache.jena.iri.IRI ;
import org.apache.jena.iri.IRIRelativize ;
import org.apache.jena.riot.system.IRIResolver ;
import org.apache.jena.riot.system.PrefixMap ;
import org.apache.jena.riot.system.PrefixMapFactory ;
import org.apache.jena.riot.system.RiotChars ;
/** Node formatter for Turtle using single line strings */
public class NodeFormatterTTL extends NodeFormatterNT
{
private final NodeToLabel nodeToLabel ;
private final PrefixMap prefixMap ;
private final String baseIRI ;
private final IRI iriResolver ;
public NodeFormatterTTL(String baseIRI, PrefixMap prefixMap) {
this(baseIRI, prefixMap, NodeToLabel.createBNodeByLabelEncoded()) ;
}
public NodeFormatterTTL(String baseIRI, PrefixMap prefixMap, NodeToLabel nodeToLabel) {
super(CharSpace.UTF8) ;
this.nodeToLabel = nodeToLabel ;
if ( prefixMap == null )
prefixMap = PrefixMapFactory.create() ;
this.prefixMap = prefixMap ;
this.baseIRI = baseIRI ;
this.iriResolver =
baseIRI != null ? IRIResolver.iriFactory.construct(baseIRI) : null ;
}
@Override
public void formatURI(AWriter w, String uriStr) {
Pair<String, String> pName = prefixMap.abbrev(uriStr) ;
// Check if legal
if ( pName != null ) {
// Check legal - need to check legal, not for illegal.
String pref = pName.getLeft() ;
String ln = pName.getRight() ;
if ( safeForPrefix(pref) && safeForPrefixLocalname(ln) ) {
w.print(pName.getLeft()) ;
w.print(':') ;
w.print(pName.getRight()) ;
return ;
}
}
// Attempt base abbreviation.
if ( iriResolver != null ) {
String x = abbrevByBase(uriStr) ;
if ( x != null ) {
w.print('<') ;
w.print(x) ;
w.print('>') ;
return ;
}
}
// else
super.formatURI(w, uriStr) ;
}
static private int relFlags = IRIRelativize.SAMEDOCUMENT | IRIRelativize.CHILD ;
/** Abbreviate the URI */
private String abbrevByBase(String uri) {
IRI rel = iriResolver.relativize(uri, relFlags) ;
String r = null ;
try {
r = rel.toASCIIString() ;
} catch (MalformedURLException ex) {
r = rel.toString() ;
}
return r ;
}
/* private-testing */
static boolean safeForPrefix(String str) {
int N = str.length() ;
if ( N == 0 )
return true ;
int idx = 0 ;
idx = skip1_PN_CHARS_BASE(str, idx) ;
if ( idx == -1 )
return false ;
idx = skipAny_PN_CHARS_or_DOT(str, idx, N - 1) ;
if ( idx == -1 )
return false ;
// Final char
idx = skip1_PN_CHARS(str, idx) ;
if ( idx == -1 )
return false ;
return (idx == N) ;
}
// @Override
// public void formatVar(WriterI w, String name)
// @Override
// public void formatBNode(WriterI w, String label)
@Override
public void formatBNode(AWriter w, Node n) {
String x = nodeToLabel.get(null, n) ;
w.print(x) ;
}
// @Override
// public void formatLitString(WriterI w, String lex)
// @Override
// public void formatLitLang(WriterI w, String lex, String langTag)
/* private-testing */static boolean safeForPrefixLocalname(String str) {
int N = str.length() ;
if ( N == 0 )
return true ;
int idx = 0 ;
idx = skip1_PN_CHARS_U_or_digit(str, idx) ;
if ( idx == -1 )
return false ;
idx = skipAny_PN_CHARS_or_DOT(str, idx, N - 1) ;
if ( idx == -1 )
return false ;
idx = skip1_PN_CHARS(str, idx) ;
// Final char
return (idx == N) ;
}
private static boolean is_PN_CHARS_BASE(int ch) {
return RiotChars.isAlpha(ch) ;
}
private static boolean is_PN_CHARS_U(int ch) {
return is_PN_CHARS_BASE(ch) || ch == '_' ;
}
private static boolean is_PN_CHARS(int ch) {
return is_PN_CHARS_U(ch) || ch == '-' || RiotChars.isDigit(ch) || isCharsExtra(ch) ;
}
private static boolean isCharsExtra(int ch) {
return ch == '\u00B7' || RiotChars.range(ch, '\u0300', '\u036F') || RiotChars.range(ch, '\u203F', '\u2040') ;
}
private static int skip1_PN_CHARS_U_or_digit(String str, int idx) {
char ch = str.charAt(idx) ;
if ( is_PN_CHARS_U(ch) )
return idx + 1 ;
if ( RiotChars.isDigit(ch) )
return idx + 1 ;
return -1 ;
}
private static int skip1_PN_CHARS_BASE(String str, int idx) {
char ch = str.charAt(idx) ;
if ( is_PN_CHARS_BASE(ch) )
return idx + 1 ;
return -1 ;
}
private static int skipAny_PN_CHARS_or_DOT(String str, int idx, int max) {
for (int i = idx; i < max; i++) {
char ch = str.charAt(i) ;
if ( !is_PN_CHARS(ch) && ch != '.' )
return i ;
}
return max ;
}
private static int skip1_PN_CHARS(String str, int idx) {
char ch = str.charAt(idx) ;
if ( is_PN_CHARS(ch) )
return idx + 1 ;
return -1 ;
}
private static final String dtDecimal = XSDDatatype.XSDdecimal.getURI() ;
private static final String dtInteger = XSDDatatype.XSDinteger.getURI() ;
private static final String dtDouble = XSDDatatype.XSDdouble.getURI() ;
private static final String dtBoolean = XSDDatatype.XSDboolean.getURI() ;
@Override
public void formatLitDT(AWriter w, String lex, String datatypeURI) {
boolean b = writeLiteralAbbreviated(w, lex, datatypeURI) ;
if ( b ) return ;
writeLiteralLongForm(w, lex, datatypeURI) ;
}
protected void writeLiteralLongForm(AWriter w, String lex, String datatypeURI) {
writeLiteralOneLine(w, lex, datatypeURI);
}
protected void writeLiteralOneLine(AWriter w, String lex, String datatypeURI) {
super.formatLitDT(w, lex, datatypeURI) ;
}
/** Write in a short form, e.g. integer.
* @return True if a short form was output else false.
*/
protected boolean writeLiteralAbbreviated(AWriter w, String lex, String datatypeURI) {
if ( dtDecimal.equals(datatypeURI) ) {
if ( validDecimal(lex) ) {
w.print(lex) ;
return true ;
}
} else if ( dtInteger.equals(datatypeURI) ) {
if ( validInteger(lex) ) {
w.print(lex) ;
return true ;
}
} else if ( dtDouble.equals(datatypeURI) ) {
if ( validDouble(lex) ) {
w.print(lex) ;
return true ;
}
} else if ( dtBoolean.equals(datatypeURI) ) {
// We leave "0" and "1" as-is assumign that if written like that,
// there was a reason.
if ( lex.equals("true") || lex.equals("false") ) {
w.print(lex) ;
return true ;
}
}
return false ;
}
private static boolean validInteger(String lex) {
int N = lex.length() ;
if ( N == 0 )
return false ;
int idx = 0 ;
idx = skipSign(lex, idx) ;
idx = skipDigits(lex, idx) ;
return (idx == N) ;
}
private static boolean validDecimal(String lex) {
// case : In N3, "." illegal, as is "+." and -." but legal in Turtle.
int N = lex.length() ;
if ( N <= 1 )
return false ;
int idx = 0 ;
idx = skipSign(lex, idx) ;
idx = skipDigits(lex, idx) ; // Maybe none.
// DOT required.
if ( idx >= N )
return false ;
char ch = lex.charAt(idx) ;
if ( ch != '.' )
return false ;
idx++ ;
// Digit required.
if ( idx >= N )
return false ;
idx = skipDigits(lex, idx) ;
return (idx == N) ;
}
private static boolean validDouble(String lex) {
int N = lex.length() ;
if ( N == 0 )
return false ;
int idx = 0 ;
// Decimal part (except 12. is legal)
idx = skipSign(lex, idx) ;
int idx2 = skipDigits(lex, idx) ;
boolean initialDigits = (idx != idx2) ;
idx = idx2 ;
// Exponent required.
if ( idx >= N )
return false ;
char ch = lex.charAt(idx) ;
if ( ch == '.' ) {
idx++ ;
if ( idx >= N )
return false ;
idx2 = skipDigits(lex, idx) ;
boolean trailingDigits = (idx != idx2) ;
idx = idx2 ;
if ( idx >= N )
return false ;
if ( !initialDigits && !trailingDigits )
return false ;
}
// "e" or "E"
ch = lex.charAt(idx) ;
if ( ch != 'e' && ch != 'E' )
return false ;
idx++ ;
if ( idx >= N )
return false ;
idx = skipSign(lex, idx) ;
if ( idx >= N )
return false ; // At least one digit.
idx = skipDigits(lex, idx) ;
return (idx == N) ;
}
/**
* Skip digits [0-9] and return the index just after the digits, which may
* be beyond the length of the string. May skip zero.
*/
private static int skipDigits(String str, int start) {
int N = str.length() ;
for (int i = start; i < N; i++) {
char ch = str.charAt(i) ;
if ( !RiotChars.isDigit(ch) )
return i ;
}
return N ;
}
/** Skip any plus or minus */
private static int skipSign(String str, int idx) {
int N = str.length() ;
char ch = str.charAt(idx) ;
if ( ch == '+' || ch == '-' )
return idx + 1 ;
return idx ;
}
}