/* * Copyright 2012 astamuse company,Ltd. * * 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 com.astamuse.asta4d.template; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.jsoup.helper.StringUtil; import org.jsoup.nodes.Attribute; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; import org.jsoup.select.Elements; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.astamuse.asta4d.Configuration; import com.astamuse.asta4d.extnode.ExtNode; import com.astamuse.asta4d.extnode.ExtNodeConstants; import com.astamuse.asta4d.extnode.GroupNode; import com.astamuse.asta4d.util.IdGenerator; import com.astamuse.asta4d.util.SelectorUtil; public class TemplateUtil { private static class SnippetNode extends ExtNode { /** * * @param renderer * a plain text renderer declaration */ public SnippetNode(String renderer) { super(ExtNodeConstants.SNIPPET_NODE_TAG); this.attr(ExtNodeConstants.SNIPPET_NODE_ATTR_STATUS, ExtNodeConstants.SNIPPET_NODE_ATTR_STATUS_READY); this.attr(ExtNodeConstants.SNIPPET_NODE_ATTR_RENDER, renderer); } } private final static Logger logger = LoggerFactory.getLogger(TemplateUtil.class); public final static void regulateElement(String path, Document doc) throws TemplateException, TemplateNotFoundException { // disabled. see {@link #loadStaticEmebed} // load static embed at first // loadStaticEmebed(doc); regulateSnippets(path, doc); regulateMsgs(path, doc); regulateEmbed(doc); } private final static String createSnippetRef() { return "sn-" + IdGenerator.createId(); } private final static void regulateMsgs(String path, Document doc) { List<Element> msgElems = doc.select(ExtNodeConstants.MSG_NODE_TAG_SELECTOR); for (Element element : msgElems) { // record template path if (!element.hasAttr(ExtNodeConstants.ATTR_TEMPLATE_PATH)) { element.attr(ExtNodeConstants.ATTR_TEMPLATE_PATH, path); } } } private final static void regulateSnippets(String path, Document doc) { // find nodes emebed with snippet attribute String snippetSelector = SelectorUtil.attr(ExtNodeConstants.SNIPPET_NODE_ATTR_RENDER_WITH_NS); snippetSelector = SelectorUtil.not(snippetSelector, ExtNodeConstants.SNIPPET_NODE_TAG_SELECTOR); List<Element> embedSnippets = new ArrayList<>(doc.select(snippetSelector)); // Element // Node parent; SnippetNode fakedSnippetNode; String render; for (Element element : embedSnippets) { render = element.attr(ExtNodeConstants.SNIPPET_NODE_ATTR_RENDER_WITH_NS); fakedSnippetNode = new SnippetNode(render); fakedSnippetNode.attr(ExtNodeConstants.SNIPPET_NODE_ATTR_TYPE, ExtNodeConstants.SNIPPET_NODE_ATTR_TYPE_FAKE); // move the original node under the faked node element.after(fakedSnippetNode); element.remove(); fakedSnippetNode.appendChild(element); element.removeAttr(ExtNodeConstants.SNIPPET_NODE_ATTR_RENDER_WITH_NS); // set parallel type if (element.hasAttr(ExtNodeConstants.SNIPPET_NODE_ATTR_PARALLEL_WITH_NS)) { fakedSnippetNode.attr(ExtNodeConstants.SNIPPET_NODE_ATTR_PARALLEL, ""); element.removeAttr(ExtNodeConstants.SNIPPET_NODE_ATTR_PARALLEL_WITH_NS); } } /* * set all the nodes without status attribute or with an illegal status * value to ready */ // first, we regulate the snippets to legal form List<Element> snippetNodes = doc.select(ExtNodeConstants.SNIPPET_NODE_TAG_SELECTOR); String status; for (Element sn : snippetNodes) { // record template path if (!sn.hasAttr(ExtNodeConstants.ATTR_TEMPLATE_PATH)) { sn.attr(ExtNodeConstants.ATTR_TEMPLATE_PATH, path); } // regulate status if (sn.hasAttr(ExtNodeConstants.SNIPPET_NODE_ATTR_STATUS)) { status = sn.attr(ExtNodeConstants.SNIPPET_NODE_ATTR_STATUS); switch (status) { case ExtNodeConstants.SNIPPET_NODE_ATTR_STATUS_READY: case ExtNodeConstants.SNIPPET_NODE_ATTR_STATUS_WAITING: case ExtNodeConstants.SNIPPET_NODE_ATTR_STATUS_FINISHED: // do nothing; break; default: sn.attr(ExtNodeConstants.SNIPPET_NODE_ATTR_STATUS, ExtNodeConstants.SNIPPET_NODE_ATTR_STATUS_READY); } } else { sn.attr(ExtNodeConstants.SNIPPET_NODE_ATTR_STATUS, ExtNodeConstants.SNIPPET_NODE_ATTR_STATUS_READY); } // regulate id if (!sn.hasAttr(ExtNodeConstants.ATTR_SNIPPET_REF)) { sn.attr(ExtNodeConstants.ATTR_SNIPPET_REF, createSnippetRef()); } // regulate type if (!sn.hasAttr(ExtNodeConstants.SNIPPET_NODE_ATTR_TYPE)) { sn.attr(ExtNodeConstants.SNIPPET_NODE_ATTR_TYPE, ExtNodeConstants.SNIPPET_NODE_ATTR_TYPE_USERDEFINE); } switch (sn.attr(ExtNodeConstants.SNIPPET_NODE_ATTR_TYPE)) { case ExtNodeConstants.SNIPPET_NODE_ATTR_TYPE_FAKE: case ExtNodeConstants.SNIPPET_NODE_ATTR_TYPE_USERDEFINE: // do nothing; break; default: // we do not allow snippet node has user customized type // attribute sn.attr(ExtNodeConstants.SNIPPET_NODE_ATTR_TYPE, ExtNodeConstants.SNIPPET_NODE_ATTR_TYPE_USERDEFINE); } } // then let us check the nested relation for nodes without block attr snippetSelector = SelectorUtil.attr(ExtNodeConstants.SNIPPET_NODE_ATTR_BLOCK); snippetSelector = SelectorUtil.not(ExtNodeConstants.SNIPPET_NODE_TAG_SELECTOR, snippetSelector); snippetNodes = doc.select(snippetSelector); setBlockingParentSnippetId(snippetNodes); } private final static void regulateEmbed(Document doc) throws TemplateException, TemplateNotFoundException { // check nodes without block attr for blocking parent snippets String selector = SelectorUtil.attr(ExtNodeConstants.EMBED_NODE_ATTR_BLOCK); selector = SelectorUtil.not(ExtNodeConstants.EMBED_NODE_TAG_SELECTOR, selector); List<Element> embedElemes = doc.select(selector); setBlockingParentSnippetId(embedElemes); } /** * Disabled static embed at 2014.09.26. * * Developers would like to use different snippets to render a same static embed file as following: * * <pre> * <afd:snippet render="SomeSnippet"> * <afd:embed target="/someEmbed.html" static/> * </afd:snippet> * </pre> * * Which confuses rendering logic and makes bad source smell, thus we decide to disable this feature. * * @param doc * @throws TemplateException * @throws TemplateNotFoundException */ @SuppressWarnings("unused") @Deprecated private final static void loadStaticEmebed(Document doc) throws TemplateException, TemplateNotFoundException { String selector = SelectorUtil.attr(SelectorUtil.tag(ExtNodeConstants.EMBED_NODE_TAG_SELECTOR), ExtNodeConstants.EMBED_NODE_ATTR_STATIC, null); int embedNodeListCount; do { List<Element> embedNodeList = doc.select(selector); embedNodeListCount = embedNodeList.size(); Iterator<Element> embedNodeIterator = embedNodeList.iterator(); Element embed; Element embedContent; while (embedNodeIterator.hasNext()) { embed = embedNodeIterator.next(); embedContent = getEmbedNodeContent(embed); mergeBlock(doc, embedContent); embed.before(embedContent); embed.remove(); } } while (embedNodeListCount > 0); } private final static void setBlockingParentSnippetId(List<Element> elems) { Element searchElem; String blockingParentId; for (Element elem : elems) { searchElem = elem.parent(); blockingParentId = ""; while (searchElem != null) { if (searchElem.tagName().equals(ExtNodeConstants.SNIPPET_NODE_TAG)) { blockingParentId = searchElem.attr(ExtNodeConstants.ATTR_SNIPPET_REF); break; } else { searchElem = searchElem.parent(); } } elem.attr(ExtNodeConstants.SNIPPET_NODE_ATTR_BLOCK, blockingParentId); // TODO should we replace the blocked element to a dummy place // holder element to avoid being rendered by parent snippets } } public final static void resetSnippetRefs(Element elem) { String snippetRefSelector = SelectorUtil.attr(ExtNodeConstants.ATTR_SNIPPET_REF); List<Element> snippets = new ArrayList<>(elem.select(snippetRefSelector)); String oldRef, newRef; String blockedSnippetSelector; List<Element> blockedSnippets; for (Element element : snippets) { oldRef = element.attr(ExtNodeConstants.ATTR_SNIPPET_REF); newRef = createSnippetRef(); // find blocked snippet blockedSnippetSelector = SelectorUtil.attr(ExtNodeConstants.SNIPPET_NODE_TAG_SELECTOR, ExtNodeConstants.SNIPPET_NODE_ATTR_BLOCK, oldRef); blockedSnippets = new ArrayList<>(elem.select(blockedSnippetSelector)); // find blocked embed blockedSnippetSelector = SelectorUtil.attr(ExtNodeConstants.EMBED_NODE_TAG_SELECTOR, ExtNodeConstants.SNIPPET_NODE_ATTR_BLOCK, oldRef); blockedSnippets.addAll(elem.select(blockedSnippetSelector)); for (Element be : blockedSnippets) { be.attr(ExtNodeConstants.SNIPPET_NODE_ATTR_BLOCK, newRef); } element.attr(ExtNodeConstants.ATTR_SNIPPET_REF, newRef); } } public final static Element getEmbedNodeContent(Element elem) throws TemplateException { String target; Configuration conf = Configuration.getConfiguration(); TemplateResolver templateResolver = conf.getTemplateResolver(); target = elem.attr(ExtNodeConstants.EMBED_NODE_ATTR_TARGET); if (target == null || target.isEmpty()) { String message = "Target not defined[" + elem.toString() + "]"; throw new TemplateException(message); } Template embedTarget; try { embedTarget = templateResolver.findTemplate(target); } catch (TemplateNotFoundException e) { throw new TemplateException(e); } // TODO all of the following process should be merged into template // analyze process and be cached. Document embedDoc = embedTarget.getDocumentClone(); /* Elements children = embedDoc.body().children(); Element wrappingNode = ElementUtil.wrapElementsToSingleNode(children); */ Element wrappingNode = new GroupNode(ExtNodeConstants.GROUP_NODE_ATTR_TYPE_EMBED_WRAPPER); // retrieve all the blocks that misincluded into head Element head = embedDoc.head(); Elements headChildren = head.children(); for (Element child : headChildren) { if (StringUtil.in(child.tagName(), "script", "link", ExtNodeConstants.BLOCK_NODE_TAG)) { child.remove(); wrappingNode.appendChild(child); } } Element body = embedDoc.body(); Elements bodyChildren = body.children(); wrappingNode.insertChildren(-1, bodyChildren); // copy all the attrs to the wrapping group node Iterator<Attribute> attrs = elem.attributes().iterator(); Attribute attr; while (attrs.hasNext()) { attr = attrs.next(); wrappingNode.attr(attr.getKey(), attr.getValue()); } // a embed template file may by included many times in same parent // template, so we have to avoid duplicated snippet refs resetSnippetRefs(wrappingNode); return wrappingNode; } public final static void mergeBlock(Document doc, Element content) { Iterator<Element> blockIterator = content.select(ExtNodeConstants.BLOCK_NODE_TAG_SELECTOR).iterator(); Element block, targetBlock; String blockTarget, blockType; List<Node> childNodes; while (blockIterator.hasNext()) { block = blockIterator.next(); if (block.hasAttr(ExtNodeConstants.BLOCK_NODE_ATTR_OVERRIDE)) { blockType = ExtNodeConstants.BLOCK_NODE_ATTR_OVERRIDE; } else if (block.hasAttr(ExtNodeConstants.BLOCK_NODE_ATTR_APPEND)) { blockType = ExtNodeConstants.BLOCK_NODE_ATTR_APPEND; } else if (block.hasAttr(ExtNodeConstants.BLOCK_NODE_ATTR_INSERT)) { blockType = ExtNodeConstants.BLOCK_NODE_ATTR_INSERT; } else if (!block.hasAttr("id")) { // TODO I want a approach to logging out template file path here logger.warn("The block does not declare its action or id correctlly.[{}]", block.toString()); continue; } else { continue; } blockTarget = block.attr(blockType); if (blockTarget == null || blockTarget.isEmpty()) { // TODO I want a approach to logging out template file path here logger.warn("The block does not declare its target action correctlly.[{}]", block.toString()); continue; } targetBlock = doc.select(SelectorUtil.id(ExtNodeConstants.BLOCK_NODE_TAG_SELECTOR, blockTarget)).first(); if (targetBlock == null) { // TODO I want a approach to logging out template file path here logger.warn("The block declares a not existed target block.[{}]", block.toString()); continue; } childNodes = new ArrayList<>(block.childNodes()); switch (blockType) { case ExtNodeConstants.BLOCK_NODE_ATTR_OVERRIDE: targetBlock.empty(); targetBlock.insertChildren(-1, childNodes); break; case ExtNodeConstants.BLOCK_NODE_ATTR_APPEND: targetBlock.insertChildren(-1, childNodes); break; case ExtNodeConstants.BLOCK_NODE_ATTR_INSERT: targetBlock.insertChildren(0, childNodes); break; } block.remove(); } } }