/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.wicket.markup;
import java.util.Deque;
import java.util.LinkedList;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.util.lang.Args;
import org.apache.wicket.util.value.IValueMap;
import org.apache.wicket.util.value.ValueMap;
/**
* Some utils to handle tags which otherwise would bloat the Tag API.
*
* @author Juergen Donnerstag
*/
public class TagUtils
{
private static final String DEFAULT_ATTRIBUTE_SEPARATOR = "; ";
/**
* A map that keeps the separators which should be used for the different HTML
* element attributes.
*/
// 'public' so that user applications can add/modify the entries, if needed
public static final IValueMap ATTRIBUTES_SEPARATORS = new ValueMap();
static {
ATTRIBUTES_SEPARATORS.put("class", " ");
ATTRIBUTES_SEPARATORS.put("style", DEFAULT_ATTRIBUTE_SEPARATOR);
ATTRIBUTES_SEPARATORS.put("onclick", DEFAULT_ATTRIBUTE_SEPARATOR);
}
/**
* Constructor
*/
public TagUtils()
{
}
/**
* @return True, if tag name equals '<body ...>'
*
* @param tag
*/
public static boolean isBodyTag(final ComponentTag tag)
{
return ("body".equalsIgnoreCase(tag.getName()) && (tag.getNamespace() == null));
}
/**
*
* @param elem
* @return True, if tag name equals '<head ...>'
*/
public static boolean isHeadTag(final MarkupElement elem)
{
if (elem instanceof ComponentTag)
{
ComponentTag tag = (ComponentTag)elem;
if ("head".equalsIgnoreCase(tag.getName()) && (tag.getNamespace() == null))
{
return true;
}
}
return false;
}
/**
*
* @param markup
* @param i
* @return True if the markup element at index 'i' is a WicketTag
*/
public static boolean isWicketTag(final IMarkupFragment markup, final int i)
{
MarkupElement elem = markup.get(i);
return elem instanceof WicketTag;
}
/**
*
* @param markup
* @param i
* @return True if the markup element at index 'i' is a <wicket:extend> tag
*/
public static boolean isExtendTag(final IMarkupFragment markup, final int i)
{
MarkupElement elem = markup.get(i);
if (elem instanceof WicketTag)
{
WicketTag wtag = (WicketTag)elem;
return wtag.isExtendTag();
}
return false;
}
/**
*
* @param elem
* @return True if the current markup element is a <wicket:head> tag
*/
public static boolean isWicketHeadTag(final MarkupElement elem)
{
if (elem instanceof WicketTag)
{
WicketTag wtag = (WicketTag)elem;
if (wtag.isHeadTag())
{
return true;
}
}
return false;
}
/**
*
* @param elem
* @return True if the current markup element is a <wicket:header-items> tag
*/
public static boolean isWicketHeaderItemsTag(final MarkupElement elem)
{
if (elem instanceof WicketTag)
{
WicketTag wtag = (WicketTag)elem;
if (wtag.isHeaderItemsTag())
{
return true;
}
}
return false;
}
/**
*
* @param elem
* @return True if the current markup element is a <wicket:body> tag
*/
public static boolean isWicketBodyTag(final MarkupElement elem)
{
if (elem instanceof WicketTag)
{
WicketTag wtag = (WicketTag)elem;
if (wtag.isBodyTag())
{
return true;
}
}
return false;
}
/**
*
* @param elem
* @return True if the current markup element is a <wicket:border> tag
*/
public static boolean isWicketBorderTag(final MarkupElement elem)
{
if (elem instanceof WicketTag)
{
WicketTag wtag = (WicketTag)elem;
if (wtag.isBorderTag())
{
return true;
}
}
return false;
}
/**
* Copy attributes from e.g. <wicket:panel> (or border) to the "calling" tag.
*
* @see <a href="http://issues.apache.org/jira/browse/WICKET-2874">WICKET-2874</a>
* @see <a href="https://issues.apache.org/jira/browse/WICKET-3812">WICKET-3812</a>
*
* @param component
* the markup container which attributes will be copied
* @param tag
* the component tag where the attributes will be applied
*/
public static void copyAttributes(final MarkupContainer component, final ComponentTag tag)
{
IMarkupFragment markup = component.getMarkup(null);
String namespace = markup.getMarkupResourceStream().getWicketNamespace() + ":";
MarkupElement elem = markup.get(0);
if (elem instanceof ComponentTag)
{
ComponentTag panelTag = (ComponentTag)elem;
for (String key : panelTag.getAttributes().keySet())
{
// exclude "wicket:XX" attributes
if (key.startsWith(namespace) == false)
{
String separator = ATTRIBUTES_SEPARATORS.getString(key, DEFAULT_ATTRIBUTE_SEPARATOR);
tag.append(key, panelTag.getAttribute(key), separator);
}
}
}
else
{
throw new MarkupException(markup.getMarkupResourceStream(),
"Expected a Tag but found raw markup: " + elem.toString());
}
}
/**
* Find the markup fragment of a tag with wicket:id equal to {@code id} starting at offset {@code streamOffset}.
*
* @param id
* The wicket:id of the tag being searched for.
* @param tagName
* The tag name of the tag being searched for.
* @param streamOffset
* The offset in the markup stream from which to start searching.
* @return the {@link IMarkupFragment} of the component tag if found, {@code null} is not found.
*/
public static final IMarkupFragment findTagMarkup(IMarkupFragment fragment, String id, String tagName, int streamOffset)
{
/*
* We need streamOffset because MarkupFragment starts searching from offset 1.
*/
Args.notEmpty(id, "id");
Args.withinRange(0, fragment.size() - 1, streamOffset, "streamOffset");
Deque<Boolean> openTagUsability = new LinkedList<>();
boolean canFind = true;
MarkupStream stream = new MarkupStream(fragment);
stream.setCurrentIndex(streamOffset);
while (stream.isCurrentIndexInsideTheStream())
{
MarkupElement elem = stream.get();
if (elem instanceof ComponentTag)
{
ComponentTag tag = stream.getTag();
if (tag.isOpen() || tag.isOpenClose())
{
if (canFind && tag.getId().equals(id) && (tagName==null || tag.getName().equals(tagName)))
{
return stream.getMarkupFragment();
}
else if (tag.isOpen() && !tag.hasNoCloseTag())
{
openTagUsability.push(canFind);
if (tag instanceof WicketTag)
{
WicketTag wtag = (WicketTag)tag;
if (wtag.isExtendTag())
{
canFind = true;
}
else if (wtag.isFragmentTag() || wtag.isContainerTag())
{
canFind = false;
}
/*
* We should potentially also not try find child markup inside some other
* Wicket tags. Other tags that we should think about refusing to look for
* child markup inside include: container, body, border, child (we already
* have special extend handling).
*/
}
else if (!"head".equals(tag.getName()) && !tag.isAutoComponentTag())
{
canFind = false;
}
}
}
else if (tag.isClose())
{
if (openTagUsability.isEmpty())
{
canFind = false;
}
else
{
canFind = openTagUsability.pop();
}
}
}
stream.next();
}
return null;
}
}