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

org.nasdanika.html.model.app.gen.Util Maven / Gradle / Ivy

package org.nasdanika.html.model.app.gen;

import static org.nasdanika.common.Util.isBlank;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.zip.Inflater;
import java.util.zip.InflaterOutputStream;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.text.StringEscapeUtils;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.json.JSONObject;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.nasdanika.common.Context;
import org.nasdanika.common.MutableContext;
import org.nasdanika.common.NasdanikaException;
import org.nasdanika.common.ProgressMonitor;
import org.nasdanika.exec.resources.Container;
import org.nasdanika.html.Button;
import org.nasdanika.html.HTMLFactory;
import org.nasdanika.html.Tag;
import org.nasdanika.html.TagName;
import org.nasdanika.html.bootstrap.BootstrapFactory;
import org.nasdanika.html.bootstrap.Breakpoint;
import org.nasdanika.html.bootstrap.Color;
import org.nasdanika.html.model.app.Action;
import org.nasdanika.html.model.app.ActionReference;
import org.nasdanika.html.model.app.AppFactory;
import org.nasdanika.html.model.app.AppPackage;
import org.nasdanika.html.model.app.ContentPanel;
import org.nasdanika.html.model.app.Footer;
import org.nasdanika.html.model.app.Header;
import org.nasdanika.html.model.app.Label;
import org.nasdanika.html.model.app.Link;
import org.nasdanika.html.model.app.NavigationBar;
import org.nasdanika.html.model.app.NavigationPanel;
import org.nasdanika.html.model.app.SectionStyle;
import org.nasdanika.html.model.bootstrap.Appearance;
import org.nasdanika.html.model.bootstrap.Item;
import org.nasdanika.html.model.bootstrap.Modal;

import com.mxgraph.io.mxCodec;
import com.mxgraph.model.mxGraphModel;
import com.mxgraph.model.mxICell;
import com.mxgraph.util.mxXmlUtils;


public final class Util {
	
	// Util
	private Util() {
		throw new UnsupportedOperationException("Utility class, not to be instantiated");
	}

	/**
	 * Creates Bootstrap Navs from a list of items.
	 * @param items
	 * @param bootstrapFactory
	 * @return
	 */
	public static Tag navs(List items, BootstrapFactory bootstrapFactory) {
		HTMLFactory htmlFactory = bootstrapFactory.getHTMLFactory();
		Tag navs = htmlFactory.tag(TagName.ul).addClass("nav");
		for (Object item: items) {
			Tag li = htmlFactory.tag(TagName.li, item).addClass("nav-item");
			navs.accept(li);
			if (item instanceof Tag) {
				Tag itemTag = (Tag) item;
				if (TagName.a.name().equalsIgnoreCase(itemTag.getTagName())) {
					itemTag.addClass("nav-link");
					Object data = itemTag.getData();
					if (data instanceof Item) {
						Item itemData = (Item) data;
						if (itemData.isActive()) {
							itemTag.addClass("active");
						} else if (itemData.isDisabled()) {
							itemTag.addClass("disabled");
						}
					}
				} else if (TagName.span.name().equalsIgnoreCase(itemTag.getTagName())) {
					itemTag.addClass("navbar-text");
				}
			}
		}
		return navs;
	}
	
