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 }