| /*=============================================================================# |
| # Copyright (c) 2009, 2018 Stephan Wahlbrink 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, or the Apache License, Version 2.0 |
| # which is available at https://www.apache.org/licenses/LICENSE-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 |
| # |
| # Contributors: |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation |
| #=============================================================================*/ |
| |
| package org.eclipse.statet.docmlet.wikitext.core.ast; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.eclipse.mylyn.wikitext.parser.Attributes; |
| import org.eclipse.mylyn.wikitext.parser.DocumentBuilder; |
| import org.eclipse.mylyn.wikitext.parser.LinkAttributes; |
| import org.eclipse.mylyn.wikitext.parser.Locator; |
| |
| import org.eclipse.statet.jcommons.string.InternStringFactory; |
| import org.eclipse.statet.jcommons.string.StringFactory; |
| import org.eclipse.statet.jcommons.text.core.TextRegion; |
| import org.eclipse.statet.jcommons.text.core.util.HtmlUtils; |
| |
| import org.eclipse.statet.docmlet.wikitext.core.markup.IMarkupLanguage; |
| import org.eclipse.statet.docmlet.wikitext.core.markup.IWikitextLocator; |
| import org.eclipse.statet.docmlet.wikitext.core.markup.MarkupParser2; |
| import org.eclipse.statet.docmlet.wikitext.core.source.EmbeddingAttributes; |
| import org.eclipse.statet.docmlet.wikitext.core.source.ImageByRefAttributes; |
| import org.eclipse.statet.docmlet.wikitext.core.source.LabelInfo; |
| import org.eclipse.statet.docmlet.wikitext.core.source.LinkByRefAttributes; |
| import org.eclipse.statet.docmlet.wikitext.core.source.LinkRefDefinitionAttributes; |
| import org.eclipse.statet.docmlet.wikitext.core.source.SourceTextBlockAttributes; |
| import org.eclipse.statet.ltk.core.SourceContent; |
| |
| |
| public class WikidocParser extends DocumentBuilder { |
| |
| |
| private final StringFactory labelFactory; |
| |
| private IMarkupLanguage markupLanguage; |
| |
| private SourceContent content; |
| |
| private IWikitextLocator locator2; |
| |
| private WikitextAstNode currentNode; |
| |
| private boolean headingText; |
| |
| private int collectText; |
| private Text currentText; |
| private final StringBuilder textBuilder= new StringBuilder(); |
| |
| private int depth; |
| private final List<List<WikitextAstNode>> childrenStack= new ArrayList<>(); |
| |
| private List<Embedded> embeddedList; |
| |
| |
| public WikidocParser(final StringFactory labelFactory) { |
| this.labelFactory= (labelFactory != null) ? labelFactory : InternStringFactory.INSTANCE; |
| } |
| |
| |
| public void setMarkupLanguage(final IMarkupLanguage markupLanguage) { |
| this.markupLanguage= markupLanguage; |
| } |
| |
| @Override |
| public void setLocator(final Locator locator) { |
| super.setLocator(locator); |
| this.locator2= (IWikitextLocator) locator; |
| } |
| |
| public void setCollectEmebeddedNodes(final boolean enable) { |
| this.embeddedList= (enable) ? new ArrayList<>(32) : null; |
| } |
| |
| public List<Embedded> getEmbeddedNodes() { |
| return this.embeddedList; |
| } |
| |
| public void setCollectHeadingText(final boolean enable) { |
| this.headingText= enable; |
| } |
| |
| |
| public SourceComponent parse(final SourceContent content) { |
| try { |
| if (this.embeddedList != null) { |
| this.embeddedList.clear(); |
| } |
| this.content= content; |
| this.depth= -1; |
| |
| final MarkupParser2 markupParser= new MarkupParser2(this.markupLanguage, this); |
| markupParser.disable(MarkupParser2.GENERATIVE_CONTENT); |
| markupParser.enable(MarkupParser2.SOURCE_STRUCT); |
| |
| markupParser.parse(content, true); |
| |
| return (SourceComponent) this.currentNode; |
| } |
| finally { |
| while (this.depth >= 0) { |
| final List<WikitextAstNode> list= this.childrenStack.get(this.depth); |
| list.clear(); |
| this.depth--; |
| } |
| } |
| } |
| |
| |
| private void addChildNode(final WikitextAstNode node) { |
| final List<WikitextAstNode> children= this.childrenStack.get(this.depth); |
| children.add(node); |
| } |
| |
| private void finishNode() { |
| final List<WikitextAstNode> children= this.childrenStack.get(this.depth); |
| if (!children.isEmpty()) { |
| ((ContainerNode) this.currentNode).children= children.toArray(new WikitextAstNode[children.size()]); |
| children.clear(); |
| } |
| } |
| |
| private void enterNode(final WikitextAstNode node) { |
| if (this.depth >= 0) { |
| addChildNode(node); |
| } |
| |
| this.depth++; |
| this.currentNode= node; |
| |
| if (this.depth == this.childrenStack.size()) { |
| this.childrenStack.add(new ArrayList<WikitextAstNode>()); |
| } |
| } |
| |
| private void exitNode(final int offset) { |
| finishNode(); |
| |
| // if (offset < this.currentNode.endOffset) { |
| // System.out.println("endOffset: " + offset); |
| // } |
| // if (offset > this.currentNode.endOffset) { |
| this.currentNode.endOffset= offset; |
| // } |
| // else { |
| // offset= this.currentNode.endOffset; |
| // } |
| this.currentNode= this.currentNode.parent; |
| // if (offset > this.currentNode.endOffset) { |
| // this.currentNode.endOffset= offset; |
| // } |
| this.depth--; |
| } |
| |
| |
| private void finishText() { |
| if (this.currentText == null) { |
| return; |
| } |
| this.currentText.text= this.textBuilder.toString(); |
| this.currentText= null; |
| } |
| |
| |
| @Override |
| public void beginDocument() { |
| enterNode(new SourceComponent(this.content.getText().length())); |
| } |
| |
| @Override |
| public void endDocument() { |
| assert (this.currentNode instanceof SourceComponent); |
| finishText(); |
| finishNode(); |
| |
| this.depth--; |
| } |
| |
| @Override |
| public void beginBlock(final BlockType type, final Attributes attributes) { |
| finishText(); |
| final WikitextAstNode node; |
| if (attributes instanceof EmbeddingAttributes) { |
| final EmbeddingAttributes embeddingAttributes= (EmbeddingAttributes) attributes; |
| final Embedded embedded; |
| node= embedded= new Embedded(this.currentNode, |
| this.locator2.getBeginOffset(), this.locator2.getEndOffset(), |
| embeddingAttributes.getForeignType(), embeddingAttributes.getEmbedDescr() ); |
| if (this.embeddedList != null) { |
| this.embeddedList.add(embedded); |
| } |
| } |
| else if (attributes instanceof SourceTextBlockAttributes) { |
| node= new Block.TextBlock(this.currentNode, this.locator2.getBeginOffset(), |
| type, attributes.getId(), ((SourceTextBlockAttributes) attributes).getTextRegions() ); |
| } |
| else { |
| node= new Block.Common(this.currentNode, this.locator2.getBeginOffset(), |
| type, attributes.getId() ); |
| } |
| enterNode(node); |
| } |
| |
| @Override |
| public void endBlock() { |
| finishText(); |
| exitNode(this.locator2.getEndOffset()); |
| } |
| |
| @Override |
| public void beginSpan(final SpanType type, final Attributes attributes) { |
| finishText(); |
| final WikitextAstNode node; |
| if (attributes instanceof EmbeddingAttributes) { |
| final EmbeddingAttributes embeddingAttributes= (EmbeddingAttributes) attributes; |
| final Embedded embedded; |
| final TextRegion contentRegion= embeddingAttributes.getContentRegion(); |
| if (contentRegion != null |
| && contentRegion.getStartOffset() > this.locator2.getBeginOffset() ) { |
| final Span span= new Span(this.currentNode, |
| this.locator2.getBeginOffset(), this.locator2.getEndOffset(), |
| type, attributes.getId()); |
| node= span; |
| embedded= new Embedded(span, |
| contentRegion.getStartOffset(), contentRegion.getEndOffset(), |
| embeddingAttributes.getForeignType(), embeddingAttributes.getEmbedDescr() ); |
| span.children= new WikitextAstNode[] { embedded }; |
| } |
| else { |
| embedded= new Embedded(this.currentNode, |
| this.locator2.getBeginOffset(), this.locator2.getEndOffset(), |
| embeddingAttributes.getForeignType(), embeddingAttributes.getEmbedDescr() ); |
| node= embedded; |
| } |
| if (this.embeddedList != null) { |
| this.embeddedList.add(embedded); |
| } |
| } |
| else if (type == SpanType.LINK && attributes instanceof LinkAttributes) { |
| node= createLink(attributes, null); |
| } |
| else { |
| node= new Span(this.currentNode, |
| this.locator2.getBeginOffset(), this.locator2.getEndOffset(), |
| type, attributes.getId()); |
| } |
| enterNode(node); |
| } |
| |
| @Override |
| public void endSpan() { |
| finishText(); |
| exitNode(this.locator2.getEndOffset()); |
| } |
| |
| @Override |
| public void beginHeading(final int level, final Attributes attributes) { |
| finishText(); |
| enterNode(new Heading(this.currentNode, this.locator2.getBeginOffset(), level, |
| attributes.getId() )); |
| if (this.headingText) { |
| this.collectText++; |
| } |
| } |
| |
| @Override |
| public void endHeading() { |
| finishText(); |
| if (this.headingText) { |
| this.collectText--; |
| } |
| exitNode(this.locator2.getEndOffset()); |
| } |
| |
| @Override |
| public void characters(final String text) { |
| if (this.currentText == null) { |
| this.currentText= new Text(this.currentNode, |
| this.locator2.getBeginOffset(), this.locator2.getEndOffset() ); |
| addChildNode(this.currentText); |
| this.textBuilder.setLength(0); |
| } |
| else if (this.locator2.getEndOffset() > this.currentText.endOffset) { |
| this.currentText.endOffset= this.locator2.getEndOffset(); |
| } |
| if (this.collectText > 0) { |
| this.textBuilder.append(text); |
| } |
| } |
| |
| @Override |
| public void entityReference(final String entity) { |
| if (this.collectText > 0) { |
| try { |
| final String resovled= HtmlUtils.resolveEntity(HtmlUtils.getEntityReference(entity)); |
| if (resovled != null) { |
| characters(resovled); |
| return; |
| } |
| } |
| catch (final IllegalArgumentException e) {} |
| } |
| // characters(null); |
| } |
| |
| @Override |
| public void image(final Attributes attributes, final String url) { |
| finishText(); |
| addChildNode(createImage(attributes, url)); |
| } |
| |
| private Image createImage(final Attributes attributes, final String src) { |
| final byte linkType; |
| final LabelInfo referenceInfo; |
| if (attributes instanceof ImageByRefAttributes) { |
| linkType= Image.SRC_BY_REF; |
| referenceInfo= ((ImageByRefAttributes) attributes).getReferenceLabel(); |
| } |
| else { |
| linkType= 0; |
| referenceInfo= null; |
| } |
| if (linkType != 0 && referenceInfo != null) { |
| final Label referenceNode= createLabel(referenceInfo); |
| return new Image.Ref(this.currentNode, |
| this.locator2.getBeginOffset(), this.locator2.getEndOffset(), |
| linkType, referenceNode ); |
| } |
| return new Image.Common(this.currentNode, |
| this.locator2.getBeginOffset(), this.locator2.getEndOffset(), |
| Image.COMMON, src ); |
| } |
| |
| @Override |
| public void link(final Attributes attributes, final String hrefOrHashName, final String text) { |
| finishText(); |
| addChildNode(createLink(attributes, null)); |
| } |
| |
| private Link createLink(final Attributes attributes, String href) { |
| if (href == null && attributes instanceof LinkAttributes) { |
| href= ((LinkAttributes) attributes).getHref(); |
| } |
| final byte linkType; |
| final LabelInfo referenceInfo; |
| if (attributes instanceof LinkRefDefinitionAttributes) { |
| linkType= Link.LINK_REF_DEFINITION; |
| referenceInfo= ((LinkRefDefinitionAttributes) attributes).getReferenceLabel(); |
| } |
| else if (attributes instanceof LinkByRefAttributes) { |
| linkType= Link.LINK_BY_REF; |
| referenceInfo= ((LinkByRefAttributes) attributes).getReferenceLabel(); |
| } |
| else { |
| linkType= 0; |
| referenceInfo= null; |
| } |
| if (linkType != 0 && referenceInfo != null) { |
| final Label referenceNode= createLabel(referenceInfo); |
| return new Link.Ref(this.currentNode, |
| this.locator2.getBeginOffset(), this.locator2.getEndOffset(), |
| linkType, referenceNode ); |
| } |
| return new Link.Common(this.currentNode, |
| this.locator2.getBeginOffset(), this.locator2.getEndOffset(), |
| Link.COMMON, href ); |
| } |
| |
| private Label createLabel(final LabelInfo labelInfo) { |
| return new Label(null, labelInfo.getStartOffset(), labelInfo.getEndOffset(), labelInfo.getLabel()); |
| } |
| |
| @Override |
| public void imageLink(final Attributes linkAttributes, final Attributes imageAttributes, final String href, |
| final String imageUrl) { |
| finishText(); |
| } |
| |
| @Override |
| public void acronym(final String text, final String definition) { |
| finishText(); |
| } |
| |
| @Override |
| public void lineBreak() { |
| finishText(); |
| addChildNode(new Control(this.currentNode, |
| this.locator2.getBeginOffset(), this.locator2.getEndOffset(), |
| Control.LINE_BREAK )); |
| } |
| |
| @Override |
| public void charactersUnescaped(final String literal) { |
| finishText(); |
| } |
| |
| } |