/* * 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.engine.processor; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.Stack; import org.cyberneko.html.HTMLElements; import org.seasar.mayaa.builder.SequenceIDGenerator; import org.seasar.mayaa.cycle.ServiceCycle; import org.seasar.mayaa.cycle.script.CompiledScript; import org.seasar.mayaa.engine.Page; import org.seasar.mayaa.engine.processor.ProcessStatus; import org.seasar.mayaa.engine.processor.ProcessorProperty; import org.seasar.mayaa.engine.processor.ProcessorTreeWalker; import org.seasar.mayaa.engine.specification.Namespace; import org.seasar.mayaa.engine.specification.NodeTreeWalker; import org.seasar.mayaa.engine.specification.PrefixAwareName; import org.seasar.mayaa.engine.specification.PrefixMapping; 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.builder.BuilderUtil; import org.seasar.mayaa.impl.cycle.CycleUtil; import org.seasar.mayaa.impl.cycle.DefaultCycleLocalInstantiator; import org.seasar.mayaa.impl.engine.specification.SpecificationUtil; import org.seasar.mayaa.impl.util.StringUtil; /** * @author Masataka Kurihara (Gluegent, Inc.) */ public class ElementProcessor extends AbstractAttributableProcessor implements CONST_IMPL { private static final long serialVersionUID = -1041576023468766303L; private static final String SUFFIX_DUPLICATED = "_d"; private static final Set XHTML_EMPTY_ELEMENTS; private static final String RENDERED_NS_STACK_KEY = ElementProcessor.class.getName() + "#renderedNSStack"; private static final String CURRENT_NS_KEY = ElementProcessor.class.getName() + "#currentNS"; static { XHTML_EMPTY_ELEMENTS = new HashSet(); XHTML_EMPTY_ELEMENTS.add("base"); XHTML_EMPTY_ELEMENTS.add("meta"); XHTML_EMPTY_ELEMENTS.add("link"); XHTML_EMPTY_ELEMENTS.add("hr"); XHTML_EMPTY_ELEMENTS.add("br"); XHTML_EMPTY_ELEMENTS.add("param"); XHTML_EMPTY_ELEMENTS.add("img"); XHTML_EMPTY_ELEMENTS.add("area"); XHTML_EMPTY_ELEMENTS.add("input"); XHTML_EMPTY_ELEMENTS.add("col"); // transitional XHTML_EMPTY_ELEMENTS.add("basefont"); XHTML_EMPTY_ELEMENTS.add("isindex"); // nonstandard XHTML_EMPTY_ELEMENTS.add("frame"); XHTML_EMPTY_ELEMENTS.add("wbr"); XHTML_EMPTY_ELEMENTS.add("bgsound"); XHTML_EMPTY_ELEMENTS.add("nextid"); XHTML_EMPTY_ELEMENTS.add("sound"); XHTML_EMPTY_ELEMENTS.add("spacer"); CycleUtil.registVariableFactory(RENDERED_NS_STACK_KEY, new DefaultCycleLocalInstantiator() { public Object create(Object[] params) { return new Stack(); }}); CycleUtil.registVariableFactory(CURRENT_NS_KEY, new DefaultCycleLocalInstantiator() { public Object create(Object[] params) { SpecificationNode originalNode = (SpecificationNode) params[0]; Namespace currentNS = SpecificationUtil.createNamespace(); currentNS.setParentSpace(originalNode.getParentSpace()); for (Iterator it = originalNode.iteratePrefixMapping(false); it.hasNext();) { PrefixMapping prefixMapping = (PrefixMapping) it.next(); currentNS.addPrefixMapping(prefixMapping.getPrefix(), prefixMapping.getNamespaceURI()); } currentNS.setDefaultNamespaceURI(originalNode.getDefaultNamespaceURI()); return currentNS; }}); } private PrefixAwareName _name; private boolean _duplicated; protected void clearCurrentNS() { CycleUtil.clearGlobalVariable(CURRENT_NS_KEY); } protected Namespace getCurrentNS() { return (Namespace) CycleUtil.getGlobalVariable(CURRENT_NS_KEY, new Object[] { getOriginalNode() }); } public void notifyBeginRender(Page topLevelPage) { CycleUtil.clearGlobalVariable(RENDERED_NS_STACK_KEY); } protected Stack getRenderedNS() { return (Stack) CycleUtil.getGlobalVariable(RENDERED_NS_STACK_KEY, null); } protected void addRendered(PrefixMapping mapping) { ((List) getRenderedNS().peek()).add(mapping); } protected String alreadyRenderedPrefix(URI namespaceURI) { Iterator iteratorRendered = getRenderedNS().iterator(); while (iteratorRendered.hasNext()) { Iterator mappings = ((List) iteratorRendered.next()).iterator(); while (mappings.hasNext()) { PrefixMapping mapping = (PrefixMapping) mappings.next(); if (mapping.getNamespaceURI().equals(namespaceURI)) { return mapping.getPrefix(); } } } return null; } // MLD property public void setDuplicated(boolean duplicated) { _duplicated = duplicated; } // exported property public boolean isDuplicated() { return _duplicated; } // MLD property public void setName(PrefixAwareName name) { if (name == null) { throw new IllegalArgumentException(); } _name = name; } protected PrefixAwareName getName() { if (_name == null) { throw new IllegalStateException(); } return _name; } public String getUniqueID() { String uniqueID = super.getUniqueID(); if (isDuplicated()) { uniqueID = uniqueID + SUFFIX_DUPLICATED; } return uniqueID; } public Class getExpectedClass() { return String.class; } protected void resolvePrefix(PrefixAwareName name, Namespace currentNS) { if (name == null) { throw new IllegalArgumentException(); } URI namespaceURI = name.getQName().getNamespaceURI(); PrefixMapping mapping = currentNS.getMappingFromURI(namespaceURI, true); if (mapping != null) { return; } Namespace namespace = getInjectedNode().getParentSpace(); if (namespace != null) { mapping = namespace.getMappingFromURI(namespaceURI, true); if (mapping != null) { currentNS.addPrefixMapping( mapping.getPrefix(), mapping.getNamespaceURI()); return; } } currentNS.addPrefixMapping(name.getPrefix(), namespaceURI); } protected void resolvePrefixAll() { Namespace currentNS = getCurrentNS(); if (CycleUtil.isDraftWriting() == false) { currentNS = SpecificationUtil.copyNamespace(currentNS); } resolvePrefix(getName(), currentNS); for (Iterator it = iterateProcesstimeProperties(); it.hasNext();) { ProcessorProperty prop = (ProcessorProperty) it.next(); resolvePrefix(prop.getName(), currentNS); } for (Iterator it = iterateInformalProperties(); it.hasNext();) { ProcessorProperty prop = (ProcessorProperty) it.next(); resolvePrefix(prop.getName(), currentNS); } } protected String getResolvedPrefix(PrefixAwareName name) { if (name == null) { throw new IllegalArgumentException(); } if (URI_MAYAA.equals(name.getQName().getNamespaceURI())) { return ""; } String prefix = alreadyRenderedPrefix( name.getQName().getNamespaceURI()); if (prefix != null) { return prefix; } Namespace currentNS = getCurrentNS(); URI namespaceURI = name.getQName().getNamespaceURI(); PrefixMapping mapping = currentNS.getMappingFromURI(namespaceURI, true); if (mapping != null) { return mapping.getPrefix(); } // new inject prefixMapping currentNS.addPrefixMapping("", namespaceURI); return ""; } protected boolean appendPrefixMappingString( StringBuffer buffer, PrefixMapping mapping) { String pre = mapping.getPrefix(); URI uri = mapping.getNamespaceURI(); if (URI_MAYAA.equals(uri)) { return false; } if (URI_XML.equals(uri) && pre.equals("xml")) { return false; } if (StringUtil.hasValue(pre)) { buffer.append(" xmlns:").append(pre); } else { buffer.append(" xmlns"); } buffer.append("=\"").append(uri).append('"'); addRendered(mapping); return true; } protected void appendPrefixMappingStrings( StringBuffer buffer, Namespace namespace) { if (namespace == null) { throw new IllegalArgumentException(); } for (Iterator it = namespace.iteratePrefixMapping(false); it.hasNext();) { appendPrefixMappingString(buffer, (PrefixMapping) it.next()); } } protected void appendAttributeString( StringBuffer buffer, PrefixAwareName propName, Object value) { QName qName = propName.getQName(); if (URI_MAYAA.equals(qName.getNamespaceURI())) { return; } if (getInjectedNode().getQName().equals(QM_DUPLECATED)) { if (getChildProcessorSize() > 0 && getChildProcessor(0) instanceof JspProcessor) { JspProcessor processor = (JspProcessor)getChildProcessor(0); URI injectNS = processor.getInjectedNode().getQName().getNamespaceURI(); if (injectNS == qName.getNamespaceURI()) { return; } } } String attrPrefix = propName.getPrefix(); if (StringUtil.hasValue(attrPrefix)) { attrPrefix = getResolvedPrefix(propName); if (StringUtil.hasValue(attrPrefix)) { attrPrefix = attrPrefix + ":"; } } StringBuffer temp = new StringBuffer(32); temp.append(" "); temp.append(attrPrefix); temp.append(qName.getLocalName()); if (value != null) { temp.append("=\""); if (value instanceof CompiledScript) { CompiledScript script = (CompiledScript) value; if (CycleUtil.isDraftWriting()) { temp.append(script.getScriptText()); } else { Object result = script.execute(null); if (result == null) { return; } temp.append(result); } } else { temp.append(value.toString()); } temp.append("\""); } buffer.append(temp.toString()); } protected boolean needsCloseElement(QName qName) { if (isXHTML(qName) && XHTML_EMPTY_ELEMENTS.contains(qName.getLocalName())) { return false; } else if (isHTML(qName)) { HTMLElements.Element element = HTMLElements.getElement(qName.getLocalName()); return element.isEmpty() == false; } return getChildProcessorSize() > 0; } protected void write(String value) { ServiceCycle cycle = CycleUtil.getServiceCycle(); cycle.getResponse().write(value); } /** * 要素名をbufferに書き出します。 * * @param buffer 書き出す対象 */ protected void writeElementName(StringBuffer buffer) { QName qName = getName().getQName(); String prefix; prefix = getResolvedPrefix(getName()); if (StringUtil.hasValue(prefix)) { buffer.append(prefix).append(":"); } buffer.append(qName.getLocalName()); } /** * "<"と要素名をbufferに書き出します。 * * @param buffer 書き出す対象 */ protected void writePart1(StringBuffer buffer) { buffer.append("<"); writeElementName(buffer); } /** * 動的なattributeをbufferに書き出します。 * * @param buffer 書き出す対象 */ protected void writePart2(StringBuffer buffer) { for (Iterator it = iterateProcesstimeProperties(); it.hasNext();) { ProcessorProperty prop = (ProcessorProperty) it.next(); appendAttributeString(buffer, prop.getName(), prop.getValue()); } for (Iterator it = iterateInformalProperties(); it.hasNext();) { ProcessorProperty prop = (ProcessorProperty) it.next(); if (hasProcesstimeProperty(prop) == false && prop.getValue().isLiteral() == false) { appendAttributeString(buffer, prop.getName(), prop.getValue()); } } } /** * 静的なattributeと">"をbufferに書き出します。 * * @param buffer 書き出す対象 */ protected void writePart3(StringBuffer buffer) { for (Iterator it = iterateInformalProperties(); it.hasNext();) { ProcessorProperty prop = (ProcessorProperty) it.next(); if (hasProcesstimeProperty(prop) == false && prop.getValue().isLiteral()) { appendAttributeString(buffer, prop.getName(), prop.getValue()); } } QName qName = getName().getQName(); if (isXHTML(qName) && XHTML_EMPTY_ELEMENTS.contains(qName.getLocalName())) { buffer.append(" />"); } else if (isHTML(qName) || getChildProcessorSize() > 0) { buffer.append(">"); } else { buffer.append("></"); writeElementName(buffer); buffer.append(">"); } } /** * 静的な閉じタグをbufferに書き出します。 * * @param buffer 書き出す対象 */ protected void writePart4(StringBuffer buffer) { QName qName = getName().getQName(); if (needsCloseElement(qName)) { buffer.append("</"); writeElementName(buffer); buffer.append(">"); } } protected ProcessStatus writeStartElement() { if (getName() == null) { throw new IllegalStateException(); } StringBuffer buffer = new StringBuffer(128); writePart1(buffer); appendPrefixMappingStrings(buffer, getCurrentNS()); writePart2(buffer); writePart3(buffer); write(buffer.toString()); return ProcessStatus.EVAL_BODY_INCLUDE; } protected void writeBody(String body) { write(body); } protected void writeEndElement() { if (getName() == null) { throw new IllegalStateException(); } StringBuffer buffer = new StringBuffer(16); writePart4(buffer); write(buffer.toString()); } public ProcessStatus processStart(Page topLevelPage) { if (topLevelPage != null) { topLevelPage.registBeginRenderNotifier(this); } renderInit(); return super.processStart(topLevelPage); } public ProcessStatus processEnd() { ProcessStatus result = super.processEnd(); renderExit(); return result; } protected void renderInit() { clearCurrentNS(); getRenderedNS().push(new ArrayList()); resolvePrefixAll(); } protected void renderExit() { getRenderedNS().pop(); } public ProcessorTreeWalker[] divide(SequenceIDGenerator sequenceIDGenerator) { pushProcesstimeInfo(); renderInit(); try { if (getInjectedNode().getQName().equals( QM_TEMPLATE_ELEMENT) == false) { return new ProcessorTreeWalker[] { this }; } StringBuffer xmlnsDefs = new StringBuffer(); appendPrefixMappingStrings(xmlnsDefs, getCurrentNS()); if (xmlnsDefs.length() > 0) { // ネームスペース宣言がコンポーネントに引き継がれなくなるので動的なものとする。 return new ProcessorTreeWalker[] { this }; } List list = new ArrayList(); StringBuffer buffer = new StringBuffer(); writePart1(buffer); if (buffer.toString().length() > 0) { LiteralCharactersProcessor part1 = new LiteralCharactersProcessor(buffer.toString()); BuilderUtil.characterProcessorCopy( this, part1, sequenceIDGenerator); list.add(part1); } for (Iterator it = iterateProcesstimeProperties(); it.hasNext();) { ProcessorProperty prop = (ProcessorProperty) it.next(); buffer = new StringBuffer(); appendAttributeString(buffer, prop.getName(), prop.getValue()); CharactersProcessor part2 = new CharactersProcessor(prop, buffer.toString()); BuilderUtil.characterProcessorCopy(this, part2, sequenceIDGenerator); list.add(part2); } for (Iterator it = iterateInformalProperties(); it.hasNext();) { ProcessorProperty prop = (ProcessorProperty) it.next(); if (hasProcesstimeProperty(prop) == false && prop.getValue().isLiteral() == false) { buffer = new StringBuffer(); appendAttributeString(buffer, prop.getName(), prop.getValue()); CharactersProcessor part2 = new CharactersProcessor(prop, buffer.toString()); BuilderUtil.characterProcessorCopy(this, part2, sequenceIDGenerator); list.add(part2); } } buffer = new StringBuffer(); writePart3(buffer); if (buffer.toString().length() > 0) { LiteralCharactersProcessor part3 = new LiteralCharactersProcessor(buffer.toString()); BuilderUtil.characterProcessorCopy(this, part3, sequenceIDGenerator); list.add(part3); } int size = getChildProcessorSize(); for (int i = 0; i < size; i++) { list.add(getChildProcessor(i)); } buffer = new StringBuffer(); writePart4(buffer); if (buffer.toString().length() > 0) { LiteralCharactersProcessor part4 = new LiteralCharactersProcessor(buffer.toString()); BuilderUtil.characterProcessorCopy( this, part4, sequenceIDGenerator); list.add(part4); } //optimizeNodes(); clearChildProcessors(); return (ProcessorTreeWalker[]) list.toArray( new ProcessorTreeWalker[list.size()]); } finally { renderExit(); popProcesstimeInfo(); } } protected void optimizeNodes() { SpecificationNode originalNode = getOriginalNode(); NodeTreeWalker originalParent = originalNode.getParentNode(); for (Iterator it = originalParent.iterateChildNode(); it.hasNext(); ) { if (it.next() == originalNode) { it.remove(); break; } } for (Iterator it = getOriginalNode().iterateChildNode(); it.hasNext(); ) { NodeTreeWalker child = (NodeTreeWalker)it.next(); child.setParentNode(originalParent); originalParent.addChildNode(child); } getOriginalNode().clearChildNodes(); getOriginalNode().clearAttributes(); } }