	/**
	 * Creates Bootstrap Navs from a list of items.
	 * @param items
	 * @param bootstrapFactory
	 * @return
	 */
	public static Tag navbar(
			Tag brand, 
			List items,
			Breakpoint expand, 
			boolean dark,
			Color background,
			BootstrapFactory bootstrapFactory) {
		HTMLFactory htmlFactory = bootstrapFactory.getHTMLFactory();
		Tag navbar = htmlFactory.tag(TagName.ul).addClass("navbar", dark ? "navbar-dark" : "navbar-light");
		if (expand != null) {
			navbar.addClass("navbar-expand-" + expand.code);
		}
		if (background != null) {
			navbar.addClass("bg-" + background.code);
		}
		if (brand != null) {
			brand.addClass("navbar-brand");
			navbar.accept(brand);
		}
		String navbarContentId = htmlFactory.nextId();
		Button toggler = htmlFactory.button(htmlFactory.span().addClass("navbar-toggler-icon"));
		toggler
			.addClass("navbar-toggler")
			.attribute("type", "button")
			.attribute("data-toggle", "collapse")
			.attribute("data-target", "#" + navbarContentId)
			.attribute("aria-expanded", "false")
			.attribute("aria-label", "Toggle navigation");
		navbar.accept(toggler);
		
		Tag content = htmlFactory.div().addClass("collapse", "navbar-collapse").id(navbarContentId);
		navbar.accept(content);
		if (items != null && !items.isEmpty()) {
			Tag navs = navs(items, bootstrapFactory);
			navs.removeClass("nav").addClass("navbar-nav", "mr-auto");
			content.accept(navs);
		}
		return navbar;
	}
	
	/**
	 * Generates application site from an {@link Action} model root. 
	 * @param root Root action to generate header and footer. Can be null.
	 * @param pageTemplate Page template.
	 * @param context Context used for uri resolution - interpolation of action locations and names. can be null.
	 * @param container Receiver of generated resources.
	 * @param progressMonitor Progress monitor.
	 */
	public static void generateSite(
			Action root, 
			org.nasdanika.html.model.bootstrap.Page pageTemplate,
			Container container,
			Context context,
			ProgressMonitor progressMonitor) {
	
		Action principal = null;
		EList rootChildren = root.getChildren();
		if (rootChildren.size() > 0) {
			EObject firstChild = root.getChildren().get(0);
			if (firstChild instanceof Action) {
				principal = (Action) firstChild;
			} else if (firstChild instanceof ActionReference) {
				principal = ((ActionReference) firstChild).getTarget();
			}
		}
		
		generateSite(root, principal, root, Collections.emptyList(), pageTemplate, container, context, progressMonitor);
	}
	
	/**
	 * Generates application site from an {@link Action} model using a random base URI and a caching URI resolver created from the argument context with base-uri injected.
	 * @param root Root action to generate header and footer. Can be null.
	 * @param principal Principal action to generate navigation bar and navigation panel. Can be null.
	 * @param activeAction Active action to generate the content panel.
	 * @param actionPath Action path to show in breadcrumbs.
	 * @param pageTemplate Page template.
	 * @param context Context used for uri resolution - interpolation of action locations and names. can be null.
	 * @param container Receiver of generated resources.
	 * @param progressMonitor Progress monitor.
	 */
	public static void generateSite(
			Action root, 
			Action principal, 
			Action activeAction, 
			List actionPath,
			org.nasdanika.html.model.bootstrap.Page pageTemplate,
			Container container,
			Context context,
			ProgressMonitor progressMonitor) {
	
		if (context == null) {
			context = Context.EMPTY_CONTEXT;
		}
		if (context.get(Context.BASE_URI_PROPERTY) == null) {
			context = context.fork();
			((MutableContext) context).put(Context.BASE_URI_PROPERTY, URI.createURI("temp://" + UUID.randomUUID() + "/" + UUID.randomUUID() + "/"));
		}
		BiFunction uriResolver = uriResolver(root, context);		
		generateSite(
				root, 
				principal, 
				activeAction,
				actionPath,
				pageTemplate,
				uriResolver, 
				(URI) context.get(Context.BASE_URI_PROPERTY), 
				container, 
				progressMonitor);
	}
	
