mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2024-11-22 14:22:05 +01:00
Add support for XEP-0394: Message Markup
Fixes SMACK-794.
This commit is contained in:
parent
b3b76b9ff4
commit
a729a7c43b
13 changed files with 1167 additions and 0 deletions
|
@ -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
|
||||
|
|
68
documentation/extensions/messagemarkup.md
Normal file
68
documentation/extensions/messagemarkup.md
Normal 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.
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
|
@ -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>
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue