Author: hlship Date: Tue Oct 25 19:18:47 2011 New Revision: 1188867 URL: http://svn.apache.org/viewvc?rev=1188867&view=rev Log: TAP5-1720: Supply a "<!DOCTYPE html>" for any component template that doesn't have a specific <!DOCTYPE>
Added: tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/internal/services/html_entities.tml Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/dynamic/DynamicTemplateParserImpl.java tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/dynamic/DynamicTemplateSaxParser.java tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DTDData.java tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SaxTemplateParser.java tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/TemplateParser.java tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/TemplateParserImpl.java tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/XMLTokenStream.java tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/TemplateParserImplTest.java Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/dynamic/DynamicTemplateParserImpl.java URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/dynamic/DynamicTemplateParserImpl.java?rev=1188867&r1=1188866&r2=1188867&view=diff ============================================================================== --- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/dynamic/DynamicTemplateParserImpl.java (original) +++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/dynamic/DynamicTemplateParserImpl.java Tue Oct 25 19:18:47 2011 @@ -14,9 +14,8 @@ package org.apache.tapestry5.internal.dynamic; -import java.util.Map; - import org.apache.tapestry5.internal.services.PageSource; +import org.apache.tapestry5.internal.services.TemplateParser; import org.apache.tapestry5.ioc.Resource; import org.apache.tapestry5.ioc.annotations.PostInjection; import org.apache.tapestry5.ioc.internal.util.CollectionFactory; @@ -28,6 +27,8 @@ import org.apache.tapestry5.services.Upd import org.apache.tapestry5.services.dynamic.DynamicTemplate; import org.apache.tapestry5.services.dynamic.DynamicTemplateParser; +import java.util.Map; + public class DynamicTemplateParserImpl implements DynamicTemplateParser, UpdateListener { private final Map<Resource, DynamicTemplate> cache = CollectionFactory.newConcurrentMap(); @@ -38,10 +39,13 @@ public class DynamicTemplateParserImpl i private final URLChangeTracker tracker; - public DynamicTemplateParserImpl(ClasspathURLConverter converter, BindingSource bindingSource, PageSource pageSource) + private final TemplateParser componentTemplateParser; + + public DynamicTemplateParserImpl(ClasspathURLConverter converter, BindingSource bindingSource, PageSource pageSource, TemplateParser componentTemplateParser) { this.bindingSource = bindingSource; this.pageSource = pageSource; + this.componentTemplateParser = componentTemplateParser; tracker = new URLChangeTracker(converter); } @@ -69,7 +73,7 @@ public class DynamicTemplateParserImpl i private DynamicTemplate doParse(Resource resource) { - return new DynamicTemplateSaxParser(resource, bindingSource).parse(); + return new DynamicTemplateSaxParser(resource, bindingSource, componentTemplateParser.getDTDURLMappings()).parse(); } public void checkForUpdates() Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/dynamic/DynamicTemplateSaxParser.java URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/dynamic/DynamicTemplateSaxParser.java?rev=1188867&r1=1188866&r2=1188867&view=diff ============================================================================== --- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/dynamic/DynamicTemplateSaxParser.java (original) +++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/dynamic/DynamicTemplateSaxParser.java Tue Oct 25 19:18:47 2011 @@ -14,15 +14,6 @@ package org.apache.tapestry5.internal.dynamic; -import java.net.URL; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.xml.namespace.QName; - import org.apache.tapestry5.Binding; import org.apache.tapestry5.BindingConstants; import org.apache.tapestry5.Block; @@ -44,7 +35,16 @@ import org.apache.tapestry5.services.Bin import org.apache.tapestry5.services.dynamic.DynamicDelegate; import org.apache.tapestry5.services.dynamic.DynamicTemplate; -/** Does the heavy lifting for {@link DynamicTemplateParserImpl}. */ +import javax.xml.namespace.QName; +import java.net.URL; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Does the heavy lifting for {@link DynamicTemplateParserImpl}. + */ public class DynamicTemplateSaxParser { private final Resource resource; @@ -53,8 +53,6 @@ public class DynamicTemplateSaxParser private final XMLTokenStream tokenStream; - private final Map<String, URL> publicIdToURL = Collections.emptyMap(); - private static final Pattern PARAM_ID_PATTERN = Pattern.compile("^param:(\\p{Alpha}\\w*)$", Pattern.CASE_INSENSITIVE); @@ -69,7 +67,7 @@ public class DynamicTemplateSaxParser } }; - public DynamicTemplateSaxParser(Resource resource, BindingSource bindingSource) + public DynamicTemplateSaxParser(Resource resource, BindingSource bindingSource, Map<String, URL> publicIdToURL) { this.resource = resource; this.bindingSource = bindingSource; @@ -84,8 +82,7 @@ public class DynamicTemplateSaxParser tokenStream.parse(); return toDynamicTemplate(root()); - } - catch (Exception ex) + } catch (Exception ex) { throw new TapestryException(String.format("Failure parsing dynamic template %s: %s", resource, InternalUtils.toMessage(ex)), tokenStream.getLocation(), ex); @@ -219,7 +216,7 @@ public class DynamicTemplateSaxParser } private static DynamicTemplateElement createElementWriterElement(final String elementURI, final String elementName, - final List<DynamicTemplateAttribute> attributes, List<DynamicTemplateElement> body) + final List<DynamicTemplateAttribute> attributes, List<DynamicTemplateElement> body) { final Flow<DynamicTemplateElement> bodyFlow = F.flow(body).reverse(); @@ -269,8 +266,7 @@ public class DynamicTemplateSaxParser Block block = delegate.getBlock(blockId); queue.push((RenderCommand) block); - } - catch (Exception ex) + } catch (Exception ex) { throw new TapestryException(String.format( "Exception rendering block '%s' as part of dynamic template: %s", blockId, @@ -443,7 +439,7 @@ public class DynamicTemplateSaxParser } private static Mapper<DynamicDelegate, String> createExpansionExtractor(final String expression, - final Location location, final BindingSource bindingSource) + final Location location, final BindingSource bindingSource) { return new Mapper<DynamicDelegate, String>() { @@ -458,8 +454,7 @@ public class DynamicTemplateSaxParser Object boundValue = binding.get(); return boundValue == null ? null : boundValue.toString(); - } - catch (Throwable t) + } catch (Throwable t) { throw new TapestryException(InternalUtils.toMessage(t), location, t); } Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DTDData.java URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DTDData.java?rev=1188867&r1=1188866&r2=1188867&view=diff ============================================================================== --- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DTDData.java (original) +++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DTDData.java Tue Oct 25 19:18:47 2011 @@ -1,4 +1,4 @@ -// Copyright 2009 The Apache Software Foundation +// Copyright 2009, 2011 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,14 +19,17 @@ import org.xml.sax.ext.LexicalHandler; /** * A capturing of the data from * {@link LexicalHandler#startDTD(String, String, String)}. - * + * * @since 5.2.0 */ -public interface DTDData +public class DTDData { - String getRootName(); + public final String rootName, publicId, systemId; - String getPublicId(); - - String getSystemId(); + public DTDData(String rootName, String publicId, String systemId) + { + this.rootName = rootName; + this.publicId = publicId; + this.systemId = systemId; + } } Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SaxTemplateParser.java URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SaxTemplateParser.java?rev=1188867&r1=1188866&r2=1188867&view=diff ============================================================================== --- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SaxTemplateParser.java (original) +++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SaxTemplateParser.java Tue Oct 25 19:18:47 2011 @@ -1,4 +1,4 @@ -// Copyright 2009 The Apache Software Foundation +// Copyright 2009, 2011 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -293,8 +293,8 @@ public class SaxTemplateParser { DTDData dtdInfo = tokenStream.getDTDInfo(); - tokenAccumulator.add(new DTDToken(dtdInfo.getRootName(), dtdInfo.getPublicId(), dtdInfo - .getSystemId(), getLocation())); + tokenAccumulator.add(new DTDToken(dtdInfo.rootName, dtdInfo.publicId, dtdInfo + .systemId, getLocation())); } private Location getLocation() Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/TemplateParser.java URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/TemplateParser.java?rev=1188867&r1=1188866&r2=1188867&view=diff ============================================================================== --- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/TemplateParser.java (original) +++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/TemplateParser.java Tue Oct 25 19:18:47 2011 @@ -1,4 +1,4 @@ -// Copyright 2006, 2008 The Apache Software Foundation +// Copyright 2006, 2008, 2011 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import org.apache.tapestry5.ioc.Resource import org.apache.tapestry5.ioc.annotations.UsesMappedConfiguration; import java.net.URL; +import java.util.Map; /** * Parses a resource into a {@link org.apache.tapestry5.internal.parser.ComponentTemplate}. The service's configuration @@ -37,4 +38,13 @@ public interface TemplateParser * @throws RuntimeException if the resource does not exist, or if there is any kind of parse error */ ComponentTemplate parseTemplate(Resource templateResource); + + /** + * Returns a mapping from URL string to a local equivalent URL, used to avoid attempting to pull + * well-known DTDs down over the wire while parsing XML. + * + * @since 5.3 + */ + Map<String, URL> getDTDURLMappings(); } + Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/TemplateParserImpl.java URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/TemplateParserImpl.java?rev=1188867&r1=1188866&r2=1188867&view=diff ============================================================================== --- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/TemplateParserImpl.java (original) +++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/TemplateParserImpl.java Tue Oct 25 19:18:47 2011 @@ -1,4 +1,4 @@ -// Copyright 2006, 2007, 2008, 2009, 2010 The Apache Software Foundation +// Copyright 2006, 2007, 2008, 2009, 2010, 2011 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,9 +14,6 @@ package org.apache.tapestry5.internal.services; -import java.net.URL; -import java.util.Map; - import org.apache.tapestry5.SymbolConstants; import org.apache.tapestry5.internal.parser.ComponentTemplate; import org.apache.tapestry5.ioc.Invokable; @@ -24,11 +21,14 @@ import org.apache.tapestry5.ioc.Operatio import org.apache.tapestry5.ioc.Resource; import org.apache.tapestry5.ioc.annotations.Symbol; +import java.net.URL; +import java.util.Map; + /** * Parses Tapestry XML template files into {@link ComponentTemplate} instances. * A new instance of {@link SaxTemplateParser} is created for each document * parsed. - * + * * @since 5.1.0.0 */ public class TemplateParserImpl implements TemplateParser @@ -41,8 +41,8 @@ public class TemplateParserImpl implemen public TemplateParserImpl(Map<String, URL> configuration, - @Symbol(SymbolConstants.COMPRESS_WHITESPACE) - boolean defaultCompressWhitespace, OperationTracker tracker) + @Symbol(SymbolConstants.COMPRESS_WHITESPACE) + boolean defaultCompressWhitespace, OperationTracker tracker) { this.configuration = configuration; this.defaultCompressWhitespace = defaultCompressWhitespace; @@ -62,4 +62,9 @@ public class TemplateParserImpl implemen } }); } + + public Map<String, URL> getDTDURLMappings() + { + return configuration; + } } Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/XMLTokenStream.java URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/XMLTokenStream.java?rev=1188867&r1=1188866&r2=1188867&view=diff ============================================================================== --- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/XMLTokenStream.java (original) +++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/XMLTokenStream.java Tue Oct 25 19:18:47 2011 @@ -37,6 +37,11 @@ import java.util.Map; */ public class XMLTokenStream { + + public static final String TRANSITIONAL_DOCTYPE = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"; + + private static final DTDData HTML5_DTD_DATA = new DTDData("html", null, null); + private final class SaxHandler implements LexicalHandler, EntityResolver, ContentHandler { private Locator locator; @@ -62,7 +67,10 @@ public class XMLTokenStream if (cachedLocation == null) { - cachedLocation = new LocationImpl(resource, line); + // lineOffset accounts for the extra line when a doctype is injected. The line number reported + // from the XML parser inlcudes the phantom doctype line, the lineOffset is used to subtract one + // to get the real line number. + cachedLocation = new LocationImpl(resource, line + lineOffset); } return cachedLocation; @@ -138,6 +146,7 @@ public class XMLTokenStream public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { + characters(ch, start, length); } public void startDTD(final String name, final String publicId, final String systemId) @@ -145,26 +154,12 @@ public class XMLTokenStream { insideDTD = true; - DTDData data = new DTDData() + if (!ignoreDTD) { + DTDData data = html5DTD ? HTML5_DTD_DATA : new DTDData(name, publicId, systemId); - public String getSystemId() - { - return html5DTD ? null : systemId; - } - - public String getRootName() - { - return name; - } - - public String getPublicId() - { - return html5DTD ? null : publicId; - } - }; - - add(XMLTokenType.DTD).dtdData = data; + add(XMLTokenType.DTD).dtdData = data; + } } public void endDocument() throws SAXException @@ -182,12 +177,21 @@ public class XMLTokenStream this.locator = locator; } + /** + * Checks for the extra namespace injected when the transitional doctype is injected (which + * occurs when the template contains no doctype). + */ + private boolean ignoreURI(String uri) + { + return ignoreDTD && uri.equals("http://www.w3.org/1999/xhtml"); + } + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { XMLToken token = add(XMLTokenType.START_ELEMENT); - token.uri = uri; + token.uri = ignoreURI(uri) ? "" : uri; token.localName = localName; token.qName = qName; @@ -227,6 +231,11 @@ public class XMLTokenStream public void startPrefixMapping(String prefix, String uri) throws SAXException { + if (ignoreDTD && prefix.equals("") && uri.equals("http://www.w3.org/1999/xhtml")) + { + return; + } + namespaceMappings.add(new NamespaceMapping(prefix, uri)); } @@ -270,7 +279,9 @@ public class XMLTokenStream private Location exceptionLocation; - private boolean html5DTD; + private boolean html5DTD, ignoreDTD; + + private int lineOffset; public XMLTokenStream(Resource resource, Map<String, URL> publicIdToURL) { @@ -314,60 +325,95 @@ public class XMLTokenStream } } - private InputStream openStream() throws IOException + enum State { + MAYBE_XML, MAYBE_DOCTYPE, JUST_COPY + } + private InputStream openStream() throws IOException + { InputStream rawStream = resource.openStream(); InputStreamReader rawReader = new InputStreamReader(rawStream); LineNumberReader reader = new LineNumberReader(rawReader); + ByteArrayOutputStream bos = new ByteArrayOutputStream(5000); + PrintWriter writer = new PrintWriter(bos); + + State state = State.MAYBE_XML; + try { - String firstLine = reader.readLine(); - - if ("<!DOCTYPE html>".equalsIgnoreCase(firstLine)) + while (true) { - // When we hit the doctype later, ignore the transitional PUBLIC and SYSTEM ids and - // treat it like a proper HTML5 doctype. - html5DTD = true; - return substituteTransitionalDoctype(reader); - } + String line = reader.readLine(); - // Open a fresh stream for the parser to operate on. + if (line == null) + { + break; + } - return resource.openStream(); + switch (state) + { - } finally - { - reader.close(); - } - } + case MAYBE_XML: - private InputStream substituteTransitionalDoctype(LineNumberReader reader) throws IOException - { - ByteArrayOutputStream bos = new ByteArrayOutputStream(5000); - PrintWriter writer = new PrintWriter(bos); + if (line.toLowerCase().startsWith("<?xml")) + { + writer.println(line); + state = State.MAYBE_DOCTYPE; + continue; + } - writer.println("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"); + case MAYBE_DOCTYPE: - while (true) - { - String line = reader.readLine(); - if (line == null) - { - break; - } + if (line.trim().length() == 0) + { + writer.println(line); + continue; + } - writer.println(line); - } + String lineLower = line.toLowerCase(); + + if (lineLower.equals("<!doctype html>")) + { + html5DTD = true; + writer.println(TRANSITIONAL_DOCTYPE); + state = State.JUST_COPY; + continue; + } + + + if (lineLower.startsWith("<!doctype")) + { + writer.println(line); + state = State.JUST_COPY; + continue; + } - writer.close(); + // No doctype, let's provide one. + + ignoreDTD = true; + lineOffset = -1; + writer.println(TRANSITIONAL_DOCTYPE); + + state = State.JUST_COPY; + + // And drop down to writing out the actual line, and all following lines. + + case JUST_COPY: + writer.println(line); + } + } + } finally + { + writer.close(); + reader.close(); + } return new ByteArrayInputStream(bos.toByteArray()); } - private XMLToken token() { return tokens.get(cursor); Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/TemplateParserImplTest.java URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/TemplateParserImplTest.java?rev=1188867&r1=1188866&r2=1188867&view=diff ============================================================================== --- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/TemplateParserImplTest.java (original) +++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/TemplateParserImplTest.java Tue Oct 25 19:18:47 2011 @@ -985,4 +985,22 @@ public class TemplateParserImplTest exte assertMessageContains(ex, "Extension point 'batman' is already defined for this template."); } } + + @Test + public void html_entities_inside_template_without_doctype_are_allowed() throws Exception + { + + List<TemplateToken> tokens = tokens("html_entities.tml"); + + assertEquals(tokens.size(), 3); + + StartElementToken token0 = get(tokens, 0); + + assertEquals(token0.name, "html"); + + TextToken token1 = get(tokens, 1); + + assertEquals(token1.text, "\n[\u00A0]\n"); + + } } Added: tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/internal/services/html_entities.tml URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/internal/services/html_entities.tml?rev=1188867&view=auto ============================================================================== --- tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/internal/services/html_entities.tml (added) +++ tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/internal/services/html_entities.tml Tue Oct 25 19:18:47 2011 @@ -0,0 +1,3 @@ +<html> + [ ] +</html> \ No newline at end of file