	/**
	 * Generates application site from an {@link Action} model
	 * @param root Root action to generate header and footer. Can be null.
	 * @param principal Principal action to generate navigation bar and navigation panel. Can be null.
	 * @param activeAction Active action to generate the content panel.
	 * @param pageTemplate Page template.
	 * @param uriResolver Resolves {@link Action} to {@link URI}.
	 * @param baseURI Base URI for resolution, specifically to create relative URI's.
	 * @param container Receiver of generated resources.
	 * @param progressMonitor Progress monitor.
	 */
	public static void generateSite(
			Action root, 
			Action principal, 
			Action activeAction, 
			List actionPath,
			org.nasdanika.html.model.bootstrap.Page pageTemplate,
			BiFunction uriResolver, 
			URI baseURI,
			Container container,
			ProgressMonitor progressMonitor) {
		
		if (activeAction.eContainmentFeature() != AppPackage.Literals.ACTION__SECTIONS) {
			URI uri = uriResolver.apply(activeAction, baseURI);				
			if (uri != null && uri.isRelative()) {
				org.nasdanika.exec.resources.File file = container.getFile(uri.toString());
				org.nasdanika.html.model.bootstrap.Page bootstrapPage = EcoreUtil.copy(pageTemplate);
				bootstrapPage.setName(activeAction.getText());
				if (bootstrapPage.getBody().isEmpty()) {
					bootstrapPage.getBody().add(AppFactory.eINSTANCE.createPage());
				}
				for (EObject be: bootstrapPage.getBody()) {
					if (be instanceof org.nasdanika.html.model.app.Page) {
						buildAppPage(root, principal, activeAction, actionPath, (org.nasdanika.html.model.app.Page) be, uriResolver, progressMonitor);						
					}
				}
				file.getContents().add(bootstrapPage);
				
				for (org.nasdanika.exec.resources.Resource res: activeAction.getResources()) {
					((Container) file.eContainer()).getContents().add(EcoreUtil.copy(res));					
				}				
			}
		}
		
		List subActionPath = new ArrayList<>(actionPath);
		if (activeAction != root && activeAction != principal) {
			subActionPath.add(activeAction);
		}
		
		for (EObject child: resolveActionReferences(activeAction.getChildren())) {
			if (child instanceof Action) {
				generateSite(root, principal, (Action) child, subActionPath, pageTemplate, uriResolver, baseURI, container, progressMonitor);
			}
		}
		
		for (Action section: activeAction.getSections()) {
			generateSite(root, principal, (Action) section, subActionPath, pageTemplate, uriResolver, baseURI, container, progressMonitor);
		}
				
		for (EObject navigation: resolveActionReferences(activeAction.getNavigation())) {
			if (navigation instanceof Action) {
				generateSite(root, principal, (Action) navigation, subActionPath, pageTemplate, uriResolver, baseURI, container, progressMonitor);
			}
		}
		
		for (Action anonymous: activeAction.getAnonymous()) {
			generateSite(root, principal, anonymous, subActionPath, pageTemplate, uriResolver, baseURI, container, progressMonitor);
		}
		
		generateNavigationPanel(root, principal, actionPath, activeAction.getFloatLeftNavigation(), pageTemplate, uriResolver, baseURI, container, progressMonitor);
		generateNavigationPanel(root, principal, actionPath, activeAction.getFloatRightNavigation(), pageTemplate, uriResolver, baseURI, container, progressMonitor);
		generateNavigationPanel(root, principal, actionPath, activeAction.getLeftNavigation(), pageTemplate, uriResolver, baseURI, container, progressMonitor);
		generateNavigationPanel(root, principal, actionPath, activeAction.getRightNavigation(), pageTemplate, uriResolver, baseURI, container, progressMonitor);
	}
	
