/** * 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.markup.transform; import org.brixcms.markup.MarkupSource; import org.brixcms.markup.tag.Item; import org.brixcms.markup.tag.Tag; import org.brixcms.markup.tag.simple.SimpleTag; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; /** * Transformer that gathers content from all <head> and <wicket:head> sections in markup. It groups the head * content and inserts it all into the first <head> section removing all other head sections. * <p/> * Also if there are nested <body> sections it removes the inner <body> sections leaving one one (outer) * <body> section. * * @author Matej Knopp */ public class HeadTransformer extends MarkupSourceTransformer { public HeadTransformer(MarkupSource delegate) { super(delegate); } @Override protected List<Item> transform(List<Item> originalItems) { List<Item> headContent = extractHeadContent(originalItems); return transform(originalItems, headContent); } /** * Returns all items from <head> and <wicket:head> sections in the markup. * * @param items * @return */ protected List<Item> extractHeadContent(List<Item> items) { List<Item> result = new ArrayList<Item>(); int headDepth = 0; for (Item i : items) { if (i instanceof Tag) { Tag tag = (Tag) i; if (isHead(tag)) { if (tag.getType() == Tag.Type.OPEN) { ++headDepth; } else if (tag.getType() == Tag.Type.CLOSE) { if (headDepth > 0) { --headDepth; } } continue; } } if (headDepth > 0) { result.add(i); } } return result; } private boolean isHead(Tag tag) { String name = tag.getName(); return "head".equals(name) || "wicket:head".equals(name); } /** * Transform the given list of original items. If headContent is not null, the items from head content are placed in * the first head section of originalItems. All other head sections are removed. If head content is null, this * method removes all head sections from original items. This method also removes duplicate <body> tags. * * @param originalItems * @param headContent * @return */ protected List<Item> transform(List<Item> originalItems, List<Item> headContent) { List<Item> result = new ArrayList<Item>(); // do not add head section if the headContent is null boolean wasHead = headContent == null; int headDepth = 0; int bodyDepth = 0; for (Item i : originalItems) { if (i instanceof Tag) { Tag tag = (Tag) i; if (wasHead == false && (isHead(tag) || "body".equals(tag.getName()))) { Map<String, String> emptyMap = Collections.emptyMap(); result.add(new SimpleTag("head", Tag.Type.OPEN, emptyMap)); result.addAll(headContent); result.add(new SimpleTag("head", Tag.Type.CLOSE, null)); if ("body".equals(tag.getName())) { bodyDepth++; result.add(tag); } wasHead = true; if (isHead(tag) && (tag.getType() == Tag.Type.OPEN)) { ++headDepth; } continue; } if ("body".equals(tag.getName())) { if (tag.getType() == Tag.Type.OPEN) { if (bodyDepth == 0) { result.add(tag); } ++bodyDepth; } else if (tag.getType() == Tag.Type.CLOSE) { --bodyDepth; if (bodyDepth == 0) { result.add(tag); } } continue; } if (isHead(tag)) { if (tag.getType() == Tag.Type.OPEN) { ++headDepth; } else if (tag.getType() == Tag.Type.CLOSE) { if (headDepth > 0) { --headDepth; } } continue; } } if (headDepth == 0) { result.add(i); } } return result; } }