1 7 8 package com.hp.hpl.jena.xmloutput.impl; 9 10 import com.hp.hpl.jena.xmloutput.RDFXMLWriterI; 11 import com.hp.hpl.jena.rdf.model.impl.*; 12 import com.hp.hpl.jena.rdf.model.*; 13 import com.hp.hpl.jena.util.CharEncoding; 14 import com.hp.hpl.jena.util.FileUtils; 15 import com.hp.hpl.jena.rdf.model.impl.Util; 16 import com.hp.hpl.jena.JenaRuntime ; 17 18 import com.hp.hpl.jena.vocabulary.*; 19 import com.hp.hpl.jena.shared.*; 20 21 import com.hp.hpl.jena.rdf.arp.URI; 22 import com.hp.hpl.jena.rdf.arp.ARP; 23 import com.hp.hpl.jena.rdf.arp.MalformedURIException; 24 25 import java.io.*; 26 import java.util.*; 27 import java.nio.charset.Charset ; 28 29 import org.apache.xerces.util.*; 30 import org.apache.oro.text.regex.*; 31 import org.apache.commons.logging.Log; 32 import org.apache.commons.logging.LogFactory; 33 34 56 abstract public class BaseXMLWriter implements RDFXMLWriterI { 57 58 61 private static final String newline_XMLNS = 62 System.getProperty( "line.separator" ) + " xmlns"; 63 64 public BaseXMLWriter() { 65 setupMaps(); 66 } 67 68 private static Log xlogger = LogFactory.getLog( BaseXMLWriter.class ); 69 protected static SimpleLogger logger = new SimpleLogger() { 70 public void warn(String s) { 71 xlogger.warn(s); 72 } 73 public void warn(String s, Exception e) { 74 xlogger.warn(s,e); 75 } 76 }; 77 78 public static SimpleLogger setLogger(SimpleLogger lg) { 79 SimpleLogger old = logger; 80 logger= lg; 81 return old; 82 } 83 84 abstract void unblockAll(); 85 86 abstract void blockRule(Resource r); 87 88 abstract void writeBody 89 ( Model mdl, PrintWriter pw, String baseURI, boolean inclXMLBase ); 90 91 92 private String attributeQuoteChar ="\""; 93 94 protected String q(String s) { 95 return attributeQuoteChar +s + attributeQuoteChar; 96 } 97 98 protected String qq(String s) { 99 return q(Util.substituteStandardEntities(s)); 100 } 101 102 static private Set badRDF = new HashSet(); 103 104 107 private int jenaPrefixCount; 108 109 static String RDFNS = RDF.getURI(); 110 111 static private Perl5Matcher matcher = new Perl5Matcher(); 112 113 static private Pattern jenaNamespace; 114 115 static { 116 try { 117 jenaNamespace = 118 new Perl5Compiler().compile("j\\.([1-9][0-9]*|cook\\.up)"); 119 } catch (MalformedPatternException e) { 120 } 121 badRDF.add("RDF"); 122 badRDF.add("Description"); 123 badRDF.add("li"); 124 badRDF.add("about"); 125 badRDF.add("aboutEach"); 126 badRDF.add("aboutEachPrefix"); 127 badRDF.add("ID"); 128 badRDF.add("nodeID"); 129 badRDF.add("parseType"); 130 badRDF.add("datatype"); 131 badRDF.add("bagID"); 132 badRDF.add("resource"); 133 } 134 135 String xmlBase = null; 136 137 private URI baseURI; 138 139 boolean longId = false; 140 141 private boolean demandGoodURIs = true; 142 143 int tab = 2; 144 145 int width = 60; 146 147 HashMap anonMap = new HashMap(); 148 149 int anonCount = 0; 150 151 static private RDFDefaultErrorHandler defaultErrorHandler = 152 new RDFDefaultErrorHandler(); 153 154 RDFErrorHandler errorHandler = defaultErrorHandler; 155 156 Boolean showXmlDeclaration = null; 157 158 164 165 String anonId(Resource r) { 166 return longId ? longAnonId( r ) : shortAnonId( r ); 167 } 168 169 174 private String shortAnonId(Resource r) { 175 String result = (String ) 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 191 192 private String longAnonId(Resource r) { 193 String rid = r.getId().toString(); 194 return XMLChar.isValidNCName( rid ) ? rid : escapedId( rid ); 195 } 196 197 201 private boolean writingAllModelPrefixNamespaces = true; 202 203 private Relation nameSpaces = new Relation(); 204 205 private Map ns; 206 207 private Set namespacesNeeded; 208 209 void addNameSpace(String uri) { 210 namespacesNeeded.add(uri); 211 } 212 213 boolean isDefaultNamespace( String uri ) { 214 return "".equals( ns.get( uri ) ); 215 } 216 217 private void addNameSpaces( Model model ) { 218 NsIterator nsIter = model.listNameSpaces(); 219 while (nsIter.hasNext()) this.addNameSpace( nsIter.nextNs() ); 220 } 221 222 private void primeNamespace( Model model ) 223 { 224 Map m = model.getNsPrefixMap(); 225 Iterator it = m.entrySet().iterator(); 226 while (it.hasNext()) 227 { 228 Map.Entry e = (Map.Entry) it.next(); 229 String key = (String ) e.getKey(); 230 String value = (String ) e.getValue(); 231 String already = this.getPrefixFor( value ); 232 if (already == null) 233 { this.setNsPrefix( model.getNsURIPrefix( value ), value ); 234 if (writingAllModelPrefixNamespaces) this.addNameSpace( value ); } 235 } 236 } 237 238 void setupMaps() { 239 nameSpaces.set11(RDF.getURI(), "rdf"); 240 nameSpaces.set11(RDFS.getURI(), "rdfs"); 241 nameSpaces.set11(DC.getURI(), "dc"); 242 nameSpaces.set11(RSS.getURI(), "rss"); 243 nameSpaces.set11("http://www.daml.org/2001/03/daml+oil.daml#", "daml"); 244 nameSpaces.set11(VCARD.getURI(), "vcard"); 245 nameSpaces.set11("http://www.w3.org/2002/07/owl#", "owl"); 246 } 247 248 void workOutNamespaces() { 249 if (ns == null) { 250 ns = new HashMap(); 251 Set prefixesUsed = new HashSet(); 252 setFromWriterSystemProperties( ns, prefixesUsed ); 253 setFromGivenNamespaces( ns, prefixesUsed ); 254 } 255 } 256 257 private void setFromWriterSystemProperties( Map ns, Set prefixesUsed ) { 258 Iterator it = namespacesNeeded.iterator(); 259 while (it.hasNext()) { 260 String uri = (String ) it.next(); 261 String val = JenaRuntime.getSystemProperty( RDFWriter.NSPREFIXPROPBASE + uri ); 262 if (val != null && checkLegalPrefix( val ) && !prefixesUsed.contains( val )) { 263 ns.put(uri, val); 264 prefixesUsed.add(val); 265 } 266 } 267 } 268 269 private void setFromGivenNamespaces( Map ns, Set prefixesUsed ) { 270 Iterator it = namespacesNeeded.iterator(); 271 while (it.hasNext()) { 272 String uri = (String ) it.next(); 273 if (ns.containsKey(uri)) 274 continue; 275 String val = null; 276 Set s = nameSpaces.forward(uri); 277 if (s != null) { 278 Iterator it2 = s.iterator(); 279 if (it2.hasNext()) 280 val = (String ) it2.next(); 281 if (prefixesUsed.contains(val)) 282 val = null; 283 } 284 if (val == null) { 285 do { val = "j." + (jenaPrefixCount++); } while (prefixesUsed.contains( val )); 288 } 289 ns.put(uri, val); 290 prefixesUsed.add(val); 291 } 292 } 293 294 final synchronized public void setNsPrefix(String prefix, String ns) { 295 if (checkLegalPrefix(prefix)) { 296 nameSpaces.set11(ns, prefix); 297 } 298 } 299 300 final public String getPrefixFor( String uri ) 301 { 302 Set s = nameSpaces.backward( uri ); 303 if (s != null && s.size() == 1) return (String ) s.iterator().next(); 304 return null; 305 } 306 307 String xmlnsDecl() { 308 workOutNamespaces(); 309 StringBuffer rslt = new StringBuffer (); 310 Iterator it = ns.entrySet().iterator(); 311 while (it.hasNext()) { 312 Map.Entry ent = (Map.Entry) it.next(); 313 String prefix = (String ) ent.getValue(); 314 String uri = (String ) ent.getKey(); 315 rslt.append( newline_XMLNS ); 316 if (prefix.length() > 0) rslt.append( ':' ).append( prefix ); 317 rslt.append( '=' ).append( qq( checkURI( uri ) ) ); 318 } 319 return rslt.toString(); 320 } 321 322 static final private int FAST = 1; 323 static final private int START = 2; 324 static final private int END = 3; 325 static final private int ATTR = 4; 326 static final private int FASTATTR = 5; 327 328 String rdfEl(String local) { 329 return tag(RDFNS, local, FAST, true); 330 } 331 332 String startElementTag(String uri, String local) { 333 return tag(uri, local, START, false); 334 } 335 336 protected String startElementTag(String uriref) { 337 return splitTag(uriref, START); 338 } 339 340 String attributeTag(String uriref) { 341 return splitTag(uriref, ATTR); 342 } 343 344 String attributeTag(String uri, String local) { 345 return tag(uri, local, ATTR, false); 346 } 347 348 String rdfAt(String local) { 349 return tag(RDFNS, local, FASTATTR, true); 350 } 351 352 String endElementTag(String uri, String local) { 353 return tag(uri, local, END, false); 354 } 355 356 protected String endElementTag(String uriref) { 357 return splitTag(uriref, END); 358 } 359 360 String splitTag(String uriref, int type) { 361 int split = Util.splitNamespace( uriref ); 362 if (split == uriref.length()) throw new InvalidPropertyURIException( uriref ); 363 return tag( uriref.substring( 0, split ), uriref.substring( split ), type, true ); 364 } 365 366 static public boolean dbg = false; 367 368 String tag( String namespace, String local, int type, boolean localIsQname) { 369 if (dbg) 370 System.err.println(namespace + " - " + local); 371 String prefix = (String ) ns.get( namespace ); 372 if (type != FAST && type != FASTATTR) { 373 if ((!localIsQname) && !XMLChar.isValidNCName(local)) 374 return splitTag(namespace + local, type); 375 if (namespace.equals(RDFNS)) { 376 if (badRDF.contains(local)) { 379 logger.warn( "The URI rdf:" + local + " cannot be serialized in RDF/XML." ); 380 throw new InvalidPropertyURIException( "rdf:" + local ); 381 } 382 } 383 } 384 boolean cookUp = false; 385 if (prefix == null) { 386 checkURI( namespace ); 387 logger.warn( 388 "Internal error: unexpected QName URI: <" 389 + namespace 390 + ">. Fixing up with j.cook.up code.", 391 new BrokenException( "unexpected QName URI " + namespace )); 392 cookUp = true; 393 } else if (prefix.length() == 0) { 394 if (type == ATTR || type == FASTATTR) 395 cookUp = true; 396 else 397 return local; 398 } 399 if (cookUp) return cookUpAttribution( type, namespace, local ); 400 return prefix + ":" + local; 401 } 402 403 private String cookUpAttribution( int type, String namespace, String local ) 404 { 405 String prefix = "j.cook.up"; 406 switch (type) { 407 case FASTATTR : 408 case ATTR : 409 return "xmlns:" + prefix + "=" + qq( namespace ) + " " + prefix + ":" + local; 410 case START : 411 return prefix + ":" + local + " xmlns:" + prefix+ "=" + qq( namespace ); 412 default: 413 case END : 414 return prefix + ":" + local; 415 case FAST : 416 throw new BrokenException( "cookup reached final FAST" ); 418 } 419 } 420 421 427 final public void write(Model model, OutputStream out, String base) 428 { write(model, FileUtils.asUTF8(out), base); } 429 430 436 synchronized public void write(Model baseModel, Writer out, String base) 437 { 438 Model model = ModelFactory.withHiddenStatements( baseModel ); 439 this.namespacesNeeded = new HashSet(); 440 this.ns = null; 441 primeNamespace( baseModel ); 442 addNameSpace(RDF.getURI()); 443 addNameSpaces(model); 444 jenaPrefixCount = 0; 445 PrintWriter pw = out instanceof PrintWriter ? (PrintWriter) out : new PrintWriter( out ); 446 if (!Boolean.FALSE.equals(showXmlDeclaration)) writeXMLDeclaration( out, pw ); 447 writeXMLBody( model, pw, base ); 448 pw.flush(); 449 } 450 451 private void writeXMLBody( Model model, PrintWriter pw, String base ) { 452 try { 453 if (xmlBase == null) { 454 baseURI = (base == null || base.length() == 0) ? null : new URI(base); 455 writeBody(model, pw, base, false); 456 } else { 457 baseURI = xmlBase.length() == 0 ? null : new URI(xmlBase); 458 writeBody(model, pw, xmlBase, true); 459 } 460 } catch (MalformedURIException e) { 461 throw new BadURIException( e.getMessage(), e); 462 } 463 } 464 465 private void writeXMLDeclaration(Writer out, PrintWriter pw) { 466 String decl = null; 467 if (out instanceof OutputStreamWriter) { 468 String javaEnc = ((OutputStreamWriter) out).getEncoding(); 469 if (!(javaEnc.equals("UTF8") || javaEnc.equals("UTF-16"))) { 471 CharEncoding encodingInfo = CharEncoding.create(javaEnc); 472 473 String ianaEnc = encodingInfo.name(); 474 decl = "<?xml version="+q("1.0")+" encoding=" + q(ianaEnc) + "?>"; 475 if (!encodingInfo.isIANA()) 476 logger.warn(encodingInfo.warningMessage()+"\n"+ 477 " It is better to use a FileOutputStream, in place of a FileWriter."); 478 479 } 480 } 481 if (decl == null && showXmlDeclaration != null) 482 decl = "<?xml version="+q("1.0")+"?>"; 483 if (decl != null) { 484 pw.println(decl); 485 } 486 } 487 488 492 synchronized public RDFErrorHandler setErrorHandler(RDFErrorHandler errHandler) { 493 RDFErrorHandler rslt = errorHandler; 497 if (rslt == defaultErrorHandler) rslt = null; 498 errorHandler = errHandler == null ? defaultErrorHandler : errHandler; 499 return rslt; 500 } 501 502 static private final char ESCAPE = 'X'; 503 504 static private String escapedId(String id) { 505 StringBuffer result = new StringBuffer (); 506 for (int i = 0; i < id.length(); i++) { 507 char ch = id.charAt(i); 508 if (ch != ESCAPE 509 && (i == 0 ? XMLChar.isNCNameStart(ch) : XMLChar.isNCName(ch))) { 510 result.append( ch ); 511 } else { 512 escape( result, ch ); 513 } 514 } 515 return result.toString(); 516 } 517 518 static final char [] hexchar = "0123456789abcdef".toCharArray(); 519 520 static private void escape( StringBuffer sb, char ch) { 521 sb.append( ESCAPE ); 522 int charcode = ch; 523 do { 524 sb.append( hexchar[charcode & 15] ); 525 charcode = charcode >> 4; 526 } while (charcode != 0); 527 sb.append( ESCAPE ); 528 } 529 530 536 final synchronized public Object setProperty( String propName, Object propValue ) { 537 if (propName.equalsIgnoreCase("showXmlDeclaration")) { 538 return setShowXmlDeclaration(propValue); 539 } else if (propName.equalsIgnoreCase( "minimalPrefixes" )) { 540 try { return new Boolean ( !writingAllModelPrefixNamespaces ); } 541 finally { writingAllModelPrefixNamespaces = !getBoolean( propValue ); } 542 } else if (propName.equalsIgnoreCase("xmlbase")) { 543 String result = xmlBase; 544 xmlBase = (String ) propValue; 545 return result; 546 } else if (propName.equalsIgnoreCase("tab")) { 547 return setTab( propValue ); 548 } else if (propName.equalsIgnoreCase("width")) { 549 return setWidth(propValue); 550 } else if (propName.equalsIgnoreCase("longid")) { 551 Boolean result = new Boolean (longId); 552 longId = getBoolean(propValue); 553 return result; 554 } else if (propName.equalsIgnoreCase("attributeQuoteChar")) { 555 return setAttributeQuoteChar(propValue); 556 } else if (propName.equalsIgnoreCase( "allowBadURIs" )) { 557 Boolean result = new Boolean ( !demandGoodURIs ); 558 demandGoodURIs = !getBoolean(propValue); 559 return result; 560 } else if (propName.equalsIgnoreCase("prettyTypes")) { 561 return setTypes((Resource[]) propValue); 562 } else if (propName.equalsIgnoreCase("relativeURIs")) { 563 int old = relativeFlags; 564 relativeFlags = str2flags((String ) propValue); 565 return flags2str(old); 566 } else if (propName.equalsIgnoreCase("blockRules")) { 567 return setBlockRules(propValue); 568 } else { 569 logger.warn("Unsupported property: " + propName); 570 return null; 571 } 572 } 573 574 private String setAttributeQuoteChar(Object propValue) { 575 String result = attributeQuoteChar; 576 if ( "\"".equals(propValue) || "'".equals(propValue) ) 577 attributeQuoteChar = (String )propValue; 578 else 579 logger.warn("attributeQutpeChar must be either \"\\\"\" or \', not \""+propValue+"\"" ); 580 return result; 581 } 582 583 private Integer setWidth(Object propValue) { 584 Integer result = new Integer (width); 585 if (propValue instanceof Integer ) { 586 width = ((Integer ) propValue).intValue(); 587 } else { 588 try { 589 width = Integer.parseInt((String ) propValue); 590 } catch (Exception e) { 591 logger.warn( "Bad value for width: '" + propValue + "' [" + e.getMessage() + "]" ); 592 } 593 } 594 return result; 595 } 596 597 private Integer setTab(Object propValue) { 598 Integer result = new Integer (tab); 599 if (propValue instanceof Integer ) { 600 tab = ((Integer ) propValue).intValue(); 601 } else { 602 try { 603 tab = Integer.parseInt((String ) propValue); 604 } catch (Exception e) { 605 logger.warn( "Bad value for tab: '" + propValue + "' [" + e.getMessage() + "]" ); 606 } 607 } 608 return result; 609 } 610 611 private String setShowXmlDeclaration(Object propValue) { 612 String oldValue; 613 if (showXmlDeclaration == null) 614 oldValue = null; 615 else 616 oldValue = showXmlDeclaration.toString(); 617 if (propValue == null) 618 showXmlDeclaration = null; 619 else if (propValue instanceof Boolean ) 620 showXmlDeclaration = (Boolean ) propValue; 621 else if (propValue instanceof String ) { 622 String propValueStr = (String ) propValue; 623 if (propValueStr.equalsIgnoreCase("default")) { 624 showXmlDeclaration = null; 625 } 626 if (propValueStr.equalsIgnoreCase("true")) 627 showXmlDeclaration = Boolean.TRUE; 628 else if (propValueStr.equalsIgnoreCase("false")) 629 showXmlDeclaration = Boolean.FALSE; 630 else 631 throw new BadBooleanException( propValueStr ); 633 } 634 return oldValue; 635 } 636 637 641 static private boolean getBoolean( Object o ) { 642 if (o instanceof Boolean ) 643 return ((Boolean ) o).booleanValue(); 644 else 645 return Boolean.valueOf((String ) o).booleanValue(); 646 } 647 648 Resource[] setTypes( Resource x[] ) { 649 logger.warn( "prettyTypes is not a property on the Basic RDF/XML writer." ); 650 return null; 651 } 652 653 private Resource blockedRules[] = new Resource[]{RDFSyntax.propertyAttr}; 654 655 Resource[] setBlockRules(Object o) { 656 Resource rslt[] = blockedRules; 657 unblockAll(); 658 if (o instanceof Resource[]) { 659 blockedRules = (Resource[]) o; 660 } else { 661 StringTokenizer tkn = new StringTokenizer((String ) o, ", "); 662 Vector v = new Vector(); 663 while (tkn.hasMoreElements()) { 664 String frag = tkn.nextToken(); 665 if (frag.equals("daml:collection")) 667 v.add(DAML_OIL.collection); 668 else 669 v.add(new ResourceImpl(RDFSyntax.getURI() + frag)); 670 } 671 672 blockedRules = new Resource[v.size()]; 673 v.copyInto(blockedRules); 674 } 675 for (int i = 0; i < blockedRules.length; i++) 676 blockRule(blockedRules[i]); 677 return rslt; 678 } 679 687 private int relativeFlags = 688 URI.SAMEDOCUMENT | URI.ABSOLUTE | URI.RELATIVE | URI.PARENT; 689 690 695 protected String relativize( String uri ) { 696 return relativeFlags != 0 && baseURI != null 697 ? relativize( baseURI, uri ) 698 : checkURI( uri ); 699 } 700 701 705 private String relativize( URI base, String uri ) { 706 try { return base.relativize( uri, relativeFlags); } 707 catch (MalformedURIException e) { throw new JenaException( e ); } 708 } 709 710 715 private String checkURI( String uri ) { 716 if (demandGoodURIs) 717 try { new URI( uri ); } 718 catch (MalformedURIException e) { 719 throw new BadURIException( "Only well-formed absolute URIrefs can be included in RDF/XML output: " + uri, e ); 720 } 721 return uri; 722 } 723 724 729 private boolean checkLegalPrefix( String prefix ) { 730 if (prefix.equals("")) 731 return true; 732 if (prefix.toLowerCase().startsWith( "xml" )) 733 logger.warn( "Namespace prefix '" + prefix + "' is reserved by XML." ); 734 else if (!XMLChar.isValidNCName(prefix)) 735 logger.warn( "'" + prefix + "' is not a legal namespace prefix." ); 736 else if (matcher.matches(prefix, jenaNamespace)) 737 logger.warn( "Namespace prefix '" + prefix + "' is reserved by Jena." ); 738 else 739 return true; 740 return false; 741 } 742 743 static private String flags2str(int f) { 744 StringBuffer oldValue = new StringBuffer (64); 745 if ( (f&URI.SAMEDOCUMENT)!=0 ) 746 oldValue.append( "same-document, " ); 747 if ( (f&URI.NETWORK)!=0 ) 748 oldValue.append( "network, "); 749 if ( (f&URI.ABSOLUTE)!=0 ) 750 oldValue.append("absolute, "); 751 if ( (f&URI.RELATIVE)!=0 ) 752 oldValue.append("relative, "); 753 if ((f&URI.PARENT)!=0) 754 oldValue.append("parent, "); 755 if ((f&URI.GRANDPARENT)!=0) 756 oldValue.append("grandparent, "); 757 if (oldValue.length() > 0) 758 oldValue.setLength(oldValue.length()-2); 759 return oldValue.toString(); 760 } 761 762 public static int str2flags(String pv){ 763 StringTokenizer tkn = new StringTokenizer(pv,", "); 764 int rslt = 0; 765 while ( tkn.hasMoreElements() ) { 766 String flag = tkn.nextToken(); 767 if ( flag.equals("same-document") ) 768 rslt |= URI.SAMEDOCUMENT; 769 else if ( flag.equals("network") ) 770 rslt |= URI.NETWORK; 771 else if ( flag.equals("absolute") ) 772 rslt |= URI.ABSOLUTE; 773 else if ( flag.equals("relative") ) 774 rslt |= URI.RELATIVE; 775 else if ( flag.equals("parent") ) 776 rslt |= URI.PARENT; 777 else if ( flag.equals("grandparent") ) 778 rslt |= URI.GRANDPARENT; 779 else 780 781 logger.warn( 782 "Incorrect property value for relativeURIs: " + flag 783 ); 784 } 785 return rslt; 786 } 787 788 } 789 790 | Popular Tags |