/*******************************************************************************
* Copyright (c) 2010, 2011 Obeo.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.mylyn.docs.intent.parser.descriptionunit;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.mylyn.docs.intent.core.document.IntentDocumentFactory;
import org.eclipse.mylyn.docs.intent.core.document.IntentReferenceInstruction;
import org.eclipse.mylyn.docs.intent.core.document.LabelDeclaration;
import org.eclipse.mylyn.docs.intent.core.document.TypeLabel;
import org.eclipse.mylyn.docs.intent.core.document.UnitInstruction;
import org.eclipse.mylyn.docs.intent.core.document.descriptionunit.DescriptionBloc;
import org.eclipse.mylyn.docs.intent.core.document.descriptionunit.DescriptionUnit;
import org.eclipse.mylyn.docs.intent.core.document.descriptionunit.DescriptionUnitFactory;
import org.eclipse.mylyn.docs.intent.markup.builder.ModelDocumentBuilder;
import org.eclipse.mylyn.docs.intent.markup.markup.Block;
import org.eclipse.mylyn.docs.intent.markup.markup.Container;
import org.eclipse.mylyn.docs.intent.markup.markup.Document;
import org.eclipse.mylyn.docs.intent.markup.markup.MarkupFactory;
import org.eclipse.mylyn.docs.intent.markup.markup.Section;
import org.eclipse.mylyn.docs.intent.markup.markup.StructureElement;
import org.eclipse.mylyn.docs.intent.parser.IntentKeyWords;
import org.eclipse.mylyn.docs.intent.parser.modelingunit.ParseException;
import org.eclipse.mylyn.wikitext.core.parser.MarkupParser;
import org.eclipse.mylyn.wikitext.textile.core.TextileLanguage;
/**
* Parser used for parsing a Description Unit of an IntentDocument.
*
* @author <a href="mailto:alex.lagarde@obeo.fr">Alex Lagarde</a>
*/
public class DescriptionUnitParser {
/**
* All the tokens that represent a instruction relative to Intent (section reference, label...).
*/
private static String[] intentTokens = {IntentKeyWords.INTENT_FCT_EXPLICIT_LABEL_DECLARATION,
IntentKeyWords.INTENT_FCT_LAZY_LABEL_DECLARATION, IntentKeyWords.INTENT_FCT_REFERENCE,
};
/**
* Internal implementation of the Description Unit parser (using the Markup metaModel).
*/
private MarkupParser internalMarkupParser;
/**
* Internal builder of the description unit parser.
*/
private ModelDocumentBuilder builder;
/**
* Current offset.
*/
private int offSet;
/**
* DescriptionUnitParser constructor.
*/
public DescriptionUnitParser() {
this.internalMarkupParser = new MarkupParser(new TextileLanguage());
builder = new ModelDocumentBuilder();
internalMarkupParser.setBuilder(builder);
}
/**
* Parse the given textual form of a description unit.
*
* @param descriptionUnitToParse
* the textual from of a description unit to parse
* @return a DescriptionUnit corresponding to the given textual form
* @throws ParseException
* if the given String isn't well formed
*/
public DescriptionUnit parse(String descriptionUnitToParse) throws ParseException {
DescriptionUnit descriptionUnit = DescriptionUnitFactory.eINSTANCE.createDescriptionUnit();
String remainingContentToParse = descriptionUnitToParse;
String currentlyParsedSentence = null;
offSet = 0;
// while there is still content to parse
while ((remainingContentToParse.length() > 0) && (offSet != -1)) {
// We get the offset of the next instruction relative to Intent (section
// reference, label...)
offSet = getNextOffset(remainingContentToParse);
// If any offset found
if (offSet != -1) {
// We first get the content before this instruction
currentlyParsedSentence = remainingContentToParse.substring(0, offSet);
// And add the corresponding description Bloc to the description Unit (if this left part isn't
// empty)
createDescriptionBlocs(descriptionUnit, currentlyParsedSentence.trim());
// We the construct this Intent instruction
currentlyParsedSentence = remainingContentToParse.substring(offSet);
offSet = constructInstructionsFromSentence(descriptionUnit, currentlyParsedSentence);
remainingContentToParse = remainingContentToParse.substring(offSet);
// We first get the text before this
} else {
// Here we don't have any instruction relative to Intent anymore
// If the currentParseSentence is not empty, we add a descriptionBloc to the description Unit
createDescriptionBlocs(descriptionUnit, remainingContentToParse.trim());
offSet = -1;
}
}
return descriptionUnit;
}
/**
* Adds the description Blocs described by the given descriptionBlocToParse to the given descriptionUnit
* (if the content to parse isn't empty).
*
* @param descriptionUnit
* the description unit currently being constructed
* @param descriptionBlocToParse
* a String describing description Blocs (sections, paragraphs, lists) in the WikiText syntax.
*/
private void createDescriptionBlocs(DescriptionUnit descriptionUnit, String descriptionBlocToParse) {
if (descriptionBlocToParse.trim().length() > 0) {
internalMarkupParser.parse(descriptionBlocToParse);
// For each roots of the parsed document
EList<StructureElement> blocksToCreate = new BasicEList<StructureElement>();
for (EObject descriptionRoot : builder.getRoots()) {
// We inspect this root
if (descriptionRoot instanceof Document) {
// For each element contained in this document
for (StructureElement content : ((Document)descriptionRoot).getContent()) {
// We add it to the block to create list
if ((content instanceof Block) || (content instanceof Section)) {
blocksToCreate.add(content);
}
}
}
}
// For each block to create
int blocCount = 0;
for (StructureElement blockToCreate : blocksToCreate) {
// We create a container with no semantic value and it the block to this container
Container container = MarkupFactory.eINSTANCE.createSimpleContainer();
container.getContent().add(blockToCreate);
// We create the description bloc
DescriptionBloc descriptionBloc = DescriptionUnitFactory.eINSTANCE.createDescriptionBloc();
descriptionBloc.setDescriptionBloc(container);
// We determine if the block is a line breaker
blocCount++;
if (isLineBreaker(blockToCreate) && (blocCount < blocksToCreate.size())) {
descriptionBloc.setLineBreak(true);
}
// We finally add the created description Bloc to the description unit
descriptionUnit.getInstructions().add(descriptionBloc);
}
}
}
/**
* Indicates if the given bloc is a line breaker.
*
* @param blockToCreate
* the bloc to determine if it's a line breaker
* @return true if the given bloc is a line breaker, false otherwise
*/
private boolean isLineBreaker(StructureElement blockToCreate) {
return true;
}
/**
* Construct the instruction description by the given parsedSentence and add it to the DescriptionUnit.
*
* @param descriptionUnit
* the description unit currently being constructed
* @param parsedSentence
* a String starting with the description of a Intent instruction (@see, \@lazylabel...).
* @return the offset following the parsed instruction
*/
private int constructInstructionsFromSentence(DescriptionUnit descriptionUnit, String parsedSentence) {
int endOffset = -1;
// We create the right instruction
if (parsedSentence.startsWith(IntentKeyWords.INTENT_FCT_LAZY_LABEL_DECLARATION)) {
String parsedSentenceWithoutLabelDeclaration = parsedSentence.replaceFirst(
IntentKeyWords.INTENT_FCT_LAZY_LABEL_DECLARATION, "");
offSet += IntentKeyWords.INTENT_FCT_LAZY_LABEL_DECLARATION.length();
endOffset = constructLabelInstruction(descriptionUnit, parsedSentenceWithoutLabelDeclaration,
TypeLabel.LAZY);
}
if (parsedSentence.startsWith(IntentKeyWords.INTENT_FCT_EXPLICIT_LABEL_DECLARATION)) {
String parsedSentenceWithoutLabelDeclaration = parsedSentence.replaceFirst(
IntentKeyWords.INTENT_FCT_EXPLICIT_LABEL_DECLARATION, "");
offSet += IntentKeyWords.INTENT_FCT_EXPLICIT_LABEL_DECLARATION.length();
endOffset = constructLabelInstruction(descriptionUnit, parsedSentenceWithoutLabelDeclaration,
TypeLabel.EXPLICIT);
}
if (parsedSentence.startsWith(IntentKeyWords.INTENT_FCT_REFERENCE)) {
String parsedSentenceWithoutReferenceDeclaration = parsedSentence.replaceFirst(
IntentKeyWords.INTENT_FCT_REFERENCE, "");
offSet += IntentKeyWords.INTENT_FCT_REFERENCE.length();
endOffset = constructReferenceInstruction(descriptionUnit,
parsedSentenceWithoutReferenceDeclaration);
}
return endOffset;
}
/**
* Add the reference instruction described by the given parsedSentence to the given Description Unit.
*
* @param descriptionUnit
* the description unit currently being constructed
* @param parsedSentence
* a String starting with the a reference instruction
* @return the offset following the parsed instruction
*/
private int constructReferenceInstruction(DescriptionUnit descriptionUnit, String parsedSentence) {
int initialOffset = offSet;
UnitInstruction referenceInstruction = null;
// Step 1 : extracting the "Reference" value
String referenceValue = extractFirstString(parsedSentence);
String parsendSentenceWithoutReferenceValue = parsedSentence.substring(offSet - initialOffset);
String textToPrint = extractFirstString(parsendSentenceWithoutReferenceValue);
// We consider that it is a SectionReference
referenceInstruction = IntentDocumentFactory.eINSTANCE.createIntentReferenceInstruction();
((IntentReferenceInstruction)referenceInstruction).setTextToPrint(textToPrint);
((IntentReferenceInstruction)referenceInstruction).setIntentHref(referenceValue);
if (parsedSentence.length() > (offSet - initialOffset)) {
if (parsedSentence.charAt(offSet - initialOffset) == '\n') {
offSet++;
referenceInstruction.setLineBreak(true);
}
} else {
referenceInstruction.setLineBreak(true);
}
descriptionUnit.getInstructions().add(referenceInstruction);
return offSet;
}
/**
* Add the label definition described by the given parsedSentence to the given Description Unit.
*
* @param descriptionUnit
* the description unit currently being constructed
* @param parsedSentence
* a String starting with the description of a a label definition
* @param typeLabel
* the type of the label (LAZY or EXPLICIT)
* @return the offset following the parsed instruction
*/
private int constructLabelInstruction(DescriptionUnit descriptionUnit, String parsedSentence,
TypeLabel typeLabel) {
int initialOffset = offSet;
String labelValue = extractFirstString(parsedSentence);
String parsendSentenceWithoutLabelValueDeclaration = parsedSentence.substring(offSet - initialOffset);
String textToPrint = extractFirstString(parsendSentenceWithoutLabelValueDeclaration);
LabelDeclaration labelDeclaration = IntentDocumentFactory.eINSTANCE.createLabelDeclaration();
labelDeclaration.setType(typeLabel);
labelDeclaration.setLabelValue(labelValue);
if (textToPrint != null) {
labelDeclaration.setTextToPrint(textToPrint);
}
if (parsedSentence.length() > (offSet - initialOffset)) {
if (parsedSentence.charAt(offSet - initialOffset) == '\n') {
offSet++;
labelDeclaration.setLineBreak(true);
}
} else {
labelDeclaration.setLineBreak(true);
}
descriptionUnit.getInstructions().add(labelDeclaration);
return offSet;
}
/**
* Returns the first String encapsulated in quotes contained in the given parsedSentence. <br/>
* Example : extractFirstString('"first\"String" myFirstString") = 'first"String';
*
* @param parsedSentence
* the sentence to analyse
* @return the first String encapsulated in quotes contained in the given parsedSentence
*/
public String extractFirstString(String parsedSentence) {
String firstString = null;
int temporaryOffset = offSet;
boolean foundFirstString = false;
if (parsedSentence.contains("\"") || parsedSentence.contains("'")) {
StringBuilder firstStringBuilder = new StringBuilder();
char beginQuote = ' ';
char previousCharacter = ' ';
for (int i = 0; i < parsedSentence.length() && !foundFirstString; i++) {
temporaryOffset++;
char currentChar = parsedSentence.charAt(i);
// If the character is a quote (simple or double)
if ((currentChar == '"') || (currentChar == '\'')) {
// If it wasn't prefixed by a backslash
if (previousCharacter != '\\') {
// If there was no begin Quote
// CHECKSTYLE:OFF
if (beginQuote == ' ') {
// We define the current character as the begin quote
beginQuote = currentChar;
} else {
// If there was a begin quote and the current Character is the same Symbol
// for instance : 'X' or "X" but not 'X"
if (currentChar == beginQuote) {
foundFirstString = true;
} else {
firstStringBuilder.append(currentChar);
previousCharacter = currentChar;
}
}
// CHECKSTYLE:ON
} else {
firstStringBuilder.append(currentChar);
previousCharacter = currentChar;
}
} else {
// If the curent character is a backslash
if (currentChar == '\\') {
// If the previous one was a backslash
if (previousCharacter == '\\') {
// We add the backslash character in the first String
firstStringBuilder.append(currentChar);
previousCharacter = ' ';
} else {
previousCharacter = '\\';
}
} else {
if (beginQuote != ' ') {
// In all other cases, we add the current character to the constructed string
firstStringBuilder.append(currentChar);
previousCharacter = currentChar;
}
}
}
}
firstString = firstStringBuilder.toString();
}
if (foundFirstString) {
offSet = temporaryOffset;
return firstString;
}
return null;
}
/**
* Returns the next Offset containing a flow breaker token in the given String.
*
* @param currentlyParsedContent
* the String to inspect
* @return the next Offset containing useful informations in the given String, -1 if no valid character
* can be found
*/
private int getNextOffset(String currentlyParsedContent) {
// We calculate the offset of the next occurrence of each flowBreaking tokens
Integer[] possibleNextOffsets = new Integer[intentTokens.length];
for (int i = 0; i < intentTokens.length; i++) {
possibleNextOffsets[i] = currentlyParsedContent.indexOf(intentTokens[i]);
}
// We return the offset of the first token encountered
return getNextOffSetInTable(possibleNextOffsets);
}
/**
* Returns the offSet to consider in the given table of all detected offsets.
*
* @param possibleNextOffsets
* table of all detected offsets
* @return the offSet to consider in the given table of all detected offsets
*/
private int getNextOffSetInTable(Integer[] possibleNextOffsets) {
int nextOffset = -1;
for (int i = 0; i < possibleNextOffsets.length; i++) {
if ((possibleNextOffsets[i] > -1)
&& ((nextOffset == -1) || (possibleNextOffsets[i] < nextOffset))) {
nextOffset = possibleNextOffsets[i];
}
}
return nextOffset;
}
}