/* * Copyright 2004-2012 the Seasar Foundation and the Others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package org.seasar.mayaa.impl.builder; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.seasar.mayaa.engine.Template; import org.seasar.mayaa.engine.specification.NodeTreeWalker; import org.seasar.mayaa.engine.specification.QName; import org.seasar.mayaa.engine.specification.SpecificationNode; import org.seasar.mayaa.engine.specification.URI; import org.seasar.mayaa.impl.CONST_IMPL; import org.seasar.mayaa.impl.engine.CharsetConverter; import org.seasar.mayaa.impl.engine.EngineUtil; import org.seasar.mayaa.impl.engine.specification.QNameImpl; import org.seasar.mayaa.impl.engine.specification.SpecificationUtil; import org.seasar.mayaa.impl.engine.specification.URIImpl; import org.seasar.mayaa.impl.util.StringUtil; import org.xml.sax.Attributes; /** * @author Koji Suga (Gluegent, Inc.) */ public class TemplateNodeHandler extends SpecificationNodeHandler { private static final Log LOG = LogFactory.getLog(TemplateNodeHandler.class); private boolean _outputTemplateWhitespace = true; private boolean _isSSIIncludeReplacementEnabled = false; public TemplateNodeHandler(Template specification) { super(specification); } protected Template getTemplate() { return (Template) getSpecification(); } public void setOutputTemplateWhitespace(boolean outputTemplateWhitespace) { _outputTemplateWhitespace = outputTemplateWhitespace; } public void setSSIIncludeReplacementEnabled(boolean isSSIIncludeReplacementEnabled) { _isSSIIncludeReplacementEnabled = isSSIIncludeReplacementEnabled; } /** * SSI includeの記述をm:insertへ置き換える処理が有効か。 * @return 有効なら{@code true}、そうでなければ{@code false}を返す。 */ protected boolean isSSIIncludeReplacementEnabled() { return _isSSIIncludeReplacementEnabled; } protected void initNamespace() { super.initNamespace(); getCurrentInternalNamespacePrefixMap().remove(""); getCurrentInternalNamespacePrefixMap().remove("xml"); } protected SpecificationNode createChildNode( QName qName, String systemID, int lineNumber, int sequenceID) { return SpecificationUtil.createSpecificationNode( qName, systemID, lineNumber, true, sequenceID); } protected boolean isRemoveWhitespace() { return _outputTemplateWhitespace == false; } /** * metaタグのhttp-equiv="content-type" の場合、contentのcharsetを変換して * 返すようにする。 * それ以外の場合はなにもしない。 * * @param namespaceURI * @param localName * @param qName * @param attributes * @return */ protected Attributes wrapContentTypeConverter(String namespaceURI, String localName, String qName, Attributes attributes) { if (StringUtil.isEmpty(namespaceURI) || URI_HTML.equals(namespaceURI) || URI_XHTML.equals(namespaceURI)) { if ("meta".equalsIgnoreCase(localName)) { int contentTypeIndex = getContentTypeIndex(namespaceURI, attributes); if (contentTypeIndex != -1) { String contentType = attributes.getValue(contentTypeIndex); contentType = CharsetConverter.convertContentType(contentType); return new ContentTypeConvertAttributes( attributes, contentTypeIndex, contentType); } } } return attributes; } protected int getContentTypeIndex(String namespaceURI, Attributes attributes) { int httpEquivIndex = getIndexIgnoreCase(namespaceURI, "http-equiv", attributes); if (httpEquivIndex != -1 && "content-type".equalsIgnoreCase(attributes.getValue(httpEquivIndex))) { return getIndexIgnoreCase(namespaceURI, "content", attributes); } return -1; } protected int getIndexIgnoreCase( String namespaceURI, String localName, Attributes attributes) { for (int i = 0; i < attributes.getLength(); i++) { String uri = attributes.getURI(i); if ((StringUtil.isEmpty(uri) || namespaceURI.equals(uri)) && localName.equalsIgnoreCase(attributes.getLocalName(i))) { return i; } } return -1; } public void startElement(String namespaceURI, String localName, String qName, Attributes attributes) { Attributes wrapedAttributes = wrapContentTypeConverter( namespaceURI, localName, qName, attributes); super.startElement(namespaceURI, localName, qName, wrapedAttributes); } protected String removeIgnorableWhitespace(String characters) { StringBuffer buffer = new StringBuffer(characters.length()); String[] line = characters.split("\n"); for (int i = 0; i < line.length; i++) { if (line[i].trim().length() > 0) { String token = line[i].replaceAll("^[ \t]+", ""); token = token.replaceAll("[ \t]+$", ""); buffer.append(token.replaceAll("[ \t]+", " ")); if (i < line.length - 1) { buffer.append("\n"); } } else if (i == 0) { buffer.append("\n"); } } return buffer.toString(); } protected void processEntity(String name) { String entityRef = "&" + name + ";"; appendCharactersBuffer(entityRef); } public void comment(char[] buffer, int start, int length) { addCharactersNode(); String comment = new String(buffer, start, length); if (isSSIIncludeReplacementEnabled()) { String trimmed = comment.trim(); if (trimmed.startsWith(INCLUDE_PREFIX)) { Matcher match = INCLUDE_PATTERN.matcher(trimmed); if (match.find()) { LOG.debug("replace SSI to m:insert. " + trimmed); includeToInsert(match.group(2)); return; } } } SpecificationNode node = addNode(QM_COMMENT); node.setDefaultNamespaceURI(node.getParentSpace().getDefaultNamespaceURI()); node.addAttribute(QM_TEXT, comment); } public void startCDATA() { addCharactersNode(); SpecificationNode node = addNode(QM_CDATA); node.setParentNode(getCurrentNode()); setCurrentNode(node); enterCData(); } public void endCDATA() { addCharactersNode(); setCurrentNode(getCurrentNode().getParentNode()); leaveCData(); } /** * <!--#include virtual="/common/include.html?foo=bar&amp;bar=baz#fragment" --> */ private static final String INCLUDE_PREFIX = "#include"; private static final Pattern INCLUDE_PATTERN = Pattern.compile("#include\\s+(virtual|file)\\s*=\\s*\"([^\"]+)\"\\s*"); private static final Pattern AMPERSAND_PATTERN = Pattern.compile("(&|&)"); protected static final String AUTO_INSERT_NAMESPACE = "autoinsertnamespace"; /** * SSI includeの記述を m:insert に置き換える。virtual, fileの両方に対応。 * ただし、本来fileでは上位階層を指定できないが、ここでは制限を意識しない。 * <p> * <!--#include virtual="/common/include.html?foo=bar&amp;bar=baz#fragment" --> * </p> * @param virtualValue virtual属性の値 * @throws NullPointerException virtualValueがnullの場合に発生する。 */ protected void includeToInsert(String virtualValue) { if (virtualValue == null) { throw new IllegalArgumentException("virtualValue must not null."); } String[] parsed = StringUtil.parseURIQuery(virtualValue, EngineUtil.getEngineSetting(CONST_IMPL.SUFFIX_SEPARATOR, "$")); includeToInsert(parsed[0], parsed[1], parsed[2]); } protected void includeToInsert(String componentPath, String query, String componentName) { URI namespace = URI_MAYAA; NodeTreeWalker current = getCurrentNode(); if (current instanceof SpecificationNode) { namespace = ((SpecificationNode) current).getDefaultNamespaceURI(); } SpecificationNode newNode = addNode(QNameImpl.getInstance(namespace, "span")); newNode.setDefaultNamespaceURI(newNode.getParentSpace().getDefaultNamespaceURI()); String mayaaPrefix = "mmmmmmmmmmmm";// 重複しないようにまず使われないものを。 newNode.addPrefixMapping(mayaaPrefix, URI_MAYAA); newNode.addAttribute(QM_INJECT, mayaaPrefix + ":insert"); newNode.addAttribute(QNameImpl.getInstance("path"), componentPath); if (StringUtil.hasValue(componentName)) { newNode.addAttribute(QNameImpl.getInstance("name"), componentName); } if (StringUtil.hasValue(query)) { URI parameterURI = URIImpl.getInstance(AUTO_INSERT_NAMESPACE);// 重複しないようにまず使われないものを。 String[] parameters = AMPERSAND_PATTERN.split(query); for (int i = 0; i < parameters.length; i++) { String name; String value; int eq = parameters[i].indexOf('='); if (eq > 0) { name = parameters[i].substring(0, eq).trim(); value = parameters[i].substring(eq + 1).trim(); } else { name = parameters[i].trim(); value = ""; } newNode.addAttribute(QNameImpl.getInstance(parameterURI, name), value); } } } protected static class ContentTypeConvertAttributes implements Attributes { private Attributes _original; private int _targetIndex; private String _newValue; public ContentTypeConvertAttributes(Attributes original, int targetIndex, String newValue) { _original = original; _targetIndex = targetIndex; _newValue = newValue; } /** * Look up the index of an attribute by Namespace name. * @param uri The Namespace URI, or the empty string if the name has no Namespace URI. * @param localName The attribute's local name. * @return The index of the attribute, or -1 if it does not appear in the list. * @see org.xml.sax.Attributes#getIndex(java.lang.String, java.lang.String) */ public int getIndex(String uri, String localName) { return _original.getIndex(uri, localName); } /** * Look up the index of an attribute by XML 1.0 qualified name. * @param qName The qualified (prefixed) name. * @return The index of the attribute, or -1 if it does not appear in the list. * @see org.xml.sax.Attributes#getIndex(java.lang.String) */ public int getIndex(String qName) { return _original.getIndex(qName); } /** * Return the number of attributes in the list. * Once you know the number of attributes, you can iterate through the list. * @return The number of attributes in the list. * @see org.xml.sax.Attributes#getLength() */ public int getLength() { return _original.getLength(); } /** * Look up an attribute's local name by index. * @param index The attribute index (zero-based). * @return The local name, or the empty string if Namespace processing is not being performed, or null if the index is out of range. * @see org.xml.sax.Attributes#getLocalName(int) */ public String getLocalName(int index) { return _original.getLocalName(index); } /** * Look up an attribute's XML 1.0 qualified name by index. * @param The attribute index (zero-based). * @return The XML 1.0 qualified name, or the empty string if none is available, or null if the index is out of range. * @see org.xml.sax.Attributes#getQName(int) */ public String getQName(int index) { return _original.getQName(index); } /** * @param The attribute index (zero-based). * @return The attribute's type as a string, or null if the index is out of range. * @see org.xml.sax.Attributes#getType(int) */ public String getType(int index) { return _original.getType(index); } /** * @param uri The Namespace URI, or the empty String if the name has no Namespace URI. * @param localName The local name of the attribute. * @return The attribute type as a string, or null if the attribute is not in the list or if Namespace processing is not being performed. * @see org.xml.sax.Attributes#getType(java.lang.String, java.lang.String) */ public String getType(String uri, String localName) { return _original.getType(uri, localName); } /** * @param qName The XML 1.0 qualified name. * @return The attribute type as a string, or null if the attribute is not in the list or if qualified names are not available. * @see org.xml.sax.Attributes#getType(java.lang.String) */ public String getType(String qName) { return _original.getType(qName); } /** * @param index The attribute index (zero-based). * @return The Namespace URI, or the empty string if none is available, or null if the index is out of range. * @see org.xml.sax.Attributes#getURI(int) */ public String getURI(int index) { return _original.getURI(index); } /** * @param index The attribute index (zero-based). * @return The attribute's value as a string, or null if the index is out of range. * @see org.xml.sax.Attributes#getValue(int) */ public String getValue(int index) { if (_targetIndex == index) { return _newValue; } return _original.getValue(index); } /** * @param uri The Namespace URI, or the empty String if the name has no Namespace URI. * @param localName The local name of the attribute. * @return The attribute value as a string, or null if the attribute is not in the list. * @see org.xml.sax.Attributes#getValue(java.lang.String, java.lang.String) */ public String getValue(String uri, String localName) { if (getIndex(uri, localName) == _targetIndex) { return _newValue; } return _original.getValue(uri, localName); } /** * @param qName The XML 1.0 qualified name. * @return The attribute value as a string, or null if the attribute is not in the list or if qualified names are not available. * @see org.xml.sax.Attributes#getValue(java.lang.String) */ public String getValue(String qName) { if (getIndex(qName) == _targetIndex) { return _newValue; } return _original.getValue(qName); } } }