001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * 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 */
018
019package org.fcrepo.jena;
020
021import java.io.OutputStream ;
022import java.io.OutputStreamWriter ;
023import java.io.PrintWriter ;
024import java.io.Writer ;
025import java.util.* ;
026import java.util.Map.Entry ;
027import java.util.regex.Pattern ;
028
029import org.apache.jena.JenaRuntime ;
030import org.apache.jena.iri.IRI ;
031import org.apache.jena.iri.IRIFactory ;
032import org.apache.jena.rdf.model.* ;
033import org.apache.jena.rdf.model.impl.RDFDefaultErrorHandler ;
034import org.apache.jena.rdf.model.impl.ResourceImpl ;
035import org.apache.jena.rdf.model.impl.Util ;
036import org.apache.jena.rdfxml.xmloutput.RDFXMLWriterI ;
037import org.apache.jena.rdfxml.xmloutput.impl.SimpleLogger;
038import org.apache.jena.shared.* ;
039import org.apache.jena.util.CharEncoding ;
040import org.apache.jena.util.FileUtils ;
041import org.apache.jena.vocabulary.* ;
042import org.apache.xerces.util.XMLChar ;
043import org.slf4j.Logger ;
044import org.slf4j.LoggerFactory ;
045
046/**
047 * This is not part of the public API.
048 * Base class for XML serializers.
049 * All methods with side-effects should be synchronized in this class and its
050 * subclasses. (i. e. XMLWriters assume that the world is not changing around
051 * them while they are writing).
052 *
053 * Functionality:
054 *
055 * <ul>
056 * <li>setProperty etc
057 * <li>namespace prefixes
058 * <li>xmlbase
059 * <li>relative URIs
060 * <li>encoding issues
061 * <li>anonymous node presentational
062 * <li>errorHandler
063 * </ul>
064 */
065abstract public class CopyOfBaseXMLWriter implements RDFXMLWriterI {
066
067    private static final String newline =
068            JenaRuntime.getSystemProperty( "line.separator" );
069    static private final String DEFAULT_NS_ENTITY_NAME = "this";
070    static private final String DEFAULT_NS_ENTITY_NAME_ALT = "here";
071    private String defaultNSEntityName = "UNSET" ;
072
073    public CopyOfBaseXMLWriter() {
074        setupMaps();
075    }
076
077    private static Logger xlogger = LoggerFactory.getLogger( CopyOfBaseXMLWriter.class );
078
079    protected static SimpleLogger logger = new SimpleLogger() {
080        @Override
081        public void warn(String s) {
082            xlogger.warn(s);
083        }
084        @Override
085        public void warn(String s, Exception e) {
086            xlogger.warn(s,e);
087        }
088    };
089
090    public static SimpleLogger setLogger(SimpleLogger lg) {
091        SimpleLogger old = logger;
092        logger= lg;
093        return old;
094    }
095
096    abstract protected void unblockAll();
097
098    abstract protected void blockRule(Resource r);
099
100    abstract protected void writeBody
101            ( Model mdl, PrintWriter pw, String baseUri, boolean inclXMLBase );
102
103    static private Set<String> badRDF = new HashSet<>();
104
105    /**
106     Counter used for allocating Jena transient namespace declarations.
107     */
108    private int jenaPrefixCount;
109
110    static String RDFNS = RDF.getURI();
111
112
113    static private Pattern jenaNamespace;
114
115    static {
116        jenaNamespace =
117                Pattern.compile("j\\.([1-9][0-9]*|cook\\.up)");
118
119        badRDF.add("RDF");
120        badRDF.add("Description");
121        badRDF.add("li");
122        badRDF.add("about");
123        badRDF.add("aboutEach");
124        badRDF.add("aboutEachPrefix");
125        badRDF.add("ID");
126        badRDF.add("nodeID");
127        badRDF.add("parseType");
128        badRDF.add("datatype");
129        badRDF.add("bagID");
130        badRDF.add("resource");
131    }
132
133    String xmlBase = null;
134
135    private IRI baseURI;
136
137    boolean longId = false;
138
139    private boolean demandGoodURIs = true;
140
141    int tabSize = 2;
142
143    int width = 60;
144
145    HashMap<AnonId, String> anonMap = new HashMap<>();
146
147    int anonCount = 0;
148
149    static private RDFDefaultErrorHandler defaultErrorHandler =
150            new RDFDefaultErrorHandler();
151
152    RDFErrorHandler errorHandler = defaultErrorHandler;
153
154    Boolean showXmlDeclaration = null;
155
156    protected Boolean showDoctypeDeclaration = Boolean.FALSE;
157
158        /*
159         * There are two sorts of id's for anonymous resources.  Short id's are the
160         * default, but require a mapping table.  The mapping table means that
161         * serializing a large model could run out of memory.  Long id's require no
162         * mapping table, but are less readable.
163         */
164
165    String anonId(Resource r)  {
166        return longId ? longAnonId( r ) : shortAnonId( r );
167    }
168
169    /*
170     * A shortAnonId is computed by maintaining a mapping table from the internal
171     * id's of anon resources.  The short id is the index into the table of the
172     * internal id.
173     */
174    private String shortAnonId(Resource r)  {
175        String result = anonMap.get(r.getId());
176        if (result == null) {
177            result = "A" + Integer.toString(anonCount++);
178            anonMap.put(r.getId(), result);
179        }
180        return result;
181    }
182
183        /*
184         * A longAnonId is the internal id of the anon resource expressed as a
185         * character string.
186         *
187         * This code makes no assumptions about the characters used in the
188         * implementation of an anon id.  It checks if they are valid namechar
189         * characters and escapes the id if not.
190         */
191
192    private String longAnonId(Resource r)  {
193        String rid = r.getId().toString();
194        return XMLChar.isValidNCName( rid ) ? rid : escapedId( rid );
195    }
196
197    /**
198     true means all namespaces defined in the model prefixes will be noted in xmlns
199     declarations; false means only "required" ones will be noted. Hook for configuration.
200     */
201    private boolean writingAllModelPrefixNamespaces = true;
202
203    private CopyOfRelation<String> nameSpaces = new CopyOfRelation<>();
204
205    private Map<String, String> ns;
206
207    private PrefixMapping modelPrefixMapping;
208
209    private Set<String> namespacesNeeded;
210
211    void addNameSpace(String uri) {
212        namespacesNeeded.add(uri);
213    }
214
215    boolean isDefaultNamespace( String uri ) {
216        return "".equals( ns.get( uri ) );
217    }
218
219    private void addNameSpaces( Model model )  {
220        NsIterator nsIter = model.listNameSpaces();
221        while (nsIter.hasNext()) this.addNameSpace( nsIter.nextNs() );
222    }
223
224    private void primeNamespace( Model model )
225    {
226        Map<String, String> m = model.getNsPrefixMap();
227        for ( Entry<String, String> e : m.entrySet() )
228        {
229            String value = e.getValue();
230            String already = this.getPrefixFor( value );
231            if ( already == null )
232            {
233                this.setNsPrefix( model.getNsURIPrefix( value ), value );
234                if ( writingAllModelPrefixNamespaces )
235                {
236                    this.addNameSpace( value );
237                }
238            }
239        }
240
241        if ( usesPrefix(model, "") )
242        {
243            // Doing "" prefix.  Ensure it is a non-clashing, non-empty entity name.
244            String entityForEmptyPrefix = DEFAULT_NS_ENTITY_NAME ;
245            if ( usesPrefix(model, entityForEmptyPrefix) )
246                entityForEmptyPrefix = DEFAULT_NS_ENTITY_NAME_ALT ;
247            int i = 0 ;
248            while ( usesPrefix(model,entityForEmptyPrefix) )
249            {
250                entityForEmptyPrefix = DEFAULT_NS_ENTITY_NAME_ALT+"."+i ;
251                i++ ;
252            }
253            defaultNSEntityName = entityForEmptyPrefix ;
254        }
255    }
256
257    void setupMaps() {
258        nameSpaces.set11(RDF.getURI(), "rdf");
259        nameSpaces.set11(RDFS.getURI(), "rdfs");
260        nameSpaces.set11(DC.getURI(), "dc");
261        nameSpaces.set11(RSS.getURI(), "rss");
262        nameSpaces.set11("http://www.daml.org/2001/03/daml+oil.daml#", "daml");
263        nameSpaces.set11(VCARD.getURI(), "vcard");
264        nameSpaces.set11("http://www.w3.org/2002/07/owl#", "owl");
265    }
266
267    void workOutNamespaces() {
268        if (ns == null) {
269            ns = new HashMap<>();
270            Set<String> prefixesUsed = new HashSet<>();
271            setFromWriterSystemProperties( ns, prefixesUsed );
272            setFromGivenNamespaces( ns, prefixesUsed );
273        }
274    }
275
276    private void setFromWriterSystemProperties( Map<String, String> ns, Set<String> prefixesUsed ) {
277        for ( String uri : namespacesNeeded )
278        {
279            String val = JenaRuntime.getSystemProperty( RDFWriter.NSPREFIXPROPBASE + uri );
280            if ( val != null && checkLegalPrefix( val ) && !prefixesUsed.contains( val ) )
281            {
282                ns.put( uri, val );
283                prefixesUsed.add( val );
284            }
285        }
286    }
287
288    private void setFromGivenNamespaces( Map<String, String> ns, Set<String> prefixesUsed ) {
289        for ( String uri : namespacesNeeded )
290        {
291            if ( ns.containsKey( uri ) )
292            {
293                continue;
294            }
295            String val = null;
296            Set<String> s = nameSpaces.forward( uri );
297            if ( s != null )
298            {
299                Iterator<String> it2 = s.iterator();
300                if ( it2.hasNext() )
301                {
302                    val = it2.next();
303                }
304                if ( prefixesUsed.contains( val ) )
305                {
306                    val = null;
307                }
308            }
309            if ( val == null )
310            {
311                // just in case the prefix has already been used, look for a free one.
312                // (the usual source of such prefixes is reading in a model we wrote out earlier)
313                do
314                {
315                    val = "j." + ( jenaPrefixCount++ );
316                }
317                while ( prefixesUsed.contains( val ) );
318            }
319            ns.put( uri, val );
320            prefixesUsed.add( val );
321        }
322    }
323
324    final synchronized public void setNsPrefix(String prefix, String ns) {
325        if (checkLegalPrefix(prefix)) {
326            nameSpaces.set11(ns, prefix);
327        }
328    }
329
330    final public String getPrefixFor( String uri )
331    {
332        // xml and xmlns namespaces are pre-bound
333        if ("http://www.w3.org/XML/1998/namespace".equals(uri)) return "xml";
334        if ("http://www.w3.org/2000/xmlns/".equals(uri)) return "xmlns";
335        Set<String> s = nameSpaces.backward( uri );
336        if (s != null && s.size() == 1) return s.iterator().next();
337        return null;
338    }
339
340    String xmlnsDecl() {
341        workOutNamespaces();
342        StringBuilder result = new StringBuilder();
343        Iterator<Entry<String, String>> it = ns.entrySet().iterator();
344        while (it.hasNext()) {
345            Entry<String, String> ent = it.next();
346            String prefix = ent.getValue();
347            String uri = ent.getKey();
348            result.append( newline ).append( "    xmlns" );
349            if (prefix.length() > 0) result.append( ':' ).append( prefix );
350            result.append( '=' ).append( substitutedAttribute( checkURI( uri ) ) );
351        }
352        return result.toString();
353    }
354
355    static final private int FAST = 1;
356    static final private int START = 2;
357    static final private int END = 3;
358    static final private int ATTR = 4;
359    static final private int FASTATTR = 5;
360
361    String rdfEl(String local) {
362        return tag(RDFNS, local, FAST, true);
363    }
364
365    String startElementTag(String uri, String local) {
366        return tag(uri, local, START, false);
367    }
368
369    protected String startElementTag(String uriref) {
370        return splitTag(uriref, START);
371    }
372
373    String attributeTag(String uriref) {
374        return splitTag(uriref, ATTR);
375    }
376
377    String attributeTag(String uri, String local) {
378        return tag(uri, local, ATTR, false);
379    }
380
381    String rdfAt(String local) {
382        return tag(RDFNS, local, FASTATTR, true);
383    }
384
385    String endElementTag(String uri, String local) {
386        return tag(uri, local, END, false);
387    }
388
389    protected String endElementTag(String uriref) {
390        return splitTag(uriref, END);
391    }
392
393    String splitTag(String uriref, int type) {
394        int split = Util.splitNamespaceXML( uriref );
395        if (split == uriref.length()) throw new InvalidPropertyURIException( uriref );
396        return tag( uriref.substring( 0, split ), uriref.substring( split ), type, true );
397    }
398
399    static public boolean dbg = false;
400
401    String tag( String namespace, String local, int type, boolean localIsQname)  {
402        if (dbg)
403            System.err.println(namespace + " - " + local);
404        String prefix = ns.get( namespace );
405        if (type != FAST && type != FASTATTR) {
406            if ((!localIsQname) && !XMLChar.isValidNCName(local))
407                return splitTag(namespace + local, type);
408            if (namespace.equals(RDFNS)) {
409                // Description, ID, nodeID, about, aboutEach, aboutEachPrefix, li
410                // bagID parseType resource datatype RDF
411                if (badRDF.contains(local)) {
412                    logger.warn(        "The URI rdf:" + local + " cannot be serialized in RDF/XML." );
413                    throw new InvalidPropertyURIException( "rdf:" + local );
414                }
415            }
416        }
417        boolean cookUp = false;
418        if (prefix == null) {
419            checkURI( namespace );
420            logger.warn(
421                    "Internal error: unexpected QName URI: <"
422                            + namespace
423                            + ">.  Fixing up with j.cook.up code.",
424                    new BrokenException( "unexpected QName URI " + namespace ));
425            cookUp = true;
426        } else if (prefix.length() == 0) {
427            if (type == ATTR || type == FASTATTR)
428                cookUp = true;
429            else
430                return local;
431        }
432        if (cookUp) return cookUpAttribution( type, namespace, local );
433        return prefix + ":" + local;
434    }
435
436    private String cookUpAttribution( int type, String namespace, String local )
437    {
438        String prefix = "j.cook.up";
439        switch (type) {
440            case FASTATTR :
441            case ATTR :
442                return "xmlns:" + prefix + "=" + substitutedAttribute( namespace ) + " " + prefix + ":" + local;
443            case START :
444                return prefix  + ":" + local + " xmlns:" + prefix+ "=" + substitutedAttribute( namespace );
445            default:
446            case END :
447                return prefix + ":" + local;
448            case FAST :
449                //  logger.error("Unreachable code - reached.");
450                throw new BrokenException( "cookup reached final FAST" );
451        }
452    }
453
454    /** Write out an XML serialization of a model.
455     * @param model the model to be serialized
456     * @param out the OutputStream to receive the serialization
457     * @param base The URL at which the file will be placed.
458     */
459    @Override
460    final public void write(Model model, OutputStream out, String base)
461    { write( model, FileUtils.asUTF8(out), base ); }
462
463    /** Serialize Model <code>model</code> to Writer <code>out</out>.
464     * @param model The model to be written.
465     * @param out The Writer to which the serialization should be sent.
466     * @param base the base URI for relative URI calculations.  <code>null</code> means use only absolute URI's.
467     */
468    @Override
469    synchronized public void write(Model model, Writer out, String base)
470    {
471        setupNamespaces( model );
472        PrintWriter pw = out instanceof PrintWriter ? (PrintWriter) out : new PrintWriter( out );
473        if (!Boolean.FALSE.equals(showXmlDeclaration)) writeXMLDeclaration( out, pw );
474        writeXMLBody( model, pw, base );
475        pw.flush();
476    }
477
478    /**
479     @param baseModel
480     @param model
481     */
482    private void setupNamespaces( Model model )
483    {
484        this.namespacesNeeded = new HashSet<>();
485        this.ns = null;
486        this.modelPrefixMapping = model;
487        primeNamespace( model );
488        addNameSpace( RDF.getURI() );
489        addNameSpaces(model);
490        jenaPrefixCount = 0;
491    }
492
493    @SuppressWarnings("deprecation")
494    static IRIFactory factory = IRIFactory.jenaImplementation();
495
496
497    private void writeXMLBody( Model model, PrintWriter pw, String base ) {
498        if (showDoctypeDeclaration.booleanValue()) generateDoctypeDeclaration( model, pw );
499//              try {
500        // errors?
501        if (xmlBase == null) {
502            baseURI = (base == null || base.length() == 0) ? null : factory.create(base);
503            writeBody(model, pw, base, false);
504        } else {
505            baseURI = xmlBase.length() == 0 ? null : factory.create(xmlBase);
506            writeBody(model, pw, xmlBase, true);
507        }
508//              } catch (MalformedURIException e) {
509//                      throw new BadURIException( e.getMessage(), e);
510//              }
511    }
512
513    protected static final Pattern predefinedEntityNames = Pattern.compile( "amp|lt|gt|apos|quot" );
514
515    public boolean isPredefinedEntityName( String name )
516    { return predefinedEntityNames.matcher( name ).matches(); }
517
518    private String attributeQuoteChar ="\"";
519
520    protected String attributeQuoted( String s )
521    { return attributeQuoteChar + s + attributeQuoteChar; }
522
523    protected String substitutedAttribute( String s )
524    {
525        String substituted = Util.substituteStandardEntities( s );
526        if (!showDoctypeDeclaration.booleanValue())
527            return attributeQuoted( substituted );
528        else
529        {
530            int split = Util.splitNamespaceXML( substituted );
531            String namespace = substituted.substring(  0, split );
532            String prefix = modelPrefixMapping.getNsURIPrefix( namespace );
533            return prefix == null || isPredefinedEntityName( prefix )
534                    ? attributeQuoted( substituted )
535                    : attributeQuoted( "&" + strForPrefix(prefix) + ";" + substituted.substring( split ) )
536                    ;
537        }
538    }
539
540    private void generateDoctypeDeclaration( Model model, PrintWriter pw )
541    {
542        String rdfns = RDF.getURI();
543        String rdfRDF = model.qnameFor( rdfns + "RDF" );
544        if ( rdfRDF == null ) {
545            model.setNsPrefix("rdf",rdfns);
546            rdfRDF = "rdf:RDF";
547        }
548        Map<String, String> prefixes = model.getNsPrefixMap();
549        pw.print( "<!DOCTYPE " + rdfRDF +" [" );
550        for ( String prefix : prefixes.keySet() )
551        {
552            if ( isPredefinedEntityName( prefix ) )
553            {
554                continue;
555            }
556            pw.print(
557                    newline + "  <!ENTITY " + strForPrefix( prefix ) + " '" + Util.substituteEntitiesInEntityValue(
558                            prefixes.get( prefix ) ) + "'>" );
559        }
560        pw.print( "]>" + newline );
561    }
562
563    private String strForPrefix(String prefix)
564    {
565        if ( prefix.length() == 0 )
566            return defaultNSEntityName ;
567        return prefix ;
568    }
569
570    private static boolean usesPrefix(Model model, String prefix)
571    {
572        return model.getNsPrefixURI(prefix) != null ;
573    }
574
575    private void writeXMLDeclaration(Writer out, PrintWriter pw) {
576        String decl = null;
577        if (out instanceof OutputStreamWriter) {
578            String javaEnc = ((OutputStreamWriter) out).getEncoding();
579            // System.err.println(javaEnc);
580            if (!(javaEnc.equals("UTF8") || javaEnc.equals("UTF-16"))) {
581                CharEncoding encodingInfo = CharEncoding.create(javaEnc);
582
583                String ianaEnc = encodingInfo.name();
584                decl = "<?xml version="+attributeQuoted("1.0")+" encoding=" + attributeQuoted(ianaEnc) + "?>";
585                if (!encodingInfo.isIANA())
586                    logger.warn(encodingInfo.warningMessage()+"\n"+
587                            "   It is better to use a FileOutputStream, in place of a FileWriter.");
588
589            }
590        }
591        if (decl == null && showXmlDeclaration != null)
592            decl = "<?xml version="+attributeQuoted("1.0")+"?>";
593        if (decl != null) {
594            pw.println(decl);
595        }
596    }
597
598    /** Set an error handler.
599     * @param errHandler The new error handler to be used, or null for the default handler.
600     * @return the old error handler
601     */
602    @Override
603    synchronized public RDFErrorHandler setErrorHandler(RDFErrorHandler errHandler) {
604        // null means no user defined error handler.
605        // We implement this using defaultErrorHandler,
606        // but hide this fact from the user.
607        RDFErrorHandler rslt = errorHandler;
608        if (rslt == defaultErrorHandler) rslt = null;
609        errorHandler = errHandler == null ? defaultErrorHandler : errHandler;
610        return rslt;
611    }
612
613    static private final char ESCAPE = 'X';
614
615    static private String escapedId(String id) {
616        StringBuffer result = new StringBuffer();
617        for (int i = 0; i < id.length(); i++) {
618            char ch = id.charAt(i);
619            if (ch != ESCAPE
620                    && (i == 0 ? XMLChar.isNCNameStart(ch) : XMLChar.isNCName(ch))) {
621                result.append( ch );
622            } else {
623                escape( result, ch );
624            }
625        }
626        return result.toString();
627    }
628
629    static final char [] hexchar = "0123456789abcdef".toCharArray();
630
631    static private void escape( StringBuffer sb, char ch) {
632        sb.append( ESCAPE );
633        int charcode = ch;
634        do {
635            sb.append( hexchar[charcode & 15] );
636            charcode = charcode >> 4;
637        } while (charcode != 0);
638        sb.append( ESCAPE );
639    }
640
641    /**
642     Set the writer property propName to the value obtained from propValue. Return an
643     Object representation of the original value.
644
645     @see org.apache.jena.rdf.model.RDFWriter#setProperty(java.lang.String, java.lang.Object)
646     */
647    @Override
648    final synchronized public Object setProperty( String propName, Object propValue ) {
649        if (propName.equalsIgnoreCase("showXmlDeclaration")) {
650            return setShowXmlDeclaration(propValue);
651        } else if (propName.equalsIgnoreCase( "showDoctypeDeclaration" )) {
652            return setShowDoctypeDeclaration( propValue );
653        } else if (propName.equalsIgnoreCase( "minimalPrefixes" )) {
654            try { return new Boolean( !writingAllModelPrefixNamespaces ); }
655            finally { writingAllModelPrefixNamespaces = !getBoolean( propValue ); }
656        } else if (propName.equalsIgnoreCase("xmlbase")) {
657            String result = xmlBase;
658            xmlBase = (String) propValue;
659            return result;
660        } else if (propName.equalsIgnoreCase("tab")) {
661            return setTab( propValue );
662        } else if (propName.equalsIgnoreCase("width")) {
663            return setWidth(propValue);
664        } else if (propName.equalsIgnoreCase("longid")) {
665            Boolean result = new Boolean(longId);
666            longId = getBoolean(propValue);
667            return result;
668        } else if (propName.equalsIgnoreCase("attributeQuoteChar")) {
669            return setAttributeQuoteChar(propValue);
670        } else if (propName.equalsIgnoreCase( "allowBadURIs" )) {
671            Boolean result = new Boolean( !demandGoodURIs );
672            demandGoodURIs = !getBoolean(propValue);
673            return result;
674        } else if (propName.equalsIgnoreCase("prettyTypes")) {
675            return setTypes((Resource[]) propValue);
676        } else if (propName.equalsIgnoreCase("relativeURIs")) {
677            int old = relativeFlags;
678            relativeFlags = str2flags((String) propValue);
679            return flags2str(old);
680        } else if (propName.equalsIgnoreCase("blockRules")) {
681            return setBlockRules(propValue);
682        } else {
683            logger.warn("Unsupported property: " + propName);
684            return null;
685        }
686    }
687
688    private String setAttributeQuoteChar(Object propValue) {
689        String oldValue = attributeQuoteChar;
690        if ( "\"".equals(propValue) || "'".equals(propValue) )
691            attributeQuoteChar = (String)propValue;
692        else
693            logger.warn("attributeQutpeChar must be either \"\\\"\" or \', not \""+propValue+"\"" );
694        return oldValue;
695    }
696
697    private Integer setWidth(Object propValue) {
698        Integer oldValue = new Integer(width);
699        if (propValue instanceof Integer) {
700            width = ((Integer) propValue).intValue();
701        } else {
702            try {
703                width = Integer.parseInt((String) propValue);
704            } catch (Exception e) {
705                logger.warn(    "Bad value for width: '" + propValue + "' [" + e.getMessage() + "]" );
706            }
707        }
708        return oldValue;
709    }
710
711    private Integer setTab(Object propValue) {
712        Integer result = new Integer(tabSize);
713        if (propValue instanceof Integer) {
714            tabSize = ((Integer) propValue).intValue();
715        } else {
716            try {
717                tabSize = Integer.parseInt((String) propValue);
718            } catch (Exception e) {
719                logger.warn(    "Bad value for tab: '" + propValue + "' [" + e.getMessage() + "]" );
720            }
721        }
722        return result;
723    }
724
725    private String setShowDoctypeDeclaration( Object propValue )
726    {
727        String oldValue = showDoctypeDeclaration.toString();
728        showDoctypeDeclaration = getBooleanValue( propValue, Boolean.FALSE );
729        return oldValue;
730    }
731
732    private String setShowXmlDeclaration( Object propValue )
733    {
734        String oldValue = showXmlDeclaration == null ? null : showXmlDeclaration.toString();
735        showXmlDeclaration = getBooleanValue( propValue, null );
736        return oldValue;
737    }
738
739    /**
740     Answer the boolean value corresponding to o, which must either be a Boolean,
741     or a String parsable as a Boolean.
742     */
743    static private boolean getBoolean( Object o )
744    { return getBooleanValue( o, Boolean.FALSE ).booleanValue(); }
745
746    private static Boolean getBooleanValue( Object propValue, Boolean theDefault )
747    {
748        if (propValue == null)
749            return theDefault;
750        else if (propValue instanceof Boolean)
751            return (Boolean) propValue;
752        else if (propValue instanceof String)
753            return stringToBoolean( (String) propValue, theDefault );
754        else
755            throw new JenaException( "cannot treat as boolean: " + propValue );
756    }
757
758    private static Boolean stringToBoolean( String b, Boolean theDefault )
759    {
760        if (b.equals( "default" )) return theDefault;
761        if (b.equalsIgnoreCase( "true" )) return Boolean.TRUE;
762        if (b.equalsIgnoreCase( "false" )) return Boolean.FALSE;
763        throw new BadBooleanException( b );
764    }
765
766    Resource[] setTypes( Resource x[] ) {
767        logger.warn( "prettyTypes is not a property on the Basic RDF/XML writer." );
768        return null;
769    }
770
771    private Resource blockedRules[] = new Resource[]{RDFSyntax.propertyAttr};
772
773    Resource[] setBlockRules(Object o) {
774        Resource rslt[] = blockedRules;
775        unblockAll();
776        if (o instanceof Resource[]) {
777            blockedRules = (Resource[]) o;
778        } else {
779            StringTokenizer tkn = new StringTokenizer((String) o, ", ");
780            Vector<Resource> v = new Vector<>();
781            while (tkn.hasMoreElements()) {
782                String frag = tkn.nextToken();
783                //  System.err.println("Blocking " + frag);
784                v.add(new ResourceImpl(RDFSyntax.getURI() + frag));
785            }
786
787            blockedRules = new Resource[v.size()];
788            v.copyInto(blockedRules);
789        }
790        for ( Resource blockedRule : blockedRules )
791        {
792            blockRule( blockedRule );
793        }
794        return rslt;
795    }
796    /*
797    private boolean sameDocument = true;
798    private boolean network = false;
799    private boolean absolute = true;
800    private boolean relative = true;
801    private boolean parent = true;
802    private boolean grandparent = false;
803    */
804    private int relativeFlags =
805            IRI.SAMEDOCUMENT | IRI.ABSOLUTE | IRI.CHILD | IRI.PARENT;
806
807    /**
808     Answer the form of the URI after relativisation according to the relativeFlags set
809     by properties. If the flags are 0 or the base URI is null, answer the original URI.
810     Throw an exception if the URI is "bad" and we demandGoodURIs.
811     */
812    protected String relativize( String uri ) {
813        return relativeFlags != 0 && baseURI != null
814                ? relativize( baseURI, uri )
815                : checkURI( uri );
816    }
817
818    /**
819     Answer the relative form of the URI against the base, according to the relativeFlags.
820     */
821    private String relativize( IRI base, String uri )  {
822        // errors?
823        return base.relativize( uri, relativeFlags).toString();
824    }
825
826    /**
827     Answer the argument URI, but if we demandGoodURIs and it isn't good, throw
828     a JenaException that encapsulates a MalformedURIException. There doesn't
829     appear to be a convenient URI.checkGood() kind of method, alas.
830     */
831    private String checkURI( String uri ) {
832        if (demandGoodURIs) {
833            IRI iri = factory.create( uri );
834
835            if (iri.hasViolation(false) )
836                throw new BadURIException( "Only well-formed absolute URIrefs can be included in RDF/XML output: "
837                        + (iri.violations(false).next()).getShortMessage());
838        }
839
840
841        return uri;
842    }
843
844    /**
845     Answer true iff prefix is a "legal" prefix to use, ie, is empty [for the default namespace]
846     or an NCName that does not start with "xml" and does not match the reserved-to-Jena
847     pattern.
848     */
849    private boolean checkLegalPrefix( String prefix ) {
850        if (prefix.equals(""))
851            return true;
852        if (prefix.toLowerCase().startsWith( "xml" ))
853            logger.warn( "Namespace prefix '" + prefix + "' is reserved by XML." );
854        else if (!XMLChar.isValidNCName(prefix))
855            logger.warn( "'" + prefix + "' is not a legal namespace prefix." );
856        else if (jenaNamespace.matcher(prefix).matches())
857            logger.warn( "Namespace prefix '" + prefix + "' is reserved by Jena." );
858        else
859            return true;
860        return false;
861    }
862
863    static private String flags2str(int f) {
864        StringBuffer oldValue = new StringBuffer(64);
865        if ( (f&IRI.SAMEDOCUMENT)!=0 )
866            oldValue.append( "same-document, " );
867        if ( (f&IRI.NETWORK)!=0 )
868            oldValue.append( "network, ");
869        if ( (f&IRI.ABSOLUTE)!=0 )
870            oldValue.append("absolute, ");
871        if ( (f&IRI.CHILD)!=0 )
872            oldValue.append("relative, ");
873        if ((f&IRI.PARENT)!=0)
874            oldValue.append("parent, ");
875        if ((f&IRI.GRANDPARENT)!=0)
876            oldValue.append("grandparent, ");
877        if (oldValue.length() > 0)
878            oldValue.setLength(oldValue.length()-2);
879        return oldValue.toString();
880    }
881
882    public static int str2flags(String pv){
883        StringTokenizer tkn = new StringTokenizer(pv,", ");
884        int rslt = 0;
885        while ( tkn.hasMoreElements() ) {
886            String flag = tkn.nextToken();
887            if ( flag.equals("same-document") )
888                rslt |= IRI.SAMEDOCUMENT;
889            else if ( flag.equals("network") )
890                rslt |= IRI.NETWORK;
891            else if ( flag.equals("absolute") )
892                rslt |= IRI.ABSOLUTE;
893            else if ( flag.equals("relative") )
894                rslt |= IRI.CHILD;
895            else if ( flag.equals("parent") )
896                rslt |= IRI.PARENT;
897            else if ( flag.equals("grandparent") )
898                rslt |= IRI.GRANDPARENT;
899            else
900
901                logger.warn(
902                        "Incorrect property value for relativeURIs: " + flag
903                );
904        }
905        return rslt;
906    }
907
908}