Add support for XEP-0394: Message Markup

Fixes SMACK-794.
This commit is contained in:
Paul Schaub 2018-02-21 20:49:01 +01:00 committed by Florian Schmaus
parent b3b76b9ff4
commit a729a7c43b
13 changed files with 1167 additions and 0 deletions

View File

@ -98,6 +98,7 @@ Experimental Smack Extensions and currently supported XEPs of smack-experimental
| [OMEMO Multi End Message and Object Encryption](omemo.md) | [XEP-XXXX](https://conversations.im/omemo/xep-omemo.html) | Encrypt messages using OMEMO encryption (currently only with smack-omemo-signal -> GPLv3). |
| [Consistent Color Generation](consistent_colors.md) | [XEP-0392](http://xmpp.org/extensions/xep-0392.html) | Generate consistent colors for identifiers like usernames to provide a consistent user experience. |
| Google GCM JSON payload | n/a | Semantically the same as XEP-0335: JSON Containers |
| [Message Markup](messagemarkup.md) | [XEP-0394](http://xmpp.org/extensions/xep-0394.html)| Style message bodies while keeping body and markup information separated. |
Legacy Smack Extensions and currently supported XEPs of smack-legacy

View File

@ -0,0 +1,68 @@
Message Markup
==============
[Back](index.md)
[Message Markup (XEP-0394)](https://xmpp.org/extensions/xep-0394.html) can be used as a an alternative to XHTML-IM to style messages, while keeping the body and markup information strictly separated.
This implementation can *not* be used to render message bodies, but will offer a simple to use interface for creating ExtensionElements which encode the markup information.
## Usage
The most important class is the `MarkupElement` class, which contains a Builder.
To start creating a Message Markup Extension, call `MarkupElement.getBuilder()`.
(Almost) all method calls documented below will be made on the builder.
Whenever a method call receives a `start` and `end` index, `start` represents the first character, which is affected by the styling, while `end` is the character *after* the last affected character.
### Inline styling
Currently there are 3 styles available:
* *emphasis*, which should be rendered by a client as *italic*, or **bold**
* *code*, which should be rendered in `monospace`
* *deleted*, which should be rendered as ~~strikethrough~~.
Those styles are available by calling `builder.setEmphasis(int start, int end)`,
`builder.setDeleted(int start, int end)` and `builder.setCode(int start, int end)`.
If you want to apply multiple inline styles to a section, you can do the following:
```
Set<SpanElement.SpanStyle> spanStyles = new HashSet<>();
styles.add(SpanElement.SpanStyle.emphasis);
styles.add(SpanElement.SpanStyle.deleted);
builder.addSpan(start, end, spanStyles);
```
Note, that spans cannot overlap one another.
### Block Level Styling
Available block level styles are:
* Code blocks, which should be rendered as
```
blocks
of
code
```
* Itemized lists, which should render as
* Lists
* with possibly multiple
* entries
* Block Quotes, which should be rendered by the client
> as quotes, which
>> also can be nested
To mark a section as code block, call `builder.setCodeBlock(start, end)`.
To create a list, call `MarkupElement.Builder.ListBuilder lbuilder = builder.beginList()`, which will return a list builder.
On this you can call `lbuilder.addEntry(start, end)` to add an entry.
Note: If you add an entry, the start value MUST be equal to the end value of the previous added entry!
To end the list, call `lbuilder.endList()`, which will return the MessageMarkup builder.
To create a block quote, call `builder.setBlockQuote(start, end)`.
Note that block level elements MUST NOT overlap each other boundaries, but may be fully contained (nested) within each other.

View File

@ -0,0 +1,62 @@
/**
*
* 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 org.jivesoftware.smack.util.XmlStringBuilder;
public class BlockQuoteElement implements MarkupElement.BlockLevelMarkupElement {
public static final String ELEMENT = "bquote";
private final int start, end;
/**
* Create a new Block Quote element.
*
* @param start start index
* @param end end index
*/
public BlockQuoteElement(int start, int end) {
this.start = start;
this.end = end;
}
@Override
public int getStart() {
return start;
}
@Override
public int getEnd() {
return end;
}
@Override
public String getElementName() {
return ELEMENT;
}
@Override
public XmlStringBuilder toXML() {
XmlStringBuilder xml = new XmlStringBuilder();
xml.halfOpenElement(this);
xml.attribute(ATTR_START, getStart());
xml.attribute(ATTR_END, getEnd());
xml.closeEmptyElement();
return xml;
}
}

View File

@ -0,0 +1,62 @@
/**
*
* 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 org.jivesoftware.smack.util.XmlStringBuilder;
public class CodeBlockElement implements MarkupElement.BlockLevelMarkupElement {
public static final String ELEMENT = "bcode";
private final int start, end;
/**
* Create a new Code Block element.
*
* @param start start index
* @param end end index
*/
public CodeBlockElement(int start, int end) {
this.start = start;
this.end = end;
}
@Override
public int getStart() {
return start;
}
@Override
public int getEnd() {
return end;
}
@Override
public String getElementName() {
return ELEMENT;
}
@Override
public XmlStringBuilder toXML() {
XmlStringBuilder xml = new XmlStringBuilder();
xml.halfOpenElement(this);
xml.attribute(ATTR_START, getStart());
xml.attribute(ATTR_END, getEnd());
xml.closeEmptyElement();
return xml;
}
}

View File

@ -0,0 +1,121 @@
/**
*
* 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.Collections;
import java.util.List;
import org.jivesoftware.smack.packet.NamedElement;
import org.jivesoftware.smack.util.XmlStringBuilder;
public class ListElement implements MarkupElement.MarkupChildElement {
public static final String ELEMENT = "list";
public static final String ELEM_LI = "li";
private final int start, end;
private final List<ListEntryElement> entries;
/**
* Create a new List element.
*
* @param start start index of the list
* @param end end index of the list
* @param entries list entries
*/
public ListElement(int start, int end, List<ListEntryElement> entries) {
this.start = start;
this.end = end;
this.entries = Collections.unmodifiableList(entries);
}
@Override
public int getStart() {
return start;
}
@Override
public int getEnd() {
return end;
}
/**
* Return a list of all list entries.
*
* @return entries
*/
public List<ListEntryElement> getEntries() {
return entries;
}
@Override
public String getElementName() {
return ELEMENT;
}
@Override
public CharSequence toXML() {
XmlStringBuilder xml = new XmlStringBuilder();
xml.halfOpenElement(this);
xml.attribute(ATTR_START, getStart());
xml.attribute(ATTR_END, getEnd());
xml.rightAngleBracket();
for (ListEntryElement li : getEntries()) {
xml.append(li.toXML());
}
xml.closeElement(this);
return xml;
}
public static class ListEntryElement implements NamedElement {
private final int start;
/**
* Create a new ListEntry element.
*
* @param start start index
*/
public ListEntryElement(int start) {
this.start = start;
}
/**
* Return the start index of this entry.
* @return start index
*/
public int getStart() {
return start;
}
@Override
public String getElementName() {
return ELEM_LI;
}
@Override
public XmlStringBuilder toXML() {
XmlStringBuilder xml = new XmlStringBuilder();
xml.halfOpenElement(this);
xml.attribute(ATTR_START, getStart());
xml.closeEmptyElement();
return xml;
}
}
}

View File

@ -0,0 +1,312 @@
/**
*
* 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.packet.NamedElement;
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
*/
public List<MarkupChildElement> getChildElements() {
return childElements;
}
@Override
public String getNamespace() {
return NAMESPACE;
}
@Override
public String getElementName() {
return ELEMENT;
}
@Override
public XmlStringBuilder toXML() {
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
*/
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
*/
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
*/
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
*/
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
*/
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
*/
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
*/
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 interface MarkupChildElement extends NamedElement {
String ATTR_START = "start";
String ATTR_END = "end";
/**
* Return the start index of this element.
*
* @return start index
*/
int getStart();
/**
* Return the end index of this element.
*
* @return end index
*/
int getEnd();
}
/**
* Interface for block level child elements.
*/
public interface BlockLevelMarkupElement extends MarkupChildElement {
}
}

View File

@ -0,0 +1,93 @@
/**
*
* 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.Collections;
import java.util.Set;
import org.jivesoftware.smack.util.XmlStringBuilder;
public class SpanElement implements MarkupElement.MarkupChildElement {
public static final String ELEMENT = "span";
private final int start, end;
private final Set<SpanStyle> styles;
/**
* Create a new Span element.
*
* @param start start index
* @param end end index
* @param styles list of styles that apply to this span
*/
public SpanElement(int start, int end, Set<SpanStyle> styles) {
this.start = start;
this.end = end;
this.styles = Collections.unmodifiableSet(styles);
}
@Override
public int getStart() {
return start;
}
@Override
public int getEnd() {
return end;
}
/**
* Return all styles of this span.
*
* @return styles
*/
public Set<SpanStyle> getStyles() {
return styles;
}
public static final String emphasis = "emphasis";
public static final String code = "code";
public static final String deleted = "deleted";
public enum SpanStyle {
emphasis,
code,
deleted
}
@Override
public String getElementName() {
return ELEMENT;
}
@Override
public XmlStringBuilder toXML() {
XmlStringBuilder xml = new XmlStringBuilder();
xml.halfOpenElement(this);
xml.attribute(ATTR_START, getStart());
xml.attribute(ATTR_END, getEnd());
xml.rightAngleBracket();
for (SpanStyle style : getStyles()) {
xml.halfOpenElement(style.toString()).closeEmptyElement();
}
xml.closeElement(this);
return xml;
}
}

