blob: f3c90e3398a15c2debbac028b3a5e9c07c5bdb70 [file] [log] [blame]
/*
* 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.rdfxml.xmloutput.impl;
import java.io.OutputStream ;
import java.io.OutputStreamWriter ;
import java.io.PrintWriter ;
import java.io.Writer ;
import java.util.* ;
import java.util.Map.Entry ;
import java.util.regex.Pattern ;
import org.apache.jena.JenaRuntime ;
import org.apache.jena.irix.IRIException;
import org.apache.jena.irix.IRIProviderJenaIRI.IRIxJena;
import org.apache.jena.irix.IRIs;
import org.apache.jena.irix.IRIx;
import org.apache.jena.rdf.model.* ;
import org.apache.jena.rdf.model.impl.RDFDefaultErrorHandler ;
import org.apache.jena.rdf.model.impl.ResourceImpl ;
import org.apache.jena.rdf.model.impl.Util ;
import org.apache.jena.rdfxml.xmloutput.RDFXMLWriterI ;
import org.apache.jena.shared.* ;
import org.apache.jena.util.CharEncoding ;
import org.apache.jena.util.FileUtils ;
import org.apache.jena.util.SplitIRI;
import org.apache.jena.util.XMLChar;
import org.apache.jena.vocabulary.* ;
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;
/**
* This is not part of the public API.
* Base class for XML serializers.
* All methods with side-effects should be synchronized in this class and its
* subclasses. (i. e. XMLWriters assume that the world is not changing around
* them while they are writing).
*
* Functionality:
*
* <ul>
* <li>setProperty etc
* <li>namespace prefixes
* <li>xmlbase
* <li>relative URIs
* <li>encoding issues
* <li>anonymous node presentational
* <li>errorHandler
* </ul>
*/
abstract public class BaseXMLWriter implements RDFXMLWriterI {
private static final String newline =
JenaRuntime.getSystemProperty( "line.separator" );
static private final String DEFAULT_NS_ENTITY_NAME = "this";
static private final String DEFAULT_NS_ENTITY_NAME_ALT = "here";
private String defaultNSEntityName = "UNSET" ;
public BaseXMLWriter() {
setupMaps();
}
private static Logger xlogger = LoggerFactory.getLogger( BaseXMLWriter.class );
protected static SimpleLogger logger = new SimpleLogger() {
@Override
public void warn(String s) {
xlogger.warn(s);
}
@Override
public void warn(String s, Exception e) {
xlogger.warn(s,e);
}
};
public static SimpleLogger setLogger(SimpleLogger lg) {
SimpleLogger old = logger;
logger= lg;
return old;
}
abstract protected void unblockAll();
abstract protected void blockRule(Resource r);
abstract protected void writeBody
( Model mdl, PrintWriter pw, String baseUri, boolean inclXMLBase );
static private Set<String> badRDF = Set.of("RDF",
"Description",
"li",
"about",
"aboutEach",
"aboutEachPrefix",
"ID",
"nodeID",
"parseType",
"datatype",
"bagID",
"resource");
/**
Counter used for allocating Jena transient namespace declarations.
*/
private int jenaPrefixCount;
static String RDFNS = RDF.getURI();
static private final Pattern jenaNamespace = Pattern.compile("j\\.([1-9][0-9]*|cook\\.up)");
String xmlBase = null;
private IRIx baseURI;
boolean longId = false;
private boolean demandGoodURIs = true;
int tabSize = 2;
int width = 60;
HashMap<AnonId, String> anonMap = new HashMap<>();
int anonCount = 0;
static private RDFDefaultErrorHandler defaultErrorHandler =
new RDFDefaultErrorHandler();
RDFErrorHandler errorHandler = defaultErrorHandler;
Boolean showXmlDeclaration = null;
protected Boolean showDoctypeDeclaration = Boolean.FALSE;
/*
* There are two sorts of id's for anonymous resources. Short id's are the
* default, but require a mapping table. The mapping table means that
* serializing a large model could run out of memory. Long id's require no
* mapping table, but are less readable.
*/
String anonId(Resource r) {
return longId ? longAnonId( r ) : shortAnonId( r );
}
/*
* A shortAnonId is computed by maintaining a mapping table from the internal
* id's of anon resources. The short id is the index into the table of the
* internal id.
*/
private String shortAnonId(Resource r) {
String result = anonMap.get(r.getId());
if (result == null) {
result = "A" + Integer.toString(anonCount++);
anonMap.put(r.getId(), result);
}
return result;
}
/*
* A longAnonId is the internal id of the anon resource expressed as a
* character string.
*
* This code makes no assumptions about the characters used in the
* implementation of an anon id. It checks if they are valid namechar
* characters and escapes the id if not.
*/
private String longAnonId(Resource r) {
String rid = r.getId().toString();
return XMLChar.isValidNCName( rid ) ? rid : escapedId( rid );
}
/**
true means all namespaces defined in the model prefixes will be noted in xmlns
declarations; false means only "required" ones will be noted. Hook for configuration.
*/
private boolean writingAllModelPrefixNamespaces = true;
private Relation<String> nameSpaces = new Relation<>();
private Map<String, String> ns;
private PrefixMapping modelPrefixMapping;
private Set<String> namespacesNeeded;
void addNameSpace(String uri) {
namespacesNeeded.add(uri);
}
boolean isDefaultNamespace( String uri ) {
return "".equals( ns.get( uri ) );
}
private void addNameSpaces( Model model ) {
NsIterator nsIter = model.listNameSpaces();
while (nsIter.hasNext()) this.addNameSpace( nsIter.nextNs() );
}
private void primeNamespace(Model model) {
Map<String, String> m = model.getNsPrefixMap();
for ( Entry<String, String> e : m.entrySet() ) {
String value = e.getValue();
String already = this.getPrefixFor(value);
if ( already == null ) {
this.setNsPrefix(model.getNsURIPrefix(value), value);
if ( writingAllModelPrefixNamespaces ) {
this.addNameSpace(value);
}
}
}
if ( usesPrefix(model, "") ) {
// Doing "" prefix. Ensure it is a non-clashing, non-empty entity name.
String entityForEmptyPrefix = DEFAULT_NS_ENTITY_NAME;
if ( usesPrefix(model, entityForEmptyPrefix) )
entityForEmptyPrefix = DEFAULT_NS_ENTITY_NAME_ALT;
int i = 0;
while (usesPrefix(model, entityForEmptyPrefix)) {
entityForEmptyPrefix = DEFAULT_NS_ENTITY_NAME_ALT + "." + i;
i++;
}
defaultNSEntityName = entityForEmptyPrefix;
}
}
void setupMaps() {
nameSpaces.set11(RDF.getURI(), "rdf");
nameSpaces.set11(RDFS.getURI(), "rdfs");
nameSpaces.set11(DC.getURI(), "dc");
nameSpaces.set11(RSS.getURI(), "rss");
nameSpaces.set11("http://www.daml.org/2001/03/daml+oil.daml#", "daml");
nameSpaces.set11(VCARD.getURI(), "vcard");
nameSpaces.set11("http://www.w3.org/2002/07/owl#", "owl");
}
void workOutNamespaces() {
if (ns == null) {
ns = new HashMap<>();
Set<String> prefixesUsed = new HashSet<>();
setFromWriterSystemProperties( ns, prefixesUsed );
setFromGivenNamespaces( ns, prefixesUsed );
}
}
private void setFromWriterSystemProperties( Map<String, String> ns, Set<String> prefixesUsed ) {
for ( String uri : namespacesNeeded )
{
String val = JenaRuntime.getSystemProperty( RDFWriterI.NSPREFIXPROPBASE + uri );
if ( val != null && checkLegalPrefix( val ) && !prefixesUsed.contains( val ) )
{
ns.put( uri, val );
prefixesUsed.add( val );
}
}
}
private void setFromGivenNamespaces( Map<String, String> ns, Set<String> prefixesUsed ) {
for ( String uri : namespacesNeeded ) {
if ( ns.containsKey(uri) ) {
continue;
}
String val = null;
Set<String> s = nameSpaces.forward(uri);
if ( s != null ) {
Iterator<String> it2 = s.iterator();
if ( it2.hasNext() ) {
val = it2.next();
}
if ( prefixesUsed.contains(val) ) {
val = null;
}
}
if ( val == null ) {
// just in case the prefix has already been used, look for a free
// one.
// (the usual source of such prefixes is reading in a model we wrote
// out earlier)
do {
val = "j." + (jenaPrefixCount++);
} while (prefixesUsed.contains(val));
}
ns.put(uri, val);
prefixesUsed.add(val);
}
}
final synchronized public void setNsPrefix(String prefix, String ns) {
if (checkLegalPrefix(prefix)) {
nameSpaces.set11(ns, prefix);
}
}
final public String getPrefixFor( String uri ) {
// xml and xmlns namespaces are pre-bound
if ("http://www.w3.org/XML/1998/namespace".equals(uri)) return "xml";
if ("http://www.w3.org/2000/xmlns/".equals(uri)) return "xmlns";
Set<String> s = nameSpaces.backward( uri );
if (s != null && s.size() == 1) return s.iterator().next();
return null;
}
String xmlnsDecl() {
workOutNamespaces();
StringBuilder result = new StringBuilder();
Iterator<Entry<String, String>> it = ns.entrySet().iterator();
while (it.hasNext()) {
Entry<String, String> ent = it.next();
String prefix = ent.getValue();
String uri = ent.getKey();
result.append( newline ).append( " xmlns" );
if (prefix.length() > 0) result.append( ':' ).append( prefix );
result.append( '=' ).append( substitutedAttribute( checkURI( uri ) ) );
}
return result.toString();
}
static final private int FAST = 1;
static final private int START = 2;
static final private int END = 3;
static final private int ATTR = 4;
static final private int FASTATTR = 5;
String rdfEl(String local) {
return tag(RDFNS, local, FAST, true);
}
String startElementTag(String uri, String local) {
return tag(uri, local, START, false);
}
protected String startElementTag(String uriref) {
return splitTag(uriref, START);
}
String attributeTag(String uriref) {
return splitTag(uriref, ATTR);
}
String attributeTag(String uri, String local) {
return tag(uri, local, ATTR, false);
}
String rdfAt(String local) {
return tag(RDFNS, local, FASTATTR, true);
}
String endElementTag(String uri, String local) {
return tag(uri, local, END, false);
}
protected String endElementTag(String uriref) {
return splitTag(uriref, END);
}
String splitTag(String uriref, int type) {
int split = SplitIRI.splitXML( uriref );
if (split == uriref.length()) throw new InvalidPropertyURIException( uriref );
return tag( uriref.substring( 0, split ), uriref.substring( split ), type, true );
}
String tag( String namespace, String local, int type, boolean localIsQname) {
if (type != FAST && type != FASTATTR) {
if ((!localIsQname) && !XMLChar.isValidNCName(local))
return splitTag(namespace + local, type);
if (namespace.equals(RDFNS)) {
// Description, ID, nodeID, about, aboutEach, aboutEachPrefix, li
// bagID parseType resource datatype RDF
if (badRDF.contains(local)) {
logger.warn( "The URI rdf:" + local + " cannot be serialized in RDF/XML." );
throw new InvalidPropertyURIException( "rdf:" + local );
}
}
}
String prefix = ns.get( namespace );
boolean cookUp = false;
if (prefix == null) {
checkURI( namespace );
logger.warn("Internal error: unexpected QName URI: <" + namespace + ">. Fixing up.",
new BrokenException( "unexpected QName URI " + namespace ));
cookUp = true;
} else if (prefix.length() == 0) {
if (type == ATTR || type == FASTATTR)
cookUp = true;
else
return local;
}
if (cookUp) return cookUpAttribution( type, namespace, local );
return prefix + ":" + local;
}
private String cookUpAttribution( int type, String namespace, String local ) {
String prefix = "j.cook.up";
switch (type) {
case FASTATTR :
case ATTR :
return "xmlns:" + prefix + "=" + substitutedAttribute( namespace ) + " " + prefix + ":" + local;
case START :
return prefix + ":" + local + " xmlns:" + prefix+ "=" + substitutedAttribute( namespace );
default:
case END :
return prefix + ":" + local;
case FAST :
// logger.error("Unreachable code - reached.");
throw new BrokenException( "cookup reached final FAST" );
}
}
/** Write out an XML serialization of a model.
* @param model the model to be serialized
* @param out the OutputStream to receive the serialization
* @param base The URL at which the file will be placed.
*/
@Override
final public void write(Model model, OutputStream out, String base)
{ write( model, FileUtils.asUTF8(out), base ); }
/** Serialize Model <code>model</code> to Writer <code>out</code>.
* @param model The model to be written.
* @param out The Writer to which the serialization should be sent.
* @param base the base URI for relative URI calculations. <code>null</code> means use only absolute URI's.
*/
@Override
synchronized public void write(Model model, Writer out, String base) {
setupNamespaces(model);
PrintWriter pw = out instanceof PrintWriter ? (PrintWriter)out : new PrintWriter(out);
if ( !Boolean.FALSE.equals(showXmlDeclaration) )
writeXMLDeclaration(out, pw);
writeXMLBody(model, pw, base);
pw.flush();
}
/**
@param baseModel
@param model
*/
private void setupNamespaces(Model model) {
this.namespacesNeeded = new HashSet<>();
this.ns = null;
this.modelPrefixMapping = model;
primeNamespace(model);
addNameSpace(RDF.getURI());
addNameSpaces(model);
jenaPrefixCount = 0;
}
private void writeXMLBody( Model model, PrintWriter pw, String base ) {
if (showDoctypeDeclaration.booleanValue())
generateDoctypeDeclaration( model, pw );
if (xmlBase == null) {
baseURI = (base == null || base.length() == 0) ? null : IRIx.create(base);
writeBody(model, pw, base, false);
} else {
baseURI = xmlBase.length() == 0 ? null : IRIx.create(xmlBase);
writeBody(model, pw, xmlBase, true);
}
}
protected static final Pattern predefinedEntityNames = Pattern.compile( "amp|lt|gt|apos|quot" );
public boolean isPredefinedEntityName( String name )
{ return predefinedEntityNames.matcher( name ).matches(); }
private String attributeQuoteChar ="\"";
protected String attributeQuoted( String s )
{ return attributeQuoteChar + s + attributeQuoteChar; }
protected String substitutedAttribute( String s ) {
String substituted = Util.substituteStandardEntities( s );
if (!showDoctypeDeclaration.booleanValue())
return attributeQuoted( substituted );
else {
int split = SplitIRI.splitXML( substituted );
String namespace = substituted.substring( 0, split );
String prefix = modelPrefixMapping.getNsURIPrefix( namespace );
return prefix == null || isPredefinedEntityName( prefix )
? attributeQuoted( substituted )
: attributeQuoted( "&" + strForPrefix(prefix) + ";" + substituted.substring( split ) )
;
}
}
private void generateDoctypeDeclaration( Model model, PrintWriter pw ) {
String rdfns = RDF.getURI();
String rdfRDF = model.qnameFor( rdfns + "RDF" );
if ( rdfRDF == null ) {
model.setNsPrefix("rdf",rdfns);
rdfRDF = "rdf:RDF";
}
Map<String, String> prefixes = model.getNsPrefixMap();
pw.print( "<!DOCTYPE " + rdfRDF +" [" );
for ( String prefix : prefixes.keySet() ) {
if ( isPredefinedEntityName( prefix ) )
continue;
pw.print(newline
+ " <!ENTITY " + strForPrefix( prefix ) + " '" + Util.substituteEntitiesInEntityValue(prefixes.get( prefix ) )
+ "'>" );
}
pw.print( "]>" + newline );
}
private String strForPrefix(String prefix) {
if ( prefix.length() == 0 )
return defaultNSEntityName;
return prefix;
}
private static boolean usesPrefix(Model model, String prefix) {
return model.getNsPrefixURI(prefix) != null;
}
private void writeXMLDeclaration(Writer out, PrintWriter pw) {
String decl = null;
if (out instanceof OutputStreamWriter) {
String javaEnc = ((OutputStreamWriter) out).getEncoding();
if (!(javaEnc.equals("UTF8") || javaEnc.equals("UTF-16"))) {
CharEncoding encodingInfo = CharEncoding.create(javaEnc);
String ianaEnc = encodingInfo.name();
decl = "<?xml version="+attributeQuoted("1.0")+" encoding=" + attributeQuoted(ianaEnc) + "?>";
if (!encodingInfo.isIANA())
logger.warn(encodingInfo.warningMessage()+"\n"+
" It is better to use a FileOutputStream, in place of a FileWriter.");
}
}
if (decl == null && showXmlDeclaration != null)
decl = "<?xml version="+attributeQuoted("1.0")+"?>";
if (decl != null) {
pw.println(decl);
}
}
/** Set an error handler.
* @param errHandler The new error handler to be used, or null for the default handler.
* @return the old error handler
*/
@Override
synchronized public RDFErrorHandler setErrorHandler(RDFErrorHandler errHandler) {
// null means no user defined error handler.
// We implement this using defaultErrorHandler,
// but hide this fact from the user.
RDFErrorHandler rslt = errorHandler;
if (rslt == defaultErrorHandler) rslt = null;
errorHandler = errHandler == null ? defaultErrorHandler : errHandler;
return rslt;
}
static private final char ESCAPE = 'X';
static private String escapedId(String id) {
StringBuffer result = new StringBuffer();
for (int i = 0; i < id.length(); i++) {
char ch = id.charAt(i);
if (ch != ESCAPE
&& (i == 0 ? XMLChar.isNCNameStart(ch) : XMLChar.isNCName(ch))) {
result.append( ch );
} else {
escape( result, ch );
}
}
return result.toString();
}
static final char [] hexchar = "0123456789abcdef".toCharArray();
static private void escape( StringBuffer sb, char ch) {
sb.append( ESCAPE );
int charcode = ch;
do {
sb.append( hexchar[charcode & 15] );
charcode = charcode >> 4;
} while (charcode != 0);
sb.append( ESCAPE );
}
/**
Set the writer property propName to the value obtained from propValue. Return an
Object representation of the original value.
@see org.apache.jena.rdf.model.RDFWriterI#setProperty(java.lang.String, java.lang.Object)
*/
@Override
final synchronized public Object setProperty( String propName, Object propValue ) {
if (propName.equalsIgnoreCase("showXmlDeclaration")) {
return setShowXmlDeclaration(propValue);
} else if (propName.equalsIgnoreCase( "showDoctypeDeclaration" )) {
return setShowDoctypeDeclaration( propValue );
} else if (propName.equalsIgnoreCase( "minimalPrefixes" )) {
try { return Boolean.valueOf( !writingAllModelPrefixNamespaces ); }
finally { writingAllModelPrefixNamespaces = !getBoolean( propValue ); }
} else if (propName.equalsIgnoreCase("xmlbase")) {
String result = xmlBase;
xmlBase = (String) propValue;
return result;
} else if (propName.equalsIgnoreCase("tab")) {
return setTab( propValue );
} else if (propName.equalsIgnoreCase("width")) {
return setWidth(propValue);
} else if (propName.equalsIgnoreCase("longid")) {
Boolean result = Boolean.valueOf(longId);
longId = getBoolean(propValue);
return result;
} else if (propName.equalsIgnoreCase("attributeQuoteChar")) {
return setAttributeQuoteChar(propValue);
} else if (propName.equalsIgnoreCase( "allowBadURIs" )) {
Boolean result = Boolean.valueOf( !demandGoodURIs );
demandGoodURIs = !getBoolean(propValue);
return result;
} else if (propName.equalsIgnoreCase("prettyTypes")) {
return setTypes((Resource[]) propValue);
} else if (propName.equalsIgnoreCase("relativeURIs")) {
int old = relativeFlags;
relativeFlags = str2flags((String) propValue);
return flags2str(old);
} else if (propName.equalsIgnoreCase("blockRules")) {
return setBlockRules(propValue);
} else {
logger.warn("Unsupported property: " + propName);
return null;
}
}
private String setAttributeQuoteChar(Object propValue) {
String oldValue = attributeQuoteChar;
if ( "\"".equals(propValue) || "'".equals(propValue) )
attributeQuoteChar = (String)propValue;
else
logger.warn("attributeQutpeChar must be either \"\\\"\" or \', not \""+propValue+"\"" );
return oldValue;
}
private Integer setWidth(Object propValue) {
Integer oldValue = Integer.valueOf(width);
if (propValue instanceof Integer) {
width = ((Integer) propValue).intValue();
} else {
try {
width = Integer.parseInt((String) propValue);
} catch (Exception e) {
logger.warn( "Bad value for width: '" + propValue + "' [" + e.getMessage() + "]" );
}
}
return oldValue;
}
private Integer setTab(Object propValue) {
Integer result = Integer.valueOf(tabSize);
if (propValue instanceof Integer) {
tabSize = ((Integer) propValue).intValue();
} else {
try {
tabSize = Integer.parseInt((String) propValue);
} catch (Exception e) {
logger.warn( "Bad value for tab: '" + propValue + "' [" + e.getMessage() + "]" );
}
}
return result;
}
private String setShowDoctypeDeclaration(Object propValue) {
String oldValue = showDoctypeDeclaration.toString();
showDoctypeDeclaration = getBooleanValue( propValue, Boolean.FALSE );
return oldValue;
}
private String setShowXmlDeclaration( Object propValue ) {
String oldValue = showXmlDeclaration == null ? null : showXmlDeclaration.toString();
showXmlDeclaration = getBooleanValue( propValue, null );
return oldValue;
}
/**
Answer the boolean value corresponding to o, which must either be a Boolean,
or a String parsable as a Boolean.
*/
static private boolean getBoolean( Object o )
{ return getBooleanValue( o, Boolean.FALSE ).booleanValue(); }
private static Boolean getBooleanValue( Object propValue, Boolean theDefault ) {
if (propValue == null)
return theDefault;
else if (propValue instanceof Boolean)
return (Boolean) propValue;
else if (propValue instanceof String)
return stringToBoolean( (String) propValue, theDefault );
else
throw new JenaException( "cannot treat as boolean: " + propValue );
}
private static Boolean stringToBoolean( String b, Boolean theDefault ) {
if (b.equals( "default" )) return theDefault;
if (b.equalsIgnoreCase( "true" )) return Boolean.TRUE;
if (b.equalsIgnoreCase( "false" )) return Boolean.FALSE;
throw new BadBooleanException( b );
}
Resource[] setTypes( Resource x[] ) {
logger.warn( "prettyTypes is not a property on the Basic RDF/XML writer." );
return null;
}
private Resource blockedRules[] = new Resource[]{RDFSyntax.propertyAttr};
Resource[] setBlockRules(Object o) {
Resource rslt[] = blockedRules;
unblockAll();
if (o instanceof Resource[]) {
blockedRules = (Resource[]) o;
} else {
StringTokenizer tkn = new StringTokenizer((String) o, ", ");
Vector<Resource> v = new Vector<>();
while (tkn.hasMoreElements()) {
String frag = tkn.nextToken();
v.add(new ResourceImpl(RDFSyntax.getURI() + frag));
}
blockedRules = new Resource[v.size()];
v.copyInto(blockedRules);
}
for ( Resource blockedRule : blockedRules )
{
blockRule( blockedRule );
}
return rslt;
}
// Copy from jena-iri IRIRelativize for isolation from IRIx provider usge.
private class IRIRelativize {
/** Allow same document references (e.g. "" or "#frag").*/
static final public int SAMEDOCUMENT = 1;
/** Allow network relative references (e.g. "//example.org/a/b/c"). */
static final public int NETWORK = 2;
/** Allow absolute relative references (e.g. "/a/b/c"). */
static final public int ABSOLUTE = 4;
/** Allow child relative references (e.g. "b/c"). */
static final public int CHILD = 8;
/** Allow parent relative references (e.g. "../b/c"). */
static final public int PARENT = 16;
/** Allow grandparent relative references (e.g. "../../b/c"). */
static final public int GRANDPARENT = 32;
}
/*
private boolean sameDocument = true;
private boolean network = false;
private boolean absolute = true;
private boolean relative = true;
private boolean parent = true;
private boolean grandparent = false;
*/
//private int relativeFlags = 0;
private int dftRelativeFlags = IRIRelativize.SAMEDOCUMENT | IRIRelativize.ABSOLUTE | IRIRelativize.CHILD | IRIRelativize.PARENT;
private int relativeFlags = dftRelativeFlags;
/**
Answer the form of the URI after relativiation according to the relativeFlags set
by properties. If the flags are 0 or the base URI is null, answer the original URI.
Throw an exception if the URI is "bad" and we demandGoodURIs.
*/
protected String relativize( String uri ) {
return relativeFlags != 0 && baseURI != null
? relativize( baseURI, uri )
: checkURI( uri );
}
/**
Answer the relative form of the URI against the base, according to the relativeFlags.
*/
private String relativize( IRIx base, String uri ) {
if ( relativeFlags == 0 )
return uri;
// If jena-iri
if ( base instanceof IRIxJena ) {
org.apache.jena.iri.IRI baseImpl = ((IRIxJena)base).getImpl();
return baseImpl.relativize(uri, relativeFlags).toString();
}
try {
if ( relativeFlags != dftRelativeFlags ) {
// Use jena-iri for relativization. Backwards compatibility.
org.apache.jena.iri.IRI baseImpl = org.apache.jena.iri.IRIFactory.iriImplementation().create(base.str());
return baseImpl.relativize(uri, relativeFlags).toString();
}
// if ( relativeFlags == 1 ) {
// IRI3986 iri1 = IRI3986.create(base.str());
// IRI3986 iri2 = IRI3986.create(uri);
// IRI3986 x = AlgIRI2.relativeSameDocument(iri1, iri2);
// return x!=null ? x.str() : uri;
// }
// if ( relativeFlags == 4 ) {
// IRI3986 iri1 = IRI3986.create(base.str());
// IRI3986 iri2 = IRI3986.create(uri);
// IRI3986 x = AlgIRI2.relativeResource(iri1, iri2);
// return x!=null ? x.str() : uri;
// }
// if ( relativeFlags == 8 ) {
// IRI3986 iri1 = IRI3986.create(base.str());
// IRI3986 iri2 = IRI3986.create(uri);
// IRI3986 x = AlgIRI2.relativePath(iri1, iri2);
// return x!=null ? x.str() : uri;
// }
IRIx x = base.relativize( IRIx.create(uri) );
return x != null ? x.str() : uri ;
} catch (IRIException ex) {
return uri;
}
}
/**
Answer the argument URI, but if we demandGoodURIs and it isn't good, throw
a JenaException that encapsulates a MalformedURIException. There doesn't
appear to be a convenient URI.checkGood() kind of method, alas.
*/
private String checkURI( String uri ) {
if (demandGoodURIs)
// Valid or exception.
IRIs.checkEx(uri);
return uri;
}
/**
Answer true iff prefix is a "legal" prefix to use, ie, is empty [for the default namespace]
or an NCName that does not start with "xml" and does not match the reserved-to-Jena
pattern.
*/
private boolean checkLegalPrefix( String prefix ) {
if (prefix.equals(""))
return true;
if (prefix.toLowerCase().startsWith( "xml" ))
logger.warn( "Namespace prefix '" + prefix + "' is reserved by XML." );
else if (!XMLChar.isValidNCName(prefix))
logger.warn( "'" + prefix + "' is not a legal namespace prefix." );
else if (jenaNamespace.matcher(prefix).matches())
logger.warn( "Namespace prefix '" + prefix + "' is reserved by Jena." );
else
return true;
return false;
}
static private String flags2str(int f) {
StringBuffer oldValue = new StringBuffer(64);
if ( (f&IRIRelativize.SAMEDOCUMENT)!=0 )
oldValue.append( "same-document, " );
if ( (f&IRIRelativize.NETWORK)!=0 )
oldValue.append( "network, ");
if ( (f&IRIRelativize.ABSOLUTE)!=0 )
oldValue.append("absolute, ");
if ( (f&IRIRelativize.CHILD)!=0 )
oldValue.append("relative, ");
if ((f&IRIRelativize.PARENT)!=0)
oldValue.append("parent, ");
if ((f&IRIRelativize.GRANDPARENT)!=0)
oldValue.append("grandparent, ");
if (oldValue.length() > 0)
oldValue.setLength(oldValue.length()-2);
return oldValue.toString();
}
public static int str2flags(String pv){
StringTokenizer tkn = new StringTokenizer(pv,", ");
int rslt = 0;
while ( tkn.hasMoreElements() ) {
String flag = tkn.nextToken();
if ( flag.equals("same-document") )
rslt |= IRIRelativize.SAMEDOCUMENT;
else if ( flag.equals("network") )
rslt |= IRIRelativize.NETWORK;
else if ( flag.equals("absolute") )
rslt |= IRIRelativize.ABSOLUTE;
else if ( flag.equals("relative") )
rslt |= IRIRelativize.CHILD;
else if ( flag.equals("parent") )
rslt |= IRIRelativize.PARENT;
else if ( flag.equals("grandparent") )
rslt |= IRIRelativize.GRANDPARENT;
else
logger.warn(
"Incorrect property value for relativeURIs: " + flag
);
}
return rslt;
}
}