All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.seasar.mayaa.impl.builder.TemplateNodeHandler Maven / Gradle / Ivy

Go to download

Mayaa is server side web template engine that is strongly aware of work sharing between programmers and designers based on HTML based templates.

The newest version!
/*
 * 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.io.IOException;
import java.io.InputStream;
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.Namespace;
import org.seasar.mayaa.engine.specification.NodeAttribute;
import org.seasar.mayaa.engine.specification.NodeTreeWalker;
import org.seasar.mayaa.engine.specification.PrefixMapping;
import org.seasar.mayaa.engine.specification.QName;
import org.seasar.mayaa.engine.specification.Specification;
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.builder.parser.ParserEncodingChangedException;
import org.seasar.mayaa.impl.engine.CharsetConverter;
import org.seasar.mayaa.impl.engine.EngineUtil;
import org.seasar.mayaa.impl.engine.specification.NamespaceImpl;
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;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.ext.EntityResolver2;
import org.xml.sax.ext.LexicalHandler;

/**
 * @author Koji Suga (Gluegent, Inc.)
 */
public class TemplateNodeHandler extends SpecificationNodeHandler implements EntityResolver2, LexicalHandler {

    private static final Log LOG = LogFactory.getLog(TemplateNodeHandler.class);

    private SpecificationNode _dtdNode = null;
    private boolean _outputTemplateWhitespace = true;
    private boolean _isSSIIncludeReplacementEnabled = false;

    private static final QName QH_ATTR_HTTP_EQUIV = QNameImpl.getInstance(CONST_IMPL.URI_HTML, "http-equiv");
    private static final QName QH_ATTR_CONTENT = QNameImpl.getInstance(CONST_IMPL.URI_HTML, "content");
    private static final QName QX_ATTR_HTTP_EQUIV = QNameImpl.getInstance(CONST_IMPL.URI_XHTML, "http-equiv");
    private static final QName QX_ATTR_CONTENT = QNameImpl.getInstance(CONST_IMPL.URI_XHTML, "content");
    private static final QName QH_ATTR_CHARSET = QNameImpl.getInstance(CONST_IMPL.URI_HTML, "charset");