View File

@ -0,0 +1,25 @@
/**
*
* 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.
*/
/**
* XEP-0394: Message Markup.
*
* @see <a href="http://xmpp.org/extensions/xep-0394.html">XEP-0394: Message
* Markup</a>
*
*/
package org.jivesoftware.smackx.message_markup.element;

View File

@ -0,0 +1,25 @@
/**
*
* 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.
*/
/**
* XEP-0394: Message Markup.
*
* @see <a href="http://xmpp.org/extensions/xep-0394.html">XEP-0394: Message
* Markup</a>
*
*/
package org.jivesoftware.smackx.message_markup;

View File

@ -0,0 +1,139 @@
/**
*
* 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.provider;
import static org.xmlpull.v1.XmlPullParser.END_TAG;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.provider.ExtensionElementProvider;
import org.jivesoftware.smack.util.ParserUtils;
import org.jivesoftware.smackx.message_markup.element.BlockQuoteElement;
import org.jivesoftware.smackx.message_markup.element.CodeBlockElement;
import org.jivesoftware.smackx.message_markup.element.ListElement;
import org.jivesoftware.smackx.message_markup.element.MarkupElement;
import org.jivesoftware.smackx.message_markup.element.SpanElement;
import org.xmlpull.v1.XmlPullParser;
public class MarkupElementProvider extends ExtensionElementProvider<MarkupElement> {
@Override
public MarkupElement parse(XmlPullParser parser, int initialDepth) throws Exception {
MarkupElement.Builder markup = MarkupElement.getBuilder();
int spanStart = -1, spanEnd = -1;
Set<SpanElement.SpanStyle> spanStyles = new HashSet<>();
int listStart = -1, listEnd = -1;
List<ListElement.ListEntryElement> lis = new ArrayList<>();
while (true) {
int tag = parser.next();
String name = parser.getName();
int start, end;
switch (tag) {
case START_TAG:
switch (name) {
case BlockQuoteElement.ELEMENT:
start = ParserUtils.getIntegerAttributeOrThrow(parser, BlockQuoteElement.ATTR_START,
"Message Markup BlockQuoteElement MUST contain a 'start' attribute.");
end = ParserUtils.getIntegerAttributeOrThrow(parser, BlockQuoteElement.ATTR_END,
"Message Markup BlockQuoteElement MUST contain a 'end' attribute.");
markup.setBlockQuote(start, end);
break;
case CodeBlockElement.ELEMENT:
start = ParserUtils.getIntegerAttributeOrThrow(parser, CodeBlockElement.ATTR_START,
"Message Markup CodeBlockElement MUST contain a 'start' attribute.");
end = ParserUtils.getIntegerAttributeOrThrow(parser, CodeBlockElement.ATTR_END,
"Message Markup CodeBlockElement MUST contain a 'end' attribute.");
markup.setCodeBlock(start, end);
break;
case SpanElement.ELEMENT:
spanStyles = new HashSet<>();
spanStart = ParserUtils.getIntegerAttributeOrThrow(parser, SpanElement.ATTR_START,
"Message Markup SpanElement MUST contain a 'start' attribute.");
spanEnd = ParserUtils.getIntegerAttributeOrThrow(parser, SpanElement.ATTR_END,
"Message Markup SpanElement MUST contain a 'end' attribute.");
break;
case SpanElement.code:
spanStyles.add(SpanElement.SpanStyle.code);
break;
case SpanElement.emphasis:
spanStyles.add(SpanElement.SpanStyle.emphasis);
break;
case SpanElement.deleted:
spanStyles.add(SpanElement.SpanStyle.deleted);
break;
case ListElement.ELEMENT:
lis = new ArrayList<>();
listStart = ParserUtils.getIntegerAttributeOrThrow(parser, ListElement.ATTR_START,
"Message Markup ListElement MUST contain a 'start' attribute.");
listEnd = ParserUtils.getIntegerAttributeOrThrow(parser, ListElement.ATTR_END,
"Message Markup ListElement MUST contain a 'end' attribute.");
break;
case ListElement.ELEM_LI:
start = ParserUtils.getIntegerAttributeOrThrow(parser, ListElement.ATTR_START,
"Message Markup ListElement 'li' MUST contain a 'start' attribute.");
lis.add(new ListElement.ListEntryElement(start));
break;
}
break;
case END_TAG:
switch (name) {
case SpanElement.ELEMENT:
markup.addSpan(spanStart, spanEnd, spanStyles);
spanStart = -1; spanEnd = -1;
break;
case ListElement.ELEMENT:
MarkupElement.Builder.ListBuilder listBuilder = markup.beginList();
if (lis.size() > 0 && lis.get(0).getStart() != listStart) {
throw new SmackException("Error while parsing incoming MessageMarkup ListElement: " +
"'start' attribute of first 'li' element must equal 'start' attribute of list.");
}
for (int i = 0; i < lis.size(); i++) {
int elemStart = lis.get(i).getStart();
int elemEnd = i < lis.size() - 1 ? lis.get(i + 1).getStart() : listEnd;
listBuilder.addEntry(elemStart, elemEnd);
}
listBuilder.endList();
break;
case MarkupElement.ELEMENT:
return markup.build();
}
}
}
}
}

View File

@ -0,0 +1,25 @@
/**
*
* 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.
*/
/**
* XEP-0394: Message Markup.
*
* @see <a href="http://xmpp.org/extensions/xep-0394.html">XEP-0394: Message
* Markup</a>
*
*/
package org.jivesoftware.smackx.message_markup.provider;

