package org.qrone.r7.parser; import java.io.IOException; import java.io.InputStream; import java.io.Writer; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.qrone.coder.QClass; import org.qrone.coder.QFunc; import org.qrone.coder.QState; import org.qrone.coder.render.QLangJQuery; import org.qrone.r7.parser.HTML5Deck.HTML5Set; import org.qrone.r7.resolver.URIResolver; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.Text; import org.w3c.dom.css.CSSMediaRule; import org.w3c.dom.css.CSSStyleRule; import org.w3c.dom.stylesheets.MediaList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import se.fishtank.css.selectors.NodeSelectorException; import se.fishtank.css.selectors.dom.DOMNodeSelector; public class HTML5OM { private HTML5Deck deck; private CSS3Deck cssdeck; private Document document; private Element body; private List<CSS3OM> stylesheets = new LinkedList<CSS3OM>(); private List<String> javascripts = new LinkedList<String>(); private List<String> jslibs = new LinkedList<String>(); private List<Element> csslibs = new LinkedList<Element>(); private boolean hasQroneObjectInJS = false; private Map<String, String> metamap = new Hashtable<String, String>(); private DOMNodeSelector nodeselector; private Map<String, Set<Node>> selectcache = new Hashtable<String, Set<Node>>(); private URI uri; public HTML5OM(HTML5Deck deck, CSS3Deck cssdeck, URI uri){ this.uri = uri; this.deck = deck; this.cssdeck = cssdeck; } public HTML5Deck getDeck(){ return deck; } public CSS3Deck getCSS3Deck(){ return cssdeck; } public URI getURI(){ return uri; } public Document getDocument(){ return document; } public Element getBody(){ return body; } public Set<Node> select(String selector){ try{ Set<Node> nodes = selectcache.get(selector); if(nodes == null){ nodes = nodeselector.querySelectorAll(selector); selectcache.put(selector, nodes); } return nodes; }catch(NodeSelectorException e){} return null; } private Map<String, Map<Object,Set<Node>>> selectorCache = new HashMap<String, Map<Object,Set<Node>>>(); public Set<Node> select(String selector, Object o){ try{ Map<Object,Set<Node>> cache = selectorCache.get(selector); if(cache != null){ Set<Node> rset = cache.get(o); if(rset != null){ return rset; } }else{ cache = new HashMap<Object, Set<Node>>(); selectorCache.put(selector, cache); } Set<Node> lhs = null; if(o instanceof Element){ DOMNodeSelector ns = new DOMNodeSelector((Element)o); lhs = ns.querySelectorAll(selector); }else if(o instanceof Set){ DOMNodeSelector ns; lhs = new LinkedHashSet<Node>(); Set<Node> l = (Set<Node>)o; for (Iterator<Node> i = l.iterator(); i.hasNext();) { Node n = i.next(); ns = new DOMNodeSelector(n); lhs.addAll(ns.querySelectorAll(selector)); } } cache.put(o, lhs); return lhs; } catch (NodeSelectorException e) {} return null; } public void parse(final URIResolver resolver) throws SAXException, IOException{ InputStream in = resolver.getInputStream(uri); try{ document = HTML5Parser.parse(new InputSource(in)); }finally{ in.close(); } nodeselector = new DOMNodeSelector(document); HTML5Visitor visitor = new HTML5Visitor() { private boolean inBody = false; private boolean inScript = false; private boolean inStyle = false; @Override public void visit(Element e) { NamedNodeMap map = e.getAttributes(); for (int i = 0; i < map.getLength(); i++) { Node n = map.item(i); if(n.getNodeName().startsWith("on")){ e.setAttribute(n.getNodeName(), JSParser.compress(n.getNodeValue(),"__QRONE_ID__")); }else if(n.getNodeName().equals("href") && n.getNodeValue().startsWith("javascript:")){ String js = n.getNodeValue(); if(js.startsWith("javascript:")){ js = js.substring("javascript:".length()); e.setAttribute(n.getNodeName(), "javascript:" + JSParser.compress(js,"__QRONE_ID__")); } }else if(n.getNodeName().equals("style")){ String css = n.getNodeValue(); try { e.setAttribute("style", new CSS3Serializer().append(CSS3Parser.parsestyle(css)).toString()); } catch (DOMException e1) { } catch (IOException e1) { } } } if(e.getNodeName().equals("head")){ accept(e); }else if(e.getNodeName().equals("body")){ HTML5OM.this.body = e; inBody = true; accept(e); inBody = false; }else if(e.getNodeName().equals("script")){ if(!inBody){ inScript = true; accept(e); inScript = false; if(e.hasAttribute("src")) jslibs.add(e.getAttribute("src")); } }else if(e.getNodeName().equals("style")){ inStyle = true; accept(e); inStyle = false; }else if(e.getNodeName().equals("link")){ if(isCSSLinkTab(e)){ csslibs.add(e); } }else if(e.getNodeName().equals("meta")){ metamap.put(e.getAttribute("name"), e.getAttribute("content")); accept(e); }else{ accept(e); } } @Override public void visit(Text n) { if(inScript){ String js = n.getNodeValue(); String cjs = JSParser.compress(JSParser.clean(js),"__QRONE_ID__"); if(cjs.indexOf("__QRONE_ID__")>=0){ hasQroneObjectInJS = true; } javascripts.add(cjs); }else if(inStyle){ CSS3OM cssom; try { cssom = cssdeck.compile(getURI(), CSS3Parser.clean(n.getNodeValue())); parseStyleSheet(cssom); } catch (Exception e) {} } } }; visitor.visit(document); } public static boolean isCSSLinkTab(Element e){ String href = e.getAttribute("href"); if(href != null){ if(href.toLowerCase().endsWith(".css")){ return true; } String rel = e.getAttribute("rel"); if(rel != null && rel.toLowerCase().equals("stylesheet")){ return true; } String type = e.getAttribute("type"); if(type != null && type.toLowerCase().equals("text/css")){ return true; } } return false; } public void process(final HTML5Writer w, final HTML5Template t, final Node node, String id, final Set<HTML5OM> xomlist, final String ticket){ if(xomlist != null && !xomlist.contains(this)){ xomlist.add(this); } final HTML5Set set; if(node == document){ set = getRecursive(id, xomlist, ticket); }else{ set = null; } HTML5TagWriter s = new HTML5Selializer(body,set,deck,node,uri,w,t,this, id, ticket); s.visit(node); } private void parseStyleSheet(final CSS3OM cssom) throws IOException{ stylesheets.add(cssom); } private String scriptCache = null; public String getScripts(boolean inline){ if(scriptCache != null){ return scriptCache; } StringBuilder b = new StringBuilder(); if(inline){ b.append("qrone[\"" + getURI().toString() + "\"]=function(){};"); } for (Iterator<String> i = javascripts.iterator(); i .hasNext();) { b.append(i.next().replaceAll("__QRONE_ID__", "qrone[\"" + getURI().toString() + "\"]")); } scriptCache = b.toString(); return b.toString(); } private HTML5Set getRecursive(String id, Set<HTML5OM> xomlist, String ticket){ HTML5Set set = new HTML5Set(); if(xomlist != null){ for (Iterator<HTML5OM> i = xomlist.iterator(); i.hasNext();) { if(i.next().hasQroneObjectInJS){ set.js.append("if(!window.qrone)window.qrone=function(){};"); jslibs.add("/system/resource/qrone.js"); break; } } for (Iterator<HTML5OM> i = xomlist.iterator(); i.hasNext();) { HTML5OM xom = i.next(); set.js.append(xom.getScripts(xom.hasQroneObjectInJS)); set.css.addAll(xom.stylesheets); set.jslibs.addAll(xom.jslibs); set.csslibs.addAll(xom.csslibs); } } return set; } }