##### Coverage Report - org.jbehave.core.parsers.RegexStoryParser

Classes in this File | Line Coverage | Branch Coverage | Complexity
RegexStoryParser |
98% | 116/118
97% | 43/44
| 1.84375;1.844

1 |  |
     package org.jbehave.core.parsers;
2 |  |
3 |  |
     import java.io.File;
4 |  |
     import java.util.ArrayList;
5 |  |
     import java.util.List;
6 |  |
     import java.util.regex.Matcher;
7 |  |
     import java.util.regex.Pattern;
8 |  |
9 |  |
     import org.apache.commons.lang.StringUtils;
10 |  |
     import org.jbehave.core.configuration.Keywords;
11 |  |
     import org.jbehave.core.i18n.LocalizedKeywords;
12 |  |
     import org.jbehave.core.model.Description;
13 |  |
     import org.jbehave.core.model.ExamplesTable;
14 |  |
     import org.jbehave.core.model.ExamplesTableFactory;
15 |  |
     import org.jbehave.core.model.GivenStories;
16 |  |
     import org.jbehave.core.model.Meta;
17 |  |
     import org.jbehave.core.model.Narrative;
18 |  |
     import org.jbehave.core.model.Scenario;
19 |  |
     import org.jbehave.core.model.Story;
20 |  |
21 |  |
     import static java.util.regex.Pattern.DOTALL;
22 |  |
     import static java.util.regex.Pattern.compile;
23 |  |
     import static org.apache.commons.lang.StringUtils.removeStart;
24 |  |
25 |  |
     /**
26 |  |
      * Pattern-based story parser, which uses the keywords provided to parse the
27 |  |
      * textual story into a {@link Story}.
28 |  |
      */
