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; 018 019import java.util.Iterator; 020 021import org.apache.wicket.markup.parser.filter.HtmlHandler; 022import org.apache.wicket.util.lang.Args; 023import org.apache.wicket.util.string.AppendingStringBuffer; 024 025/** 026 * Represents a portion of a markup file, but always spans a complete tag. E.g. 027 * 028 * <pre> 029 * open-body-close: <span>body</span> 030 * open-close: <span/> 031 * open-no-close: <input ...>body 032 * </pre> 033 * 034 * @see Markup 035 * @see MarkupElement 036 * 037 * @author Juergen Donnerstag 038 */ 039public class MarkupFragment extends AbstractMarkupFragment 040{ 041 /** The parent markup. Must not be null. */ 042 private final IMarkupFragment markup; 043 044 /** The index at which the fragment starts, relative to the parent markup */ 045 private final int startIndex; 046 047 /** The size of the fragment (usually from open to close tag) */ 048 private final int size; 049 050 /** 051 * Construct. 052 * 053 * @param markup 054 * The parent markup. May not be null. 055 * @param startIndex 056 * The start index of the child markup 057 * @throws IndexOutOfBoundsException 058 * if the index is out of range (<tt>index < 0 || index >= size()</tt>) 059 */ 060 public MarkupFragment(final IMarkupFragment markup, final int startIndex) 061 { 062 Args.notNull(markup, "markup"); 063 064 if (startIndex < 0) 065 { 066 throw new IllegalArgumentException("Parameter 'startIndex' must not be < 0"); 067 } 068 069 // cache the value for better performance 070 int markupSize = markup.size(); 071 072 if (startIndex >= markupSize) 073 { 074 throw new IllegalArgumentException( 075 "Parameter 'startIndex' must not be >= markup.size()"); 076 } 077 078 this.markup = markup; 079 this.startIndex = startIndex; 080 081 // Make sure we are at an open tag 082 MarkupElement startElem = markup.get(startIndex); 083 if ((startElem instanceof ComponentTag) == false) 084 { 085 throw new IllegalArgumentException( 086 "Parameter 'startIndex' does not point to a Wicket open tag"); 087 } 088 089 // Determine the size. Find the close tag 090 int endIndex; 091 ComponentTag startTag = (ComponentTag)startElem; 092 if (startTag.isOpenClose()) 093 { 094 endIndex = startIndex; 095 } 096 else if (startTag.hasNoCloseTag()) 097 { 098 if (HtmlHandler.requiresCloseTag(startTag.getName()) == false) 099 { 100 // set endIndex to a "good" value 101 endIndex = startIndex; 102 } 103 else 104 { 105 // set endIndex to a value which will indicate an error 106 endIndex = markupSize; 107 } 108 } 109 else 110 { 111 for (endIndex = startIndex + 1; endIndex < markupSize; endIndex++) 112 { 113 MarkupElement elem = markup.get(endIndex); 114 if (elem instanceof ComponentTag) 115 { 116 ComponentTag tag = (ComponentTag)elem; 117 if (tag.closes(startTag)) 118 { 119 break; 120 } 121 } 122 } 123 } 124 125 if (endIndex >= markupSize) 126 { 127 throw new MarkupException("Unable to find close tag for: '" + startTag.toString() + 128 "' in " + getRootMarkup().getMarkupResourceStream().toString()); 129 } 130 131 size = endIndex - startIndex + 1; 132 } 133 134 @Override 135 public final MarkupElement get(final int index) 136 { 137 if ((index < 0) || (index > size)) 138 { 139 throw new IndexOutOfBoundsException("Parameter 'index' is out of range: 0 <= " + index + 140 " <= " + size); 141 } 142 143 // Ask the parent markup 144 return markup.get(startIndex + index); 145 } 146 147 @Override 148 public final IMarkupFragment find(final String id) 149 { 150 if (size < 2) 151 { 152 return null; 153 } 154 return find(id, 1); 155 } 156 157 @Override 158 public final MarkupResourceStream getMarkupResourceStream() 159 { 160 return markup.getMarkupResourceStream(); 161 } 162 163 @Override 164 public final int size() 165 { 166 return size; 167 } 168 169 /** 170 * @return The parent markup. Null if that is a markup file. 171 */ 172 private IMarkupFragment getParentMarkup() 173 { 174 return markup; 175 } 176 177 /** 178 * @return The Markup representing the underlying markup file with all its content 179 */ 180 public final Markup getRootMarkup() 181 { 182 IMarkupFragment markup = getParentMarkup(); 183 while ((markup != null) && !(markup instanceof Markup)) 184 { 185 markup = ((MarkupFragment)markup).getParentMarkup(); 186 } 187 return (Markup)markup; 188 } 189 190 @Override 191 public String toString(boolean markupOnly) 192 { 193 final AppendingStringBuffer buf = new AppendingStringBuffer(400); 194 if (markupOnly == false) 195 { 196 buf.append(getRootMarkup().getMarkupResourceStream().toString()); 197 buf.append('\n'); 198 } 199 200 for (int i = 0; i < size(); i++) 201 { 202 buf.append(get(i)); 203 } 204 return buf.toString(); 205 } 206 207 @Override 208 public Iterator<MarkupElement> iterator() 209 { 210 return new Iterator<MarkupElement>() { 211 int index = 0; 212 213 @Override 214 public boolean hasNext() { 215 return index < size; 216 } 217 218 @Override 219 public MarkupElement next() { 220 return get(index++); 221 } 222 223 @Override 224 public void remove() { 225 throw new UnsupportedOperationException("Cannot remove"); 226 } 227 }; 228 } 229}