    protected int _columnNumberBeforeFirstElement = -1;
    private int _inEntity;

    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;
    }

    /**
     * テンプレート由来のノードオブジェクトを生成する。
     */
    @Override
    protected SpecificationNode createChildNode(
            QName qName, String systemID, int lineNumber, int sequenceID, String prefix) {
        return SpecificationUtil.createSpecificationNode(
                qName, systemID, lineNumber, true, sequenceID, prefix);
    }

    protected boolean isRemoveWhitespace() {
        return _outputTemplateWhitespace == false;
    }

    /**
     * metaタグのhttp-equiv="content-type" の場合、contentのcharsetを変換して
     * ノードの属性にセットする。
     * それ以外の場合はなにもしない。
     *
     * @param node 対象となるSpecificationNodeインスタンス
     */
    final void convertContentType(final SpecificationNode node) {
        if (URI_HTML.equals(node.getDefaultNamespaceURI())) {
            convertContentType(node, QH_ATTR_HTTP_EQUIV, QH_ATTR_CONTENT);
        } else if (URI_XHTML.equals(node.getDefaultNamespaceURI())) {
            convertContentType(node, QX_ATTR_HTTP_EQUIV, QX_ATTR_CONTENT);
        }
    }
    protected void convertContentType(final SpecificationNode node, final QName attrHttpEquiv, final QName attrContent) {
        NodeAttribute na = node.getAttribute(attrHttpEquiv);
        if (na != null && "content-type".equalsIgnoreCase(na.getValue())) {
            na = node.getAttribute(attrContent);
            if (na != null) {
                String contentType = na.getValue();
                String encoding = CharsetConverter.extractEncodingExplict(contentType);
                String cunnrentEncoding = getSpecifiedEncoding();
                if (cunnrentEncoding == null || cunnrentEncoding.isEmpty()) {
                    if (encoding != null && !encoding.equals(CONST_IMPL.TEMPLATE_DEFAULT_CHARSET)) {
                        throw new ParserEncodingChangedException(encoding);
                    }
                } else if (encoding != null && !encoding.equals(cunnrentEncoding)) {
                    throw new ParserEncodingChangedException(encoding);
                }
                contentType = CharsetConverter.convertContentType(na.getValue());
                node.removeAttribute(attrContent);
                node.addAttribute(attrContent, contentType);
                return;
            }
        }
        // 
        na = node.getAttribute(QH_ATTR_CHARSET);
        if (na != null) {
            String encoding = na.getValue();
            String cunnrentEncoding = getSpecifiedEncoding();
            if (cunnrentEncoding == null || cunnrentEncoding.isEmpty()) {
                if (encoding != null && !encoding.equals(CONST_IMPL.TEMPLATE_DEFAULT_CHARSET)) {
                    throw new ParserEncodingChangedException(encoding);
                }
            } else if (encoding != null && !encoding.equals(cunnrentEncoding)) {
                throw new ParserEncodingChangedException(encoding);
            }
            encoding = CharsetConverter.encodingToCharset(encoding);

            node.removeAttribute(QH_ATTR_CHARSET);
            node.addAttribute(QH_ATTR_CHARSET, encoding);
        }
    }
    @Override
    public void startElement(String namespaceURI,
            String localName, String qName, Attributes attributes) {

        super.startElement(namespaceURI, localName, qName, attributes);
        if (qName.equalsIgnoreCase("meta")) {
            convertContentType((SpecificationNode) getCurrentNode());
        }
    }

    @Override
    public void ignorableWhitespace(char[] buffer, int start, int length) {
        appendCharactersBuffer(buffer, start, length);
    }

    @Override
    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);
    }

    @Override
    public void startCDATA() {
        addCharactersNode();
        SpecificationNode node = addNode(QM_CDATA);

        node.setParentNode(getCurrentNode());
        setCurrentNode(node);
        enterCData();
    }

    @Override
    public void endCDATA() {
        addCharactersNode();
        setCurrentNode(getCurrentNode().getParentNode());
        leaveCData();
    }

    @Override
    public void xmlDecl(String version, String encoding, String standalone) {
        super.xmlDecl(version, encoding, standalone);
        if ("xml".equalsIgnoreCase(getTemplate().getExtension())) {
            // XMLファイルはXercesパーサが使用されるがXML宣言の後の改行を返却してこないため強制的に付加する.
            // 改行がない時も付加してしまうが、互換性のため残す。
            appendCharactersBuffer("\n");
        }
    }

    /**
     * <!--#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では上位階層を指定できないが、ここでは制限を意識しない。
     * 

* <!--#include virtual="/common/include.html?foo=bar&amp;bar=baz#fragment" --> *

* @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); } } } //- implementation of DTD Catalog. // Java9 より標準の javax.xml.catalog.CatalogReader が提供されるので最低バージョンが上がるタイミングで標準半に置き換えたい。 @Override public InputSource getExternalSubset(String name, String baseURI) throws SAXException, IOException { return null; } @Override public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { return this.resolveEntity(null, publicId, null, systemId); } @Override /** * xhtmlのパースの際にリモートのDTD定義を取得しに行こうとするためJAR内に格納したものを返す。 */ public InputSource resolveEntity(String name, String publicId, String baseURI, String systemId) throws SAXException, IOException { String path = null; switch (systemId) { case "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd": case "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd": case "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd": path = "/dtd/" + systemId.substring("http://www.w3.org/".length()); break; case "xhtml-lat1.ent": case "xhtml-symbol.ent": case "xhtml-special.ent": path = "/dtd/" + systemId; break; default: break; } if (path != null) { InputStream is = getClass().getResourceAsStream(path); return new InputSource(is); } return null; } @Override Namespace getTopLevelNamespace() { Specification spec = getSpecification(); PrefixMapping defaultMapping = BuilderUtil.getPrefixMapping(spec.getSystemID()); URI defaultURI = defaultMapping.getNamespaceURI(); Namespace _topLevelNamespace = new NamespaceImpl(); _topLevelNamespace.setDefaultNamespaceURI(defaultURI); return _topLevelNamespace; } @Override public void startDTD(String name, String publicId, String systemId) { if (_dtdNode == null) { addCharactersNode(); _dtdNode = addNode(QM_DOCTYPE); } _dtdNode.removeAttribute(QM_NAME); _dtdNode.addAttribute(QM_NAME, name); _dtdNode.removeAttribute(QM_PUBLIC_ID); if (StringUtil.hasValue(publicId)) { _dtdNode.addAttribute(QM_PUBLIC_ID, publicId); } _dtdNode.removeAttribute(QM_SYSTEM_ID); if (StringUtil.hasValue(systemId)) { _dtdNode.addAttribute(QM_SYSTEM_ID, systemId); } } @Override public void endDTD() { } @Override public void characters(char[] buffer, int start, int length) { if (_inEntity == 0) { appendCharactersBuffer(buffer, start, length); } } protected void processEntity(String name) { appendCharactersBuffer('&' + name + ';'); } @Override public void startEntity(String name) { processEntity(name); ++_inEntity; } @Override public void endEntity(String name) { --_inEntity; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy