001/** 002 * 003 * Copyright © 2018 Paul Schaub 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * 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.jivesoftware.smackx.message_markup.element; 018 019import java.util.ArrayList; 020import java.util.Collections; 021import java.util.List; 022import java.util.Set; 023 024import org.jivesoftware.smack.packet.ExtensionElement; 025import org.jivesoftware.smack.util.XmlStringBuilder; 026 027public class MarkupElement implements ExtensionElement { 028 029 public static final String NAMESPACE = "urn:xmpp:markup:0"; 030 public static final String ELEMENT = "markup"; 031 032 private final List<MarkupChildElement> childElements; 033 034 /** 035 * Create a new MarkupElement. 036 * 037 * @param childElements child elements. 038 */ 039 public MarkupElement(List<MarkupChildElement> childElements) { 040 this.childElements = Collections.unmodifiableList(childElements); 041 } 042 043 /** 044 * Return a new Builder for Message Markup elements. 045 * @return builder. 046 */ 047 public static Builder getBuilder() { 048 return new Builder(); 049 } 050 051 /** 052 * Return a list of all child elements. 053 * @return children TODO javadoc me please 054 */ 055 public List<MarkupChildElement> getChildElements() { 056 return childElements; 057 } 058 059 @Override 060 public String getNamespace() { 061 return NAMESPACE; 062 } 063 064 @Override 065 public String getElementName() { 066 return ELEMENT; 067 } 068 069 @Override 070 public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { 071 XmlStringBuilder xml = new XmlStringBuilder(this).rightAngleBracket(); 072 073 for (MarkupChildElement child : getChildElements()) { 074 xml.append(child.toXML()); 075 } 076 077 xml.closeElement(this); 078 return xml; 079 } 080 081 082 083 public static final class Builder { 084 085 private final List<SpanElement> spans = new ArrayList<>(); 086 private final List<BlockQuoteElement> quotes = new ArrayList<>(); 087 private final List<CodeBlockElement> codes = new ArrayList<>(); 088 private final List<ListElement> lists = new ArrayList<>(); 089 090 private Builder() { 091 092 } 093 094 /** 095 * Mark a section of a message as deleted. 096 * 097 * @param start start index 098 * @param end end index 099 * @return builder TODO javadoc me please 100 */ 101 public Builder setDeleted(int start, int end) { 102 return addSpan(start, end, Collections.singleton(SpanElement.SpanStyle.deleted)); 103 } 104 105 /** 106 * Mark a section of a message as emphasized. 107 * 108 * @param start start index 109 * @param end end index 110 * @return builder TODO javadoc me please 111 */ 112 public Builder setEmphasis(int start, int end) { 113 return addSpan(start, end, Collections.singleton(SpanElement.SpanStyle.emphasis)); 114 } 115 116 /** 117 * Mark a section of a message as inline code. 118 * 119 * @param start start index 120 * @param end end index 121 * @return builder TODO javadoc me please 122 */ 123 public Builder setCode(int start, int end) { 124 return addSpan(start, end, Collections.singleton(SpanElement.SpanStyle.code)); 125 } 126 127 /** 128 * Add a span element. 129 * 130 * @param start start index 131 * @param end end index 132 * @param styles list of text styles for that span 133 * @return builder TODO javadoc me please 134 */ 135 public Builder addSpan(int start, int end, Set<SpanElement.SpanStyle> styles) { 136 verifyStartEnd(start, end); 137 138 for (SpanElement other : spans) { 139 if ((start >= other.getStart() && start <= other.getEnd()) || 140 (end >= other.getStart() && end <= other.getEnd())) { 141 throw new IllegalArgumentException("Spans MUST NOT overlap each other."); 142 } 143 } 144 145 spans.add(new SpanElement(start, end, styles)); 146 return this; 147 } 148 149 /** 150 * Mark a section of a message as block quote. 151 * 152 * @param start start index 153 * @param end end index 154 * @return builder TODO javadoc me please 155 */ 156 public Builder setBlockQuote(int start, int end) { 157 verifyStartEnd(start, end); 158 159 for (BlockQuoteElement other : quotes) { 160 // 1 if out, 0 if on, -1 if in 161 Integer s = start; 162 Integer e = end; 163 int startPos = s.compareTo(other.getStart()) * s.compareTo(other.getEnd()); 164 int endPos = e.compareTo(other.getStart()) * e.compareTo(other.getEnd()); 165 int allowed = startPos * endPos; 166 167 if (allowed < 1) { 168 throw new IllegalArgumentException("BlockQuotes MUST NOT overlap each others boundaries"); 169 } 170 } 171 172 quotes.add(new BlockQuoteElement(start, end)); 173 return this; 174 } 175 176 /** 177 * Mark a section of a message as a code block. 178 * 179 * @param start start index 180 * @param end end index 181 * @return builder TODO javadoc me please 182 */ 183 public Builder setCodeBlock(int start, int end) { 184 verifyStartEnd(start, end); 185 186 codes.add(new CodeBlockElement(start, end)); 187 return this; 188 } 189 190 /** 191 * Begin a list. 192 * 193 * @return list builder 194 */ 195 public Builder.ListBuilder beginList() { 196 return new Builder.ListBuilder(this); 197 } 198 199 public static final class ListBuilder { 200 private final Builder markup; 201 private final ArrayList<ListElement.ListEntryElement> entries = new ArrayList<>(); 202 private int end = -1; 203 204 private ListBuilder(Builder markup) { 205 this.markup = markup; 206 } 207 208 /** 209 * Add an entry to the list. 210 * The start index of an entry must correspond to the end index of the previous entry 211 * (if a previous entry exists.) 212 * 213 * @param start start index 214 * @param end end index 215 * @return list builder 216 */ 217 public Builder.ListBuilder addEntry(int start, int end) { 218 verifyStartEnd(start, end); 219 220 ListElement.ListEntryElement last = entries.size() == 0 ? null : entries.get(entries.size() - 1); 221 // Entries themselves do not store end values, that's why we store the last entries end value in this.end 222 if (last != null && start != this.end) { 223 throw new IllegalArgumentException("Next entries start must be equal to last entries end (" + this.end + ")."); 224 } 225 entries.add(new ListElement.ListEntryElement(start)); 226 this.end = end; 227 228 return this; 229 } 230 231 /** 232 * End the list. 233 * 234 * @return builder TODO javadoc me please 235 */ 236 public Builder endList() { 237 if (entries.size() > 0) { 238 ListElement.ListEntryElement first = entries.get(0); 239 ListElement list = new ListElement(first.getStart(), end, entries); 240 markup.lists.add(list); 241 } 242 243 return markup; 244 } 245 } 246 247 /** 248 * Build a Message Markup element. 249 * 250 * @return extension element 251 */ 252 public MarkupElement build() { 253 List<MarkupElement.MarkupChildElement> children = new ArrayList<>(); 254 children.addAll(spans); 255 children.addAll(quotes); 256 children.addAll(codes); 257 children.addAll(lists); 258 return new MarkupElement(children); 259 } 260 261 private static void verifyStartEnd(int start, int end) { 262 if (start >= end || start < 0) { 263 throw new IllegalArgumentException("Start value (" + start + ") MUST be greater equal than 0 " + 264 "and MUST be smaller than end value (" + end + ")."); 265 } 266 } 267 } 268 269 /** 270 * Interface for child elements. 271 */ 272 public abstract static class MarkupChildElement implements ExtensionElement { 273 274 public static final String NAMESPACE = MarkupElement.NAMESPACE; 275 276 public static final String ATTR_START = "start"; 277 public static final String ATTR_END = "end"; 278 279 private final int start, end; 280 281 protected MarkupChildElement(int start, int end) { 282 this.start = start; 283 this.end = end; 284 } 285 286 /** 287 * Return the start index of this element. 288 * 289 * @return start index 290 */ 291 public final int getStart() { 292 return start; 293 } 294 295 /** 296 * Return the end index of this element. 297 * 298 * @return end index 299 */ 300 public final int getEnd() { 301 return end; 302 } 303 304 @Override 305 public final String getNamespace() { 306 return NAMESPACE; 307 } 308 309 @Override 310 public final XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { 311 XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace); 312 xml.attribute(ATTR_START, getStart()); 313 xml.attribute(ATTR_END, getEnd()); 314 315 afterXmlPrelude(xml); 316 return xml; 317 } 318 319 protected abstract void afterXmlPrelude(XmlStringBuilder xml); 320 } 321 322 public abstract static class NonEmptyChildElement extends MarkupChildElement { 323 324 protected NonEmptyChildElement(int start, int end) { 325 super(start, end); 326 } 327 328 @Override 329 protected final void afterXmlPrelude(XmlStringBuilder xml) { 330 xml.rightAngleBracket(); 331 332 appendInnerXml(xml); 333 334 xml.closeElement(this); 335 } 336 337 protected abstract void appendInnerXml(XmlStringBuilder xml); 338 339 } 340 341 /** 342 * Interface for block level child elements. 343 */ 344 public abstract static class BlockLevelMarkupElement extends MarkupChildElement { 345 346 protected BlockLevelMarkupElement(int start, int end) { 347 super(start, end); 348 } 349 350 @Override 351 protected final void afterXmlPrelude(XmlStringBuilder xml) { 352 xml.closeEmptyElement(); 353 } 354 355 } 356}