/*******************************************************************************
* Copyright (c) 2012, 2016 EclipseSource and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* EclipseSource - initial API and implementation
******************************************************************************/
package org.eclipse.swt.internal.widgets;
import java.io.StringReader;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.eclipse.rap.rwt.SingletonUtil;
import org.eclipse.swt.widgets.Widget;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.helpers.DefaultHandler;
public class MarkupValidator {
// Used by Eclipse Scout project
public static final String MARKUP_VALIDATION_DISABLED
= "org.eclipse.rap.rwt.markupValidationDisabled";
private static final String DTD = createDTD();
private static final Map<String, String[]> SUPPORTED_ELEMENTS = createSupportedElementsMap();
private final SAXParser saxParser;
public static MarkupValidator getInstance() {
return SingletonUtil.getSessionInstance( MarkupValidator.class );
}
public MarkupValidator() {
saxParser = createSAXParser();
}
public void validate( String text ) {
StringBuilder markup = new StringBuilder();
markup.append( DTD );
markup.append( "<html>" );
markup.append( text );
markup.append( "</html>" );
InputSource inputSource = new InputSource( new StringReader( markup.toString() ) );
try {
saxParser.parse( inputSource, new MarkupHandler() );
} catch( RuntimeException exception ) {
throw exception;
} catch( Exception exception ) {
throw new IllegalArgumentException( "Failed to parse markup text", exception );
}
}
public static boolean isValidationDisabledFor( Widget widget ) {
return Boolean.TRUE.equals( widget.getData( MARKUP_VALIDATION_DISABLED ) );
}
private static SAXParser createSAXParser() {
SAXParser result = null;
SAXParserFactory parserFactory = SAXParserFactory.newInstance();
try {
result = parserFactory.newSAXParser();
} catch( Exception exception ) {
throw new RuntimeException( "Failed to create SAX parser", exception );
}
return result;
}
private static String createDTD() {
StringBuilder result = new StringBuilder();
result.append( "<!DOCTYPE html [" );
result.append( "<!ENTITY quot \""\">" );
result.append( "<!ENTITY amp \"&\">" );
result.append( "<!ENTITY apos \"'\">" );
result.append( "<!ENTITY lt \"<\">" );
result.append( "<!ENTITY gt \">\">" );
result.append( "<!ENTITY nbsp \" \">" );
result.append( "<!ENTITY ensp \" \">" );
result.append( "<!ENTITY emsp \" \">" );
result.append( "<!ENTITY ndash \"–\">" );
result.append( "<!ENTITY mdash \"—\">" );
result.append( "]>" );
return result.toString();
}
private static Map<String, String[]> createSupportedElementsMap() {
Map<String, String[]> result = new HashMap<>();
result.put( "html", new String[ 0 ] );
result.put( "br", new String[ 0 ] );
result.put( "b", new String[] { "style", "class", "id" } );
result.put( "strong", new String[] { "style", "class", "id" } );
result.put( "i", new String[] { "style", "class", "id" } );
result.put( "em", new String[] { "style", "class", "id" } );
result.put( "sub", new String[] { "style", "class", "id" } );
result.put( "sup", new String[] { "style", "class", "id" } );
result.put( "big", new String[] { "style", "class", "id" } );
result.put( "small", new String[] { "style", "class", "id" } );
result.put( "del", new String[] { "style", "class", "id" } );
result.put( "ins", new String[] { "style", "class", "id" } );
result.put( "code", new String[] { "style", "class", "id" } );
result.put( "samp", new String[] { "style", "class", "id" } );
result.put( "kbd", new String[] { "style", "class", "id" } );
result.put( "var", new String[] { "style", "class", "id" } );
result.put( "cite", new String[] { "style", "class", "id" } );
result.put( "dfn", new String[] { "style", "class", "id" } );
result.put( "q", new String[] { "style", "class", "id" } );
result.put( "abbr", new String[] { "style", "class", "id", "title" } );
result.put( "span", new String[] { "style", "class", "id" } );
result.put( "img",
new String[] { "style", "class", "id", "src", "width", "height", "title", "alt" } );
result.put( "a", new String[] { "style", "class", "id", "href", "target", "title" } );
return result;
}
private static class MarkupHandler extends DefaultHandler {
@Override
public void startElement( String uri, String localName, String name, Attributes attributes ) {
checkSupportedElements( name );
checkSupportedAttributes( name, attributes );
checkMandatoryAttributes( name, attributes );
}
private static void checkSupportedElements( String elementName ) {
if( !SUPPORTED_ELEMENTS.containsKey( elementName ) ) {
throw new IllegalArgumentException( "Unsupported element in markup text: " + elementName );
}
}
private static void checkSupportedAttributes( String elementName, Attributes attributes ) {
if( attributes.getLength() > 0 ) {
List<String> supportedAttributes = Arrays.asList( SUPPORTED_ELEMENTS.get( elementName ) );
int index = 0;
String attributeName = attributes.getQName( index );
while( attributeName != null ) {
if( !supportedAttributes.contains( attributeName ) ) {
String message = "Unsupported attribute \"{0}\" for element \"{1}\" in markup text";
message = MessageFormat.format( message, new Object[] { attributeName, elementName } );
throw new IllegalArgumentException( message );
}
index++;
attributeName = attributes.getQName( index );
}
}
}
private static void checkMandatoryAttributes( String elementName, Attributes attributes ) {
checkIntAttribute( elementName, attributes, "img", "width" );
checkIntAttribute( elementName, attributes, "img", "height" );
}
private static void checkIntAttribute( String elementName,
Attributes attributes,
String checkedElementName,
String checkedAttributeName )
{
if( checkedElementName.equals( elementName ) ) {
String attribute = attributes.getValue( checkedAttributeName );
try {
Integer.parseInt( attribute );
} catch( @SuppressWarnings( "unused" ) NumberFormatException exception ) {
String message
= "Mandatory attribute \"{0}\" for element \"{1}\" is missing or not a valid integer";
Object[] arguments = new Object[] { checkedAttributeName, checkedElementName };
message = MessageFormat.format( message, arguments );
throw new IllegalArgumentException( message );
}
}
}
}
}