	private static void buildAppPage(
			Action root, 
			Action principal, 
			Action activeAction, 
			List actionPath,
			org.nasdanika.html.model.app.Page appPage,
			BiFunction uriResolver, 
			ProgressMonitor progressMonitor) {
		
		if (root != null) {
			Label title = createLabel(root, activeAction, uriResolver, null, "header/title", false, false);
			EList rootChildren = root.getChildren();
			if (title != null || rootChildren.size() > 1) {
				// Header
				Header header = appPage.getHeader();
				if (header == null) {
					header = AppFactory.eINSTANCE.createHeader();
					appPage.setHeader(header);
				}
				if (title != null) {
					header.setTitle(title);
				}
				List headerItems = header.getItems();
				rootChildren.listIterator(1).forEachRemaining(rac -> {
					if (rac instanceof Action) {
						headerItems.add(createLabel((Action) rac, activeAction, uriResolver, null, "header/navigation", true, false));
					} else {
						headerItems.add(EcoreUtil.copy(rac));
					}
				});
			}
			
			List rootNavigation = resolveActionReferences(root.getNavigation());
			if (!rootNavigation.isEmpty()) {
				Footer footer = appPage.getFooter();
				if (footer == null) {
					footer = AppFactory.eINSTANCE.createFooter();
					appPage.setFooter(footer);
				}
				EList footerItems = footer.getItems();
				rootNavigation.forEach(ran -> {
					if (ran instanceof Action) {
						footerItems.add(createLabel((Action) ran, activeAction, uriResolver, null, "footer/navigation", true, false));
					} else {
						footerItems.add(EcoreUtil.copy(ran));
					}
				});
				
			}
		}
		
		if (principal != null) {
			// Navbar 
			Label brand = createLabel(principal, activeAction, uriResolver, null, "navbar/brand", false, false);
			List principalNavigation = resolveActionReferences(principal.getNavigation());
			if (brand != null || !principalNavigation.isEmpty()) {
				NavigationBar navBar = appPage.getNavigationBar();
				if (navBar == null) {
					navBar = AppFactory.eINSTANCE.createNavigationBar();
					appPage.setNavigationBar(navBar);
				}
				if (brand != null) {
					navBar.setBrand(brand);
				}
				EList navBarItems = navBar.getItems();
				principalNavigation.forEach(principalNavigationElement -> {
					if (principalNavigationElement instanceof Action) {
						navBarItems.add(createLabel((Action) principalNavigationElement, activeAction, uriResolver, null, "navbar/item", true, false));
					} else {
						navBarItems.add(EcoreUtil.copy(principalNavigationElement));
					}
				});
			}
			
			// Navigation panel
			List principalChildren = resolveActionReferences(principal.getChildren());
			if (!principalChildren.isEmpty()) {
				NavigationPanel navPanel = appPage.getNavigationPanel();
				if (navPanel == null) {
					navPanel = AppFactory.eINSTANCE.createNavigationPanel();
					appPage.setNavigationPanel(navPanel);
				}
				if (isBlank(navPanel.getId())) {
					navPanel.setId(principal.getId() + "-navigation-panel");
				}
				Function navItemIdProvider = na -> isBlank(na.getId()) ? null : "nsd-app-nav-item-" + na.getId();
				List navPanelItems = navPanel.getItems();
				principalChildren.forEach(principalChild -> {
					if (principalChild instanceof Action) {
						navPanelItems.add(createLabel((Action) principalChild, activeAction, uriResolver, navItemIdProvider, "nav-panel", true, true));
					} else {
						navPanelItems.add(EcoreUtil.copy(principalChild));
					}
				});
				
			}
		}
		
		// Content panel
		ContentPanel contentPanel = appPage.getContentPanel();
		if (contentPanel == null) {
			contentPanel = AppFactory.eINSTANCE.createContentPanel();
			appPage.setContentPanel(contentPanel);
		}
		
		buildContentPanel(activeAction, actionPath, activeAction == root || activeAction == principal, contentPanel, uriResolver, progressMonitor);	
	}
	
	private static void buildContentPanel(
			Action action, 
			List path,
			boolean rootOrPrincipal,
			ContentPanel contentPanel,
			BiFunction uriResolver, 
			ProgressMonitor progressMonitor) {
		
		if (!rootOrPrincipal) {
			if (path != null && !path.isEmpty()) {
				EList