blob: 63c850b80044dcbe70100a89115858528655fa8e [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2015, 2018 David Green and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0.
#
# SPDX-License-Identifier: EPL-2.0
#
# Contributors:
# David Green - org.eclipse.mylyn.docs: initial API and implementation
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.internal.docmlet.wikitext.commonmark.core.inlines;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.regex.Matcher;
import org.eclipse.mylyn.wikitext.parser.DocumentBuilder;
import com.google.common.escape.Escaper;
import com.google.common.net.UrlEscapers;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.docmlet.wikitext.core.source.LabelInfo;
import org.eclipse.statet.internal.docmlet.wikitext.commonmark.core.CommonmarkLocator;
import org.eclipse.statet.internal.docmlet.wikitext.commonmark.core.Cursor;
import org.eclipse.statet.internal.docmlet.wikitext.commonmark.core.Line;
import org.eclipse.statet.internal.docmlet.wikitext.commonmark.core.ProcessingContext;
import org.eclipse.statet.internal.docmlet.wikitext.commonmark.core.ProcessingContext.UriWithTitle;
public class PotentialBracketCloseDelimiter extends InlineWithText {
private final PotentialBracketRegex shared;
public PotentialBracketCloseDelimiter(final Line line, final int offset,
final PotentialBracketRegex shared) {
super(line, offset, 1, 1, "]");
this.shared= shared;
}
@Override
public void apply(final ProcessingContext context, final List<Inline> inlines,
final Cursor cursor, final boolean inBlock) {
final int openingDelimiterIndex= findLastPotentialBracketDelimiter(inlines);
if (openingDelimiterIndex >= 0) {
final PotentialBracketOpenDelimiter openingDelimiter= (PotentialBracketOpenDelimiter) inlines.get(openingDelimiterIndex);
if (openingDelimiter.isActive()) {
final boolean referenceDefinition= (inBlock
&& cursor.hasNext() && cursor.getNext() == ':'
&& isEligibleForReferenceDefinition(inlines, openingDelimiter, openingDelimiterIndex) );
final Matcher matcher= (cursor.hasNext()) ?
cursor.setup((referenceDefinition) ?
this.shared.getReferenceDefinitionEndMatcher() :
this.shared.getEndMatcher(),
1) :
null;
final List<Inline> contents= InlineParser.secondPass(
inlines.subList(openingDelimiterIndex + 1, inlines.size() ));
if (!openingDelimiter.isLinkDelimiter() || !containsLink(contents)) {
if (matcher != null /*== cursor.hasNext()*/ && matcher.matches()) {
final LabelInfo referenceLabel= (referenceDefinition) ?
referenceLabel(context, cursor, contents) :
null;
final String uri= linkUri(matcher, context, cursor);
if (!referenceDefinition
|| (referenceLabel != null && uri != null && !uri.isEmpty()) ) {
final String title= linkTitle(matcher, context, cursor);
final int closingLength= 1 + matcher.end(4) - matcher.regionStart();
cursor.advance(closingLength);
final int startOffset= openingDelimiter.getStartOffset();
final int endOffset= cursor.getOffset();
truncate(inlines, openingDelimiter, openingDelimiterIndex,
referenceDefinition );
if (openingDelimiter.isLinkDelimiter()) {
inactivatePreceding(inlines);
}
if (referenceDefinition) {
inlines.add(new ReferenceDefinition(openingDelimiter.getLine(),
startOffset, endOffset - startOffset,
uri, title, referenceLabel ));
}
else if (openingDelimiter.isImageDelimiter()) {
inlines.add(new Image(openingDelimiter.getLine(),
startOffset, endOffset - startOffset, uri,
title, contents ));
}
else {
inlines.add(new Link(openingDelimiter.getLine(),
startOffset, endOffset - startOffset,
uri, title, contents ));
}
return;
}
}
else {
int closingLength= 1;
LabelInfo referenceLabel= referenceLabel(context, cursor, contents);
if (cursor.hasNext()) {
final Matcher referenceLabelMatcher= cursor.setup(
this.shared.getReferenceLabelMatcher(),
1 );
if (referenceLabelMatcher.matches()) {
final String label= context.normalizeLabel(referenceLabelMatcher.group(2));
if (label != null) {
final int start= cursor.getMatcherOffset(referenceLabelMatcher.start(2));
final int end= cursor.getMatcherOffset(referenceLabelMatcher.end(2));
referenceLabel= new LabelInfo(label, start, end);
}
closingLength+= referenceLabelMatcher.end(1) - referenceLabelMatcher.regionStart();
}
}
if (referenceLabel != null) {
if (context.getMode() == ProcessingContext.PARSE_SOURCE_STRUCT) {
cursor.advance(closingLength);
final int startOffset= openingDelimiter.getStartOffset();
final int endOffset= cursor.getOffset();
truncate(inlines, openingDelimiter, openingDelimiterIndex, false);
if (openingDelimiter.isLinkDelimiter()) {
inactivatePreceding(inlines);
}
if (openingDelimiter.isLinkDelimiter()) {
inlines.add(new Link(openingDelimiter.getLine(),
startOffset, endOffset - startOffset,
referenceLabel, ImCollections.<Inline>emptyList() ));
}
else {
inlines.add(new Image(openingDelimiter.getLine(),
startOffset, endOffset - startOffset,
referenceLabel, contents ));
}
return;
}
final UriWithTitle uriWithTitle= context.getNamedUri(referenceLabel.getLabel());
if (uriWithTitle != null) {
cursor.advance(closingLength);
final int startOffset= openingDelimiter.getStartOffset();
final int endOffset= cursor.getOffset();
truncate(inlines, openingDelimiter, openingDelimiterIndex, false);
if (openingDelimiter.isLinkDelimiter()) {
inactivatePreceding(inlines);
}
if (openingDelimiter.isLinkDelimiter()) {
inlines.add(new Link(openingDelimiter.getLine(),
startOffset, endOffset - startOffset,
uriWithTitle.getUri(), uriWithTitle.getTitle(), contents ));
}
else {
inlines.add(new Image(openingDelimiter.getLine(),
startOffset, endOffset - startOffset,
uriWithTitle.getUri(), uriWithTitle.getTitle(), contents ));
}
return;
}
}
}
}
}
replaceDelimiter(inlines, openingDelimiterIndex, openingDelimiter);
}
if (Characters.append(inlines, inlines.size(), this)) {
cursor.advance(getLength());
return;
}
super.apply(context, inlines, cursor, inBlock);
}
@Override
public void emit(final ProcessingContext context,
final CommonmarkLocator locator, final DocumentBuilder builder) {
builder.characters(this.text);
}
private LabelInfo referenceLabel(final ProcessingContext context, final Cursor cursor,
final List<Inline> contents) {
if (contents.isEmpty()) {
return null;
}
final int start= contents.get(0).getStartOffset();
final int end= getStartOffset();
String name= cursor.getText(
cursor.toCursorOffset(start),
cursor.toCursorOffset(end) );
if (this.shared.getReferenceNameMatcher().reset(name).matches()) {
name= context.normalizeLabel(name);
if (name != null) {
return new LabelInfo(name, start, end);
}
}
return null;
}
private boolean containsLink(final List<Inline> contents) {
for (final Inline inline : contents) {
if (inline instanceof Link) {
return true;
} else if (inline instanceof InlineWithNestedContents
&& containsLink(((InlineWithNestedContents) inline).getContents())) {
return true;
}
}
return false;
}
private int findLastPotentialBracketDelimiter(final List<Inline> inlines) {
for (int idx= inlines.size() - 1; idx >= 0; --idx) {
final Inline inline= inlines.get(idx);
if (inline instanceof PotentialBracketOpenDelimiter) {
return idx;
}
}
return -1;
}
private boolean isIndentInline(final Inline inline, final Line line) {
return (inline instanceof Characters
&& inline.getStartOffset() == line.getStartOffset()
&& inline.getLength() == line.getIndentLength() );
}
boolean isEligibleForReferenceDefinition(final List<Inline> inlines,
final PotentialBracketOpenDelimiter openingDelimiter, final int openingDelimiterIndex) {
final Line openingLine= openingDelimiter.getLine();
if (openingDelimiter.isLinkDelimiter()
&& openingLine.getIndent() < 4
&& openingDelimiter.getStartOffset() == openingLine.getStartOffset() + openingLine.getIndentLength() ) {
if (openingDelimiterIndex > 0) {
int idx= openingDelimiterIndex - 1;
{ final Inline inline= inlines.get(idx--);
if (!(inline instanceof ReferenceDefinition
|| isIndentInline(inline, openingLine) )) {
return false;
}
}
while (idx >= 0) {
final Inline inline= inlines.get(idx--);
if (!(inline instanceof ReferenceDefinition)) {
return false;
}
}
}
return true;
}
return false;
}
private String linkTitle(final Matcher matcher, final ProcessingContext context, final Cursor cursor) {
final int start= matcher.start(3);
if (start != -1) {
final int end= matcher.end(3);
final String title= cursor.getText(start + 1, end - 1);
return context.getHelper().replaceEscaping(title);
}
return "";
}
private String linkUri(final Matcher matcher, final ProcessingContext context, final Cursor cursor) {
String uriWithEscapes= matcher.group(1);
if (uriWithEscapes == null) {
uriWithEscapes= matcher.group(2);
if (uriWithEscapes == null) {
uriWithEscapes= "";
}
}
final String uri= context.getHelper().replaceEscaping(uriWithEscapes);
return normalizeUri(uri);
}
private String normalizeUri(final String uri) {
try {
final String decoded= URLDecoder.decode(uri, StandardCharsets.UTF_8.name());
final Escaper escaper= UrlEscapers.urlFragmentEscaper();
final int indexOfHash= decoded.indexOf('#');
if (indexOfHash != -1) {
String uriWithHash= escaper.escape(decoded.substring(0, indexOfHash)) + '#';
if ((indexOfHash + 1) < decoded.length()) {
uriWithHash+= escaper.escape(decoded.substring(indexOfHash + 1));
}
return uriWithHash;
}
return escaper.escape(decoded);
} catch (final Exception e) {
return uri;
}
}
public void truncate(final List<Inline> inlines,
final PotentialBracketOpenDelimiter openingDelimiter, final int indexOfOpeningDelimiter,
final boolean removeIndent) {
while (inlines.size() > indexOfOpeningDelimiter) {
inlines.remove(indexOfOpeningDelimiter);
}
if (removeIndent && indexOfOpeningDelimiter > 0
&& isIndentInline(inlines.get(indexOfOpeningDelimiter - 1), openingDelimiter.getLine()) ) {
inlines.remove(indexOfOpeningDelimiter - 1);
}
}
private void inactivatePreceding(final List<Inline> inlines) {
for (int idx= inlines.size() - 1; idx >= 0; idx--) {
final Inline inline= inlines.get(idx);
if (inline instanceof PotentialBracketOpenDelimiter) {
final PotentialBracketOpenDelimiter openDelimiter= (PotentialBracketOpenDelimiter) inline;
if (openDelimiter.isLinkDelimiter()) {
if (openDelimiter.isActive()) {
openDelimiter.setInactive();
}
else {
return;
}
}
}
}
}
private void replaceDelimiter(final List<Inline> inlines,
final int index, final PotentialBracketOpenDelimiter delimiter) {
if (Characters.append(inlines, index, delimiter)) {
inlines.remove(index);
return;
}
inlines.set(index,
new Characters(delimiter.getLine(),
delimiter.getStartOffset(), delimiter.getLength(), delimiter.getCursorLength(),
delimiter.getText() ));
}
}