/******************************************************************************* * 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.internal; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.emf.ecore.EObject; import org.eclipse.mylyn.docs.intent.parser.IntentKeyWords; import org.eclipse.mylyn.docs.intent.parser.IntentParserUtil; import org.eclipse.mylyn.docs.intent.parser.modelingunit.ModelingUnitParser; import org.eclipse.mylyn.docs.intent.parser.modelingunit.ParseException; import org.eclipse.mylyn.docs.intent.serializer.IntentPositionManager; /** * Parser which parse any IntentStructuredElement and its content. * * @author <a href="mailto:alex.lagarde@obeo.fr">Alex Lagarde</a> * @author <a href="mailto:william.piers@obeo.fr">William Piers</a> */ public class IntentDocumentParser { /** * The position manager that handle the mapping between Intent element to positions. */ private IntentPositionManager positionManager; /** * The builder used for generating the parsed elements. */ private IntentBuilder builder; /** * Creates a new Intent documents parser. */ public IntentDocumentParser() { this.positionManager = new IntentPositionManager(); } /** * Indicates if the given textual form describing a IntentStructuredElement. * * @param contentToParse * the given textual to study * @return true if the given textual form describing a IntentStructuredElement, false otherwise */ public boolean isParserFor(String contentToParse) { // This textual form describes a IntentStructured Element if it starts with the correct keyword. // The valid combinations are : // "[internal|hidden]? Chapter" // "[internal|hidden]? Section" String[] validFirstKeyWords = getValidFirstKeyWords(); int validFirstKeyWordIndice = 0; boolean isValidContent = false; while (validFirstKeyWordIndice < validFirstKeyWords.length && !isValidContent) { // We simply test, for any valid combination, if the given content match one of them isValidContent = contentToParse.startsWith(validFirstKeyWords[validFirstKeyWordIndice]); validFirstKeyWordIndice++; } return isValidContent; } /** * Parse the given textual form of a IntentStructuredElement. * * @param contentToParse * the contentToParse * @param rootCompleteLevel * the root complete level * @return the IntentStructuredElement described by the given content to parse * @throws ParseException * if the given content to parse contains errors */ public EObject parse(String contentToParse, String rootCompleteLevel) throws ParseException { // Step 1 : creating the builder which will generate the elements builder = new IntentBuilder(positionManager, rootCompleteLevel); String remainingContentToParse = contentToParse; String currentlyParsedSentence = null; int cursor = 0; int offset = 0; // Step 2 : parsing // while there is still content to parse while ((remainingContentToParse.length() > 0) && (cursor != -1)) { // Step 2.1 : We get the offset of the next breaking flow token (for example "}") cursor = IntentParserUtil.getNextOffset(remainingContentToParse); // If any offset found if (cursor != -1) { offset += cursor; // We get the sentence to parse (i.e the remaining content to parse from // the beginning to the calculated offset currentlyParsedSentence = remainingContentToParse.substring(0, cursor); // Step 2.2 : We send the appropriate signal to the builder sendSignal(offset - currentlyParsedSentence.length(), currentlyParsedSentence.trim()); // Step 2.3 (optional - if the current parsedSentence is a ModelingUnit declaration) if (currentlyParsedSentence.contains(ModelingUnitParser.MODELING_UNIT_PREFIX)) { int beginModelingUnit = remainingContentToParse .indexOf(ModelingUnitParser.MODELING_UNIT_PREFIX); // We parse until we find the suffix of this Modeling Unit int endModelingUnit = remainingContentToParse .indexOf(ModelingUnitParser.MODELING_UNIT_SUFFIX); if (cursor < 0 || endModelingUnit < 0) { throw new ParseException("Unclosed Modeling Unit", cursor, ModelingUnitParser.MODELING_UNIT_SUFFIX.length()); } endModelingUnit += ModelingUnitParser.MODELING_UNIT_SUFFIX.length(); String modelingUnitContent = remainingContentToParse.substring(beginModelingUnit, endModelingUnit); // We send the appropriate signal to the builder builder.modelingUnitContent(offset + beginModelingUnit - cursor, modelingUnitContent); offset += endModelingUnit - cursor; remainingContentToParse = remainingContentToParse.substring(endModelingUnit); } else { // Step 2.4 : We update the remaining content to parse by removing the parsed sentence remainingContentToParse = remainingContentToParse.substring(cursor); } } } // Step 3 : we finally return the root generated by the builder // if there are multiple roots, throw an exception because only one root should be described return builder.getRoot(); } /** * Sends the correct signal(s) to the builder according to the given parsedSentence value. * * @param offset * the offset of the sentence in the document * @param parsedSentence * the String corresponding to the signal to send * @throws ParseException * if the sent signal contains any error */ private void sendSignal(int offset, String parsedSentence) throws ParseException { // First, we determine if the new Keywords ends a description Unit // If so, we send the corresponding signal to the builder String parsedSentenceWithoutDescriptionUnit = sendDescriptionUnitSignalIfNecessary(offset, parsedSentence); if (parsedSentenceWithoutDescriptionUnit.contains(IntentKeyWords.INTENT_KEYWORD_CLOSE)) { builder.endStructuredElement(offset + parsedSentenceWithoutDescriptionUnit.indexOf(IntentKeyWords.INTENT_KEYWORD_CLOSE)); } if (parsedSentenceWithoutDescriptionUnit.contains(IntentKeyWords.INTENT_KEYWORD_DOCUMENT)) { builder.beginDocument(offset, parsedSentence.trim().length()); } if (parsedSentenceWithoutDescriptionUnit.contains(IntentKeyWords.INTENT_KEYWORD_CHAPTER)) { String title = ""; Matcher m = Pattern.compile(IntentParserUtil.EXPREG_OPEN_CHAPTER).matcher(parsedSentence.trim()); if (m.matches()) { title = m.group(1); } builder.beginChapter(offset, parsedSentence.trim().length(), title); } if (parsedSentenceWithoutDescriptionUnit.contains(IntentKeyWords.INTENT_KEYWORD_SECTION)) { String title = ""; Matcher m = Pattern.compile(IntentParserUtil.EXPREG_OPEN_SECTION).matcher( parsedSentenceWithoutDescriptionUnit.trim()); if (m.matches()) { title = m.group(1); } builder.beginSection(offset, parsedSentenceWithoutDescriptionUnit.trim().length(), title); } } /** * If the parsed Sentence matches a description Unit, sends the corresponding signal to the builder. * * @param offset * the offset of the sentence in the document * @param parsedSentence * the sentence to analyze. * @return the parsedSentence from witch we remove the description unit * @throws ParseException * if the sent description unit contains any parse error. */ private String sendDescriptionUnitSignalIfNecessary(int offset, String parsedSentence) throws ParseException { String descriptionUnitContent = parsedSentence; String parsedSentenceWithoutDescriptionUnit = parsedSentence; // For each keyWord that can end a description unit for (String endingDescriptionUnitKeyword : IntentParserUtil.getEndingDescriptionUnitTokens()) { Pattern ptr = Pattern.compile(endingDescriptionUnitKeyword); Matcher matcher = ptr.matcher(descriptionUnitContent); // If the parsed Sentence contains this keyWord (i.e. ends a description unit), we remove it if (matcher.find()) { descriptionUnitContent = descriptionUnitContent.substring(0, matcher.start()).trim(); } } if (descriptionUnitContent.trim().length() > 0) { parsedSentenceWithoutDescriptionUnit = parsedSentenceWithoutDescriptionUnit.replace( descriptionUnitContent, ""); builder.descriptionUnitContent(offset, descriptionUnitContent); } return parsedSentenceWithoutDescriptionUnit; } /** * Returns all the valid combinations that can represent the first key word of a IntentDocument. * * @return all the valid combinations that can represent the first key word of a IntentDocument */ private String[] getValidFirstKeyWords() { // The valid combinations are : // Document // "[internal|hidden]? Chapter" // "[internal|hidden]? Section" final int numberOfValidKeyWords = 7; String[] validKeyWords = new String[numberOfValidKeyWords]; validKeyWords[0] = IntentKeyWords.INTENT_KEYWORD_CHAPTER; validKeyWords[1] = IntentKeyWords.INTENT_KEYWORD_VISIBILITY_INTERNAL + IntentKeyWords.INTENT_WHITESPACE + IntentKeyWords.INTENT_KEYWORD_CHAPTER; validKeyWords[2] = IntentKeyWords.INTENT_KEYWORD_VISIBILITY_HIDDEN + IntentKeyWords.INTENT_WHITESPACE + IntentKeyWords.INTENT_KEYWORD_CHAPTER; validKeyWords[3] = IntentKeyWords.INTENT_KEYWORD_SECTION; validKeyWords[4] = IntentKeyWords.INTENT_KEYWORD_VISIBILITY_HIDDEN + IntentKeyWords.INTENT_WHITESPACE + IntentKeyWords.INTENT_KEYWORD_SECTION; validKeyWords[5] = IntentKeyWords.INTENT_KEYWORD_VISIBILITY_INTERNAL + IntentKeyWords.INTENT_WHITESPACE + IntentKeyWords.INTENT_KEYWORD_SECTION; validKeyWords[6] = IntentKeyWords.INTENT_KEYWORD_DOCUMENT; return validKeyWords; } /** * Clears all informations contained by the serializer (elements positions, current offset...). */ public void clear() { this.positionManager.clear(); } /** * Returns the position manager of this serializer. * * @return the position manager of this serializer. */ public IntentPositionManager getPositionManager() { return this.positionManager; } }