001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.markup.parser.filter; 018 019import java.text.ParseException; 020import java.util.ArrayDeque; 021 022import org.apache.wicket.Application; 023import org.apache.wicket.Component; 024import org.apache.wicket.MarkupContainer; 025import org.apache.wicket.WicketRuntimeException; 026import org.apache.wicket.markup.ComponentTag; 027import org.apache.wicket.markup.MarkupElement; 028import org.apache.wicket.markup.MarkupResourceStream; 029import org.apache.wicket.markup.MarkupStream; 030import org.apache.wicket.markup.WicketTag; 031import org.apache.wicket.markup.html.TransparentWebMarkupContainer; 032import org.apache.wicket.markup.parser.AbstractMarkupFilter; 033import org.apache.wicket.markup.resolver.IComponentResolver; 034import org.apache.wicket.util.string.StringValueConversionException; 035import org.apache.wicket.util.string.Strings; 036import org.apache.wicket.util.value.IValueMap; 037 038 039/** 040 * This is a markup inline filter. It identifies xml tags which include a href attribute and which 041 * are not Wicket specific components and flags these tags (ComponentTag) as autolink enabled. A 042 * component resolver will later resolve the href and assign a BookmarkablePageLink to it 043 * (automatically). 044 * <p> 045 * An application setting is used as default value, which might be modified for specific regions. 046 * These regions are identified by <wicket:link> tags with an optional 'autolink' attribute. 047 * The default value for the attribute is true, thus enabling autolinking. An open-close 048 * <wicket:link/> tag will change the autolink status until the end of the markup document or 049 * the next <wicket:link> tag respectively. <wicket:link> regions may be nested. 050 * 051 * @author Juergen Donnerstag 052 */ 053public class WicketLinkTagHandler extends AbstractMarkupFilter implements IComponentResolver 054{ 055 private static final long serialVersionUID = 1L; 056 057 /** */ 058 public static final String LINK = "link"; 059 060 /** The id of autolink components */ 061 public static final String AUTOLINK_ID = "_autolink_"; 062 063 /** Allow to have link regions within link regions */ 064 private ArrayDeque<Boolean> autolinkStatus; 065 066 /** Current status */ 067 private boolean autolinking = true; 068 069 /** 070 * Construct. 071 */ 072 public WicketLinkTagHandler() 073 { 074 this(null); 075 } 076 077 public WicketLinkTagHandler(MarkupResourceStream resourceStream) 078 { 079 super(resourceStream); 080 setAutomaticLinking(Application.get().getMarkupSettings().getAutomaticLinking()); 081 } 082 083 /** 084 * Set the default value for autolinking 085 * 086 * @param enable 087 * if true, autolinks are enabled 088 */ 089 public void setAutomaticLinking(final boolean enable) 090 { 091 autolinking = enable; 092 } 093 094 @Override 095 protected final MarkupElement onComponentTag(ComponentTag tag) throws ParseException 096 { 097 // Only xml tags not already identified as Wicket components will be 098 // considered for autolinking. This is because it is assumed that Wicket 099 // components like images or all other kind of Wicket Links will handle 100 // it themselves. 101 // Subclass analyzeAutolinkCondition() to implement your own 102 // implementation and register the new tag handler with the markup 103 // parser through Application.newMarkupParser(). 104 if ((autolinking == true) && (analyzeAutolinkCondition(tag) == true)) 105 { 106 // Mark it as autolink enabled 107 tag.enableAutolink(true); 108 109 // Just a dummy name. The ComponentTag will not be forwarded. 110 tag.setId(AUTOLINK_ID + getRequestUniqueId()); 111 tag.setAutoComponentTag(true); 112 tag.setModified(true); 113 return tag; 114 } 115 116 // For all <wicket:link ..> tags which probably change the 117 // current autolink status. 118 if (tag instanceof WicketTag) 119 { 120 final WicketTag wtag = (WicketTag)tag; 121 if (wtag.isLinkTag()) 122 { 123 // Beginning of the region 124 if (tag.isOpen() || tag.isOpenClose()) 125 { 126 if (tag.isOpen()) 127 { 128 if (autolinkStatus == null) 129 { 130 autolinkStatus = new ArrayDeque<>(); 131 } 132 133 // remember the current setting to be reset after the 134 // region 135 autolinkStatus.push(autolinking); 136 } 137 138 // html allows to represent true in different ways 139 final String autolink = tag.getAttributes().getString("autolink"); 140 try 141 { 142 autolinking = Strings.isEmpty(autolink) || Strings.isTrue(autolink); 143 } 144 catch (StringValueConversionException e) 145 { 146 throw new WicketRuntimeException("Invalid autolink attribute value \"" + 147 autolink + "\""); 148 } 149 } 150 else if (tag.isClose()) 151 { 152 // restore the autolink setting from before the region 153 autolinking = autolinkStatus.pop(); 154 } 155 156 return wtag; 157 } 158 } 159 160 return tag; 161 } 162 163 /** 164 * Analyze the tag. If return value == true, a autolink component will be created. 165 * <p> 166 * Subclass analyzeAutolinkCondition() to implement you own implementation and register the new 167 * tag handler with the markup parser through Application.newMarkupParser(). 168 * 169 * @param tag 170 * The current tag being parsed 171 * @return If true, tag will become auto-component 172 */ 173 protected boolean analyzeAutolinkCondition(final ComponentTag tag) 174 { 175 if (tag.getId() == null) 176 { 177 IValueMap attributes = tag.getAttributes(); 178 String ref = attributes.getString("href"); 179 if (checkRef(ref)) 180 { 181 return true; 182 } 183 ref = attributes.getString("src"); 184 if (checkRef(ref)) 185 { 186 return true; 187 } 188 } 189 190 return false; 191 } 192 193 /** 194 * 195 * @param ref 196 * @return true if ref is not null and does not contain namespace 197 */ 198 private boolean checkRef(String ref) 199 { 200 return (ref != null) && (!ref.contains(":")); 201 } 202 203 @Override 204 public Component resolve(final MarkupContainer container, final MarkupStream markupStream, 205 final ComponentTag tag) 206 { 207 if (tag instanceof WicketTag) 208 { 209 WicketTag wtag = (WicketTag)tag; 210 if (wtag.isLinkTag() && (wtag.getNamespace() != null)) 211 { 212 String id = tag.getId(); 213 214 return new TransparentWebMarkupContainer(id); 215 } 216 } 217 218 // We were not able to handle the tag 219 return null; 220 } 221}