1 /*
   2  * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
   3  */
   4 /*
   5  * Licensed to the Apache Software Foundation (ASF) under one or more
   6  * contributor license agreements.  See the NOTICE file distributed with
   7  * this work for additional information regarding copyright ownership.
   8  * The ASF licenses this file to You under the Apache License, Version 2.0
   9  * (the "License"); you may not use this file except in compliance with
  10  * the License.  You may obtain a copy of the License at
  11  *
  12  *      http://www.apache.org/licenses/LICENSE-2.0
  13  *
  14  * Unless required by applicable law or agreed to in writing, software
  15  * distributed under the License is distributed on an "AS IS" BASIS,
  16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17  * See the License for the specific language governing permissions and
  18  * limitations under the License.
  19  */
  20 
  21 package com.sun.org.apache.xerces.internal.jaxp.validation;
  22 
  23 import com.sun.org.apache.xerces.internal.impl.Constants;
  24 import com.sun.org.apache.xerces.internal.impl.XMLErrorReporter;
  25 import com.sun.org.apache.xerces.internal.impl.validation.EntityState;
  26 import com.sun.org.apache.xerces.internal.impl.validation.ValidationManager;
  27 import com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator;
  28 import com.sun.org.apache.xerces.internal.impl.xs.util.SimpleLocator;
  29 import com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl;
  30 import com.sun.org.apache.xerces.internal.util.NamespaceSupport;
  31 import com.sun.org.apache.xerces.internal.util.SymbolTable;
  32 import com.sun.org.apache.xerces.internal.util.XMLAttributesImpl;
  33 import com.sun.org.apache.xerces.internal.util.XMLSymbols;
  34 import com.sun.org.apache.xerces.internal.xni.NamespaceContext;
  35 import com.sun.org.apache.xerces.internal.xni.QName;
  36 import com.sun.org.apache.xerces.internal.xni.XMLString;
  37 import com.sun.org.apache.xerces.internal.xni.XNIException;
  38 import com.sun.org.apache.xerces.internal.xni.parser.XMLParseException;
  39 import java.io.IOException;
  40 import java.util.Enumeration;
  41 import javax.xml.parsers.DocumentBuilder;
  42 import javax.xml.parsers.DocumentBuilderFactory;
  43 import javax.xml.parsers.ParserConfigurationException;
  44 import javax.xml.transform.Result;
  45 import javax.xml.transform.Source;
  46 import javax.xml.transform.dom.DOMResult;
  47 import javax.xml.transform.dom.DOMSource;
  48 import org.w3c.dom.Attr;
  49 import org.w3c.dom.CDATASection;
  50 import org.w3c.dom.Comment;
  51 import org.w3c.dom.Document;
  52 import org.w3c.dom.DocumentType;
  53 import org.w3c.dom.Entity;
  54 import org.w3c.dom.NamedNodeMap;
  55 import org.w3c.dom.Node;
  56 import org.w3c.dom.ProcessingInstruction;
  57 import org.w3c.dom.Text;
  58 import org.xml.sax.SAXException;
  59 
  60 /**
  61  * <p>A validator helper for <code>DOMSource</code>s.</p>
  62  *
  63  * @author Michael Glavassevich, IBM
  64  * @LastModified: Oct 2017
  65  */
  66 final class DOMValidatorHelper implements ValidatorHelper, EntityState {
  67 
  68     //
  69     // Constants
  70     //
  71 
  72     /** Chunk size (1024). */
  73     private static final int CHUNK_SIZE = (1 << 10);
  74 
  75     /** Chunk mask (CHUNK_SIZE - 1). */
  76     private static final int CHUNK_MASK = CHUNK_SIZE - 1;
  77 
  78     // property identifiers
  79 
  80     /** Property identifier: error reporter. */
  81     private static final String ERROR_REPORTER =
  82         Constants.XERCES_PROPERTY_PREFIX + Constants.ERROR_REPORTER_PROPERTY;
  83 
  84     /** Property identifier: namespace context. */
  85     private static final String NAMESPACE_CONTEXT =
  86         Constants.XERCES_PROPERTY_PREFIX + Constants.NAMESPACE_CONTEXT_PROPERTY;
  87 
  88     /** Property identifier: XML Schema validator. */
  89     private static final String SCHEMA_VALIDATOR =
  90         Constants.XERCES_PROPERTY_PREFIX + Constants.SCHEMA_VALIDATOR_PROPERTY;
  91 
  92     /** Property identifier: symbol table. */
  93     private static final String SYMBOL_TABLE =
  94         Constants.XERCES_PROPERTY_PREFIX + Constants.SYMBOL_TABLE_PROPERTY;
  95 
  96     /** Property identifier: validation manager. */
  97     private static final String VALIDATION_MANAGER =
  98         Constants.XERCES_PROPERTY_PREFIX + Constants.VALIDATION_MANAGER_PROPERTY;
  99 
 100     //
 101     // Data
 102     //
 103 
 104     /** Error reporter. */
 105     private XMLErrorReporter fErrorReporter;
 106 
 107     /** The namespace context of this document: stores namespaces in scope. **/
 108     private NamespaceSupport fNamespaceContext;
 109 
 110     /** The namespace context of the DOMSource, includes context from ancestor nodes. **/
 111     private DOMNamespaceContext fDOMNamespaceContext = new DOMNamespaceContext();
 112 
 113     /** Schema validator. **/
 114     private XMLSchemaValidator fSchemaValidator;
 115 
 116     /** Symbol table **/
 117     private SymbolTable fSymbolTable;
 118 
 119     /** Validation manager. **/
 120     private ValidationManager fValidationManager;
 121 
 122     /** Component manager. **/
 123     private XMLSchemaValidatorComponentManager fComponentManager;
 124 
 125     /** Simple Locator. **/
 126     private final SimpleLocator fXMLLocator = new SimpleLocator(null, null, -1, -1, -1);
 127 
 128     /** DOM document handler. **/
 129     private DOMDocumentHandler fDOMValidatorHandler;
 130 
 131     /** DOM result augmentor. **/
 132     private final DOMResultAugmentor fDOMResultAugmentor = new DOMResultAugmentor(this);
 133 
 134     /** DOM result builder. **/
 135     private final DOMResultBuilder fDOMResultBuilder = new DOMResultBuilder();
 136 
 137     /** Map for tracking unparsed entities. **/
 138     private NamedNodeMap fEntities = null;
 139 
 140     /** Array for holding character data. **/
 141     private char [] fCharBuffer = new char[CHUNK_SIZE];
 142 
 143     /** Root node. **/
 144     private Node fRoot;
 145 
 146     /** Current element. **/
 147     private Node fCurrentElement;
 148 
 149     /** Fields for start element, end element and characters. **/
 150     final QName fElementQName = new QName();
 151     final QName fAttributeQName = new QName();
 152     final XMLAttributesImpl fAttributes = new XMLAttributesImpl();
 153     final XMLString fTempString = new XMLString();
 154 
 155     public DOMValidatorHelper(XMLSchemaValidatorComponentManager componentManager) {
 156         fComponentManager = componentManager;
 157         fErrorReporter = (XMLErrorReporter) fComponentManager.getProperty(ERROR_REPORTER);
 158         fNamespaceContext = (NamespaceSupport) fComponentManager.getProperty(NAMESPACE_CONTEXT);
 159         fSchemaValidator = (XMLSchemaValidator) fComponentManager.getProperty(SCHEMA_VALIDATOR);
 160         fSymbolTable = (SymbolTable) fComponentManager.getProperty(SYMBOL_TABLE);
 161         fValidationManager = (ValidationManager) fComponentManager.getProperty(VALIDATION_MANAGER);
 162     }
 163 
 164     /*
 165      * ValidatorHelper methods
 166      */
 167 
 168     public void validate(Source source, Result result)
 169         throws SAXException, IOException {
 170         if (result instanceof DOMResult || result == null) {
 171             final DOMSource domSource = (DOMSource) source;
 172             final DOMResult domResult = (DOMResult) result;
 173             Node node = domSource.getNode();
 174             fRoot = node;
 175             if (node != null) {
 176                 fComponentManager.reset();
 177                 fValidationManager.setEntityState(this);
 178                 fDOMNamespaceContext.reset();
 179                 String systemId = domSource.getSystemId();
 180                 fXMLLocator.setLiteralSystemId(systemId);
 181                 fXMLLocator.setExpandedSystemId(systemId);
 182                 fErrorReporter.setDocumentLocator(fXMLLocator);
 183                 try {
 184                     // regardless of what type of node this is, fire start and end document events
 185                     setupEntityMap((node.getNodeType() == Node.DOCUMENT_NODE) ? (Document) node : node.getOwnerDocument());
 186                     setupDOMResultHandler(domSource, domResult);
 187                     fSchemaValidator.startDocument(fXMLLocator, null, fDOMNamespaceContext, null);
 188                     validate(node);
 189                     fSchemaValidator.endDocument(null);
 190                 }
 191                 catch (XMLParseException e) {
 192                     throw Util.toSAXParseException(e);
 193                 }
 194                 catch (XNIException e) {
 195                     throw Util.toSAXException(e);
 196                 }
 197                 finally {
 198                     // Release references to application objects
 199                     fRoot = null;
 200                     //fCurrentElement = null; -- keep the reference to support current-element-node property
 201                     fEntities = null;
 202                     if (fDOMValidatorHandler != null) {
 203                         fDOMValidatorHandler.setDOMResult(null);
 204                     }
 205                 }
 206             }
 207             return;
 208         }
 209         throw new IllegalArgumentException(JAXPValidationMessageFormatter.formatMessage(fComponentManager.getLocale(),
 210                 "SourceResultMismatch",
 211                 new Object [] {source.getClass().getName(), result.getClass().getName()}));
 212     }
 213 
 214     /*
 215      * EntityState methods
 216      */
 217 
 218     public boolean isEntityDeclared(String name) {
 219         return false;
 220     }
 221 
 222     public boolean isEntityUnparsed(String name) {
 223         if (fEntities != null) {
 224             Entity entity = (Entity) fEntities.getNamedItem(name);
 225             if (entity != null) {
 226                 return (entity.getNotationName() != null);
 227             }
 228         }
 229         return false;
 230     }
 231 
 232     /*
 233      * Other methods
 234      */
 235 
 236     /** Traverse the DOM and fire events to the schema validator. */
 237     private void validate(Node node) {
 238         final Node top = node;
 239         // Performs a non-recursive traversal of the DOM. This
 240         // will avoid a stack overflow for DOMs with high depth.
 241         while (node != null) {
 242             beginNode(node);
 243             Node next = node.getFirstChild();
 244             while (next == null) {
 245                 finishNode(node);
 246                 if (top == node) {
 247                     break;
 248                 }
 249                 next = node.getNextSibling();
 250                 if (next == null) {
 251                     node = node.getParentNode();
 252                     if (node == null || top == node) {
 253                         if (node != null) {
 254                             finishNode(node);
 255                         }
 256                         next = null;
 257                         break;
 258                     }
 259                 }
 260             }
 261             node = next;
 262         }
 263     }
 264 
 265     /** Do processing for the start of a node. */
 266     private void beginNode(Node node) {
 267         switch (node.getNodeType()) {
 268             case Node.ELEMENT_NODE:
 269                 fCurrentElement = node;
 270                 // push namespace context
 271                 fNamespaceContext.pushContext();
 272                 // start element
 273                 fillQName(fElementQName, node);
 274                 processAttributes(node.getAttributes());
 275                 fSchemaValidator.startElement(fElementQName, fAttributes, null);
 276                 break;
 277             case Node.TEXT_NODE:
 278                 if (fDOMValidatorHandler != null) {
 279                     fDOMValidatorHandler.setIgnoringCharacters(true);
 280                     sendCharactersToValidator(node.getNodeValue());
 281                     fDOMValidatorHandler.setIgnoringCharacters(false);
 282                     fDOMValidatorHandler.characters((Text) node);
 283                 }
 284                 else {
 285                     sendCharactersToValidator(node.getNodeValue());
 286                 }
 287                 break;
 288             case Node.CDATA_SECTION_NODE:
 289                 if (fDOMValidatorHandler != null) {
 290                     fDOMValidatorHandler.setIgnoringCharacters(true);
 291                     fSchemaValidator.startCDATA(null);
 292                     sendCharactersToValidator(node.getNodeValue());
 293                     fSchemaValidator.endCDATA(null);
 294                     fDOMValidatorHandler.setIgnoringCharacters(false);
 295                     fDOMValidatorHandler.cdata((CDATASection) node);
 296                 }
 297                 else {
 298                     fSchemaValidator.startCDATA(null);
 299                     sendCharactersToValidator(node.getNodeValue());
 300                     fSchemaValidator.endCDATA(null);
 301                 }
 302                 break;
 303             case Node.PROCESSING_INSTRUCTION_NODE:
 304                 /**
 305                  * The validator does nothing with processing instructions so bypass it.
 306                  * Send the ProcessingInstruction node directly to the result builder.
 307                  */
 308                 if (fDOMValidatorHandler != null) {
 309                     fDOMValidatorHandler.processingInstruction((ProcessingInstruction) node);
 310                 }
 311                 break;
 312             case Node.COMMENT_NODE:
 313                 /**
 314                  * The validator does nothing with comments so bypass it.
 315                  * Send the Comment node directly to the result builder.
 316                  */
 317                 if (fDOMValidatorHandler != null) {
 318                     fDOMValidatorHandler.comment((Comment) node);
 319                 }
 320                 break;
 321             case Node.DOCUMENT_TYPE_NODE:
 322                 /**
 323                  * Send the DocumentType node directly to the result builder.
 324                  */
 325                 if (fDOMValidatorHandler != null) {
 326                     fDOMValidatorHandler.doctypeDecl((DocumentType) node);
 327                 }
 328                 break;
 329             default: // Ignore other node types.
 330                 break;
 331         }
 332     }
 333 
 334     /** Do processing for the end of a node. */
 335     private void finishNode(Node node) {
 336         if (node.getNodeType() == Node.ELEMENT_NODE) {
 337             fCurrentElement = node;
 338             // end element
 339             fillQName(fElementQName, node);
 340             fSchemaValidator.endElement(fElementQName, null);
 341             // pop namespace context
 342             fNamespaceContext.popContext();
 343         }
 344     }
 345 
 346     /**
 347      * Extracts NamedNodeMap of entities. We need this to validate
 348      * elements and attributes of type xs:ENTITY, xs:ENTITIES or
 349      * types dervied from them.
 350      */
 351     private void setupEntityMap(Document doc) {
 352         if (doc != null) {
 353             DocumentType docType = doc.getDoctype();
 354             if (docType != null) {
 355                 fEntities = docType.getEntities();
 356                 return;
 357             }
 358         }
 359         fEntities = null;
 360     }
 361 
 362     /**
 363      * Sets up handler for <code>DOMResult</code>.
 364      */
 365     private void setupDOMResultHandler(DOMSource source, DOMResult result) throws SAXException {
 366         // If there's no DOMResult, unset the validator handler
 367         if (result == null) {
 368             fDOMValidatorHandler = null;
 369             fSchemaValidator.setDocumentHandler(null);
 370             return;
 371         }
 372         final Node nodeResult = result.getNode();
 373         // If the source node and result node are the same use the DOMResultAugmentor.
 374         // Otherwise use the DOMResultBuilder.
 375         if (source.getNode() == nodeResult) {
 376             fDOMValidatorHandler = fDOMResultAugmentor;
 377             fDOMResultAugmentor.setDOMResult(result);
 378             fSchemaValidator.setDocumentHandler(fDOMResultAugmentor);
 379             return;
 380         }
 381         if (result.getNode() == null) {
 382             try {
 383                 DocumentBuilderFactory factory = fComponentManager.getFeature(Constants.ORACLE_FEATURE_SERVICE_MECHANISM) ?
 384                                     DocumentBuilderFactory.newInstance() : new DocumentBuilderFactoryImpl();
 385                 factory.setNamespaceAware(true);
 386                 DocumentBuilder builder = factory.newDocumentBuilder();
 387                 result.setNode(builder.newDocument());
 388             }
 389             catch (ParserConfigurationException e) {
 390                 throw new SAXException(e);
 391             }
 392         }
 393         fDOMValidatorHandler = fDOMResultBuilder;
 394         fDOMResultBuilder.setDOMResult(result);
 395         fSchemaValidator.setDocumentHandler(fDOMResultBuilder);
 396     }
 397 
 398     private void fillQName(QName toFill, Node node) {
 399         final String prefix = node.getPrefix();
 400         final String localName = node.getLocalName();
 401         final String rawName = node.getNodeName();
 402         final String namespace = node.getNamespaceURI();
 403 
 404         toFill.uri = (namespace != null && namespace.length() > 0) ? fSymbolTable.addSymbol(namespace) : null;
 405         toFill.rawname = (rawName != null) ? fSymbolTable.addSymbol(rawName) : XMLSymbols.EMPTY_STRING;
 406 
 407         // Is this a DOM level1 document?
 408         if (localName == null) {
 409             int k = rawName.indexOf(':');
 410             if (k > 0) {
 411                 toFill.prefix = fSymbolTable.addSymbol(rawName.substring(0, k));
 412                 toFill.localpart = fSymbolTable.addSymbol(rawName.substring(k + 1));
 413             }
 414             else {
 415                 toFill.prefix = XMLSymbols.EMPTY_STRING;
 416                 toFill.localpart = toFill.rawname;
 417             }
 418         }
 419         else {
 420             toFill.prefix = (prefix != null) ? fSymbolTable.addSymbol(prefix) : XMLSymbols.EMPTY_STRING;
 421             toFill.localpart = (localName != null) ? fSymbolTable.addSymbol(localName) : XMLSymbols.EMPTY_STRING;
 422         }
 423     }
 424 
 425     private void processAttributes(NamedNodeMap attrMap) {
 426         final int attrCount = attrMap.getLength();
 427         fAttributes.removeAllAttributes();
 428         for (int i = 0; i < attrCount; ++i) {
 429             Attr attr = (Attr) attrMap.item(i);
 430             String value = attr.getValue();
 431             if (value == null) {
 432                 value = XMLSymbols.EMPTY_STRING;
 433             }
 434             fillQName(fAttributeQName, attr);
 435             // REVISIT: Assuming all attributes are of type CDATA. The actual type may not matter. -- mrglavas
 436             fAttributes.addAttributeNS(fAttributeQName, XMLSymbols.fCDATASymbol, value);
 437             fAttributes.setSpecified(i, attr.getSpecified());
 438             // REVISIT: Should we be looking at non-namespace attributes
 439             // for additional mappings? Should we detect illegal namespace
 440             // declarations and exclude them from the context? -- mrglavas
 441             if (fAttributeQName.uri == NamespaceContext.XMLNS_URI) {
 442                 // process namespace attribute
 443                 if (fAttributeQName.prefix == XMLSymbols.PREFIX_XMLNS) {
 444                     fNamespaceContext.declarePrefix(fAttributeQName.localpart, value.length() != 0 ? fSymbolTable.addSymbol(value) : null);
 445                 }
 446                 else {
 447                     fNamespaceContext.declarePrefix(XMLSymbols.EMPTY_STRING, value.length() != 0 ? fSymbolTable.addSymbol(value) : null);
 448                 }
 449             }
 450         }
 451     }
 452 
 453     private void sendCharactersToValidator(String str) {
 454         if (str != null) {
 455             final int length = str.length();
 456             final int remainder = length & CHUNK_MASK;
 457             if (remainder > 0) {
 458                 str.getChars(0, remainder, fCharBuffer, 0);
 459                 fTempString.setValues(fCharBuffer, 0, remainder);
 460                 fSchemaValidator.characters(fTempString, null);
 461             }
 462             int i = remainder;
 463             while (i < length) {
 464                 str.getChars(i, i += CHUNK_SIZE, fCharBuffer, 0);
 465                 fTempString.setValues(fCharBuffer, 0, CHUNK_SIZE);
 466                 fSchemaValidator.characters(fTempString, null);
 467             }
 468         }
 469     }
 470 
 471     Node getCurrentElement() {
 472         return fCurrentElement;
 473     }
 474 
 475     /**
 476      * NamespaceContext for the DOMSource, includes context for ancestor nodes.
 477      */
 478     final class DOMNamespaceContext implements NamespaceContext {
 479 
 480         //
 481         // Data
 482         //
 483 
 484         /**
 485          * Namespace binding information. This array is composed of a
 486          * series of tuples containing the namespace binding information:
 487          * &lt;prefix, uri&gt;.
 488          */
 489         protected String[] fNamespace = new String[16 * 2];
 490 
 491         /** The size of the namespace information array. */
 492         protected int fNamespaceSize = 0;
 493 
 494         /**
 495          * Flag indicating whether the namespace context
 496          * has been from the root node's ancestors.
 497          */
 498         protected boolean fDOMContextBuilt = false;
 499 
 500         //
 501         // Methods
 502         //
 503 
 504         public void pushContext() {
 505             fNamespaceContext.pushContext();
 506         }
 507 
 508         public void popContext() {
 509             fNamespaceContext.popContext();
 510         }
 511 
 512         public boolean declarePrefix(String prefix, String uri) {
 513             return fNamespaceContext.declarePrefix(prefix, uri);
 514         }
 515 
 516         public String getURI(String prefix) {
 517             String uri = fNamespaceContext.getURI(prefix);
 518             if (uri == null) {
 519                 if (!fDOMContextBuilt) {
 520                     fillNamespaceContext();
 521                     fDOMContextBuilt = true;
 522                 }
 523                 if (fNamespaceSize > 0 &&
 524                     !fNamespaceContext.containsPrefix(prefix)) {
 525                     uri = getURI0(prefix);
 526                 }
 527             }
 528             return uri;
 529         }
 530 
 531         public String getPrefix(String uri) {
 532             return fNamespaceContext.getPrefix(uri);
 533         }
 534 
 535         public int getDeclaredPrefixCount() {
 536             return fNamespaceContext.getDeclaredPrefixCount();
 537         }
 538 
 539         public String getDeclaredPrefixAt(int index) {
 540             return fNamespaceContext.getDeclaredPrefixAt(index);
 541         }
 542 
 543         public Enumeration<String> getAllPrefixes() {
 544             return fNamespaceContext.getAllPrefixes();
 545         }
 546 
 547         public void reset() {
 548             fDOMContextBuilt = false;
 549             fNamespaceSize = 0;
 550         }
 551 
 552         private void fillNamespaceContext() {
 553             if (fRoot != null) {
 554                 Node currentNode = fRoot.getParentNode();
 555                 while (currentNode != null) {
 556                     if (Node.ELEMENT_NODE == currentNode.getNodeType()) {
 557                         NamedNodeMap attributes = currentNode.getAttributes();
 558                         final int attrCount = attributes.getLength();
 559                         for (int i = 0; i < attrCount; ++i) {
 560                             Attr attr = (Attr) attributes.item(i);
 561                             String value = attr.getValue();
 562                             if (value == null) {
 563                                 value = XMLSymbols.EMPTY_STRING;
 564                             }
 565                             fillQName(fAttributeQName, attr);
 566                             // REVISIT: Should we be looking at non-namespace attributes
 567                             // for additional mappings? Should we detect illegal namespace
 568                             // declarations and exclude them from the context? -- mrglavas
 569                             if (fAttributeQName.uri == NamespaceContext.XMLNS_URI) {
 570                                 // process namespace attribute
 571                                 if (fAttributeQName.prefix == XMLSymbols.PREFIX_XMLNS) {
 572                                     declarePrefix0(fAttributeQName.localpart, value.length() != 0 ? fSymbolTable.addSymbol(value) : null);
 573                                 }
 574                                 else {
 575                                     declarePrefix0(XMLSymbols.EMPTY_STRING, value.length() != 0 ? fSymbolTable.addSymbol(value) : null);
 576                                 }
 577                             }
 578                         }
 579 
 580                     }
 581                     currentNode = currentNode.getParentNode();
 582                 }
 583             }
 584         }
 585 
 586         private void declarePrefix0(String prefix, String uri) {
 587             // resize array, if needed
 588             if (fNamespaceSize == fNamespace.length) {
 589                 String[] namespacearray = new String[fNamespaceSize * 2];
 590                 System.arraycopy(fNamespace, 0, namespacearray, 0, fNamespaceSize);
 591                 fNamespace = namespacearray;
 592             }
 593 
 594             // bind prefix to uri in current context
 595             fNamespace[fNamespaceSize++] = prefix;
 596             fNamespace[fNamespaceSize++] = uri;
 597         }
 598 
 599         private String getURI0(String prefix) {
 600             // find prefix in the DOM context
 601             for (int i = 0; i < fNamespaceSize; i += 2) {
 602                 if (fNamespace[i] == prefix) {
 603                     return fNamespace[i + 1];
 604                 }
 605             }
 606             // prefix not found
 607             return null;
 608         }
 609     }
 610 
 611 } // DOMValidatorHelper