001/*
002 * Licensed to DuraSpace under one or more contributor license agreements.
003 * See the NOTICE file distributed with this work for additional information
004 * regarding copyright ownership.
005 *
006 * DuraSpace licenses this file to you under the Apache License,
007 * Version 2.0 (the "License"); you may not use this file except in
008 * compliance with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.fcrepo.jena;
019
020import org.apache.jena.atlas.io.AWriter;
021import org.apache.jena.datatypes.RDFDatatype;
022import org.apache.jena.datatypes.xsd.XSDDatatype;
023import org.apache.jena.graph.Node;
024import org.apache.jena.riot.out.NodeFormatterTTL;
025import org.apache.jena.riot.out.NodeToLabel;
026import org.apache.jena.riot.system.PrefixMap;
027import org.apache.jena.riot.system.RiotChars;
028
029/**
030 * @author awoods
031 * @since 2017/01/03
032 */
033public class FedoraNodeFormatterTTL extends NodeFormatterTTL {
034
035    /**
036     * @param baseIRI
037     * @param prefixMap
038     * @param nodeToLabel
039     */
040    public FedoraNodeFormatterTTL(final String baseIRI, final PrefixMap prefixMap, final NodeToLabel nodeToLabel) {
041        super(baseIRI, prefixMap, nodeToLabel);
042    }
043
044    /**
045     * @param w
046     * @param n
047     */
048    @Override
049    public void formatLiteral(final AWriter w, final Node n) {
050        final RDFDatatype dt = n.getLiteralDatatype();
051        final String lang = n.getLiteralLanguage();
052        final String lex = n.getLiteralLexicalForm();
053
054        if (lang != null && !lang.equals("")) {
055            formatLitLang(w, lex, lang);
056        } else if (dt == null) {
057            // RDF 1.0, simple literal.
058            formatLitString(w, lex);
059            // NOTE, Fedora change: remove condition
060//        } else if ( JenaRuntime.isRDF11 && dt.equals(XSDDatatype.XSDstring) ) {
061//            // RDF 1.1, xsd:string - output as short string.
062//            formatLitString(w, lex) ;
063        } else {
064            // Datatype, no language tag, not short string.
065            formatLitDT(w, lex, dt.getURI());
066        }
067    }
068
069    // NOTE, Fedora change: private -> protected
070    protected static final String dtDecimal = XSDDatatype.XSDdecimal.getURI() ;
071    protected static final String dtInteger = XSDDatatype.XSDinteger.getURI() ;
072    protected static final String dtDouble  = XSDDatatype.XSDdouble.getURI() ;
073
074    /**
075     * Write in a short form, e.g. integer.
076     *
077     * @return True if a short form was output else false.
078     */
079    protected boolean writeLiteralAbbreviated(final AWriter w, final String lex, final String datatypeURI) {
080        if (dtDecimal.equals(datatypeURI)) {
081            if (validDecimal(lex)) {
082                w.print(lex);
083                return true;
084            }
085        } else if (dtInteger.equals(datatypeURI)) {
086            if (validInteger(lex)) {
087                w.print(lex);
088                return true;
089            }
090        } else if (dtDouble.equals(datatypeURI)) {
091            if (validDouble(lex)) {
092                w.print(lex);
093                return true;
094            }
095            // NOTE, Fedora change: Remove condition
096//        } else if ( dtBoolean.equals(datatypeURI) ) {
097//            // We leave "0" and "1" as-is assumign that if written like that,
098//            // there was a reason.
099//            if ( lex.equals("true") || lex.equals("false") ) {
100//                w.print(lex) ;
101//                return true ;
102//            }
103        }
104        return false;
105    }
106
107    //********************************************************************
108    // NOTE, Fedora: Below are added from NodeFormatterTTL without change.
109    //********************************************************************
110
111    private static boolean validInteger(String lex) {
112        int N = lex.length() ;
113        if ( N == 0 )
114            return false ;
115        int idx = 0 ;
116
117        idx = skipSign(lex, idx) ;
118        idx = skipDigits(lex, idx) ;
119        return (idx == N) ;
120    }
121
122    private static boolean validDecimal(String lex) {
123        // case : In N3, "." illegal, as is "+." and -." but legal in Turtle.
124        int N = lex.length() ;
125        if ( N <= 1 )
126            return false ;
127        int idx = 0 ;
128
129        idx = skipSign(lex, idx) ;
130        idx = skipDigits(lex, idx) ; // Maybe none.
131
132        // DOT required.
133        if ( idx >= N )
134            return false ;
135
136        char ch = lex.charAt(idx) ;
137        if ( ch != '.' )
138            return false ;
139        idx++ ;
140        // Digit required.
141        if ( idx >= N )
142            return false ;
143        idx = skipDigits(lex, idx) ;
144        return (idx == N) ;
145    }
146
147    private static boolean validDouble(String lex) {
148        int N = lex.length() ;
149        if ( N == 0 )
150            return false ;
151        int idx = 0 ;
152
153        // Decimal part (except 12. is legal)
154
155        idx = skipSign(lex, idx) ;
156
157        int idx2 = skipDigits(lex, idx) ;
158        boolean initialDigits = (idx != idx2) ;
159        idx = idx2 ;
160        // Exponent required.
161        if ( idx >= N )
162            return false ;
163        char ch = lex.charAt(idx) ;
164        if ( ch == '.' ) {
165            idx++ ;
166            if ( idx >= N )
167                return false ;
168            idx2 = skipDigits(lex, idx) ;
169            boolean trailingDigits = (idx != idx2) ;
170            idx = idx2 ;
171            if ( idx >= N )
172                return false ;
173            if ( !initialDigits && !trailingDigits )
174                return false ;
175        }
176        // "e" or "E"
177        ch = lex.charAt(idx) ;
178        if ( ch != 'e' && ch != 'E' )
179            return false ;
180        idx++ ;
181        if ( idx >= N )
182            return false ;
183        idx = skipSign(lex, idx) ;
184        if ( idx >= N )
185            return false ; // At least one digit.
186        idx = skipDigits(lex, idx) ;
187        return (idx == N) ;
188    }
189
190    /**
191     * Skip digits [0-9] and return the index just after the digits, which may
192     * be beyond the length of the string. May skip zero.
193     */
194    private static int skipDigits(String str, int start) {
195        int N = str.length() ;
196        for (int i = start; i < N; i++) {
197            char ch = str.charAt(i) ;
198            if ( !RiotChars.isDigit(ch) )
199                return i ;
200        }
201        return N ;
202    }
203
204    /** Skip any plus or minus */
205    private static int skipSign(String str, int idx) {
206        int N = str.length() ;
207        char ch = str.charAt(idx) ;
208        if ( ch == '+' || ch == '-' )
209            return idx + 1 ;
210        return idx ;
211    }
212
213}