29 |  |
     public class RegexStoryParser implements StoryParser {
30 |  |
31 |  |
         private static final String NONE = "";
32 |  |
         private final Keywords keywords;
33 |  |
         private final ExamplesTableFactory tableFactory;
34 |  |
35 |  |
         public RegexStoryParser() {
36 | 34 |
             this(new LocalizedKeywords());
37 | 34 |
         }
38 |  |
39 |  |
         public RegexStoryParser(Keywords keywords) {
40 | 695 |
             this(keywords, new ExamplesTableFactory());
41 | 695 |
         }
42 |  |
43 |  |
         public RegexStoryParser(ExamplesTableFactory tableFactory) {
44 | 0 |
             this(new LocalizedKeywords(), tableFactory);
45 | 0 |
         }
46 |  |
47 | 695 |
         public RegexStoryParser(Keywords keywords, ExamplesTableFactory tableFactory) {
48 | 695 |
             this.keywords = keywords;
49 | 695 |
             this.tableFactory = tableFactory;
50 | 695 |
         }
51 |  |
52 |  |
         public Story parseStory(String storyAsText) {
53 | 4 |
             return parseStory(storyAsText, null);
54 |  |
         }
55 |  |
56 |  |
         public Story parseStory(String storyAsText, String storyPath) {
57 | 28 |
             Description description = parseDescriptionFrom(storyAsText);
58 | 28 |
             Meta meta = parseStoryMetaFrom(storyAsText);
59 | 28 |
             Narrative narrative = parseNarrativeFrom(storyAsText);
60 | 28 |
             GivenStories givenStories = parseGivenStories(storyAsText);
61 | 28 |
             List<Scenario> scenarios = parseScenariosFrom(storyAsText);
62 | 28 |
             Story story = new Story(storyPath, description, meta, narrative, givenStories, scenarios);
63 | 28 |
             if (storyPath != null) {
64 | 21 |
                 story.namedAs(new File(storyPath).getName());
65 |  |
             }
66 | 28 |
             return story;
67 |  |
         }
68 |  |
69 |  |
         private Description parseDescriptionFrom(String storyAsText) {
70 | 28 |
             Matcher findingDescription = patternToPullDescriptionIntoGroupOne().matcher(storyAsText);
71 | 28 |
             if (findingDescription.matches()) {
72 | 17 |
                 return new Description(findingDescription.group(1).trim());
73 |  |
             }
74 | 11 |
             return Description.EMPTY;
75 |  |
         }
76 |  |
77 |  |
         private Meta parseStoryMetaFrom(String storyAsText) {
78 | 28 |
             Matcher findingMeta = patternToPullStoryMetaIntoGroupOne().matcher(preScenarioText(storyAsText));
79 | 28 |
             if (findingMeta.matches()) {
80 | 4 |
                 String meta = findingMeta.group(1).trim();
81 | 4 |
                 return Meta.createMeta(meta, keywords);
82 |  |
             }
83 | 24 |
             return Meta.EMPTY;
84 |  |
         }
85 |  |
86 |  |
         private String preScenarioText(String storyAsText) {
87 | 28 |
             String[] split = storyAsText.split(keywords.scenario());
88 | 28 |
             return split.length > 0 ? split[0] : storyAsText;
89 |  |
         }
90 |  |
91 |  |
         private Narrative parseNarrativeFrom(String storyAsText) {
92 | 28 |
             Matcher findingNarrative = patternToPullNarrativeIntoGroupOne().matcher(storyAsText);
93 | 28 |
             if (findingNarrative.matches()) {
94 | 3 |
                 String narrative = findingNarrative.group(1).trim();
95 | 3 |
                 return createNarrative(narrative);
96 |  |
             }
97 | 25 |
             return Narrative.EMPTY;
98 |  |
         }
99 |  |
100 |  |
         private Narrative createNarrative(String narrative) {
101 | 3 |
             Pattern findElements = patternToPullNarrativeElementsIntoGroups();
102 | 3 |
             Matcher findingElements = findElements.matcher(narrative);
103 | 3 |
             if (findingElements.matches()) {
104 | 2 |
                 String inOrderTo = findingElements.group(1).trim();
105 | 2 |
                 String asA = findingElements.group(2).trim();
106 | 2 |
                 String iWantTo = findingElements.group(3).trim();
107 | 2 |
                 return new Narrative(inOrderTo, asA, iWantTo);
108 |  |
             }
109 | 1 |
             return Narrative.EMPTY;
110 |  |
         }
111 |  |
112 |  |
         private GivenStories parseGivenStories(String storyAsText) {
113 | 28 |
             String scenarioKeyword = keywords.scenario();
114 |  |
             // use text before scenario keyword, if found
115 | 28 |
             String beforeScenario = "";
116 | 28 |
             if (StringUtils.contains(storyAsText, scenarioKeyword)) {
117 | 16 |
                 beforeScenario = StringUtils.substringBefore(storyAsText, scenarioKeyword);
118 |  |
             }
119 | 28 |
             Matcher findingGivenStories = patternToPullStoryGivenStoriesIntoGroupOne().matcher(beforeScenario);
120 | 28 |
             String givenStories = findingGivenStories.find() ? findingGivenStories.group(1).trim() : NONE;
121 | 28 |
             return new GivenStories(givenStories);
122 |  |
         }
123 |  |
124 |  |
         private List<Scenario> parseScenariosFrom(String storyAsText) {
125 | 28 |
             List<Scenario> parsed = new ArrayList<Scenario>();
126 | 28 |
             for (String scenarioAsText : splitScenarios(storyAsText)) {
127 | 129 |
                 parsed.add(parseScenario(scenarioAsText));
128 |  |
             }
129 | 28 |
             return parsed;
130 |  |
         }
131 |  |
132 |  |
         private List<String> splitScenarios(String storyAsText) {
133 | 28 |
             List<String> scenarios = new ArrayList<String>();
134 | 28 |
             String scenarioKeyword = keywords.scenario();
135 |  |
136 |  |
             // use text after scenario keyword, if found
137 | 28 |
             if (StringUtils.contains(storyAsText, scenarioKeyword)) {
138 | 16 |
                 storyAsText = StringUtils.substringAfter(storyAsText, scenarioKeyword);
139 |  |
             }
140 |  |
141 | 159 |
             for (String scenarioAsText : storyAsText.split(scenarioKeyword)) {
142 | 131 |
                 if (scenarioAsText.trim().length() > 0) {
143 | 129 |
                     scenarios.add(scenarioKeyword + "\n" + scenarioAsText);
144 |  |
                 }
145 |  |
             }
146 |  |
147 | 28 |
             return scenarios;
148 |  |
         }
149 |  |
150 |  |
         private Scenario parseScenario(String scenarioAsText) {
151 | 129 |
             String title = findScenarioTitle(scenarioAsText);
152 | 129 |
             String scenarioWithoutKeyword = removeStart(scenarioAsText, keywords.scenario()).trim();
153 | 129 |
             String scenarioWithoutTitle = removeStart(scenarioWithoutKeyword, title);
154 | 129 |
             if ( !scenarioWithoutTitle.startsWith("\n") ){ // always ensure scenario starts with newline
155 | 17 |
                 scenarioWithoutTitle = "\n" + scenarioWithoutTitle;
156 |  |
             }
157 | 129 |
             Meta meta = findScenarioMeta(scenarioWithoutTitle);
158 | 129 |
             ExamplesTable examplesTable = findExamplesTable(scenarioWithoutTitle);
159 | 129 |
             GivenStories givenStories = findScenarioGivenStories(scenarioWithoutTitle);
160 | 129 |
             if (givenStories.requireParameters()) {
161 | 1 |
                 givenStories.useExamplesTable(examplesTable);
162 |  |
             }
163 | 129 |
             List<String> steps = findSteps(scenarioWithoutTitle);
164 | 129 |
             return new Scenario(title, meta, givenStories, examplesTable, steps);
165 |  |
         }
166 |  |
167 |  |
         private String findScenarioTitle(String scenarioAsText) {
168 | 129 |
             Matcher findingTitle = patternToPullScenarioTitleIntoGroupOne().matcher(scenarioAsText);
169 | 129 |
             return findingTitle.find() ? findingTitle.group(1).trim() : NONE;
170 |  |
         }
171 |  |
172 |  |
         private Meta findScenarioMeta(String scenarioAsText) {
173 | 129 |
             Matcher findingMeta = patternToPullScenarioMetaIntoGroupOne().matcher(scenarioAsText);
174 | 129 |
             if (findingMeta.matches()) {
175 | 5 |
                 String meta = findingMeta.group(1).trim();
176 | 5 |
                 return Meta.createMeta(meta, keywords);
177 |  |
             }
178 | 124 |
             return Meta.EMPTY;
179 |  |
         }
180 |  |
181 |  |
         private ExamplesTable findExamplesTable(String scenarioAsText) {
182 | 129 |
             Matcher findingTable = patternToPullExamplesTableIntoGroupOne().matcher(scenarioAsText);
183 | 129 |
             String tableInput = findingTable.find() ? findingTable.group(1).trim() : NONE;
184 | 129 |
             return tableFactory.createExamplesTable(tableInput);
185 |  |
         }
186 |  |
187 |  |
         private GivenStories findScenarioGivenStories(String scenarioAsText) {
188 | 129 |
             Matcher findingGivenStories = patternToPullScenarioGivenStoriesIntoGroupOne().matcher(scenarioAsText);
189 | 129 |
             String givenStories = findingGivenStories.find() ? findingGivenStories.group(1).trim() : NONE;
190 | 129 |
             return new GivenStories(givenStories);
191 |  |
         }
192 |  |
193 |  |
         private List<String> findSteps(String scenarioAsText) {
194 | 129 |
             Matcher matcher = patternToPullStepsIntoGroupOne().matcher(scenarioAsText);
195 | 129 |
             List<String> steps = new ArrayList<String>();
196 | 129 |
             int startAt = 0;
197 | 15203 |
             while (matcher.find(startAt)) {
198 | 15074 |
                 steps.add(StringUtils.substringAfter(matcher.group(1), "\n"));
199 | 15074 |
                 startAt = matcher.start(4);
200 |  |
             }
201 | 129 |
             return steps;
202 |  |
         }
203 |  |
204 |  |
         // Regex Patterns
205 |  |
206 |  |
         private Pattern patternToPullDescriptionIntoGroupOne() {
207 | 28 |
             String metaOrNarrativeOrScenario = concatenateWithOr(keywords.meta(), keywords.narrative(), keywords.scenario());
208 | 28 |
             return compile("(.*?)(" + metaOrNarrativeOrScenario + ").*", DOTALL);
209 |  |
         }
210 |  |
211 |  |
         private Pattern patternToPullStoryMetaIntoGroupOne() {
212 | 28 |
             return compile(".*" + keywords.meta() + "(.*?)\\s*(\\Z|" + keywords.narrative() + "|" +  keywords.givenStories() + ").*", DOTALL);
213 |  |
         }
214 |  |
215 |  |
         private Pattern patternToPullNarrativeIntoGroupOne() {
216 | 28 |
             return compile(".*" + keywords.narrative() + "(.*?)\\s*(" + keywords.givenStories() + "|" + keywords.scenario() + ").*", DOTALL);
217 |  |
         }
218 |  |
219 |  |
         private Pattern patternToPullNarrativeElementsIntoGroups() {
220 | 3 |
             return compile(".*" + keywords.inOrderTo() + "(.*)\\s*" + keywords.asA() + "(.*)\\s*" + keywords.iWantTo()
221 |  |
                     + "(.*)", DOTALL);
222 |  |
         }
223 |  |
224 |  |
         private Pattern patternToPullScenarioTitleIntoGroupOne() {
225 | 129 |
             String startingWords = concatenateWithOr("\\n", "", keywords.startingWords());
226 | 129 |
             return compile(keywords.scenario() + "((.)*?)\\s*(" + keywords.meta() + "|" + startingWords + ").*", DOTALL);
227 |  |
         }
228 |  |
229 |  |
         private Pattern patternToPullScenarioMetaIntoGroupOne() {
230 | 129 |
             String startingWords = concatenateWithOr("\\n", "", keywords.startingWords());
231 | 129 |
             return compile(".*" + keywords.meta() + "(.*?)\\s*(" + keywords.givenStories() + "|" + startingWords + ").*",
232 |  |
                     DOTALL);
233 |  |
         }
234 |  |
235 |  |
         private Pattern patternToPullStoryGivenStoriesIntoGroupOne() {
236 | 28 |
             return compile(".*" + keywords.givenStories() + "\\s*(.*)", DOTALL);
237 |  |
         }
238 |  |
239 |  |
         private Pattern patternToPullScenarioGivenStoriesIntoGroupOne() {
240 | 129 |
             String startingWords = concatenateWithOr("\\n", "", keywords.startingWords());
241 | 129 |
             return compile("\\n" + keywords.givenStories() + "((.|\\n)*?)\\s*(" + startingWords + ").*", DOTALL);
242 |  |
         }
243 |  |
244 |  |
         private Pattern patternToPullStepsIntoGroupOne() {
245 | 129 |
             String initialStartingWords = concatenateWithOr("\\n", "", keywords.startingWords());
246 | 129 |
             String followingStartingWords = concatenateWithOr("\\n", "\\s", keywords.startingWords());
247 | 129 |
             return compile(
248 |  |
                     "((" + initialStartingWords + ")\\s(.)*?)\\s*(\\Z|" + followingStartingWords + "|\\n"
249 |  |
                             + keywords.examplesTable() + ")", DOTALL);
250 |  |
         }
251 |  |
252 |  |
         private Pattern patternToPullExamplesTableIntoGroupOne() {
253 | 129 |
             return compile("\\n" + keywords.examplesTable() + "\\s*(.*)", DOTALL);
254 |  |
         }
255 |  |
256 |  |
         private String concatenateWithOr(String... keywords) {
257 | 28 |
             return concatenateWithOr(null, null, keywords);
258 |  |
         }
259 |  |
260 |  |
         private String concatenateWithOr(String beforeKeyword, String afterKeyword, String[] keywords) {
261 | 673 |
             StringBuilder builder = new StringBuilder();
262 | 673 |
             String before = beforeKeyword != null ? beforeKeyword : NONE;
263 | 673 |
             String after = afterKeyword != null ? afterKeyword : NONE;
264 | 3992 |
             for (String keyword : keywords) {
265 | 3319 |
                 builder.append(before).append(keyword).append(after).append("|");
266 |  |
             }
267 | 673 |
             return StringUtils.chomp(builder.toString(), "|"); // chop off the last
268 |  |
                                                                // "|"
269 |  |
         }
270 |  |
271 |  |
     }