View File

@ -296,4 +296,11 @@
<className>org.jivesoftware.smackx.eme.provider.ExplicitMessageEncryptionProvider</className>
</extensionProvider>
<!-- XEP-0394: Message Markup -->
<extensionProvider>
<elementName>markup</elementName>
<namespace>urn:xmpp:markup:0</namespace>
<className>org.jivesoftware.smackx.message_markup.provider.MarkupElementProvider</className>
</extensionProvider>
</smackProviders>

View File

@ -0,0 +1,227 @@
/**
*
* 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;
import static junit.framework.TestCase.assertEquals;
import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
import java.util.List;
import org.jivesoftware.smack.test.util.SmackTestSuite;
import org.jivesoftware.smack.test.util.TestUtils;
import org.jivesoftware.smackx.message_markup.element.BlockQuoteElement;
import org.jivesoftware.smackx.message_markup.element.CodeBlockElement;
import org.jivesoftware.smackx.message_markup.element.ListElement;
import org.jivesoftware.smackx.message_markup.element.MarkupElement;
import org.jivesoftware.smackx.message_markup.element.SpanElement;
import org.jivesoftware.smackx.message_markup.provider.MarkupElementProvider;
import org.junit.Test;
import org.xmlpull.v1.XmlPullParser;
public class MessageMarkupTest extends SmackTestSuite {
@Test
public void emphasisTest() throws Exception {
String xml =
"<markup xmlns='urn:xmpp:markup:0'>" +
"<span start='9' end='15'>" +
"<emphasis/>" +
"</span>" +
"</markup>";
MarkupElement.Builder m = MarkupElement.getBuilder();
m.setEmphasis(9, 15);
assertXMLEqual(xml, m.build().toXML().toString());
XmlPullParser parser = TestUtils.getParser(xml);
MarkupElement parsed = new MarkupElementProvider().parse(parser);
List<MarkupElement.MarkupChildElement> children = parsed.getChildElements();
assertEquals(1, children.size());
SpanElement spanElement = (SpanElement) children.get(0);
assertEquals(9, spanElement.getStart());
assertEquals(15, spanElement.getEnd());
assertEquals(1, spanElement.getStyles().size());
assertEquals(SpanElement.SpanStyle.emphasis, spanElement.getStyles().iterator().next());
}
@Test
public void codeTest() throws Exception {
String xml =
"<markup xmlns='urn:xmpp:markup:0'>" +
"<span start='9' end='15'>" +
"<code/>" +
"</span>" +
"</markup>";
MarkupElement.Builder m = MarkupElement.getBuilder();
m.setCode(9, 15);
assertXMLEqual(xml, m.build().toXML().toString());
XmlPullParser parser = TestUtils.getParser(xml);
MarkupElement parsed = new MarkupElementProvider().parse(parser);
List<MarkupElement.MarkupChildElement> children = parsed.getChildElements();
assertEquals(1, children.size());
SpanElement spanElement = (SpanElement) children.get(0);
assertEquals(9, spanElement.getStart());
assertEquals(15, spanElement.getEnd());
assertEquals(1, spanElement.getStyles().size());
assertEquals(SpanElement.SpanStyle.code, spanElement.getStyles().iterator().next());
}
@Test
public void deletedTest() throws Exception {
String xml =
"<markup xmlns='urn:xmpp:markup:0'>" +
"<span start='9' end='15'>" +
"<deleted/>" +
"</span>" +
"</markup>";
MarkupElement.Builder m = MarkupElement.getBuilder();
m.setDeleted(9, 15);
assertXMLEqual(xml, m.build().toXML().toString());
XmlPullParser parser = TestUtils.getParser(xml);
MarkupElement parsed = new MarkupElementProvider().parse(parser);
List<MarkupElement.MarkupChildElement> children = parsed.getChildElements();
assertEquals(1, children.size());
SpanElement spanElement = (SpanElement) children.get(0);
assertEquals(9, spanElement.getStart());
assertEquals(15, spanElement.getEnd());
assertEquals(1, spanElement.getStyles().size());
assertEquals(SpanElement.SpanStyle.deleted, spanElement.getStyles().iterator().next());
}
@Test(expected = IllegalArgumentException.class)
public void wrongStartEndTest() {
MarkupElement.getBuilder().setEmphasis(12, 10);
}
@Test(expected = IllegalArgumentException.class)
public void overlappingSpansTest() {
MarkupElement.Builder m = MarkupElement.getBuilder();
m.setEmphasis(0, 10);
m.setDeleted(5, 15);
}
@Test
public void codeBlockTest() throws Exception {
String xml =
"<markup xmlns='urn:xmpp:markup:0'>" +
"<bcode start='23' end='48'/>" +
"</markup>";
MarkupElement.Builder m = MarkupElement.getBuilder();
m.setCodeBlock(23, 48);
assertXMLEqual(xml, m.build().toXML().toString());
XmlPullParser parser = TestUtils.getParser(xml);
MarkupElement parsed = new MarkupElementProvider().parse(parser);
List<MarkupElement.MarkupChildElement> children = parsed.getChildElements();
assertEquals(1, children.size());
CodeBlockElement codeBlock = (CodeBlockElement) children.get(0);
assertEquals(23, codeBlock.getStart());
assertEquals(48, codeBlock.getEnd());
}
@Test
public void listTest() throws Exception {
String xml =
"<markup xmlns='urn:xmpp:markup:0'>" +
"<list start='31' end='89'>" +
"<li start='31'/>" +
"<li start='47'/>" +
"<li start='61'/>" +
"<li start='69'/>" +
"</list>" +
"</markup>";
MarkupElement.Builder m = MarkupElement.getBuilder();
m = m.beginList()
.addEntry(31, 47)
.addEntry(47, 61)
.addEntry(61, 69)
.addEntry(69, 89)
.endList();
assertXMLEqual(xml, m.build().toXML().toString());
XmlPullParser parser = TestUtils.getParser(xml);
MarkupElement parsed = new MarkupElementProvider().parse(parser);
List<MarkupElement.MarkupChildElement> children = parsed.getChildElements();
assertEquals(1, children.size());
ListElement list = (ListElement) children.get(0);
assertEquals(31, list.getStart());
assertEquals(89, list.getEnd());
assertEquals(4, list.getEntries().size());
assertEquals(list.getStart(), list.getEntries().get(0).getStart());
assertEquals(47, list.getEntries().get(1).getStart());
}
@Test(expected = IllegalArgumentException.class)
public void listWrongSecondEntryTest() {
MarkupElement.Builder m = MarkupElement.getBuilder();
m.beginList().addEntry(0,1).addEntry(3,4);
}
@Test
public void blockQuoteTest() throws Exception {
String xml =
"<markup xmlns='urn:xmpp:markup:0'>" +
"<bquote start='9' end='32'/>" +
"</markup>";
MarkupElement.Builder m = MarkupElement.getBuilder();
m.setBlockQuote(9 ,32);
assertXMLEqual(xml, m.build().toXML().toString());
XmlPullParser parser = TestUtils.getParser(xml);
MarkupElement parsed = new MarkupElementProvider().parse(parser);
List<MarkupElement.MarkupChildElement> children = parsed.getChildElements();
assertEquals(1, children.size());
BlockQuoteElement quote = (BlockQuoteElement) children.get(0);
assertEquals(9, quote.getStart());
assertEquals(32, quote.getEnd());
}
@Test
public void nestedBlockQuoteTest() throws Exception {
String xml =
"<markup xmlns='urn:xmpp:markup:0'>" +
"<bquote start='0' end='57'/>" +
"<bquote start='11' end='34'/>" +
"</markup>";
MarkupElement.Builder m = MarkupElement.getBuilder();
m.setBlockQuote(0, 57);
m.setBlockQuote(11, 34);
assertXMLEqual(xml, m.build().toXML().toString());
XmlPullParser parser = TestUtils.getParser(xml);
MarkupElement parsed = new MarkupElementProvider().parse(parser);
List<MarkupElement.MarkupChildElement> children = parsed.getChildElements();
assertEquals(2, children.size());
BlockQuoteElement q1 = (BlockQuoteElement) children.get(0);
BlockQuoteElement q2 = (BlockQuoteElement) children.get(1);
assertEquals(0, q1.getStart());
assertEquals(57, q1.getEnd());
assertEquals(11, q2.getStart());
assertEquals(34, q2.getEnd());
}
}