Smack/smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/element/MarkupElement.java

355 lines
11 KiB
Java

/**
*
* Copyright © 2018 Paul Schaub
*
* 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.jivesoftware.smackx.message_markup.element;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.util.XmlStringBuilder;
public class MarkupElement implements ExtensionElement {
public static final String NAMESPACE = "urn:xmpp:markup:0";
public static final String ELEMENT = "markup";
private final List<MarkupChildElement> childElements;
/**
* Create a new MarkupElement.
*
* @param childElements child elements.
*/
public MarkupElement(List<MarkupChildElement> childElements) {
this.childElements = Collections.unmodifiableList(childElements);
}
/**
* Return a new Builder for Message Markup elements.
* @return builder.
*/
public static Builder getBuilder() {
return new Builder();
}
/**
* Return a list of all child elements.
* @return children TODO javadoc me please
*/
public List<MarkupChildElement> getChildElements() {
return childElements;
}
@Override
public String getNamespace() {
return NAMESPACE;
}
@Override
public String getElementName() {
return ELEMENT;
}
@Override
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder(this).rightAngleBracket();
for (MarkupChildElement child : getChildElements()) {
xml.append(child.toXML());
}
xml.closeElement(this);
return xml;
}
public static final class Builder {
private final List<SpanElement> spans = new ArrayList<>();
private final List<BlockQuoteElement> quotes = new ArrayList<>();
private final List<CodeBlockElement> codes = new ArrayList<>();
private final List<ListElement> lists = new ArrayList<>();
private Builder() {
}
/**
* Mark a section of a message as deleted.
*
* @param start start index
* @param end end index
* @return builder TODO javadoc me please
*/
public Builder setDeleted(int start, int end) {
return addSpan(start, end, Collections.singleton(SpanElement.SpanStyle.deleted));
}
/**
* Mark a section of a message as emphasized.
*
* @param start start index
* @param end end index
* @return builder TODO javadoc me please
*/
public Builder setEmphasis(int start, int end) {
return addSpan(start, end, Collections.singleton(SpanElement.SpanStyle.emphasis));
}
/**
* Mark a section of a message as inline code.
*
* @param start start index
* @param end end index
* @return builder TODO javadoc me please
*/
public Builder setCode(int start, int end) {
return addSpan(start, end, Collections.singleton(SpanElement.SpanStyle.code));
}
/**
* Add a span element.
*
* @param start start index
* @param end end index
* @param styles list of text styles for that span
* @return builder TODO javadoc me please
*/
public Builder addSpan(int start, int end, Set<SpanElement.SpanStyle> styles) {
verifyStartEnd(start, end);
for (SpanElement other : spans) {
if ((start >= other.getStart() && start <= other.getEnd()) ||
(end >= other.getStart() && end <= other.getEnd())) {
throw new IllegalArgumentException("Spans MUST NOT overlap each other.");
}
}
spans.add(new SpanElement(start, end, styles));
return this;
}
/**
* Mark a section of a message as block quote.
*
* @param start start index
* @param end end index
* @return builder TODO javadoc me please
*/
public Builder setBlockQuote(int start, int end) {
verifyStartEnd(start, end);
for (BlockQuoteElement other : quotes) {
// 1 if out, 0 if on, -1 if in
Integer s = start;
Integer e = end;
int startPos = s.compareTo(other.getStart()) * s.compareTo(other.getEnd());
int endPos = e.compareTo(other.getStart()) * e.compareTo(other.getEnd());
int allowed = startPos * endPos;
if (allowed < 1) {
throw new IllegalArgumentException("BlockQuotes MUST NOT overlap each others boundaries");
}
}
quotes.add(new BlockQuoteElement(start, end));
return this;
}
/**
* Mark a section of a message as a code block.
*
* @param start start index
* @param end end index
* @return builder TODO javadoc me please
*/
public Builder setCodeBlock(int start, int end) {
verifyStartEnd(start, end);
codes.add(new CodeBlockElement(start, end));
return this;
}
/**
* Begin a list.
*
* @return list builder
*/
public Builder.ListBuilder beginList() {
return new Builder.ListBuilder(this);
}
public static final class ListBuilder {
private final Builder markup;
private final ArrayList<ListElement.ListEntryElement> entries = new ArrayList<>();
private int end = -1;
private ListBuilder(Builder markup) {
this.markup = markup;
}
/**
* Add an entry to the list.
* The start index of an entry must correspond to the end index of the previous entry
* (if a previous entry exists.)
*
* @param start start index
* @param end end index
* @return list builder
*/
public Builder.ListBuilder addEntry(int start, int end) {
verifyStartEnd(start, end);
ListElement.ListEntryElement last = entries.size() == 0 ? null : entries.get(entries.size() - 1);
// Entries themselves do not store end values, that's why we store the last entries end value in this.end
if (last != null && start != this.end) {
throw new IllegalArgumentException("Next entries start must be equal to last entries end (" + this.end + ").");
}
entries.add(new ListElement.ListEntryElement(start));
this.end = end;
return this;
}
/**
* End the list.
*
* @return builder TODO javadoc me please
*/
public Builder endList() {
if (entries.size() > 0) {
ListElement.ListEntryElement first = entries.get(0);
ListElement list = new ListElement(first.getStart(), end, entries);
markup.lists.add(list);
}
return markup;
}
}
/**
* Build a Message Markup element.
*
* @return extension element
*/
public MarkupElement build() {
List<MarkupElement.MarkupChildElement> children = new ArrayList<>();
children.addAll(spans);
children.addAll(quotes);
children.addAll(codes);
children.addAll(lists);
return new MarkupElement(children);
}
private static void verifyStartEnd(int start, int end) {
if (start >= end || start < 0) {
throw new IllegalArgumentException("Start value (" + start + ") MUST be greater equal than 0 " +
"and MUST be smaller than end value (" + end + ").");
}
}
}
/**
* Interface for child elements.
*/
public abstract static class MarkupChildElement implements ExtensionElement {
public static final String ATTR_START = "start";
public static final String ATTR_END = "end";
private final int start, end;
protected MarkupChildElement(int start, int end) {
this.start = start;
this.end = end;
}
/**
* Return the start index of this element.
*
* @return start index
*/
public final int getStart() {
return start;
}
/**
* Return the end index of this element.
*
* @return end index
*/
public final int getEnd() {
return end;
}
@Override
public final String getNamespace() {
return NAMESPACE;
}
@Override
public final XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
xml.attribute(ATTR_START, getStart());
xml.attribute(ATTR_END, getEnd());
afterXmlPrelude(xml);
return xml;
}
protected abstract void afterXmlPrelude(XmlStringBuilder xml);
}
public abstract static class NonEmptyChildElement extends MarkupChildElement {
protected NonEmptyChildElement(int start, int end) {
super(start, end);
}
@Override
protected final void afterXmlPrelude(XmlStringBuilder xml) {
xml.rightAngleBracket();
appendInnerXml(xml);
xml.closeElement(this);
}
protected abstract void appendInnerXml(XmlStringBuilder xml);
}
/**
* Interface for block level child elements.
*/
public abstract static class BlockLevelMarkupElement extends MarkupChildElement {
protected BlockLevelMarkupElement(int start, int end) {
super(start, end);
}
@Override
protected final void afterXmlPrelude(XmlStringBuilder xml) {
xml.closeEmptyElement();
}
}
}