/** * 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.brixcms.plugin.site.page; import org.brixcms.Brix; import org.brixcms.exception.BrixException; import org.brixcms.markup.MarkupSource; import org.brixcms.markup.tag.Item; import org.brixcms.markup.tag.Tag; import org.brixcms.markup.tag.Tag.Type; import org.brixcms.markup.tag.simple.SimpleComment; import org.brixcms.markup.tag.simple.SimpleTag; import org.brixcms.markup.tag.simple.SimpleText; import org.brixcms.plugin.site.page.tile.TileTag; import org.htmlparser.Attribute; import org.htmlparser.Node; import org.htmlparser.Remark; import org.htmlparser.Text; import org.htmlparser.lexer.Lexer; import org.htmlparser.util.ParserException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; /** * {@link MarkupSource} for tile markup. Parses and merges the content of tile container node and it's templates. * * @author Matej Knopp */ public class PageMarkupSource implements MarkupSource { List<Item> items = null; private final AbstractContainer node; private Iterator<Item> iterator; private String doctype = null; public PageMarkupSource(AbstractContainer node) { this.node = node; } public String getDoctype() { return doctype; } public Object getExpirationToken() { return getMostRecentLastModifiedDate(); } public boolean isMarkupExpired(Object expirationToken) { if (expirationToken != null) { Date token = (Date) expirationToken; Date current = getMostRecentLastModifiedDate(); if (current != null) { return token.compareTo(current) < 0; } else { return false; } } else { return true; } } public Item nextMarkupItem() { if (items == null) { parseMarkup(); iterator = items.iterator(); } if (iterator.hasNext()) { return iterator.next(); } else { return null; } } /** * Returns the most recent date of last modification of tile page and it's templates. The date is then used as * expiration token. * * @return */ private Date getMostRecentLastModifiedDate() { Date current = null; for (AbstractContainer node = this.node; node != null; node = (AbstractContainer) node .getTemplate()) { Date lm = node.getLastModified(); if (lm != null) { if (current == null || current.compareTo(lm) < 0) { current = lm; } } } return current; } private void parseMarkup() { items = new ArrayList<Item>(); List<AbstractContainer> nodes = new ArrayList<AbstractContainer>(); nodes.add(node); AbstractContainer n = node; while ((n = (AbstractContainer) n.getTemplate()) != null) { if (nodes.contains(n)) { // TODO: Do something nicer throw new BrixException("Loop detected."); } nodes.add(0, n); } parseNode(nodes, 0, items); } private void parseNode(List<AbstractContainer> nodes, int current, List<Item> items) { AbstractContainer node = nodes.get(current); final String content = node.getDataAsString(); final Lexer lexer = new Lexer(content); Node cursor = null; try { while ((cursor = lexer.nextNode()) != null) { if (cursor instanceof Remark) { items.add(new SimpleComment(cursor.getText())); } else if (cursor instanceof Text) { items.add(new SimpleText(cursor.toHtml())); } else if (cursor instanceof org.htmlparser.Tag) { processTag(nodes, current, items, (org.htmlparser.Tag) cursor); } else { throw new BrixException("Unknown node type " + cursor.getClass().getName()); } } } catch (ParserException e) { throw new BrixException("Couldn't parse node content: '" + node.getPath() + "'", e); } } private void processTag(List<AbstractContainer> nodes, int current, List<Item> items, org.htmlparser.Tag tag) { final Tag.Type type; final String rawName = tag.getRawTagName(); if (rawName.startsWith("/")) { type = Tag.Type.CLOSE; } else if (isOpenClose(tag)) { type = Tag.Type.OPEN_CLOSE; } else { type = Tag.Type.OPEN; } final String tagName = tag.getTagName().toLowerCase(); if ("!doctype".equals(tagName)) { this.doctype = tag.toHtml(); } else if (type == Tag.Type.CLOSE) { if (!isKnownBrixTag(tagName)) { Map<String, String> attributes = Collections.emptyMap(); items.add(new SimpleTag(tagName, type, attributes)); } } else { Map<String, String> attributes = getAttributes(tag); if (isKnownBrixTag(tagName)) { processBrixTag(nodes, current, items, tagName, getAttributes(tag), type); } else { items.add(new SimpleTag(tagName, type, attributes)); } } } private boolean isOpenClose(org.htmlparser.Tag tag) { if (tag.getRawTagName().endsWith("/")) { return true; } else { List<?> atts = tag.getAttributesEx(); Attribute a = (Attribute) atts.get(atts.size() - 1); return a.getName() != null && a.getName().equals("/"); } } private boolean isKnownBrixTag(String tagName) { if (!tagName.startsWith(Brix.NS_PREFIX)) { return false; } String simpleTagName = tagName.substring(Brix.NS_PREFIX.length()); return TemplateNode.CONTENT_TAG.equals(tagName) || "tile".equals(simpleTagName) || "fragment".equals(simpleTagName); } private void processBrixTag(List<AbstractContainer> nodes, int current, List<Item> items, String tagName, Map<String, String> attributes, Tag.Type type) { AbstractContainer node = nodes.get(current); final String simpleTagName = tagName.substring(Brix.NS_PREFIX.length()); if (TemplateNode.CONTENT_TAG.equals(tagName)) { if (current != nodes.size() - 1) { parseNode(nodes, current + 1, items); } } else if ("tile".equals(simpleTagName)) { String id = attributes.get(AbstractContainer.MARKUP_TILE_ID); //Map<String, String> newAttributes = new HashMap<String, String>(attributes); //newAttributes.remove("id"); items.add(new TileTag("div", Type.OPEN, attributes, node, id)); items.add(new SimpleTag("div", Type.CLOSE, null)); } } @SuppressWarnings("unchecked") private Map<String, String> getAttributes(org.htmlparser.Tag tag) { Map<String, String> result = new HashMap<String, String>(); List<?> original = tag.getAttributesEx(); List<Attribute> list = new ArrayList<Attribute>((Collection<? extends Attribute>) original .subList(1, original.size())); for (Attribute a : list) { if (a.getName() != null && !a.getName().equals("/") && !a.isWhitespace()) { result.put(a.getName(), a.getValue()); } } return result; } }