1 /* 2 * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.xml.internal.rngom.digested; 27 28 import java.io.FileOutputStream; 29 import java.io.OutputStream; 30 import java.util.List; 31 32 import javax.xml.namespace.QName; 33 import javax.xml.stream.XMLOutputFactory; 34 import javax.xml.stream.XMLStreamException; 35 import javax.xml.stream.XMLStreamWriter; 36 37 import com.sun.xml.internal.rngom.ast.builder.BuildException; 38 import com.sun.xml.internal.rngom.ast.builder.SchemaBuilder; 39 import com.sun.xml.internal.rngom.ast.util.CheckingSchemaBuilder; 40 import com.sun.xml.internal.rngom.nc.NameClass; 41 import com.sun.xml.internal.rngom.nc.NameClassVisitor; 42 import com.sun.xml.internal.rngom.nc.SimpleNameClass; 43 import com.sun.xml.internal.rngom.parse.Parseable; 44 import com.sun.xml.internal.rngom.parse.compact.CompactParseable; 45 import com.sun.xml.internal.rngom.parse.xml.SAXParseable; 46 import com.sun.xml.internal.rngom.xml.util.WellKnownNamespaces; 47 import org.w3c.dom.Element; 48 import org.w3c.dom.Node; 49 import org.xml.sax.ErrorHandler; 50 import org.xml.sax.InputSource; 51 import org.xml.sax.SAXException; 52 import org.xml.sax.SAXParseException; 53 import org.xml.sax.helpers.DefaultHandler; 54 55 /** 56 * Printer of RELAX NG digested model to XML using StAX {@link XMLStreamWriter}. 57 * 58 * @author <A href="mailto:demakov@ispras.ru">Alexey Demakov</A> 59 */ 60 public class DXMLPrinter { 61 protected XMLStreamWriter out; 62 protected String indentStep = "\t"; 63 protected String newLine = System.getProperty("line.separator"); 64 protected int indent; 65 protected boolean afterEnd = false; 66 protected DXMLPrinterVisitor visitor; 67 protected NameClassXMLPrinterVisitor ncVisitor; 68 protected DOMPrinter domPrinter; 69 70 /** 71 * @param out Output stream. 72 */ 73 public DXMLPrinter(XMLStreamWriter out) { 74 this.out = out; 75 this.visitor = new DXMLPrinterVisitor(); 76 this.ncVisitor = new NameClassXMLPrinterVisitor(); 77 this.domPrinter = new DOMPrinter(out); 78 } 79 80 /** 81 * Prints grammar enclosed by start/end document. 82 * 83 * @param grammar 84 * @throws XMLStreamException 85 */ 86 public void printDocument(DGrammarPattern grammar) throws XMLStreamException { 87 try { 88 visitor.startDocument(); 89 visitor.on(grammar); 90 visitor.endDocument(); 91 } catch (XMLWriterException e) { 92 throw (XMLStreamException) e.getCause(); 93 } 94 } 95 96 /** 97 * Prints XML fragment for the given pattern. 98 * 99 * @throws XMLStreamException 100 */ 101 public void print(DPattern pattern) throws XMLStreamException { 102 try { 103 pattern.accept(visitor); 104 } catch (XMLWriterException e) { 105 throw (XMLStreamException) e.getCause(); 106 } 107 } 108 109 /** 110 * Prints XML fragment for the given name class. 111 * 112 * @throws XMLStreamException 113 */ 114 public void print(NameClass nc) throws XMLStreamException { 115 try { 116 nc.accept(ncVisitor); 117 } catch (XMLWriterException e) { 118 throw (XMLStreamException) e.getCause(); 119 } 120 } 121 122 public void print(Node node) throws XMLStreamException { 123 domPrinter.print(node); 124 } 125 126 protected class XMLWriterException extends RuntimeException { 127 protected XMLWriterException(Throwable cause) { 128 super(cause); 129 } 130 } 131 132 protected class XMLWriter { 133 protected void newLine() { 134 try { 135 out.writeCharacters(newLine); 136 } catch (XMLStreamException e) { 137 throw new XMLWriterException(e); 138 } 139 } 140 141 protected void indent() { 142 try { 143 for (int i = 0; i < indent; i++) { 144 out.writeCharacters(indentStep); 145 } 146 } catch (XMLStreamException e) { 147 throw new XMLWriterException(e); 148 } 149 } 150 151 public void startDocument() { 152 try { 153 out.writeStartDocument(); 154 } catch (XMLStreamException e) { 155 throw new XMLWriterException(e); 156 } 157 } 158 159 public void endDocument() { 160 try { 161 out.writeEndDocument(); 162 } catch (XMLStreamException e) { 163 throw new XMLWriterException(e); 164 } 165 } 166 167 public final void start(String element) { 168 try { 169 newLine(); 170 indent(); 171 out.writeStartElement(element); 172 indent++; 173 afterEnd = false; 174 } catch (XMLStreamException e) { 175 throw new XMLWriterException(e); 176 } 177 } 178 179 public void end() { 180 try { 181 indent--; 182 if (afterEnd) { 183 newLine(); 184 indent(); 185 } 186 out.writeEndElement(); 187 afterEnd = true; 188 } catch (XMLStreamException e) { 189 throw new XMLWriterException(e); 190 } 191 } 192 193 public void attr(String prefix, String ns, String name, String value) { 194 try { 195 out.writeAttribute(prefix, ns, name, value); 196 } catch (XMLStreamException e) { 197 throw new XMLWriterException(e); 198 } 199 } 200 201 public void attr(String name, String value) { 202 try { 203 out.writeAttribute(name, value); 204 } catch (XMLStreamException e) { 205 throw new XMLWriterException(e); 206 } 207 } 208 209 public void ns(String prefix, String uri) { 210 try { 211 out.writeNamespace(prefix, uri); 212 } catch (XMLStreamException e) { 213 throw new XMLWriterException(e); 214 } 215 } 216 217 public void body(String text) { 218 try { 219 out.writeCharacters(text); 220 afterEnd = false; 221 } catch (XMLStreamException e) { 222 throw new XMLWriterException(e); 223 } 224 } 225 } 226 227 protected class DXMLPrinterVisitor extends XMLWriter implements DPatternVisitor<Void> { 228 protected void on(DPattern p) { 229 p.accept(this); 230 } 231 232 protected void unwrapGroup(DPattern p) { 233 if (p instanceof DGroupPattern && p.getAnnotation() == DAnnotation.EMPTY) { 234 for (DPattern d : (DGroupPattern) p) { 235 on(d); 236 } 237 } else { 238 on(p); 239 } 240 } 241 242 protected void unwrapChoice(DPattern p) { 243 if (p instanceof DChoicePattern && p.getAnnotation() == DAnnotation.EMPTY) { 244 for (DPattern d : (DChoicePattern) p) { 245 on(d); 246 } 247 } else { 248 on(p); 249 } 250 } 251 252 protected void on(NameClass nc) { 253 if (nc instanceof SimpleNameClass) { 254 QName qname = ((SimpleNameClass) nc).name; 255 String name = qname.getLocalPart(); 256 if (!qname.getPrefix().equals("")) name = qname.getPrefix() + ":"; 257 attr("name", name); 258 } else { 259 nc.accept(ncVisitor); 260 } 261 } 262 263 protected void on(DAnnotation ann) { 264 if (ann == DAnnotation.EMPTY) return; 265 for (DAnnotation.Attribute attr : ann.getAttributes().values()) { 266 attr(attr.getPrefix(), attr.getNs(), attr.getLocalName(), attr.getValue()); 267 } 268 for (Element elem : ann.getChildren()) { 269 try { 270 newLine(); 271 indent(); 272 print(elem); 273 } 274 catch (XMLStreamException e) { 275 throw new XMLWriterException(e); 276 } 277 } 278 } 279 280 public Void onAttribute(DAttributePattern p) { 281 start("attribute"); 282 on(p.getName()); 283 on(p.getAnnotation()); 284 DPattern child = p.getChild(); 285 // do not print default value 286 if (!(child instanceof DTextPattern)) { 287 on(p.getChild()); 288 } 289 end(); 290 return null; 291 } 292 293 public Void onChoice(DChoicePattern p) { 294 start("choice"); 295 on(p.getAnnotation()); 296 for (DPattern d : p) { 297 on(d); 298 } 299 end(); 300 return null; 301 } 302 303 public Void onData(DDataPattern p) { 304 List<DDataPattern.Param> params = p.getParams(); 305 DPattern except = p.getExcept(); 306 start("data"); 307 attr("datatypeLibrary", p.getDatatypeLibrary()); 308 attr("type", p.getType()); 309 on(p.getAnnotation()); 310 for (DDataPattern.Param param : params) { 311 start("param"); 312 attr("ns", param.getNs()); 313 attr("name", param.getName()); 314 body(param.getValue()); 315 end(); 316 } 317 if (except != null) { 318 start("except"); 319 unwrapChoice(except); 320 end(); 321 } 322 end(); 323 return null; 324 } 325 326 public Void onElement(DElementPattern p) { 327 start("element"); 328 on(p.getName()); 329 on(p.getAnnotation()); 330 unwrapGroup(p.getChild()); 331 end(); 332 return null; 333 } 334 335 public Void onEmpty(DEmptyPattern p) { 336 start("empty"); 337 on(p.getAnnotation()); 338 end(); 339 return null; 340 } 341 342 public Void onGrammar(DGrammarPattern p) { 343 start("grammar"); 344 ns(null, WellKnownNamespaces.RELAX_NG); 345 on(p.getAnnotation()); 346 start("start"); 347 on(p.getStart()); 348 end(); 349 for (DDefine d : p) { 350 start("define"); 351 attr("name", d.getName()); 352 on(d.getAnnotation()); 353 unwrapGroup(d.getPattern()); 354 end(); 355 } 356 end(); 357 return null; 358 } 359 360 public Void onGroup(DGroupPattern p) { 361 start("group"); 362 on(p.getAnnotation()); 363 for (DPattern d : p) { 364 on(d); 365 } 366 end(); 367 return null; 368 } 369 370 public Void onInterleave(DInterleavePattern p) { 371 start("interleave"); 372 on(p.getAnnotation()); 373 for (DPattern d : p) { 374 on(d); 375 } 376 end(); 377 return null; 378 } 379 380 public Void onList(DListPattern p) { 381 start("list"); 382 on(p.getAnnotation()); 383 unwrapGroup(p.getChild()); 384 end(); 385 return null; 386 } 387 388 public Void onMixed(DMixedPattern p) { 389 start("mixed"); 390 on(p.getAnnotation()); 391 unwrapGroup(p.getChild()); 392 end(); 393 return null; 394 } 395 396 public Void onNotAllowed(DNotAllowedPattern p) { 397 start("notAllowed"); 398 on(p.getAnnotation()); 399 end(); 400 return null; 401 } 402 403 public Void onOneOrMore(DOneOrMorePattern p) { 404 start("oneOrMore"); 405 on(p.getAnnotation()); 406 unwrapGroup(p.getChild()); 407 end(); 408 return null; 409 } 410 411 public Void onOptional(DOptionalPattern p) { 412 start("optional"); 413 on(p.getAnnotation()); 414 unwrapGroup(p.getChild()); 415 end(); 416 return null; 417 } 418 419 public Void onRef(DRefPattern p) { 420 start("ref"); 421 attr("name", p.getName()); 422 on(p.getAnnotation()); 423 end(); 424 return null; 425 } 426 427 public Void onText(DTextPattern p) { 428 start("text"); 429 on(p.getAnnotation()); 430 end(); 431 return null; 432 } 433 434 public Void onValue(DValuePattern p) { 435 start("value"); 436 if (!p.getNs().equals("")) attr("ns", p.getNs()); 437 attr("datatypeLibrary", p.getDatatypeLibrary()); 438 attr("type", p.getType()); 439 on(p.getAnnotation()); 440 body(p.getValue()); 441 end(); 442 return null; 443 } 444 445 public Void onZeroOrMore(DZeroOrMorePattern p) { 446 start("zeroOrMore"); 447 on(p.getAnnotation()); 448 unwrapGroup(p.getChild()); 449 end(); 450 return null; 451 } 452 } 453 454 protected class NameClassXMLPrinterVisitor extends XMLWriter implements NameClassVisitor<Void> { 455 public Void visitChoice(NameClass nc1, NameClass nc2) { 456 // TODO: flatten nested choices 457 start("choice"); 458 nc1.accept(this); 459 nc2.accept(this); 460 end(); 461 return null; 462 } 463 464 public Void visitNsName(String ns) { 465 start("nsName"); 466 attr("ns", ns); 467 end(); 468 return null; 469 } 470 471 public Void visitNsNameExcept(String ns, NameClass nc) { 472 start("nsName"); 473 attr("ns", ns); 474 start("except"); 475 nc.accept(this); 476 end(); 477 end(); 478 return null; 479 } 480 481 public Void visitAnyName() { 482 start("anyName"); 483 end(); 484 return null; 485 } 486 487 public Void visitAnyNameExcept(NameClass nc) { 488 start("anyName"); 489 start("except"); 490 nc.accept(this); 491 end(); 492 end(); 493 return null; 494 } 495 496 public Void visitName(QName name) { 497 start("name"); 498 if (!name.getPrefix().equals("")) { 499 body(name.getPrefix() + ":"); 500 } 501 body(name.getLocalPart()); 502 end(); 503 return null; 504 } 505 506 public Void visitNull() { 507 throw new UnsupportedOperationException("visitNull"); 508 } 509 } 510 511 public static void main(String[] args) throws Exception { 512 Parseable p; 513 514 ErrorHandler eh = new DefaultHandler() { 515 public void error(SAXParseException e) throws SAXException { 516 throw e; 517 } 518 }; 519 520 // the error handler passed to Parseable will receive parsing errors. 521 if (args[0].endsWith(".rng")) { 522 p = new SAXParseable(new InputSource(args[0]), eh); 523 } else { 524 p = new CompactParseable(new InputSource(args[0]), eh); 525 } 526 527 // the error handler passed to CheckingSchemaBuilder will receive additional 528 // errors found during the RELAX NG restrictions check. 529 // typically you'd want to pass in the same error handler, 530 // as there's really no distinction between those two kinds of errors. 531 SchemaBuilder sb = new CheckingSchemaBuilder(new DSchemaBuilderImpl(), eh); 532 try { 533 // run the parser 534 DGrammarPattern grammar = (DGrammarPattern) p.parse(sb); 535 OutputStream out = new FileOutputStream(args[1]); 536 XMLOutputFactory factory = XMLOutputFactory.newInstance(); 537 XMLStreamWriter output = factory.createXMLStreamWriter(out); 538 DXMLPrinter printer = new DXMLPrinter(output); 539 printer.printDocument(grammar); 540 output.close(); 541 out.close(); 542 } catch (BuildException e) { 543 if (e.getCause() instanceof SAXParseException) { 544 SAXParseException se = (SAXParseException) e.getCause(); 545 System.out.println("(" 546 + se.getLineNumber() 547 + "," 548 + se.getColumnNumber() 549 + "): " 550 + se.getMessage()); 551 return; 552 } else 553 // I found that Crimson doesn't show the proper stack trace 554 // when a RuntimeException happens inside a SchemaBuilder. 555 // the following code shows the actual exception that happened. 556 if (e.getCause() instanceof SAXException) { 557 SAXException se = (SAXException) e.getCause(); 558 if (se.getException() != null) 559 se.getException().printStackTrace(); 560 } 561 throw e; 562 } 563 } 564 }