/* * @(#)$Id: ExpressionAcceptor.java,v 1.44 2002/10/18 02:50:05 kk122374 Exp $ * * Copyright 2001 Sun Microsystems, Inc. All Rights Reserved. * * This software is the proprietary information of Sun Microsystems, Inc. * Use is subject to license terms. * */ package com.sun.msv.verifier.regexp; import com.sun.msv.grammar.*; import com.sun.msv.grammar.IDContextProvider; import com.sun.msv.verifier.*; import com.sun.msv.util.StartTagInfo; import com.sun.msv.util.StringRef; import com.sun.msv.util.DatatypeRef; import org.relaxng.datatype.Datatype; import org.relaxng.datatype.DatatypeException; import java.util.*; /** * {@link Acceptor} implementation. * * <p> * When you are using <code>REDocumentDeclaration</code>, then the acceptor * is always guaranteed to be a subclass of this class. * * Therefore, by using this regexp implementation of VGM, you can always downcast * {@link Acceptor} to this class and access its contents to get more information. * * <p> * If you consider VGM as an automaton, * this class can be thought as a lazy automaton acceptor. * * * @author <a href="mailto:kohsuke.kawaguchi@eng.sun.com">Kohsuke KAWAGUCHI</a> */ public abstract class ExpressionAcceptor implements Acceptor { private Expression expression; /** * gets the residual content model. * * <p> * This method returns the expression that represents the expected content model * it will read. * For example, if the original content model is (A,(B|C)) and this acceptor * has already read A, then this method returns (B|C). * * <p> * The returned residual is useful to find out what elements can appear next. * * <p> * If you consider VGM as an automaton, the residual content model * can be thought as the current state. Also, * At the same time, right language (a regular expression that represents * the language it can accept from now on). */ public Expression getExpression() { return expression; } /** this object provides various function objects */ protected final REDocumentDeclaration docDecl; /** * If true, this acceptor will ignore all undeclared attributes. * If false, this acceptor will signal an error for an undeclared attribute. * * <p> * This flag is used to implement the semantics of RELAX Core, where * undeclared attributes are allowed. */ protected final boolean ignoreUndeclaredAttributes; public ExpressionAcceptor( REDocumentDeclaration docDecl, Expression exp, boolean ignoreUndeclaredAttributes ) { this.docDecl = docDecl; this.expression = exp; this.ignoreUndeclaredAttributes = ignoreUndeclaredAttributes; } /** * creates combined child acceptor and primitive child acceptors (if necessary). * * be careful not to keep returned object too long because * it is reused whenever the method is called. * * @return null * if errRef is null and this expression cannot accept given start tag. * if errRef is non-null and error recovery is not possible. */ public Acceptor createChildAcceptor( StartTagInfo tag, StringRef errRef ) { final CombinedChildContentExpCreator cccc = docDecl.cccec; // obtains fully combined child content pattern CombinedChildContentExpCreator.ExpressionPair e = cccc.get(expression,tag); if( e.content!=Expression.nullSet ) { // successful. if( com.sun.msv.driver.textui.Debug.debug ) { System.out.println("accept start tag <"+ tag.qName+">. combined content pattern is"); System.out.println(com.sun.msv.grammar.util.ExpressionPrinter.printContentModel(e.content)); if( e.continuation!=null ) System.out.println("continuation is:\n"+ com.sun.msv.grammar.util.ExpressionPrinter.printContentModel(e.continuation) ); else System.out.println("no continuation"); } return createAcceptor( e.content, e.continuation, cccc.getMatchedElements(), cccc.numMatchedElements() ); } // no element declaration is satisfied by this start tag. // this must be an error of input document. if( errRef==null ) // bail out now to notify the caller that an error was found. return null; // no ElementExp accepts this tag name // (actually, some ElementExp may have possibly accepted this tag name, // but as a result of <concur>, no expression left ). errRef.str = diagnoseBadTagName(tag); if( errRef.str==null ) // no detailed error message was prepared. // use some generic one. errRef.str = docDecl.localizeMessage( docDecl.DIAG_BAD_TAGNAME_GENERIC, tag.qName ); // prepare child acceptor. return createRecoveryAcceptors(); } protected abstract Acceptor createAcceptor( Expression contentModel, Expression continuation/*can be null*/, ElementExp[] primitives, int numPrimitives ); public final boolean onAttribute( String namespaceURI, String localName, String qName, String value, IDContextProvider context, StringRef refErr, DatatypeRef refType ) { // instead of creating a new object each time, // use a cached copy. docDecl.attToken.reinit( namespaceURI,localName,qName, new StringToken(docDecl,value,context,refType) ); return onAttribute( docDecl.attToken, refErr ); } protected boolean onAttribute( AttributeToken token, StringRef refErr ) { Expression r = docDecl.attFeeder.feed( this.expression, token, ignoreUndeclaredAttributes ); if( r!=Expression.nullSet ) { // this attribute is properly consumed. expression = r; if(com.sun.msv.driver.textui.Debug.debug) System.out.println("-- residual after :" + com.sun.msv.grammar.util.ExpressionPrinter.printContentModel(r)); return true; } if( refErr==null ) { // refErr was not provided. bail out now. return false; } // // diagnose the error // // this attribute was not accepted. // its value may be wrong. // try feeding wild card and see if it's accepted. AttributeRecoveryToken rtoken = token.createRecoveryAttToken(); r = docDecl.attFeeder.feed( this.expression, rtoken, ignoreUndeclaredAttributes ); if( r==Expression.nullSet ) { // even the wild card was rejected. // now there are two possibilities. // the first is that this attribute name is not allowed to appear, // which is the most typical case. (e.g., type miss of the attribute name, etc). // the second is that the content model of the element is equal to the // nullSet, thus nothing can be accepted. This is usually // a problem of the schema. if( this.expression==Expression.nullSet ) { // the content model is equal to the nullSet. refErr.str = docDecl.localizeMessage( docDecl.DIAG_CONTENT_MODEL_IS_NULLSET, null ); } else { // the content model is not equal to the nullSet. // this means that this attribute // is not specified by the grammar. refErr.str = docDecl.localizeMessage( docDecl.DIAG_UNDECLARED_ATTRIBUTE, token.qName ); } // recover by using the current expression. // TODO: possibly we can make all attributes optional or something. // (because this might be a caused by the typo.) return true; } else { // wild card was accepted, so the value must be wrong. refErr.str = diagnoseBadAttributeValue( rtoken ); if( refErr.str==null ) { // no detailed error message can be provided // so use generic one. refErr.str = docDecl.localizeMessage( docDecl.DIAG_BAD_ATTRIBUTE_VALUE_GENERIC, token.qName ); } // now we know the reason. // recover by assuming that the valid value was specified for this attribute. this.expression = r; return true; } } public boolean onEndAttributes( StartTagInfo sti, StringRef refErr ) { Expression r = docDecl.attPruner.prune( this.expression ); if( r!=Expression.nullSet ) { // there was no error. this.expression = r; return true; } // there was an error. // specifically, some required attributes are missing. if( refErr==null ) return false; // refErr was not provided. bail out. if( this.expression==Expression.nullSet ) { // the content model is equal to the nullSet. refErr.str = docDecl.localizeMessage( docDecl.DIAG_CONTENT_MODEL_IS_NULLSET, null ); } else { refErr.str = diagnoseMissingAttribute(sti); if( refErr.str==null ) // no detailed error message can be provided // so use generic one. refErr.str = docDecl.localizeMessage( docDecl.DIAG_MISSING_ATTRIBUTE_GENERIC, sti.qName ); } // remove unconsumed attributes this.expression = this.expression.visit( docDecl.attRemover ); return true; } protected boolean stepForward( Token token, StringRef errRef ) { Expression residual = docDecl.resCalc.calcResidual( expression, token ); // if token is ignorable, make expression as so. if( token.isIgnorable() ) { residual = docDecl.pool.createChoice( residual, expression ); } if( com.sun.msv.driver.textui.Debug.debug ) { System.out.println("residual of stepForward("+token+")"); System.out.print(com.sun.msv.grammar.util.ExpressionPrinter.printContentModel(expression)); System.out.print(" -> "); System.out.println(com.sun.msv.grammar.util.ExpressionPrinter.printContentModel(residual)); } if( residual==Expression.nullSet ) { // error: we can't accept this token if( errRef!=null ) { // diagnose error. if( token instanceof StringToken ) errRef.str = diagnoseUnexpectedLiteral( (StringToken)token ); // docDecl.localizeMessage( docDecl.DIAG_BAD_LITERAL_VALUE_WRAPUP, // TODO: diagnosis for ElementToken // recovery by ignoring this token. // TODO: should we modify this to choice(expression,EoCR)? // we need some measures to prevent redundant choice } else { // do not mutate any member variables. // caller may call stepForward again with error recovery. } return false; } expression = residual; return true; } public boolean onText( String literal, IDContextProvider provider, StringRef refErr, DatatypeRef refType ) { return stepForward( new StringToken(docDecl,literal,provider,refType), refErr ); } public final boolean stepForwardByContinuation( Expression continuation, StringRef errRef ) { if( continuation!=Expression.nullSet ) { // successful transition if( com.sun.msv.driver.textui.Debug.debug ) System.out.println("stepForwardByCont. : " + com.sun.msv.grammar.util.ExpressionPrinter.printContentModel(continuation)); expression = continuation; return true; } if( errRef==null ) return false; // fail immediately. // TODO: diagnose uncompleted content model. return false; } /** checks if this Acceptor is satisifed */ public boolean isAcceptState( StringRef errRef ) { if( errRef==null ) return expression.isEpsilonReducible(); else { if(expression.isEpsilonReducible()) return true; // error. provide diagnosis errRef.str = diagnoseUncompletedContent(); return false; } } public int getStringCareLevel() { // if the value is cached, return cached value. // otherwise, calculate it now. OptimizationTag ot = (OptimizationTag)expression.verifierTag; if(ot==null) expression.verifierTag = ot = new OptimizationTag(); if(ot.stringCareLevel==ot.STRING_NOTCOMPUTED) ot.stringCareLevel = StringCareLevelCalculator.calc(expression); return ot.stringCareLevel; } // error recovery //================================================== private final Expression mergeContinuation( Expression exp1, Expression exp2 ) { if(exp1==null && exp2==null) return null; if(exp1==null || exp1==Expression.nullSet) return exp2; if(exp2==null || exp2==Expression.nullSet) return exp1; return docDecl.pool.createChoice(exp1,exp2); } /** * creates Acceptor that recovers from errors. * * This method also modifies the current expression in preparation to * accept newly created child acceptor. * * Recovery will be done by preparing to accept two possibilities. * * <ol> * <li>We may get back to sync by ignoring the newly found illegal element. * ( this is for mistake like "abcXdefg") * <li>We may get back to sync by replacing newly found illegal element * by one of the valid elements. * ( this is for mistake like "abcXefg") * </ol> */ private final Acceptor createRecoveryAcceptors() { final CombinedChildContentExpCreator cccc = docDecl.cccec; CombinedChildContentExpCreator.ExpressionPair combinedEoC = cccc.get( expression, null, false ); // get residual of EoC. Expression eocr = docDecl.resCalc.calcResidual( expression, AnyElementToken.theInstance ); Expression continuation = docDecl.pool.createChoice( expression, eocr ); Expression contentModel = combinedEoC.content; if( com.sun.msv.driver.textui.Debug.debug ) { System.out.println("content model of recovery acceptor:"+ com.sun.msv.grammar.util.ExpressionPrinter.printContentModel(contentModel) ); System.out.println("continuation of recovery acceptor:"+ com.sun.msv.grammar.util.ExpressionPrinter.printSmallest(continuation) ); } // by passing null as elements of concern and // using continuation, we are effectively "generating" // the content model for error recovery. return createAcceptor( contentModel, continuation, null, 0 ); } /** * format list of candidates to one string. * * this method * (1) inserts separator into appropriate positions * (2) appends "more" message when items are only a portion of candidates. */ private final String concatenateMessages( List items, boolean more, String separatorStr, String moreStr ) { String r=""; String sep = docDecl.localizeMessage(separatorStr,null); Collections.sort(items, new Comparator(){ public int compare( Object o1, Object o2 ) { return ((String)o1).compareTo((String)o2); } }); // sort candidates. for( int i=0; i<items.size(); i++ ) { if(r.length()!=0) r+= sep; r += items.get(i); } if( more ) r += docDecl.localizeMessage(moreStr,null); return r; } private final String concatenateMessages( Set items, boolean more, String separatorStr, String moreStr ) { return concatenateMessages( new Vector(items), more, separatorStr, moreStr ); } /** * gets error diagnosis message from datatype. * * @return null * if diagnosis failed. */ private final String getDiagnosisFromTypedString( DataOrValueExp exp, StringToken value ) { try { exp.getType().checkValid( value.literal, value.context ); // TODO: diagnose errors if exp is ValueExp and // this is a not correct value // it should throw an exception. // but just in case the datatype library has a bug, // we recover from this situation return null; } catch( DatatypeException e ) { return e.getMessage(); } } /** * computes diagnosis message for bad tag name * * @param exp * combined child content expression without * tag name check and attributes check. * * @return null * if diagnosis fails. */ private final String diagnoseBadTagName( StartTagInfo sti ) { final CombinedChildContentExpCreator cccc = docDecl.cccec; // try creating combined child content pattern without tag name check. Expression r = cccc.get(expression,sti,false).content; if( r==Expression.nullSet ) // no element is allowed here at all. return docDecl.localizeMessage( docDecl.DIAG_ELEMENT_NOT_ALLOWED, sti.qName ); if( cccc.isComplex() ) { // probably <concur> is used. // there is no easy way to tell which what tag name is expected. // TODO: we can reduce strength by treating concur as choice. // do it. return null; } // we are now sure that combined child content expression will be // the choice of all elements of concern. // so if tag name satisfies one of those elements, // it can be accepted. // therefore we can provide candidates for users. Set s = new java.util.HashSet(); boolean more = false; // if there is a SimpleNameClass with the same localName // but with a different namespace URI, // this variable will receive that URI. String wrongNamespace = null; final ElementExp[] eocs = cccc.getMatchedElements(); final int len = cccc.numMatchedElements(); for( int i=0; i<len; i++ ) { if( eocs[i].contentModel.getExpandedExp(docDecl.pool)==Expression.nullSet ) // this element is not allowed to appear. continue; // test some typical name class patterns. final NameClass nc = eocs[i].getNameClass(); if( nc instanceof SimpleNameClass ) { SimpleNameClass snc = (SimpleNameClass)nc; if( snc.localName.equals(sti.localName) ) { // sometimes, people simply forget to add namespace decl, // or declare the wrong name. wrongNamespace = snc.namespaceURI; } s.add( docDecl.localizeMessage( docDecl.DIAG_SIMPLE_NAMECLASS, nc.toString() ) ); continue; } if( nc instanceof NamespaceNameClass ) { s.add( docDecl.localizeMessage( docDecl.DIAG_NAMESPACE_NAMECLASS, ((NamespaceNameClass)nc).namespaceURI ) ); continue; } if( nc instanceof NotNameClass ) { NameClass ncc = ((NotNameClass)nc).child; if( ncc instanceof NamespaceNameClass ) { s.add( docDecl.localizeMessage( docDecl.DIAG_NOT_NAMESPACE_NAMECLASS, ((NamespaceNameClass)ncc).namespaceURI ) ); continue; } } // this name class is very complex and // therefore we were unable to provide appropriate suggestion. more = true; } // no candidate was collected. bail out. if( s.size()==0 ) return null; if( wrongNamespace!=null ) { if( s.size()==1 ) // only one candidate. return docDecl.localizeMessage( docDecl.DIAG_BAD_TAGNAME_WRONG_NAMESPACE, sti.localName, wrongNamespace ); else // probably wrong namespace, // but show the user that he/she has other choices. return docDecl.localizeMessage( docDecl.DIAG_BAD_TAGNAME_PROBABLY_WRONG_NAMESPACE, sti.localName, wrongNamespace ); } // there is no clue about user's intention. return docDecl.localizeMessage( docDecl.DIAG_BAD_TAGNAME_WRAPUP, sti.qName, concatenateMessages( s, more, docDecl.DIAG_BAD_TAGNAME_SEPARATOR, docDecl.DIAG_BAD_TAGNAME_MORE ) ); } /** * computes diagnosis message for bad attribute value * * @param rtoken * wild card AttributeToken that was used. * @param attIndex * index in sti.attributes of the attribute * * @return null * if diagnosis fails. */ private final String diagnoseBadAttributeValue( AttributeRecoveryToken rtoken ) { // if the combined child content expression is not complex, // only binary expressions used are choice and sequence. // this is the choice of all constraints that made this // attribute fail. Expression constraint = rtoken.getFailedExp(); // The problem here is that sti.attributes.getValue(i) // didn't satisfy this expression. // test some typical expression patterns and // provide error messages if it matchs the pattern. // otherwise provide a generic error message. if( constraint instanceof DataOrValueExp ) { // if only one AttributeExp is specified for this attribute // and if it has a TypedString as its child. // for RELAX, this is the only possible case DataOrValueExp tse = (DataOrValueExp)constraint; if( tse.getType() == com.sun.msv.grammar.relax.NoneType.theInstance ) { // if the underlying datatype is "none", // this should be reported as unexpected attribute. return docDecl.localizeMessage( docDecl.DIAG_UNDECLARED_ATTRIBUTE, rtoken.qName ); } String dtMsg = getDiagnosisFromTypedString( tse, rtoken.value ); if(dtMsg==null) return null; return docDecl.localizeMessage( docDecl.DIAG_BAD_ATTRIBUTE_VALUE_DATATYPE, rtoken.qName, dtMsg ); } if( constraint instanceof ChoiceExp ) { // choice of <string>s. // // this is also a frequently used pattern by TREX. // an expression like // // <attribute name="export"> // <choice> // <string>yes</string><string>no</string> // </choice> // </attribute> // // falls into this pattern. final Set items = new java.util.HashSet(); boolean more = false; ChoiceExp ch = (ChoiceExp)constraint; Expression[] children = ch.getChildren(); for( int i=0; i<children.length; i++ ) { if( children[i] instanceof ValueExp ) items.add( ((ValueExp)children[i]).value.toString() ); else // this is a fairly complex expression // that we can't provide diagnosis. more = true; } // no candidates was simple. bail out. if( items.size()==0 ) return null; // at least we have one suggestion. return docDecl.localizeMessage( docDecl.DIAG_BAD_ATTRIBUTE_VALUE_WRAPUP, rtoken.qName, concatenateMessages( items, more, docDecl.DIAG_BAD_ATTRIBUTE_VALUE_SEPARATOR, docDecl.DIAG_BAD_ATTRIBUTE_VALUE_MORE ) ); } return null; // this constraint didn't fall into known patterns. } /** * computes diagnosis message for missing attribute * * @return null * if diagnosis fails. */ private final String diagnoseMissingAttribute( StartTagInfo sti ) { // if( cccc.isComplex() ) // // again if the expression is complex, // // hope is remote that we can find required attributes. // // // TODO: reduce strength by converting concur to choice? // return null; Expression e = expression.visit(docDecl.attPicker); if( e.isEpsilonReducible() ) throw new Error(); // assertion // if attribute expression is epsilon reducible, then // AttributePruner must return Expression other than nullSet. // In that case, there should have been no error. final Set s = new java.util.HashSet(); boolean more = false; while( e instanceof ChoiceExp ) { ChoiceExp ch = (ChoiceExp)e; NameClass nc = ((AttributeExp)ch.exp2).nameClass; if( nc instanceof SimpleNameClass ) s.add( nc.toString() ); else more = true; e = ch.exp1; } if( e==Expression.nullSet ) // we are in the full panic mode. // abandon diagnosis. return null; if(!(e instanceof AttributeExp )) throw new Error(e.toString()); //assertion NameClass nc = ((AttributeExp)e).nameClass; if( nc instanceof SimpleNameClass ) s.add( nc.toString() ); else more = true; if( s.size()==0 ) return null; // at least one candidate is found if( s.size()==1 && !more ) {// only one candidate return docDecl.localizeMessage( docDecl.DIAG_MISSING_ATTRIBUTE_SIMPLE, sti.qName,s.iterator().next() ); } else // list candidates return docDecl.localizeMessage( docDecl.DIAG_MISSING_ATTRIBUTE_WRAPUP, sti.qName, concatenateMessages( s, more, docDecl.DIAG_MISSING_ATTRIBUTE_SEPARATOR, docDecl.DIAG_MISSING_ATTRIBUTE_MORE ) ); } /** * diagnoses an error when a StringToken is rejected. */ private final String diagnoseUnexpectedLiteral( StringToken token ) { final StringRecoveryToken srt = new StringRecoveryToken(token); // this residual corresponds to the expression we get // when we replace thie unexpected token by one of expected tokens. Expression recoveryResidual = docDecl.resCalc.calcResidual(expression,srt); if( recoveryResidual==Expression.nullSet ) // we now know that no string literal was expected at all. return docDecl.localizeMessage( docDecl.DIAG_STRING_NOT_ALLOWED, null ); // keep this.expression untouched. This is equivalent to ignore this token. // there are two possible "recovery" for this error. // (1) ignore this token // (2) replace this token by a valid token. // the following choice implements both of them. expression = docDecl.pool.createChoice( expression, recoveryResidual ); if( srt.failedExps.size()==1 ) { DataOrValueExp texp = (DataOrValueExp)srt.failedExps.iterator().next(); try { // TODO: handle ValueExp nicely texp.getType().checkValid( srt.literal, srt.context ); if(texp instanceof ValueExp) { ValueExp vexp = (ValueExp)texp; if(!vexp.dt.sameValue(vexp.value, vexp.dt.createValue(srt.literal,srt.context))) { // incorrect value return docDecl.localizeMessage( docDecl.DIAG_BAD_LITERAL_INCORRECT_VALUE, vexp.value.toString() ); } } } catch( DatatypeException de ) { // this literal is invalid. if( de.getMessage()!=null ) return de.getMessage(); // return the diagnosis. // we don't know the exact reason, but the value was wrong. return docDecl.localizeMessage( docDecl.DIAG_BAD_LITERAL_GENERIC, null ); } } else { // there are multiple candidates. final Set items = new java.util.HashSet(); boolean more = false; Iterator itr = srt.failedExps.iterator(); while(itr.hasNext()) { DataOrValueExp texp = (DataOrValueExp)itr.next(); if( texp instanceof ValueExp ) // we can list this item as one of the candidates items.add( ((ValueExp)texp).value.toString() ); else // this must be some datatype // that we can't provide diagnosis. more = true; } // no candidates was simple. bail out. if( items.size()==0 ) return null; // at least we have one suggestion. return docDecl.localizeMessage( docDecl.DIAG_BAD_LITERAL_WRAPUP, concatenateMessages( items, more, docDecl.DIAG_BAD_LITERAL_SEPARATOR, docDecl.DIAG_BAD_LITERAL_MORE ) ); } // unable to diagnose the reason of error. return null; // TODO: ID/IDREF violation diagnosis. /* // now the literal is valid. // Is this key/keyref constraint violation? if( texp instanceof NGTypedStringExp ) { NGTypedStringExp ntexp = (NGTypedStringExp)texp; if( ntexp.keyName!=null && !token.context.onID( ntexp.keyName.namespaceURI, ntexp.keyName.localName, ntexp.dt.createValue(token.literal,token.context) ) ) { if( ntexp.keyName.localName.length()==0 ) // empty key name indicates that this is an ID. return docDecl.localizeMessage( docDecl.DIAG_BAD_KEY_VALUE, token.literal.trim() ); else return docDecl.localizeMessage( docDecl.DIAG_BAD_KEY_VALUE2, token.literal.trim(), ntexp.keyName ); } } */ } /** * diagnoses "uncompleted content model" error. * It basically provides what we were expected. */ protected String diagnoseUncompletedContent() { final CombinedChildContentExpCreator cccc = docDecl.cccec; cccc.get( expression, null, false ); Set s = new java.util.HashSet(); // this set will receive possible tag names. boolean more = false; // this flag is set to true if there are more // candidate. final ElementExp[] eocs = cccc.getMatchedElements(); final int len = cccc.numMatchedElements(); for( int i=0; i<len; i++ ) { // test some typical name class patterns. final NameClass nc = eocs[i].getNameClass(); if( nc instanceof SimpleNameClass ) { s.add( docDecl.localizeMessage( docDecl.DIAG_SIMPLE_NAMECLASS, nc.toString() ) ); continue; } if( nc instanceof NamespaceNameClass ) { s.add( docDecl.localizeMessage( docDecl.DIAG_NAMESPACE_NAMECLASS, ((NamespaceNameClass)nc).namespaceURI ) ); continue; } if( nc instanceof NotNameClass ) { NameClass ncc = ((NotNameClass)nc).child; if( ncc instanceof NamespaceNameClass ) { s.add( docDecl.localizeMessage( docDecl.DIAG_NOT_NAMESPACE_NAMECLASS, ((NamespaceNameClass)ncc).namespaceURI ) ); continue; } } // this name class is very complex and // therefore we were unable to provide appropriate suggestion. more = true; } // no candidate was collected. bail out. // this happens when we are expecting a string. if( s.size()==0 ) return null; return docDecl.localizeMessage( docDecl.DIAG_UNCOMPLETED_CONTENT_WRAPUP, null, concatenateMessages( s, more, docDecl.DIAG_UNCOMPLETED_CONTENT_SEPARATOR, docDecl.DIAG_UNCOMPLETED_CONTENT_MORE ) ); } }