1 /* 2 * Copyright (c) 1997, 2015, 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.xsom.impl.parser.state; 27 28 import java.text.MessageFormat; 29 import java.util.ArrayList; 30 import java.util.Stack; 31 import java.util.StringTokenizer; 32 33 import org.xml.sax.Attributes; 34 import org.xml.sax.ContentHandler; 35 import org.xml.sax.Locator; 36 import org.xml.sax.SAXException; 37 import org.xml.sax.SAXParseException; 38 39 /** 40 * Runtime Engine for RELAXNGCC execution. 41 * 42 * This class has the following functionalities: 43 * 44 * <ol> 45 * <li>Managing a stack of NGCCHandler objects and 46 * switching between them appropriately. 47 * 48 * <li>Keep track of all Attributes. 49 * 50 * <li>manage mapping between namespace URIs and prefixes. 51 * 52 * <li>TODO: provide support for interleaving. 53 * </ol> 54 * 55 * <p><b> 56 * Auto-generated, do not edit. 57 * </b></p> 58 * @version $Id: NGCCRuntime.java,v 1.15 2002/09/29 02:55:48 okajima Exp $ 59 * @author Kohsuke Kawaguchi (kk@kohsuke.org) 60 */ 61 public class NGCCRuntime implements ContentHandler, NGCCEventSource { 62 63 public NGCCRuntime() { 64 reset(); 65 } 66 67 /** 68 * Sets the root handler, which will be used to parse the 69 * root element. 70 * <p> 71 * This method can be called right after the object is created 72 * or the reset method is called. You can't replace the root 73 * handler while parsing is in progress. 74 * <p> 75 * Usually a generated class that corresponds to the {@code <start>} 76 * pattern will be used as the root handler, but any NGCCHandler 77 * can be a root handler. 78 * 79 * @exception IllegalStateException 80 * If this method is called but it doesn't satisfy the 81 * pre-condition stated above. 82 */ 83 public void setRootHandler( NGCCHandler rootHandler ) { 84 if(currentHandler!=null) 85 throw new IllegalStateException(); 86 currentHandler = rootHandler; 87 } 88 89 90 /** 91 * Cleans up all the data structure so that the object can be reused later. 92 * Normally, applications do not need to call this method directly, 93 * 94 * as the runtime resets itself after the endDocument method. 95 */ 96 public void reset() { 97 attStack.clear(); 98 currentAtts = null; 99 currentHandler = null; 100 indent=0; 101 locator = null; 102 namespaces.clear(); 103 needIndent = true; 104 redirect = null; 105 redirectionDepth = 0; 106 text = new StringBuffer(); 107 108 // add a dummy attributes at the bottom as a "centinel." 109 attStack.push(new AttributesImpl()); 110 } 111 112 // current content handler can be acccessed via set/getContentHandler. 113 114 private Locator locator; 115 public void setDocumentLocator( Locator _loc ) { this.locator=_loc; } 116 /** 117 * Gets the source location of the current event. 118 * 119 * <p> 120 * One can call this method from RelaxNGCC handlers to access 121 * the line number information. Note that to 122 */ 123 public Locator getLocator() { return locator; } 124 125 126 /** stack of {@link Attributes}. */ 127 private final Stack attStack = new Stack(); 128 /** current attributes set. always equal to attStack.peek() */ 129 private AttributesImpl currentAtts; 130 131 /** 132 * Attributes that belong to the current element. 133 * <p> 134 * It's generally not recommended for applications to use 135 * this method. RelaxNGCC internally removes processed attributes, 136 * so this doesn't correctly reflect all the attributes an element 137 * carries. 138 */ 139 public Attributes getCurrentAttributes() { 140 return currentAtts; 141 } 142 143 /** accumulated text. */ 144 private StringBuffer text = new StringBuffer(); 145 146 147 148 149 /** The current NGCCHandler. Always equals to handlerStack.peek() */ 150 private NGCCEventReceiver currentHandler; 151 152 public int replace( NGCCEventReceiver o, NGCCEventReceiver n ) { 153 if(o!=currentHandler) 154 throw new IllegalStateException(); // bug of RelaxNGCC 155 currentHandler = n; 156 157 return 0; // we only have one thread. 158 } 159 160 /** 161 * Processes buffered text. 162 * 163 * This method will be called by the start/endElement event to process 164 * buffered text as a text event. 165 * 166 * <p> 167 * Whitespace handling is a tricky business. Consider the following 168 * schema fragment: 169 * 170 * <pre>{@code 171 * <element name="foo"> 172 * <choice> 173 * <element name="bar"><empty/></element> 174 * <text/> 175 * </choice> 176 * </element> 177 * }</pre> 178 * 179 * Assume we hit the following instance: 180 * <pre>{@code 181 * <foo> <bar/></foo> 182 * }</pre> 183 * 184 * Then this first space needs to be ignored (for otherwise, we will 185 * end up treating this space as the match to {@code <text/>} and won't 186 * be able to process {@code <bar>}.) 187 * 188 * Now assume the following instance: 189 * <pre>{@code 190 * <foo/> 191 * }</pre> 192 * 193 * This time, we need to treat this empty string as a text, for 194 * otherwise we won't be able to accept this instance. 195 * 196 * <p> 197 * This is very difficult to solve in general, but one seemingly 198 * easy solution is to use the type of next event. If a text is 199 * followed by a start tag, it follows from the constraint on 200 * RELAX NG that that text must be either whitespaces or a match 201 * to {@code <text/>}. 202 * 203 * <p> 204 * On the contrary, if a text is followed by a end tag, then it 205 * cannot be whitespace unless the content model can accept empty, 206 * in which case sending a text event will be harmlessly ignored 207 * by the NGCCHandler. 208 * 209 * <p> 210 * Thus this method take one parameter, which controls the 211 * behavior of this method. 212 * 213 * <p> 214 * TODO: according to the constraint of RELAX NG, if characters 215 * follow an end tag, then they must be either whitespaces or 216 * must match to {@code <text/>}. 217 * 218 * @param ignorable 219 * True if the buffered character can be ignorabale. False if 220 * it needs to be consumed. 221 */ 222 private void processPendingText(boolean ignorable) throws SAXException { 223 if(ignorable && text.toString().trim().length()==0) 224 ; // ignore. See the above javadoc comment for the description 225 else 226 currentHandler.text(text.toString()); // otherwise consume this token 227 228 // truncate StringBuffer, but avoid excessive allocation. 229 if(text.length()>1024) text = new StringBuffer(); 230 else text.setLength(0); 231 } 232 233 public void processList( String str ) throws SAXException { 234 StringTokenizer t = new StringTokenizer(str, " \t\r\n"); 235 while(t.hasMoreTokens()) 236 currentHandler.text(t.nextToken()); 237 } 238 239 public void startElement(String uri, String localname, String qname, Attributes atts) 240 throws SAXException { 241 242 if(redirect!=null) { 243 redirect.startElement(uri,localname,qname,atts); 244 redirectionDepth++; 245 } else { 246 processPendingText(true); 247 // System.out.println("startElement:"+localname+"->"+_attrStack.size()); 248 currentHandler.enterElement(uri, localname, qname, atts); 249 } 250 } 251 252 /** 253 * Called by the generated handler code when an enter element 254 * event is consumed. 255 * 256 * <p> 257 * Pushes a new attribute set. 258 * 259 * <p> 260 * Note that attributes are NOT pushed at the startElement method, 261 * because the processing of the enterElement event can trigger 262 * other attribute events and etc. 263 * <p> 264 * This method will be called from one of handlers when it truely 265 * consumes the enterElement event. 266 */ 267 public void onEnterElementConsumed( 268 String uri, String localName, String qname,Attributes atts) throws SAXException { 269 attStack.push(currentAtts=new AttributesImpl(atts)); 270 nsEffectiveStack.push( new Integer(nsEffectivePtr) ); 271 nsEffectivePtr = namespaces.size(); 272 } 273 274 public void onLeaveElementConsumed(String uri, String localName, String qname) throws SAXException { 275 attStack.pop(); 276 if(attStack.isEmpty()) 277 currentAtts = null; 278 else 279 currentAtts = (AttributesImpl)attStack.peek(); 280 nsEffectivePtr = ((Integer)nsEffectiveStack.pop()).intValue(); 281 } 282 283 public void endElement(String uri, String localname, String qname) 284 throws SAXException { 285 286 if(redirect!=null) { 287 redirect.endElement(uri,localname,qname); 288 redirectionDepth--; 289 290 if(redirectionDepth!=0) 291 return; 292 293 // finished redirection. 294 for( int i=0; i<namespaces.size(); i+=2 ) 295 redirect.endPrefixMapping((String)namespaces.get(i)); 296 redirect.endDocument(); 297 298 redirect = null; 299 // then process this element normally 300 } 301 302 processPendingText(false); 303 304 currentHandler.leaveElement(uri, localname, qname); 305 // System.out.println("endElement:"+localname); 306 } 307 308 public void characters(char[] ch, int start, int length) throws SAXException { 309 if(redirect!=null) 310 redirect.characters(ch,start,length); 311 else 312 text.append(ch,start,length); 313 } 314 public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { 315 if(redirect!=null) 316 redirect.ignorableWhitespace(ch,start,length); 317 else 318 text.append(ch,start,length); 319 } 320 321 public int getAttributeIndex(String uri, String localname) { 322 return currentAtts.getIndex(uri, localname); 323 } 324 public void consumeAttribute(int index) throws SAXException { 325 final String uri = currentAtts.getURI(index); 326 final String local = currentAtts.getLocalName(index); 327 final String qname = currentAtts.getQName(index); 328 final String value = currentAtts.getValue(index); 329 currentAtts.removeAttribute(index); 330 331 currentHandler.enterAttribute(uri,local,qname); 332 currentHandler.text(value); 333 currentHandler.leaveAttribute(uri,local,qname); 334 } 335 336 337 public void startPrefixMapping( String prefix, String uri ) throws SAXException { 338 if(redirect!=null) 339 redirect.startPrefixMapping(prefix,uri); 340 else { 341 namespaces.add(prefix); 342 namespaces.add(uri); 343 } 344 } 345 346 public void endPrefixMapping( String prefix ) throws SAXException { 347 if(redirect!=null) 348 redirect.endPrefixMapping(prefix); 349 else { 350 namespaces.remove(namespaces.size()-1); 351 namespaces.remove(namespaces.size()-1); 352 } 353 } 354 355 public void skippedEntity( String name ) throws SAXException { 356 if(redirect!=null) 357 redirect.skippedEntity(name); 358 } 359 360 public void processingInstruction( String target, String data ) throws SAXException { 361 if(redirect!=null) 362 redirect.processingInstruction(target,data); 363 } 364 365 /** Impossible token. This value can never be a valid XML name. */ 366 static final String IMPOSSIBLE = "\u0000"; 367 368 public void endDocument() throws SAXException { 369 // consume the special "end document" token so that all the handlers 370 // currently at the stack will revert to their respective parents. 371 // 372 // this is necessary to handle a grammar like 373 // <start><ref name="X"/></start> 374 // <define name="X"> 375 // <element name="root"><empty/></element> 376 // </define> 377 // 378 // With this grammar, when the endElement event is consumed, two handlers 379 // are on the stack (because a child object won't revert to its parent 380 // unless it sees a next event.) 381 382 // pass around an "impossible" token. 383 currentHandler.leaveElement(IMPOSSIBLE,IMPOSSIBLE,IMPOSSIBLE); 384 385 reset(); 386 } 387 public void startDocument() {} 388 389 390 391 392 // 393 // 394 // event dispatching methods 395 // 396 // 397 398 public void sendEnterAttribute( int threadId, 399 String uri, String local, String qname) throws SAXException { 400 401 currentHandler.enterAttribute(uri,local,qname); 402 } 403 404 public void sendEnterElement( int threadId, 405 String uri, String local, String qname, Attributes atts) throws SAXException { 406 407 currentHandler.enterElement(uri,local,qname,atts); 408 } 409 410 public void sendLeaveAttribute( int threadId, 411 String uri, String local, String qname) throws SAXException { 412 413 currentHandler.leaveAttribute(uri,local,qname); 414 } 415 416 public void sendLeaveElement( int threadId, 417 String uri, String local, String qname) throws SAXException { 418 419 currentHandler.leaveElement(uri,local,qname); 420 } 421 422 public void sendText(int threadId, String value) throws SAXException { 423 currentHandler.text(value); 424 } 425 426 427 // 428 // 429 // redirection of SAX2 events. 430 // 431 // 432 /** When redirecting a sub-tree, this value will be non-null. */ 433 private ContentHandler redirect = null; 434 435 /** 436 * Counts the depth of the elements when we are re-directing 437 * a sub-tree to another ContentHandler. 438 */ 439 private int redirectionDepth = 0; 440 441 /** 442 * This method can be called only from the enterElement handler. 443 * The sub-tree rooted at the new element will be redirected 444 * to the specified ContentHandler. 445 * 446 * <p> 447 * Currently active NGCCHandler will only receive the leaveElement 448 * event of the newly started element. 449 * 450 * @param uri,local,qname 451 * Parameters passed to the enter element event. Used to 452 * simulate the startElement event for the new ContentHandler. 453 */ 454 public void redirectSubtree( ContentHandler child, 455 String uri, String local, String qname ) throws SAXException { 456 457 redirect = child; 458 redirect.setDocumentLocator(locator); 459 redirect.startDocument(); 460 461 // TODO: when a prefix is re-bound to something else, 462 // the following code is potentially dangerous. It should be 463 // modified to report active bindings only. 464 for( int i=0; i<namespaces.size(); i+=2 ) 465 redirect.startPrefixMapping( 466 (String)namespaces.get(i), 467 (String)namespaces.get(i+1) 468 ); 469 470 redirect.startElement(uri,local,qname,currentAtts); 471 redirectionDepth=1; 472 } 473 474 // 475 // 476 // validation context implementation 477 // 478 // 479 /** in-scope namespace mapping. 480 * namespaces[2n ] := prefix 481 * namespaces[2n+1] := namespace URI */ 482 private final ArrayList namespaces = new ArrayList(); 483 /** 484 * Index on the namespaces array, which points to 485 * the top of the effective bindings. Because of the 486 * timing difference between the startPrefixMapping method 487 * and the execution of the corresponding actions, 488 * this value can be different from {@code namespaces.size()}. 489 * <p> 490 * For example, consider the following schema: 491 * <pre>{@code 492 * <oneOrMore> 493 * <element name="foo"><empty/></element> 494 * </oneOrMore> 495 * code fragment X 496 * <element name="bob"/> 497 * }</pre> 498 * Code fragment X is executed after we see a startElement event, 499 * but at this time the namespaces variable already include new 500 * namespace bindings declared on "bob". 501 */ 502 private int nsEffectivePtr=0; 503 504 /** 505 * Stack to preserve old nsEffectivePtr values. 506 */ 507 private final Stack nsEffectiveStack = new Stack(); 508 509 public String resolveNamespacePrefix( String prefix ) { 510 for( int i = nsEffectivePtr-2; i>=0; i-=2 ) 511 if( namespaces.get(i).equals(prefix) ) 512 return (String)namespaces.get(i+1); 513 514 // no binding was found. 515 if(prefix.equals("")) return ""; // return the default no-namespace 516 if(prefix.equals("xml")) // pre-defined xml prefix 517 return "http://www.w3.org/XML/1998/namespace"; 518 else return null; // prefix undefined 519 } 520 521 522 // error reporting 523 protected void unexpectedX(String token) throws SAXException { 524 throw new SAXParseException(MessageFormat.format( 525 "Unexpected {0} appears at line {1} column {2}", 526 new Object[]{ 527 token, 528 new Integer(getLocator().getLineNumber()), 529 new Integer(getLocator().getColumnNumber()) }), 530 getLocator()); 531 } 532 533 534 535 536 // 537 // 538 // trace functions 539 // 540 // 541 private int indent=0; 542 private boolean needIndent=true; 543 private void printIndent() { 544 for( int i=0; i<indent; i++ ) 545 System.out.print(" "); 546 } 547 public void trace( String s ) { 548 if(needIndent) { 549 needIndent=false; 550 printIndent(); 551 } 552 System.out.print(s); 553 } 554 public void traceln( String s ) { 555 trace(s); 556 trace("\n"); 557 needIndent=true; 558 } 559 }