/* ================================================================== * Created [2009-4-27 下午11:32:55] by Jon.King * ================================================================== * TSS * ================================================================== * mailTo:jinpujun@hotmail.com * Copyright (c) Jon.King, 2009-2012 * ================================================================== */ package com.jinhe.tss.portal.engine; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import org.apache.log4j.Logger; import com.jinhe.tss.core.exception.BusinessException; import com.jinhe.tss.core.sso.Environment; import com.jinhe.tss.core.util.EasyUtils; import com.jinhe.tss.core.util.MacrocodeCompiler; import com.jinhe.tss.portal.EnvionmentVariables; import com.jinhe.tss.portal.PortalConstants; import com.jinhe.tss.portal.engine.macrocode.AbstractMacrocodeContainer; import com.jinhe.tss.portal.engine.macrocode.MacrocodeContainerFactory; import com.jinhe.tss.portal.engine.model.AbstractElementNode; import com.jinhe.tss.portal.engine.model.DecoratorConfigable; import com.jinhe.tss.portal.engine.model.DecoratorNode; import com.jinhe.tss.portal.engine.model.IPageElement; import com.jinhe.tss.portal.engine.model.LayoutConfigable; import com.jinhe.tss.portal.engine.model.LayoutNode; import com.jinhe.tss.portal.engine.model.Node; import com.jinhe.tss.portal.engine.model.PageNode; import com.jinhe.tss.portal.engine.model.PortalNode; import com.jinhe.tss.portal.engine.model.PortletNode; import com.jinhe.tss.portal.entity.FlowRate; import com.jinhe.tss.portal.helper.FlowrateManager; /** * <p> HTMLGenerator.java </p> * <p> * HTML页面代码生成器,可以根据Node节点生成相应的HTML页面文件信息 * </p> */ public class HTMLGenerator { Logger log = Logger.getLogger(HTMLGenerator.class); public static final String NAVIGATOR_CONTENT_INDEX = "navigatorContentIndex"; /** * 页面关键字 */ private List<String> keyword = new NoNullLinkedList<String>(); /** * 页面外挂js文件路径列表 */ private List<String> scriptFiles = new NoNullLinkedList<String>(); /** * 页面外挂css文件路径列表 */ private List<String> styleFiles = new NoNullLinkedList<String>(); /** * 页面脚本列表:页面下所有显示元素使用到的script脚本 */ private List<String> scriptCodes = new NoNullLinkedList<String>(); /** * 页面样式表列表:页面下所有显示元素使用到的样式表脚本 */ private List<String> styleCodes = new NoNullLinkedList<String>(); /** * 页面样式表列表:页面下所有显示同原型元素使用到的样式表脚本(同原型公用的样式)。 * 注意:此处用 Map 存放原型样式脚本,{组件ID, 样式},这样如果一个组件在某个页面上多次被引用到, * 原型样式CSS 输出一次就可以了 (使用Map同组件的原型样式将会相互覆盖,只留一份)。 */ private Map<Long, String> prototypeStyleCodes = new NoNullHashMap<Long, String>(); /** * 事件脚本 */ private List<String[]> eventCodes = new NoNullLinkedList<String[]>(); /** * 门户结构自定义的初始化脚本: 这些信息输出到html里只为了万一页面出错时检查用 */ private List<String> initCodes = new LinkedList<String>(); /** * 预览元素的路径(门户结构树上路径): Portlet实例,版面,……,版面,页面 */ private List<Node> treePath = new ArrayList<Node>(); /** * 目标节点:生成后对象在页面上所放的父版面ID */ private Long targetId; /** * 目标节点布局器的目标区域:生成后放到相应父版面布局器的相应区域中 */ private int targetIndex; /** * 门户加载的外部JS、CSS文件目录访问地址 */ private String portalResourseDir; /** * 页面标题 */ private String title; /** * 页面对应的自定义DOM对象 */ private Element dom = new Element(); /** * freemarker解析器 */ private FreemarkerParser freemarkerParser; /** * HTML页面生成器构造函数:用于生成完整页面HTML代码<br> * <br> * @param portal * 门户结构PortalNode节点 * @param id * 所要显示门户结构中某节点ID,可能是page、section or portletInstance * @param freemarkerParser * freemarker解析器 */ public HTMLGenerator(PortalNode portal, Long id, FreemarkerParser freemarkerParser) { this.title = portal.getName(); this.portalResourseDir = getPortalResourceDir(portal); this.freemarkerParser = freemarkerParser; PageNode page; if (id == null) { Set<Node> children = portal.getChildren(); page = children.size() > 0 ? (PageNode) children.toArray()[0] : null; } else { Node content = portal.getNodesMap().get(id); if(content == null) { throw new BusinessException("选中预览节点【ID:" + id +"】在门户缓存中不存在,如果节点为新增节点,请先刷新缓存"); } // 往上查找需要显示节点的父亲节点,直到门户根节点,把整个路径加入到treePath中来(不包括根节点) while ( !portal.equals(content) ) { treePath.add(content); content = content.getParent(); } page = treePath.size() > 0 ? (PageNode) treePath.get(treePath.size() - 1) : null; // path最后为PageNode } if (page != null) { dom = new Element(page); title = portal.getName() + "-" + page.getName(); keyword.add(portal.getName()); scriptFiles.addAll(portal.getScriptFiles()); styleFiles.addAll(portal.getStyleFiles()); scriptCodes.add(portal.getScriptCode()); styleCodes.add(portal.getStyleCode()); // 此处加入页面流量统计 FlowrateManager.getInstanse().output(new FlowRate(page.getId(), Environment.getClientIp())); } } /** 获取门户对应外部链接JS、CSS文件访问路径 */ private String getPortalResourceDir(PortalNode node) { String dirName = node.getCode() + "_" + node.getPortalId(); return EnvionmentVariables.getContextPath() + PortalConstants.PORTAL_MODEL_DIR + "/" + dirName + "/"; } /** * HTML页面生成器构造函数:生成部分页面元素HTML代码的XML字符串<br> * <br> * @param portal * 操作门户PortalNode对象<br> * @param id * 对应(版面/portlet替换)菜单中的 "版面/Portlet" ID,<br> * 即即将显示的节点Id<br> * @param targetId * 对应(版面/portlet替换)菜单中的目标版面/页面ID,<br> * 即被取代的区域(对应布局器中targetIndex的区域)所在的页面或版面ID */ public HTMLGenerator(PortalNode portal, Long id, Long targetId, FreemarkerParser freemarkerParser) { this.targetId = targetId; this.freemarkerParser = freemarkerParser; Node content = portal.getNodesMap().get(id); Node target = portal.getNodesMap().get(targetId); if( !(content instanceof IPageElement) ) { throw new BusinessException("(版面/portlet替换)菜单中指定的 版面/Portlet 有误,必须是版面或Portlet实例。"); } if( !(target instanceof LayoutConfigable) ) { throw new BusinessException("(版面/portlet替换)菜单中指定的 目标版面/页面 有误,必须是版面或页面。"); } this.dom = new Element((IPageElement)content, false); this.targetIndex = getMenuPointContentIndex((LayoutConfigable) target); } /** * <p> * 生成页面HTML代码 * </p> * @return String 当前页面的HTML代码 */ public String toHTML() { StringBuffer sb = new StringBuffer(); sb.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n"); sb.append("<HTML xmlns:TSS>\n"); sb.append("<HEAD>\n"); sb.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=gb2312\">\n"); sb.append("<TITLE>").append(title).append("</TITLE>\n"); sb.append(formatKeywords()); sb.append(formatStyleLinks()); sb.append(formatScriptLinks()); sb.append("<style>\n").append(formatStyleCodes()).append("</style>\n"); sb.append("<script language=\"javascript\">\n").append(formatScriptCodes()).append("</script>\n"); sb.append("</HEAD>\n"); return sb.append(dom).append("</HTML>").toString(); } public String toXML() { StringBuffer sb = new StringBuffer(); sb.append("<segment targetIndex=\"").append(targetIndex).append("\">"); sb.append("<html><![CDATA[").append(dom).append("]]></html>"); sb.append("<script><![CDATA[").append(formatSimpleScirptCodes()).append("]]></script>"); sb.append("<style>").append(formatStyleCodes()).append("</style>"); sb.append("<event>").append(formatEvents4XML()).append("</event>"); return sb.append("</segment>").toString(); } /** * 返回门户页面的关键字字符串 */ private String formatKeywords() { return "<META NAME=\"Keywords\" CONTENT=\"" + EasyUtils.list2Str(keyword) + "\">\n"; } /** * 导入返回外挂样式表文件标签 */ private StringBuffer formatStyleLinks() { StringBuffer sb = new StringBuffer(); for ( String filePath : styleFiles ) { sb.append("<link href=\"" + (portalResourseDir + filePath) + "\" rel=\"stylesheet\" type=\"text/css\">\n"); } return sb; } /** * 导入返回外挂脚本文件标签 */ private StringBuffer formatScriptLinks() { StringBuffer sb = new StringBuffer(); // 默认挂载的js String commonJSPath = EnvionmentVariables.getContextPath() + "/core/js/"; sb.append("<script language=\"javascript\" src=\"" + commonJSPath + "common.js\"></script>\n"); sb.append("<script language=\"javascript\" src=\"" + commonJSPath + "element.js\"></script>\n"); sb.append("<script language=\"javascript\" src=\"" + commonJSPath + "event.js\"></script>\n"); sb.append("<script language=\"javascript\" src=\"" + commonJSPath + "xml.js\"></script>\n"); sb.append("<script language=\"javascript\" src=\"" + commonJSPath + "xmlhttp.js\"></script>\n"); sb.append("<script language=\"javascript\" src=\"" + EnvionmentVariables.getContextPath() + "/pms/js/diy.js\"></script>\n"); for ( String filePath : scriptFiles ) { sb.append("<script language=\"javascript\" src=\"" + (portalResourseDir + filePath) + "\"></script>\n"); } return sb; } /** * 获取所有事件的XML代码 */ public StringBuffer formatEvents4XML() { StringBuffer sb = new StringBuffer(); for( String[] codes : eventCodes ) { sb.append("<attach event=\"" + codes[0] + "\" onevent=\"" + codes[1] + "\"/>\n"); } return sb; } /** * 所有JS脚本代码 */ private StringBuffer formatScriptCodes() { // 简单脚本代码:不包含事件部分代码 StringBuffer sb = new StringBuffer(); for( String script : scriptCodes ) { sb.append(script + "\n"); } sb.append("\n"); StringBuffer onloadEvent = new StringBuffer("window.onload = function() { \n"); for( String initCode : initCodes ) { onloadEvent.append(initCode).append("\n"); // 将每个node生成的js code换行分隔开来 } sb.append("\n"); for( String[] codes : eventCodes ) { if ("onload".equals(codes[0])) { onloadEvent.append(" " + codes[1] + "();").append("\n"); } else { sb.append("window." + codes[0] + "=" + codes[1]).append("\n"); } } onloadEvent.append("};").append("\n"); return sb.append(onloadEvent); } /** * 获取简单脚本代码:不包含事件部分代码。 * 用于toXML()方法。 */ public StringBuffer formatSimpleScirptCodes() { StringBuffer sb = new StringBuffer(); for( String script : scriptCodes ) { sb.append(script).append("\n"); } sb.append("\n"); for( String initCode : initCodes ) { sb.append(initCode).append("\n"); // 将每个node生成的js code换行分隔开来 } return sb; } /** * <p> * 所有样式表代码 * </p> * @return StringBuffer */ public StringBuffer formatStyleCodes() { StringBuffer sb = new StringBuffer(); for ( String prototypeStyle : prototypeStyleCodes.values() ) { sb.append(prototypeStyle).append("\n"); } sb.append("\n"); for ( String style : styleCodes ) { sb.append(style).append("\n"); } return sb; } /** * <p> * 【菜单所指内容】显示区域的索引号。<br> * 获取所在版面、页面默认菜单所指内容显示区域在相应布局器的序号。 * </p> * @param node * @return */ private int getMenuPointContentIndex(LayoutConfigable node) { LayoutNode layoutNode = node.getLayoutNode(); int absPortNumber = Math.abs( layoutNode.getPortNumber() ); // 【菜单所指内容】显示区域默认为布局器的【最后一个区域】 int menuPointContentIndex = absPortNumber - 1; // 取布局器参数里设置好了的菜单区域 String index = layoutNode.getParameters().get(NAVIGATOR_CONTENT_INDEX); if (index != null) { // 如果设置了默认替换区域的参数 try { int definedIndex = Integer.parseInt(index); if(definedIndex < absPortNumber) { menuPointContentIndex = definedIndex; } } catch (NumberFormatException e) { // 出错则取布局器的最后一个区域 } } return menuPointContentIndex; } /** * <p> * 页面生成时使用的自定义Element对象,用于生成页面HTML代码 * </p> */ public class Element { /** * 节点HTML片段对象 */ private List<String> htmlFragments = new ArrayList<String>(); /** * 空对象,用于空门户的展示 */ public Element() { this.htmlFragments.add("<body></body>"); } /** * 生成局部页面为XML时,为生成某个版面或Portlet实例HTML而实例化DOM对象。 * * @param node * 版面或Portlet实例 * @param isPreviewPage * true: 预览页面, 根据页面、版面下的子节点(IPageElement)创建Element对象 * false: 生成(局部页面:IPageElement)XML */ public Element(IPageElement node, boolean isPreviewPage) { Long id = node.getId(); String elementType = node.getPageElementType(); if(isPreviewPage){ HTMLGenerator.this.keyword.add(node.getName()); // 创建页面元素(包括版面和portletInstance)上下文关系的初始化代码, 定义门户结构关系 HTMLGenerator.this.initCodes.add(appentElementType(id, elementType)); HTMLGenerator.this.initCodes.add(appentElementParent(id, node.getParent().getId())); } else { // 创建页面上版面/Portlet实例对象上下文关系的修正代码 HTMLGenerator.this.initCodes.add(appentElementType(id, elementType)); HTMLGenerator.this.initCodes.add(appentElementParent(id, node.getParent().getId())); HTMLGenerator.this.initCodes.add(appentElementIndex(id, targetIndex)); HTMLGenerator.this.initCodes.add(appentElementCIndex(id, 0)); HTMLGenerator.this.initCodes.add("document.getElementById('" + targetId + "').subset[" + targetIndex + "] = [document.getElementById('" + id + "')];\n"); } createIPageElement(node); } /** * 创建页面元素(包括版面和portletInstance)的HTML代码 */ private void createIPageElement(IPageElement node) { String pageElementType = node.getPageElementType(); this.htmlFragments.add("\n<!-- (" + pageElementType + ")" + ((Node)node).getName() + " start-->\n"); this.htmlFragments.add("<div class=\"" + pageElementType + "\" id=\"" + ((Node)node).getId() + "\">"); DecoratorNode decoratorNode = ( (DecoratorConfigable) node).getDecoratorNode(); this.htmlFragments.add(new Element(decoratorNode).toHTML()); this.htmlFragments.add("</div>\n"); this.htmlFragments.add("\n<!-- (" + pageElementType + ")" + ((Node)node).getName() + " end-->\n"); } // 以下这些信息输出到html里为了万一页面出错时检查用 private String appentElementType( Object id, String type ) { // 定义门户结构的类型 return "document.getElementById('" + id + "').type = '" + type + "';\n"; } private String appentElementParent( Object id, Long parentId ) { // 定义门户结构父子关系 return "document.getElementById('" + id + "').parent = document.getElementById('" + parentId + "');\n"; } private String appentElementIndex( Object id, int index ) { return "document.getElementById('" + id + "').index = " + index + ";\n"; } private String appentElementCIndex( Object id, int cIndex ) { return "document.getElementById('" + id + "').cIndex = " + cIndex + ";\n"; } /** * Element对象构造函数,根据PageNode创建Element对象 */ public Element(PageNode node) { HTMLGenerator.this.keyword.add(node.getName()); HTMLGenerator.this.scriptFiles.addAll(node.getScriptFiles()); HTMLGenerator.this.styleFiles.addAll(node.getStyleFiles()); HTMLGenerator.this.scriptCodes.add(node.getScriptCode()); HTMLGenerator.this.styleCodes.add(node.getStyleCode()); HTMLGenerator.this.initCodes.add(appentElementType(node.getId(), "Page")); HTMLGenerator.this.initCodes.add("document.getElementById('" + node.getId() + "').portalId = " + node.getPortalId() + ";\n"); this.htmlFragments.add("<body id=\"" + node.getId() + "\">" + new Element(node.getDecoratorNode()) + "</body>"); } /** * Element对象构造函数,根据DecoratorNode创建Element对象 */ public Element(DecoratorNode node) { Long id = node.getId(); Long parentId = node.getParent().getId(); HTMLGenerator.this.scriptCodes.add(MacrocodeContainerFactory.newInstance(node.getScript(), node).compile()); HTMLGenerator.this.styleCodes.add (MacrocodeContainerFactory.newInstance(node.getStyle(), node).compile()); HTMLGenerator.this.prototypeStyleCodes.put(id, MacrocodeContainerFactory.newInstance(node.getPrototypeStyle(), node).compile()); // 创建页面修饰器上下文关系的初始化代码 HTMLGenerator.this.initCodes.add(appentElementType("D" + parentId, "Decorator")); HTMLGenerator.this.initCodes.add(appentElementParent("D" + parentId, parentId)); HTMLGenerator.this.initCodes.add("document.getElementById('" + parentId + "').decorator = document.getElementById('D" + parentId + "');\n"); HTMLGenerator.this.initCodes.add("document.getElementById('D" + parentId + "').decoratorId = " + id + ";\n"); // 添加事件 appendEventCodes(node); this.htmlFragments.add("\n<!-- (修饰器)" + node.getName() + " start-->\n"); DecoratorConfigable parent = (DecoratorConfigable) node.getParent(); Element content = createDecorateContent(parent.getDecoratorContent()); // 被修饰的内容 this.htmlFragments.add(MacrocodeContainerFactory.newInstance(node.getHtml(), node, content).compile()); this.htmlFragments.add("\n<!-- (修饰器)" + node.getName() + " end-->\n"); } /** * 创建修饰器修饰的元素(或叫修饰器的内容: layout或portlet)的Element对象 */ private Element createDecorateContent(AbstractElementNode node){ return (node instanceof LayoutNode) ? new Element((LayoutNode)node) : new Element((PortletNode)node); } /** * 设置布局器/修饰器/portlet相关事件脚本。 * 注意:各个门户结构的事件执行顺序有先后,按门户结构从上往下的顺序执行。 */ private void appendEventCodes(AbstractElementNode node) { for( Entry<String, String> entry : node.getEvents().entrySet() ){ AbstractMacrocodeContainer macrocodeContainer = MacrocodeContainerFactory.newInstance(entry.getValue(), node); HTMLGenerator.this.eventCodes.add(new String[] { entry.getKey(), macrocodeContainer.compile() } ); } } /** * Element对象构造函数,根据PortletNode创建Element对象 */ public Element(PortletNode node) { Long id = node.getId(); Long parentId = node.getParent().getId(); HTMLGenerator.this.keyword.add(node.getName()); HTMLGenerator.this.scriptCodes.add(MacrocodeContainerFactory.newInstance(node.getScript(), node).compile()); HTMLGenerator.this.styleCodes.add (MacrocodeContainerFactory.newInstance(node.getStyle(), node).compile()); HTMLGenerator.this.prototypeStyleCodes.put(id, MacrocodeContainerFactory.newInstance(node.getPrototypeStyle(), node).compile()); // 创建页面Portlet上下文关系的初始化代码 HTMLGenerator.this.initCodes.add(appentElementType("P" + parentId, "Portlet")); HTMLGenerator.this.initCodes.add(appentElementParent("P" + parentId, parentId)); HTMLGenerator.this.initCodes.add("document.getElementById('" + parentId + "').portlet = document.getElementById('P" + parentId + "');\n"); HTMLGenerator.this.initCodes.add("document.getElementById('P" + parentId + "').portletId = " + id + ";\n"); // 添加事件 appendEventCodes(node); this.htmlFragments.add("\n<!-- (Portlet)" + node.getName() + " start-->\n"); node.setFreemarkerParser(freemarkerParser); this.htmlFragments.add(MacrocodeContainerFactory.newInstance(node.getHtml(), node).compile()); this.htmlFragments.add("\n<!-- (Portlet)" + node.getName() + " end-->\n"); } /** * Element对象构造函数,根据LayoutNode创建Element对象 */ public Element(LayoutNode node) { Node parent = node.getParent(); Long id = node.getId(); Long parentId = parent.getId(); HTMLGenerator.this.scriptCodes.add(MacrocodeContainerFactory.newInstance(node.getScript(), node).compile()); HTMLGenerator.this.styleCodes.add (MacrocodeContainerFactory.newInstance(node.getStyle(), node).compile()); HTMLGenerator.this.prototypeStyleCodes.put(id, MacrocodeContainerFactory.newInstance(node.getPrototypeStyle(), node).compile()); // 创建页面布局器上下文关系的初始化代码 HTMLGenerator.this.initCodes.add(appentElementType("L" + parentId, "Layout")); HTMLGenerator.this.initCodes.add(appentElementParent("L" + parentId, parentId)); HTMLGenerator.this.initCodes.add("document.getElementById('" + parentId + "').layout = document.getElementById('L" + parentId + "');\n"); HTMLGenerator.this.initCodes.add("document.getElementById('L" + parentId + "').layoutId = " + id + ";\n"); appendEventCodes(node); // 事件 // 创建所有子节点对应的Element对象集合 int menuPointContentIndex = getMenuPointContentIndex((LayoutConfigable) parent); int portNumber = node.getPortNumber(); List<List<String>> childIds = new ArrayList<List<String>>(); // 二维List,数据如 : [[child1, child2], [child3], [child4]] Map<String, Element> portMappingElement = createChildElements(parent, portNumber, menuPointContentIndex, childIds); // 补上子节点所在版面位置索引号及子索引号定义脚本 for (int i = 0; i < childIds.size(); i++) { List<?> items = childIds.get(i); for (int j = 0; j < items.size(); j++) { HTMLGenerator.this.initCodes.add(appentElementIndex(items.get(j), i)); HTMLGenerator.this.initCodes.add(appentElementCIndex(items.get(j), j)); } } // 补上布局器所在版面(或页面)与子节点的父子关系定义脚本 HTMLGenerator.this.initCodes.add(getChildrenRelations(childIds, parent.getId())); this.htmlFragments.add("\n<!-- (布局器)" + node.getName() + " start-->\n"); this.htmlFragments.add(MacrocodeContainerFactory.newInstance(node.getHtml(), node, portMappingElement).compile()); this.htmlFragments.add("\n<!-- (布局器)" + node.getName() + " end-->\n"); } /** * <p> * 获取布局器所在版面(或页面)与子节点的父子关系定义脚本 * <pre> * document.getElementById('P1').subset = [ * [document.getElementById('child1'), document.getElementById('child2')], * [document.getElementById('child3')], * [document.getElementById('child4')] * ] * </pre> * </p> * @param childIds * @param parentId * @return StringBuffer */ private String getChildrenRelations(List<List<String>> childIds, Long parentId) { StringBuffer sb = new StringBuffer(); sb.append("document.getElementById('" + parentId + "').subset = ["); for (int i = 0; i < childIds.size(); i++) { if (i > 0) sb.append(", \n"); List<?> items = childIds.get(i); sb.append("["); for (int j = 0; j < items.size(); j++) { if (j > 0) sb.append(", \n"); sb.append("document.getElementById('" + items.get(j) + "')"); } sb.append("]"); } return sb.append("];\n").toString(); } /** * <p> * 根据页面、版面下布局器,创建子Element对象集合。 * 将布局器所在的门户结构节点下的所有子节点,逐个填入到布局器的格子中。 * 如果布局器为循环类型布局器,则把所有子节点填入;否则只把布局器填满为止,多余的子节点将不被展示。 * </p> * @param parent * 布局器所在的门户结构节点 * @param portNumber * @param menuPointContentIndex * @param childIds * @return Map */ private Map<String, Element> createChildElements(Node parent, int portNumber, int menuPointContentIndex, List<List<String>> childIds) { /* * 判断【选中浏览】的门户节点是否为当前parent的子节点。 * 如果是parent在treePath存在且非第一个节点,则说明其前面还有它的子节点存在 * treePath结构为: portletInstance(0...n)-->section(*)-->page */ int parentIndex = treePath.indexOf(parent); boolean oneChildSelected = parentIndex > 0; int index = -1; boolean flag = false; // 用来判断是否需要设置【菜单所指内容】到菜单格子 Map<String, Element> portMappingElement = new HashMap<String, Element>(); for ( Node child : parent.getChildren() ) { index++; if (index >= Math.abs(portNumber)) { if (portNumber < 0) { index = 0; // 如果布局器为循环布局器,则重新开始 } else { break; // 如果布局器已经满了则跳出,剩下的子节点将不会被生成放入进来 } } // 判断当前index是否为parent节点布局器中的【菜单所指内容】区域,且【选中浏览】的节点为parent的子节点 if(index == menuPointContentIndex && oneChildSelected) { flag = true; continue; // 先返回,该区域留待后面单独补上 } Element childElement = new Element((IPageElement) child, true); String portMacro = MacrocodeCompiler.createMacroCode("port" + index); // ${portX} // 如果portElement已经存在,则说明该index已经put过到childrenMap里。同一index再次被循环到,所以一定是循环类布局器 if (portMappingElement.get(portMacro) != null) { // 新增HTML片段对象,直接加入到上一格portElement.htmlFragments中,即两个子节点放同一Element里在同一port里显示。 Element portElement = portMappingElement.get(portMacro); portElement.htmlFragments.add(childElement.toHTML()); childIds.get(index).add(child.getId().toString()); } // 将新生产的Element对象放入childrenMap中 else { portMappingElement.put(portMacro, childElement); childIds.add( new ArrayList<String>(Arrays.asList(child.getId().toString())) ); } } // 将菜单所指的内容【版面或Portlet实例】设置到父版面或页面中指定放【菜单所指内容】的布局器窗口。 if ( flag ) { //【parentIndex - 1】即被【选中浏览】的节点在treePath里的序号,排在其parent前面。 Node content = treePath.get(parentIndex - 1); String portMacro = MacrocodeCompiler.createMacroCode("port" + menuPointContentIndex); portMappingElement.put(portMacro, new Element((IPageElement) content, true)); childIds.set(menuPointContentIndex, Arrays.asList(content.getId().toString())); } // 如果布局器的单元格中还有空的,则加一些【空Element】将其填满 int filledNum = portMappingElement.size(); // 已经填充的单元格个数 for ( int i = Math.abs(portNumber) - 1; i >= filledNum; i-- ) { final String id = "E" + parent.getId() + "_" + i; portMappingElement.put(MacrocodeCompiler.createMacroCode("port" + i), new Element() { public String toHTML() { return "<div id=\"" + id + "\" style=\"display:none\"></div>"; } }); // 放入空的element childIds.add( Arrays.asList(id) ); } return portMappingElement; } /** * 将Element对象转换成HTML代码 */ public String toHTML() { StringBuffer sb = new StringBuffer(); for ( String htmlFragment : this.htmlFragments ) { sb.append(htmlFragment); } return sb.toString(); } public String toString() { return toHTML(); } } /** * 非空值HashMap对象,所有元素值不可能为空 */ private class NoNullHashMap<K, V> extends HashMap<K, V> { private static final long serialVersionUID = -4073320799481823860L; /* 覆写HashMap的put方法,如果值为空,则不放入map中 */ public V put(K key, V value) { return EasyUtils.isNullOrEmpty(value) ? null : super.put(key, value); } } /** * 非空值LinkedList对象,所有元素不可能为空 */ private class NoNullLinkedList<T> extends LinkedList<T> { private static final long serialVersionUID = 1851415859447342905L; /* * 覆写LinkedList的add方法 * @see java.util.LinkedList#add(java.lang.Object) */ public boolean add(T obj) { return EasyUtils.isNullOrEmpty(obj) ? false : super.add(obj); } /* * 覆写LinkedList的addAll方法 * @see java.util.LinkedList#addAll(java.util.Collection) */ public boolean addAll(Collection<? extends T> c) { if(c == null) return false; for(Iterator<? extends T> it = c.iterator(); it.hasNext();){ if(it.next() == null) it.remove(); } return super.addAll(c); } } }