1
0
Fork 0
mirror of https://codeberg.org/Mercury-IM/Smack synced 2024-06-23 20:14:51 +02:00
Smack/smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/element/MarkupElement.java
Florian Schmaus 3d4e7938a7 Make ExtensionElement marker interface wrt. QNAME field
ExtensionElement is now a marker interface that requires all
implementation non-abstract classes to carry a static final QNAME
field (of type QName). This is verified by a new unit test.

Also FullyQualifiedElement is renamed to simply XmlElement. XmlElement
is used over ExtensionElement when implementing classes do not
statically know the qualified name of the XML elements they
represent. In general, XmlElement should be used sparingly, and every
XML element should be modeled by its own Java class (implementing
ExtensionElement).
2021-04-18 21:07:19 +02:00

357 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 NAMESPACE = MarkupElement.NAMESPACE;
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();
}
}
}