1 29 30 package com.caucho.relaxng; 31 32 import com.caucho.relaxng.program.EmptyItem; 33 import com.caucho.relaxng.program.Item; 34 import com.caucho.util.CharBuffer; 35 import com.caucho.util.L10N; 36 import com.caucho.util.LruCache; 37 import com.caucho.vfs.Path; 38 import com.caucho.vfs.ReadStream; 39 import com.caucho.vfs.Vfs; 40 import com.caucho.xml.QName; 41 42 import org.xml.sax.Attributes ; 43 import org.xml.sax.ErrorHandler ; 44 import org.xml.sax.Locator ; 45 import org.xml.sax.SAXException ; 46 import org.xml.sax.SAXParseException ; 47 import org.xml.sax.helpers.DefaultHandler ; 48 49 import java.io.IOException ; 50 import java.util.ArrayList ; 51 import java.util.Collections ; 52 import java.util.HashSet ; 53 import java.util.logging.Level ; 54 import java.util.logging.Logger ; 55 56 59 public class VerifierHandlerImpl extends DefaultHandler 60 implements VerifierHandler 61 { 62 private static final L10N L = new L10N(VerifierHandlerImpl.class); 63 protected static final Logger log 64 = Logger.getLogger(VerifierHandlerImpl.class.getName()); 65 66 private static final boolean _isDebug = false; 68 69 private SchemaImpl _schema; 70 private VerifierImpl _verifier; 71 private boolean _hasProgram; 72 73 private boolean _isValid = true; 74 75 private LruCache<Object ,Item> _programCache; 76 77 private QName _name; 78 private ArrayList <QName> _nameStack = new ArrayList <QName>(); 79 private String _eltLocation; 80 private ArrayList <String > _eltLocationStack = new ArrayList <String >(); 81 82 private Item _item; 83 private ArrayList <Item> _itemStack = new ArrayList <Item>(); 84 85 private Locator _locator; 86 87 private boolean _isLogFinest; 88 89 private CharBuffer _text = new CharBuffer(); 90 private boolean _hasText; 91 92 private StartKey _startKey = new StartKey(); 93 private EndElementKey _endElementKey = new EndElementKey(); 94 95 98 public VerifierHandlerImpl(SchemaImpl schema, VerifierImpl verifier) 99 { 100 _schema = schema; 101 _programCache = _schema.getProgramCache(); 102 _verifier = verifier; 103 } 104 105 108 public void setDocumentLocator(Locator locator) 109 { 110 _locator = locator; 111 } 112 113 116 public void setErrorHandler(ErrorHandler errorHandler) 117 throws SAXException 118 { 119 _verifier.setErrorHandler(errorHandler); 120 } 121 122 private String getFileName() 123 { 124 if (_locator != null) 125 return _locator.getSystemId(); 126 else 127 return null; 128 } 129 130 private int getLine() 131 { 132 if (_locator != null) 133 return _locator.getLineNumber(); 134 else 135 return -1; 136 } 137 138 141 public void startDocument() 142 throws SAXException 143 { 144 try { 145 _nameStack.clear(); 146 _itemStack.clear(); 147 _eltLocationStack.clear(); 148 149 _name = new QName("#top", ""); 150 _item = _schema.getStartItem(); 151 152 _itemStack.add(_item); 153 154 _eltLocation = getLocation(); 155 156 _isLogFinest = _isDebug && log.isLoggable(Level.FINEST); 157 _hasText = false; 158 _text.clear(); 159 } catch (Exception e) { 160 error(e); 161 } 162 } 163 164 167 public void startElement(String uri, String localName, 168 String qName, Attributes attrs) 169 throws SAXException 170 { 171 if (! _isValid) 172 return; 173 174 if (_hasText) 175 sendText(); 176 177 if (_isLogFinest) 178 log.finest("element start: " + qName); 179 180 try { 181 QName parent = _name; 182 _nameStack.add(parent); 183 184 String parentLocation = _eltLocation; 185 _eltLocationStack.add(parentLocation); 186 187 QName name = new QName(qName, uri); 188 _name = name; 189 _eltLocation = getLocation(); 190 191 Item newItem = getStartElement(_item, name); 192 193 if (newItem == null) { 194 Item parentItem = _itemStack.get(_itemStack.size() - 1); 195 196 if (parent.getName().equals("#top")) 197 throw new RelaxException(L.l("<{0}> is an unexpected top-level tag.{1}", 198 errorNodeName(name, _item, parentItem), 199 errorMessageDetail(_item, parentItem, null, name))); 200 else 201 throw new RelaxException(L.l("<{0}> is an unexpected tag (parent <{1}> starts at {2}).{3}", 202 errorNodeName(name, _item, parentItem), 203 parent.getName(), parentLocation, 204 errorMessageDetail(_item, parentItem, parent.getName(), name))); 205 } 206 207 _item = newItem; 208 _itemStack.add(newItem); 209 210 Item parentItem = newItem; 211 212 int len = attrs.getLength(); 213 for (int i = 0; i < len; i++) { 214 String attrUri = attrs.getURI(i); 215 String attrQName = attrs.getQName(i); 216 String value = attrs.getValue(i); 217 218 if (_isLogFinest) 219 log.finest("attribute: " + attrQName + "=\"" + value + "\""); 220 221 name = new QName(attrQName, attrUri); 222 223 if (attrQName.startsWith("xml:")) { 224 } 225 else if (! _item.allowAttribute(name, value)) { 226 throw new RelaxException(L.l("{0}=\"{1}\" is an unexpected attribute in <{2}>.{3}", 227 attrQName, value, qName, 228 attributeMessageDetail(_item, 229 parentItem, 230 qName, null))); 231 } 232 else 233 _item = _item.setAttribute(name, value); 234 235 if (_item == null) 236 _item = EmptyItem.create(); 237 } 238 239 newItem = _item.attributeEnd(); 240 if (newItem == null) 241 throw new RelaxException(L.l("<{0}> expects more attributes.{1}", 242 qName, 243 attributeMessageDetail(_item, 244 parentItem, 245 qName, null))); 246 _item = newItem; 247 } catch (Exception e) { 248 error(e); 249 } 250 } 251 252 private Item getStartElement(Item item, QName name) 253 throws RelaxException 254 { 255 _startKey.init(item, name); 256 257 Item newItem = null; 259 if (newItem != null) { 260 return newItem; 261 } 262 263 newItem = _item.startElement(name); 264 265 269 270 return newItem; 271 } 272 273 public void characters(char ch[], int start, int length) 274 throws SAXException 275 { 276 _hasText = true; 277 _text.append(ch, start, length); 278 } 279 280 public void sendText() 281 throws SAXException 282 { 283 if (! _hasText) 284 return; 285 286 _hasText = false; 287 String string = _text.toString(); 288 _text.clear(); 289 290 try { 291 Item newItem = _item.text(string); 292 293 if (newItem == null) { 294 Item parentItem = _itemStack.get(_itemStack.size() - 1); 295 296 throw new RelaxException(L.l("The following text is not allowed in this context.\n{0}\n{1}", string, 297 errorMessageDetail(_item, parentItem, 298 _name.getName(), null))); 299 } 300 301 _item = newItem; 302 } catch (Exception e) { 303 error(e); 304 } 305 } 306 307 310 public void endElement(String uri, String localName, String qName) 311 throws SAXException 312 { 313 if (_hasText) 314 sendText(); 315 316 if (! _isValid) 317 return; 318 319 if (_isLogFinest) 320 log.finest("element end: " + qName); 321 322 QName name = _name; 323 QName parent = _nameStack.remove(_nameStack.size() - 1); 324 _name = parent; 325 326 Item parentItem = _itemStack.remove(_itemStack.size() - 1); 327 328 String eltOpen = _eltLocation; 329 _eltLocation = _eltLocationStack.remove(_eltLocationStack.size() - 1); 330 331 try { 332 Item nextItem = getEndElement(_item); 333 334 if (nextItem == null) 335 throw new RelaxException(L.l("<{0}> closed while expecting more elements (open at {1}).{2}", 336 qName, eltOpen, 337 requiredMessageDetail(_item, parentItem, 338 qName, null))); 339 340 _item = nextItem; 341 } catch (Exception e) { 342 error(e); 343 } 344 } 345 346 private Item getEndElement(Item item) 347 throws RelaxException 348 { 349 _endElementKey.init(item); 350 351 Item newItem = null; 353 if (newItem != null) { 354 return newItem; 355 } 356 357 newItem = _item.endElement(); 358 359 363 364 return newItem; 365 } 366 367 370 private void error(SAXException e) 371 throws SAXException 372 { 373 _isValid = false; 374 375 _verifier.error(new SAXParseException (e.getMessage(), _locator)); 376 } 377 378 381 private void error(Exception e) 382 throws SAXException 383 { 384 if (e instanceof RuntimeException ) 385 throw (RuntimeException ) e; 386 else if (e instanceof SAXException ) 387 error((SAXException ) e); 388 else 389 error(new SAXException (e.getMessage(), e)); 390 } 391 392 395 private String errorNodeName(QName name, Item item, Item parentItem) 396 { 397 Item currentItem = item; 398 399 if (currentItem == null) 400 currentItem = parentItem; 401 402 if (currentItem == null) 403 return name.toString(); 404 405 HashSet <QName> values = new HashSet <QName>(); 406 currentItem.firstSet(values); 407 408 for (QName value : values) { 409 if (! name.getLocalName().equals(value.getLocalName())) { 410 } 411 else if (name.getPrefix() == null || name.getPrefix().equals("")) { 412 return name.getName() + " xmlns=\"" + name.getNamespaceURI() + "\""; 413 } 414 else { 415 return name.getName() + " xmlns:" + name.getPrefix() + "=\"" + name.getNamespaceURI() + "\""; 416 } 417 } 418 419 return name.getName(); 420 } 421 422 425 private String errorMessageDetail(Item item, Item parentItem, 426 String parentName, QName qName) 427 { 428 Item currentItem = item; 429 430 if (currentItem == null) 431 currentItem = parentItem; 432 433 HashSet <QName> values = new HashSet <QName>(); 434 currentItem.firstSet(values); 435 436 String expected = null; 437 if (values.size() <= 5) 438 expected = namesToString(values, parentName, qName, 439 currentItem.allowEmpty()); 440 441 return (getLineContext(getFileName(), getLine()) 442 + syntaxMessage(item, parentItem, parentName, qName, expected)); 443 } 444 445 448 private String requiredMessageDetail(Item item, Item parentItem, 449 String parentName, QName qName) 450 { 451 Item currentItem = item; 452 453 if (currentItem == null) 454 currentItem = parentItem; 455 456 HashSet <QName> values = new HashSet <QName>(); 457 currentItem.requiredFirstSet(values); 458 459 String expected = null; 460 461 if (values.size() <= 5) { 462 expected = namesToString(values, parentName, qName, 463 currentItem.allowEmpty()); 464 } 465 466 return (getLineContext(getFileName(), getLine()) 467 + syntaxMessage(item, parentItem, parentName, qName, expected)); 468 } 469 470 473 private String attributeMessageDetail(Item item, Item parentItem, 474 String parentName, QName qName) 475 { 476 Item currentItem = item; 477 478 if (currentItem == null) 479 currentItem = parentItem; 480 481 String allowed = allowedAttributes(currentItem, qName); 482 483 return (getLineContext(getFileName(), getLine()) 484 + syntaxMessage(item, parentItem, parentName, qName, allowed)); 485 } 486 487 490 private String syntaxMessage(Item item, Item parentItem, 491 String parentName, QName qName, 492 String expected) 493 { 494 String syntaxPrefix; 495 496 if (parentName == null || parentName.equals("#top")) 497 syntaxPrefix = "Syntax: "; 498 else 499 syntaxPrefix = "<" + parentName + "> syntax: "; 500 501 String msg = ""; 502 503 Item topParent = null; 504 for (Item parent = item; 505 parent != null; 506 parent = null) { if (qName != null && parent.allowsElement(qName)) { 508 msg = "\n Check for duplicate and out-of-order tags."; 509 510 if (expected != null) 511 msg += expected + "\n"; 512 513 msg += "\n"; 514 515 String prefix = "Syntax: "; 516 if (parent == parentItem) 517 prefix = syntaxPrefix; 518 519 msg += prefix + parent.toSyntaxDescription(prefix.length()); 520 break; 521 } 522 523 } 525 526 if (topParent == null || topParent instanceof EmptyItem) { 527 topParent = parentItem; 528 529 if (qName != null && topParent.allowsElement(qName)) { 530 msg = "\n Check for duplicate and out-of-order tags."; 531 532 if (expected != null) 533 msg += expected + "\n"; 534 535 msg += "\n"; 536 537 String prefix = syntaxPrefix; 538 msg += prefix + topParent.toSyntaxDescription(prefix.length()); 539 } 540 } 541 542 if (msg.equals("")) { 543 msg = ""; 544 545 if (expected != null) 546 msg += expected + "\n"; 547 548 msg += "\n"; 549 550 String prefix = syntaxPrefix; 551 msg += prefix + topParent.toSyntaxDescription(prefix.length()); 552 } 553 554 return msg; 555 } 556 557 560 private String requiredValues(Item item, String parentName, QName qName) 561 { 562 if (item == null) 563 return ""; 564 565 HashSet <QName> values = new HashSet <QName>(); 566 item.requiredFirstSet(values); 567 568 return namesToString(values, parentName, qName, item.allowEmpty()); 569 } 570 571 private String namesToString(HashSet <QName> values, 572 String parentName, 573 QName qName, 574 boolean allowEmpty) 575 { 576 CharBuffer cb = new CharBuffer(); 577 if (values.size() > 0) { 578 ArrayList <QName> sortedValues = new ArrayList <QName>(values); 579 Collections.sort(sortedValues); 580 581 for (int i = 0; i < sortedValues.size(); i++) { 582 QName name = sortedValues.get(i); 583 584 if (i == 0) 585 cb.append("\n\n"); 586 else if (i == sortedValues.size() - 1) 587 cb.append(" or\n"); 588 else 589 cb.append(",\n"); 590 591 if (name.getName().equals("#text")) { 592 cb.append("text"); 593 } 594 else if (name.getNamespaceURI() == null || qName == null) 595 cb.append("<" + name.getName() + ">"); 596 else if (qName.getNamespaceURI() != name.getNamespaceURI()) { 597 if (name.getPrefix() != null) 598 cb.append("<" + name.getName() + " xmlns:" + name.getPrefix() + "=\"" + name.getNamespaceURI() + "\">"); 599 else 600 cb.append("<" + name.getName() + " xmlns=\"" + name.getNamespaceURI() + "\">"); 601 } 602 else 603 cb.append("<" + name.getName() + ">"); 604 } 605 606 if (values.size() == 1) 607 cb.append(" is expected"); 608 else 609 cb.append(" are expected"); 610 611 if (allowEmpty) { 612 if (parentName == null || parentName.equals("#top")) 613 cb.append(",\nor the document may end."); 614 else 615 cb.append(",\nor </" + parentName + "> may close."); 616 } 617 else 618 cb.append("."); 619 } 620 else if (allowEmpty) { 621 if (parentName == null || parentName.equals("#top")) 622 cb.append("\n\nThe document is expected to end."); 623 else 624 cb.append("\n\n</" + parentName + "> is expected to close."); 625 } 626 627 return cb.toString(); 628 } 629 630 633 private String allowedAttributes(Item item, QName qName) 634 { 635 if (item == null) 636 return ""; 637 638 HashSet <QName> values = new HashSet <QName>(); 639 item.attributeSet(values); 640 641 CharBuffer cb = new CharBuffer(); 642 if (values.size() > 0) { 643 ArrayList <QName> sortedValues = new ArrayList <QName>(values); 644 Collections.sort(sortedValues); 645 646 for (int i = 0; i < sortedValues.size(); i++) { 647 QName name = sortedValues.get(i); 648 649 if (i == 0) 650 cb.append("\n\n"); 651 else if (i == sortedValues.size() - 1) 652 cb.append(" or "); 653 else 654 cb.append(", "); 655 656 String uri = name.getNamespaceURI(); 657 if (uri == null || uri.equals("")) 658 cb.append("'" + name.getName() + "'"); 659 else if (qName == null || qName.getName().equals(name.getName())) 660 cb.append("'" + name.getCanonicalName() + "'"); 661 else 662 cb.append("'" + name.getName() + "'"); 663 } 664 665 if (values.size() == 1) 666 cb.append(" is expected."); 667 else 668 cb.append(" are expected."); 669 } 670 671 return cb.toString(); 672 } 673 674 677 private String getLocation() 678 { 679 if (_locator == null) 680 return ""; 681 else 682 return "" + _locator.getLineNumber(); 683 } 684 685 699 public boolean isValid() throws IllegalStateException 700 { 701 return _isValid; 702 } 703 704 private String getLineContext(String filename, int errorLine) 705 { 706 if (filename == null || errorLine <= 0) 707 return ""; 708 709 ReadStream is = null; 710 try { 711 Path path = Vfs.lookup().lookup(filename); 712 713 StringBuilder sb = new StringBuilder ("\n\n"); 714 715 is = path.openRead(); 716 int line = 0; 717 String text; 718 while ((text = is.readLine()) != null) { 719 line++; 720 721 if (errorLine - 2 <= line && line <= errorLine + 2) { 722 sb.append(line); 723 sb.append(": "); 724 sb.append(text); 725 sb.append("\n"); 726 } 727 } 728 729 return sb.toString(); 730 } catch (IOException e) { 731 log.log(Level.FINEST, e.toString(), e); 732 733 return ""; 734 } finally { 735 if (is != null) 736 is.close(); 737 } 738 } 739 740 static class StartKey { 741 private Item _item; 742 private QName _name; 743 744 public StartKey(Item item, QName name) 745 { 746 _item = item; 747 _name = name; 748 } 749 750 public StartKey() 751 { 752 } 753 754 public void init(Item item, QName name) 755 { 756 _item = item; 757 _name = name; 758 } 759 760 public int hashCode() 761 { 762 return _name.hashCode() + 137 * System.identityHashCode(_item); 763 } 764 765 public boolean equals(Object o) 766 { 767 if (o == this) 768 return true; 769 770 if (o.getClass() != StartKey.class) 771 return false; 772 773 StartKey key = (StartKey) o; 774 775 return _name.equals(key._name) && _item == key._item; 776 } 777 } 778 779 static class EndElementKey { 780 private Item _item; 781 782 public EndElementKey(Item item) 783 { 784 _item = item; 785 } 786 787 public EndElementKey() 788 { 789 } 790 791 public void init(Item item) 792 { 793 _item = item; 794 } 795 796 public int hashCode() 797 { 798 return 137 + _item.hashCode(); 799 } 800 801 public boolean equals(Object o) 802 { 803 if (o == this) 804 return true; 805 806 if (o.getClass() != EndElementKey.class) 807 return false; 808 809 EndElementKey key = (EndElementKey) o; 810 811 return _item.equals(key._item); 812 } 813 } 814 } 815 | Popular Tags |