Improved sloppy parser exception handling

This commit is contained in:
Vhati 2013-09-15 19:33:50 -04:00
parent 2bf3860cbc
commit e7c3276541
3 changed files with 205 additions and 174 deletions

View file

@ -7,6 +7,7 @@ Changelog
- Fixed perpetually green "Update" button - Fixed perpetually green "Update" button
- Added --global-panic commandline arg to show mod devs typoed find tags - Added --global-panic commandline arg to show mod devs typoed find tags
- Added commandline tips in readme_modders.txt - Added commandline tips in readme_modders.txt
- Fixed sloppy parser Validate error about things not allowed at root
1.2: 1.2:
- Added a commandline interface - Added a commandline interface

View file

@ -824,7 +824,7 @@ public class ModUtilities {
xmlValid = false; xmlValid = false;
} }
catch ( Exception e ) { catch ( Exception e ) {
log.error( "Error while validating mod xml.", e ); log.error( "Error while validating mod xml with the strict parser.", e );
messages.add( new ReportMessage( messages.add( new ReportMessage(
ReportMessage.EXCEPTION, ReportMessage.EXCEPTION,
"An error occurred. See log for details." "An error occurred. See log for details."
@ -846,6 +846,9 @@ public class ModUtilities {
List<ReportMessage> messages = new ArrayList<ReportMessage>(); List<ReportMessage> messages = new ArrayList<ReportMessage>();
boolean xmlValid = true; boolean xmlValid = true;
// Meh, the parser's gonna make its own wrapper with declarations anyway.
//text = "<wrapper xmlns:mod='mod' xmlns:mod-append='mod-append' xmlns:mod-overwrite='mod-overwrite'>"+ text +"</wrapper>";
try { try {
SloppyXMLParser parser = new SloppyXMLParser(); SloppyXMLParser parser = new SloppyXMLParser();
parser.build( text ); parser.build( text );
@ -876,6 +879,7 @@ public class ModUtilities {
) ); ) );
} }
else { else {
log.error( "Error while validating mod xml with the sloppy parser.", e );
messages.add( new ReportMessage( messages.add( new ReportMessage(
ReportMessage.EXCEPTION, ReportMessage.EXCEPTION,
"An error occurred. See log for details." "An error occurred. See log for details."
@ -883,6 +887,13 @@ public class ModUtilities {
} }
xmlValid = false; xmlValid = false;
} }
catch ( Exception e ) {
log.error( "Error while validating mod xml with the sloppy parser.", e );
messages.add( new ReportMessage(
ReportMessage.EXCEPTION,
"An error occurred. See log for details."
) );
}
return new Report( messages, xmlValid ); return new Report( messages, xmlValid );
} }

View file

@ -16,6 +16,7 @@ import org.jdom2.Content;
import org.jdom2.DefaultJDOMFactory; import org.jdom2.DefaultJDOMFactory;
import org.jdom2.Document; import org.jdom2.Document;
import org.jdom2.Element; import org.jdom2.Element;
import org.jdom2.IllegalAddException;
import org.jdom2.JDOMFactory; import org.jdom2.JDOMFactory;
import org.jdom2.Namespace; import org.jdom2.Namespace;
import org.jdom2.Parent; import org.jdom2.Parent;
@ -115,198 +116,216 @@ public class SloppyXMLParser {
String tmp = null; String tmp = null;
Matcher m = declPtn.matcher( s ); Matcher m = declPtn.matcher( s );
while ( pos > lastPos && pos < sLen ) { try {
m.region( pos, sLen ); while ( pos > lastPos && pos < sLen ) {
boolean matchedChunk = false; m.region( pos, sLen );
boolean matchedChunk = false;
for ( Pattern chunkPtn : chunkPtns ) {
m.usePattern( chunkPtn ); for ( Pattern chunkPtn : chunkPtns ) {
if ( !m.lookingAt() ) continue; m.usePattern( chunkPtn );
if ( !m.lookingAt() ) continue;
if ( chunkPtn == declPtn ) {
// Don't care. if ( chunkPtn == declPtn ) {
addLineAndCol( lastLineAndCol, m.group(0) ); // Don't care.
} addLineAndCol( lastLineAndCol, m.group(0) );
else if ( chunkPtn == emptyCommentPtn ) {
String whitespace = m.group( 1 );
if ( whitespace.length() > 0 )
factory.addContent( parentNode, factory.text( whitespace ) );
addLineAndCol( lastLineAndCol, s, m.start(), m.end() );
}
else if ( chunkPtn == commentPtn ) {
String whitespace = m.group( 1 );
if ( whitespace.length() > 0 )
factory.addContent( parentNode, factory.text( whitespace ) );
tmp = m.group( 2 );
if ( tmp.length() == 0 ) {
factory.addContent( parentNode, factory.comment( "" ) );
} }
else { else if ( chunkPtn == emptyCommentPtn ) {
Matcher splicedMatcher = Pattern.compile( "(\\s*)<!--" ).matcher( tmp ); String whitespace = m.group( 1 );
int commentStart = 0; if ( whitespace.length() > 0 )
while ( splicedMatcher.find() ) { factory.addContent( parentNode, factory.text( whitespace ) );
if ( splicedMatcher.start() - commentStart > 0 ) {
String splicedChunk = tmp.substring( commentStart, splicedMatcher.start() ); addLineAndCol( lastLineAndCol, s, m.start(), m.end() );
splicedChunk = splicedChunk.replaceAll( "^-+|(?<=-)-+|-+$", "" ); }
if ( splicedChunk.startsWith( " " ) ) splicedChunk += " "; else if ( chunkPtn == commentPtn ) {
Comment commentNode = factory.comment( splicedChunk ); String whitespace = m.group( 1 );
if ( whitespace.length() > 0 )
factory.addContent( parentNode, factory.text( whitespace ) );
tmp = m.group( 2 );
if ( tmp.length() == 0 ) {
factory.addContent( parentNode, factory.comment( "" ) );
}
else {
Matcher splicedMatcher = Pattern.compile( "(\\s*)<!--" ).matcher( tmp );
int commentStart = 0;
while ( splicedMatcher.find() ) {
if ( splicedMatcher.start() - commentStart > 0 ) {
String splicedChunk = tmp.substring( commentStart, splicedMatcher.start() );
splicedChunk = splicedChunk.replaceAll( "^-+|(?<=-)-+|-+$", "" );
if ( splicedChunk.startsWith( " " ) ) splicedChunk += " ";
Comment commentNode = factory.comment( splicedChunk );
factory.addContent( parentNode, commentNode );
}
if ( splicedMatcher.group(1).length() > 0 ) {
// Whitespace between comments.
factory.addContent( parentNode, factory.text( splicedMatcher.group(1) ) );
}
commentStart = splicedMatcher.end();
}
if ( commentStart < tmp.length() ) {
String finalChunk = tmp.substring( commentStart );
finalChunk = finalChunk.replaceAll( "^-+|(?<=-)-+|-+$", "" );
Comment commentNode = factory.comment( finalChunk );
factory.addContent( parentNode, commentNode ); factory.addContent( parentNode, commentNode );
} }
if ( splicedMatcher.group(1).length() > 0 ) {
// Whitespace between comments.
factory.addContent( parentNode, factory.text( splicedMatcher.group(1) ) );
}
commentStart = splicedMatcher.end();
} }
if ( commentStart < tmp.length() ) {
String finalChunk = tmp.substring( commentStart ); addLineAndCol( lastLineAndCol, s, m.start(), m.end() );
finalChunk = finalChunk.replaceAll( "^-+|(?<=-)-+|-+$", "" ); }
Comment commentNode = factory.comment( finalChunk ); else if ( chunkPtn == emptyCDATAPtn ) {
factory.addContent( parentNode, commentNode ); String whitespace = m.group( 1 );
if ( whitespace.length() > 0 )
factory.addContent( parentNode, factory.text( whitespace ) );
addLineAndCol( lastLineAndCol, s, m.start(), m.end() );
}
else if ( chunkPtn == cdataPtn ) {
String whitespace = m.group( 1 );
if ( whitespace.length() > 0 )
factory.addContent( parentNode, factory.text( whitespace ) );
CDATA cdataNode = factory.cdata( m.group(2) );
factory.addContent( parentNode, cdataNode );
addLineAndCol( lastLineAndCol, s, m.start(), m.end() );
}
else if ( chunkPtn == sTagPtn ) {
String whitespace = m.group( 1 );
if ( whitespace.length() > 0 )
factory.addContent( parentNode, factory.text( whitespace ) );
String nodePrefix = m.group( 2 ); // Might be null.
String nodeName = m.group( 3 );
String attrString = m.group( 4 );
boolean selfClosing = ( m.group( 5 ).length() > 0 );
addLineAndCol( lastLineAndCol, s, m.start(), m.end() );
Element tagNode;
if ( nodePrefix != null ) {
Namespace nodeNS = Namespace.getNamespace( nodePrefix, nodePrefix ); // URI? *shrug*
factory.addNamespaceDeclaration( rootNode, nodeNS );
tagNode = factory.element( lastLineAndCol[0]+1, lastLineAndCol[1]+1+1, nodeName, nodeNS );
} else {
tagNode = factory.element( lastLineAndCol[0]+1, lastLineAndCol[1]+1+1, nodeName );
} }
}
if ( attrString.length() > 0 ) {
addLineAndCol( lastLineAndCol, s, m.start(), m.end() ); Matcher am = attrPtn.matcher( attrString );
} while ( am.lookingAt() ) {
else if ( chunkPtn == emptyCDATAPtn ) { String attrPrefix = am.group( 1 ); // Might be null.
String whitespace = m.group( 1 ); String attrName = am.group( 2 );
if ( whitespace.length() > 0 ) String attrValue = am.group( 3 );
factory.addContent( parentNode, factory.text( whitespace ) ); attrValue = attrValue.substring( 1, attrValue.length()-1 );
attrValue = unescape( attrValue );
addLineAndCol( lastLineAndCol, s, m.start(), m.end() );
} if ( attrPrefix != null ) {
else if ( chunkPtn == cdataPtn ) { if ( attrPrefix.equals( "xmlns" ) ) {
String whitespace = m.group( 1 ); // This is a pseudo attribute declaring a namespace prefix.
if ( whitespace.length() > 0 ) // Move it to the root node.
factory.addContent( parentNode, factory.text( whitespace ) ); Namespace attrNS = Namespace.getNamespace( attrName, attrName ); // URI? *shrug*
factory.addNamespaceDeclaration( rootNode, attrNS );
CDATA cdataNode = factory.cdata( m.group(2) ); }
factory.addContent( parentNode, cdataNode ); else {
Namespace attrNS = Namespace.getNamespace( attrPrefix, attrPrefix ); // URI? *shrug*
addLineAndCol( lastLineAndCol, s, m.start(), m.end() ); factory.addNamespaceDeclaration( rootNode, attrNS );
} Attribute attrObj = factory.attribute( attrName, attrValue, AttributeType.UNDECLARED, attrNS );
else if ( chunkPtn == sTagPtn ) { factory.setAttribute( tagNode, attrObj );
String whitespace = m.group( 1 ); }
if ( whitespace.length() > 0 ) } else if ( attrName.equals("xmlns") ) {
factory.addContent( parentNode, factory.text( whitespace ) ); // New default namespace URI within this node.
Namespace attrNS = Namespace.getNamespace( attrValue );
String nodePrefix = m.group( 2 ); // Might be null. factory.addNamespaceDeclaration( tagNode, attrNS );
String nodeName = m.group( 3 ); } else {
String attrString = m.group( 4 ); // Normal attribute.
boolean selfClosing = ( m.group( 5 ).length() > 0 ); Attribute attrObj = factory.attribute( attrName, attrValue, AttributeType.UNDECLARED, Namespace.NO_NAMESPACE );
addLineAndCol( lastLineAndCol, s, m.start(), m.end() );
Element tagNode;
if ( nodePrefix != null ) {
Namespace nodeNS = Namespace.getNamespace( nodePrefix, nodePrefix ); // URI? *shrug*
factory.addNamespaceDeclaration( rootNode, nodeNS );
tagNode = factory.element( lastLineAndCol[0]+1, lastLineAndCol[1]+1+1, nodeName, nodeNS );
} else {
tagNode = factory.element( lastLineAndCol[0]+1, lastLineAndCol[1]+1+1, nodeName );
}
if ( attrString.length() > 0 ) {
Matcher am = attrPtn.matcher( attrString );
while ( am.lookingAt() ) {
String attrPrefix = am.group( 1 ); // Might be null.
String attrName = am.group( 2 );
String attrValue = am.group( 3 );
attrValue = attrValue.substring( 1, attrValue.length()-1 );
attrValue = unescape( attrValue );
if ( attrPrefix != null ) {
if ( attrPrefix.equals( "xmlns" ) ) {
// This is a pseudo attribute declaring a namespace prefix.
// Move it to the root node.
Namespace attrNS = Namespace.getNamespace( attrName, attrName ); // URI? *shrug*
factory.addNamespaceDeclaration( rootNode, attrNS );
}
else {
Namespace attrNS = Namespace.getNamespace( attrPrefix, attrPrefix ); // URI? *shrug*
factory.addNamespaceDeclaration( rootNode, attrNS );
Attribute attrObj = factory.attribute( attrName, attrValue, AttributeType.UNDECLARED, attrNS );
factory.setAttribute( tagNode, attrObj ); factory.setAttribute( tagNode, attrObj );
} }
} else if ( attrName.equals("xmlns") ) { am.region( am.end(), am.regionEnd() );
// New default namespace URI within this node. }
Namespace attrNS = Namespace.getNamespace( attrValue ); if ( am.regionStart() < attrString.length() ) {
factory.addNamespaceDeclaration( tagNode, attrNS ); int nonspacePos = findNextNonspace( s, pos );
} else { int errorPos = ( (nonspacePos != -1) ? nonspacePos : pos );
// Normal attribute.
Attribute attrObj = factory.attribute( attrName, attrValue, AttributeType.UNDECLARED, Namespace.NO_NAMESPACE ); int[] lineAndCol = getLineAndCol( s, errorPos );
factory.setAttribute( tagNode, attrObj ); int lineNum = lineAndCol[0];
int colNum = lineAndCol[1];
SAXParseException cause = new SAXParseException( String.format( "At line %d, column %d: Strange attributes.", lineNum, colNum ), null, null, lineNum, colNum);
throw new JDOMParseException( String.format( "Error on line %d: %s", lineNum, cause.getMessage() ), cause );
} }
am.region( am.end(), am.regionEnd() );
}
if ( am.regionStart() < attrString.length() ) {
int nonspacePos = findNextNonspace( s, pos );
int errorPos = ( (nonspacePos != -1) ? nonspacePos : pos );
int[] lineAndCol = getLineAndCol( s, errorPos );
int lineNum = lineAndCol[0];
int colNum = lineAndCol[1];
SAXParseException cause = new SAXParseException( String.format( "At line %d, column %d: Strange attributes.", lineNum, colNum ), null, null, lineNum, colNum);
throw new JDOMParseException( String.format( "Error on line %d: %s", lineNum, cause.getMessage() ), cause );
} }
factory.addContent( parentNode, tagNode );
if ( !selfClosing ) parentNode = tagNode;
} }
else if ( chunkPtn == eTagPtn ) {
factory.addContent( parentNode, tagNode ); String interimText = m.group( 1 );
if ( !selfClosing ) parentNode = tagNode; interimText = unescape( interimText );
factory.addContent( parentNode, factory.text( interimText ) );
parentNode = parentNode.getParent();
addLineAndCol( lastLineAndCol, s, m.start(), m.end() );
}
else if ( chunkPtn == endSpacePtn ) {
// This is the end of the document.
}
else if ( chunkPtn == strayCharsPtn ) {
// Non-space junk between an end tag and a start tag.
String whitespace = m.group( 1 );
if ( whitespace.length() > 0 )
factory.addContent( parentNode, factory.text( whitespace ) );
addLineAndCol( lastLineAndCol, s, m.start(), m.end() );
}
matchedChunk = true;
lastPos = pos;
pos = m.end();
break;
} }
else if ( chunkPtn == eTagPtn ) {
String interimText = m.group( 1 ); if ( !matchedChunk ) {
interimText = unescape( interimText ); int nonspacePos = findNextNonspace( s, pos );
int errorPos = ( (nonspacePos != -1) ? nonspacePos : pos );
factory.addContent( parentNode, factory.text( interimText ) );
parentNode = parentNode.getParent(); int[] lineAndCol = getLineAndCol( s, errorPos );
int lineNum = lineAndCol[0];
addLineAndCol( lastLineAndCol, s, m.start(), m.end() ); int colNum = lineAndCol[1];
SAXParseException cause = new SAXParseException( String.format( "At line %d, column %d: Unexpected characters.", lineNum, colNum ), null, null, lineNum, colNum);
throw new JDOMParseException( String.format( "Error on line %d: %s", lineNum, cause.getMessage() ), cause );
} }
else if ( chunkPtn == endSpacePtn ) { }
// This is the end of the document.
if ( rootNode.getChildren().size() == 1 ) {
// No need for the wrapper, promote its only child to root.
Element newRoot = rootNode.getChildren().get( 0 );
newRoot.detach();
for ( Namespace ns : rootNode.getAdditionalNamespaces() ) {
factory.addNamespaceDeclaration( newRoot, ns );
} }
else if ( chunkPtn == strayCharsPtn ) { factory.setRoot( doc, newRoot );
// Non-space junk between an end tag and a start tag.
String whitespace = m.group( 1 );
if ( whitespace.length() > 0 )
factory.addContent( parentNode, factory.text( whitespace ) );
addLineAndCol( lastLineAndCol, s, m.start(), m.end() );
}
matchedChunk = true;
lastPos = pos;
pos = m.end();
break;
} }
if ( !matchedChunk ) {
int nonspacePos = findNextNonspace( s, pos );
int errorPos = ( (nonspacePos != -1) ? nonspacePos : pos );
int[] lineAndCol = getLineAndCol( s, errorPos );
int lineNum = lineAndCol[0];
int colNum = lineAndCol[1];
SAXParseException cause = new SAXParseException( String.format( "At line %d, column %d: Unexpected characters.", lineNum, colNum ), null, null, lineNum, colNum);
throw new JDOMParseException( String.format( "Error on line %d: %s", lineNum, cause.getMessage() ), cause );
}
} }
catch( IllegalAddException e ) {
int nonspacePos = findNextNonspace( s, pos );
int errorPos = ( (nonspacePos != -1) ? nonspacePos : pos );
if ( rootNode.getChildren().size() == 1 ) { int[] lineAndCol = getLineAndCol( s, errorPos );
// No need for the wrapper, promote its only child to root. int lineNum = lineAndCol[0];
int colNum = lineAndCol[1];
Element newRoot = rootNode.getChildren().get( 0 ); String hint = "";
newRoot.detach(); if ( e.getMessage() != null && e.getMessage().indexOf( "not allowed at the document root" ) != -1 ) {
for ( Namespace ns : rootNode.getAdditionalNamespaces() ) { hint = " (There's likely an extraneous closing tag before this point.)";
factory.addNamespaceDeclaration( newRoot, ns );
} }
factory.setRoot( doc, newRoot ); SAXParseException cause = new SAXParseException( String.format( "At line %d, column %d: %s%s", lineNum, colNum, e.getMessage(), hint ), null, null, lineNum, colNum, e);
throw new JDOMParseException( String.format( "Error on line %d: %s", lineNum, cause.getMessage() ), cause );
} }
return doc; return doc;