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}