diff --git a/src/main/java/org/jbake/app/Parser.java b/src/main/java/org/jbake/app/Parser.java index e3f413915..e4345ac8e 100644 --- a/src/main/java/org/jbake/app/Parser.java +++ b/src/main/java/org/jbake/app/Parser.java @@ -1,5 +1,7 @@ package org.jbake.app; +import static org.jbake.app.Parser.ContentBasicTags.*; + import org.apache.commons.configuration.CompositeConfiguration; import org.apache.commons.io.IOUtils; import org.jbake.app.ConfigUtil.Keys; @@ -17,11 +19,14 @@ import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.AbstractMap; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; /** * Parses a File for content. @@ -30,8 +35,11 @@ */ public class Parser { - public static final String END_OF_HEADER = "~~~~~~"; + public static final String END_OF_HEADER = createStringFilledWith('~', 6); public static final String EOL = "\n"; + public static final String CONTINUED_LINE_STARTER = createStringFilledWith(' ', 2); + + public enum ContentBasicTags { status, type, date, tags, body }; private final static Logger LOGGER = LoggerFactory.getLogger(Parser.class); @@ -67,13 +75,15 @@ public Map processFile(File file) { IOUtils.closeQuietly(is); } - boolean hasHeader = hasHeader(fileContents); + // read header from file + List header = getHeaderFrom(fileContents); + boolean hasValidHeader = header != null; ParserContext context = new ParserContext( file, fileContents, config, contentPath, - hasHeader, + hasValidHeader, content ); @@ -83,22 +93,21 @@ public Map processFile(File file) { return null; } - if (hasHeader) { - // read header from file - processHeader(fileContents, content); + if (hasValidHeader) { + content = processHeader(header, content); } // then read engine specific headers engine.processHeader(context); if (config.getString(Keys.DEFAULT_STATUS) != null) { // default status has been set - if (content.get("status") == null) { + if (content.get(status.name()) == null) { // file hasn't got status so use default - content.put("status", config.getString(Keys.DEFAULT_STATUS)); + content.put(status.name(), config.getString(Keys.DEFAULT_STATUS)); } } - if (content.get("type")==null||content.get("status")==null) { + if (content.get(type.name())==null||content.get(status.name())==null) { // output error LOGGER.warn("Error parsing meta data from header (missing type or status value) for file {}!", file); return null; @@ -115,103 +124,122 @@ public Map processFile(File file) { return null; } - if (content.get("tags") != null) { - String[] tags = (String[]) content.get("tags"); - for( int i=0; i content) { + String[] tagValues = (String[]) content.get(tags.name()); + if (tagValues == null) { + return; + } + + for( int i=0; i contents) { - boolean headerValid = false; + private List getHeaderFrom(final List contents) { boolean headerSeparatorFound = false; boolean statusFound = false; boolean typeFound = false; List header = new ArrayList(); - + + StringBuilder buffer = new StringBuilder(); for (String line : contents) { if (line.trim().isEmpty()) { continue; } - header.add(line); - if (line.contains("=")) { - if (line.matches("^type\\s*=.*")) { - typeFound = true; - } - if (line.matches("^status\\s*=.*")) { - statusFound = true; - } - } - if (line.equals(END_OF_HEADER)) { - headerSeparatorFound = true; - header.remove(line); - break; - } - } - - if (headerSeparatorFound) { - headerValid = true; - for (String headerLine : header) { - if (!headerLine.contains("=")) { - headerValid = false; - break; - } - } + boolean newEntry = ! isContinuedEntry(line); + if (buffer.length() > 0 && newEntry) { + String e = buffer.toString(); + header.add(e); + + if (! isValidEntry(e)) { + return null; + } + statusFound |= isStatusEntry(e); + typeFound |= isTypeEntry(e); + + buffer.setLength(0); + } + if (line.equals(END_OF_HEADER)) { + headerSeparatorFound = true; + break; + } + String e = newEntry ? line : line.substring(CONTINUED_LINE_STARTER.length()); + buffer.append(e); } - - return headerValid && statusFound && typeFound; + + return headerSeparatorFound && statusFound && typeFound ? header : null; } + private static boolean isContinuedEntry(final String e) { + return e.matches("^" + CONTINUED_LINE_STARTER + ".*"); + } + + private static boolean isStatusEntry(final String e) { + return e.matches("^" + status + "\\s*=.*"); + } + + private static boolean isTypeEntry(final String e) { + return e.matches("^" + type + "\\s*=.*"); + } + + private static boolean isValidEntry(final String e) { + return e.contains("="); + } + /** * Process the header of the file. * - * @param contents Contents of file + * @param header list of entries * @param content */ - private void processHeader(List contents, final Map content) { - for (String line : contents) { - if (line.equals(END_OF_HEADER)) { - break; - } else { - String[] parts = line.split("\\s*=\\s*",2); - if (parts.length == 2) { - if (parts[0].equalsIgnoreCase("date")) { - DateFormat df = new SimpleDateFormat(config.getString(Keys.DATE_FORMAT)); - Date date = null; - try { - date = df.parse(parts[1]); - content.put(parts[0], date); - } catch (ParseException e) { - e.printStackTrace(); - } - } else if (parts[0].equalsIgnoreCase("tags")) { - String[] tags = parts[1].split(","); - content.put(parts[0], tags); - } else if (parts[1].startsWith("{") && parts[1].endsWith("}")) { - // Json type - content.put(parts[0], JSONValue.parse(parts[1])); - } else { - content.put(parts[0], parts[1]); - } + private final Map processHeader(final List header, final Map content) { + for (String line : header) { + Entry entry = getHeaderEntryFrom(line); + String key = entry.getKey(); + String value = entry.getValue(); + if (key.equalsIgnoreCase(date.name())) { + DateFormat df = new SimpleDateFormat(config.getString(Keys.DATE_FORMAT)); + Date date = null; + try { + date = df.parse(value); + content.put(key, date); + } catch (ParseException e) { + e.printStackTrace(); } + } else if (key.equalsIgnoreCase(tags.name())) { + String[] tags = value.split(","); + content.put(key, tags); + } else if (value.startsWith("{") && value.endsWith("}")) { + // Json type + content.put(key, JSONValue.parse(value)); + } else { + content.put(key, value); } } + return content; + } + + private static Entry getHeaderEntryFrom(final String line) { + String[] parts = line.split("\\s*=\\s*", 2); + return new AbstractMap.SimpleEntry(parts[0], parts[1]); } /** @@ -238,7 +266,13 @@ private void processBody(List contents, final Map conten } } - content.put("body", body.toString()); + content.put(ContentBasicTags.body.name(), body.toString()); + } + + private static String createStringFilledWith(char c, int length) { + final char[] array = new char[length]; + Arrays.fill(array, c); + return new String(array); } } diff --git a/src/test/java/org/jbake/app/ParserTest.java b/src/test/java/org/jbake/app/ParserTest.java index 0db13c74a..71e73855b 100644 --- a/src/test/java/org/jbake/app/ParserTest.java +++ b/src/test/java/org/jbake/app/ParserTest.java @@ -1,16 +1,25 @@ package org.jbake.app; import static org.assertj.core.api.Assertions.assertThat; +import static org.jbake.app.Parser.CONTINUED_LINE_STARTER; import static org.jbake.app.Parser.END_OF_HEADER; import static org.jbake.app.Parser.EOL; +import static org.jbake.app.Parser.ContentBasicTags.body; +import static org.jbake.app.Parser.ContentBasicTags.date; +import static org.jbake.app.Parser.ContentBasicTags.status; +import static org.jbake.app.Parser.ContentBasicTags.tags; +import static org.jbake.app.Parser.ContentBasicTags.type; import java.io.File; import java.io.PrintWriter; +import java.util.Arrays; import java.util.Calendar; import java.util.Date; +import java.util.List; import java.util.Map; import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.commons.lang.StringUtils; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; @@ -37,7 +46,8 @@ public class ParserTest { private File validAsciiDocFileWithBlankFirstLineInHeader; private File validAsciiDocFileWithEmptyRandomLineInHeader; private File validAsciiDocFileWithBlankRandomLineInHeader; - private File validAsciiDocFileWithSpacesAroundEqualInHeader; + private File validAsciiDocFileWithSpacesAroundEqualInHeader; + private File validAsciiDocFileWithContinuedLineInHeader; private String validHeader = "title=This is a Title = This is a valid Title" + EOL + "status=draft" + EOL @@ -46,21 +56,39 @@ public class ParserTest { + END_OF_HEADER; private String invalidHeader = "title=This is a Title" + EOL + END_OF_HEADER; - private int rp = 2; - private int pos = validHeader.indexOf(EOL, rp); + private int rp = 2; // "random" number + private int pos = validHeader.indexOf(EOL, rp); // position where to insert a line + + private String validHeaderWithEmptyFirstLineContent = "This is a test with empty first line in header."; private String validHeaderWithEmptyFirstLine = EOL + validHeader; + private String validHeaderWithBlankFirstLineContent = "This is a test with blank first line in header."; private String validHeaderWithBlankFirstLine = " " + EOL + validHeader; + private String validHeaderWithEmptyRandomLineContent = "This is a test with empty random line in header."; private String validHeaderWithEmptyRandomLine = validHeader.substring(0, pos) + EOL + validHeader.substring(pos + 1); + private String validHeaderWithBlankRandomLineContent = "This is a test with blank random line in header."; private String validHeaderWithBlankRandomLine = validHeader.substring(0, pos) + " " + EOL + validHeader.substring(pos + 1); - private String validHeaderWithSpacesAroundEqual = "title = This is a Title = This is a valid Title" + EOL + private String continuedTitle = "This is a Title = This is a valid Title"; + private String validHeaderWithSpacesAroundEqualContent = "This is a test with spaces around equals in header."; + private String validHeaderWithSpacesAroundEqual = "title = " + continuedTitle + EOL + "status =draft" + EOL + "type= post"+ EOL + "date = 2013-09-02"+ EOL + END_OF_HEADER; + private String validHeaderWithContinuedLineContent = "This is a test with continued line in header."; + private List tagsSample = Arrays.asList(new String[] {"Concurso Público", "Database", "dbconsole", "oracle"}); + private String tagsEntryValue = StringUtils.join(tagsSample, ","); + private String validHeaderWithContinuedLine = "title = " + + CONTINUED_LINE_STARTER + continuedTitle + EOL + + "status =draft" + EOL + + "type= post" + EOL + + "date = 2013-09-02" + EOL + + "tags=" + EOL + + CONTINUED_LINE_STARTER + StringUtils.join(tagsSample, ',' + EOL + CONTINUED_LINE_STARTER) + EOL + + END_OF_HEADER; @Before public void createSampleFile() throws Exception { @@ -138,34 +166,40 @@ public void createSampleFile() throws Exception { out.println("----"); out.close(); - validAsciiDocFileWithEmptyFirstLineInHeader = folder.newFile("validwithemptyfirstlineinheader.ad"); + validAsciiDocFileWithEmptyFirstLineInHeader = folder.newFile("validAsciiDocFileWithEmptyFirstLineInHeader.ad"); out = new PrintWriter(validAsciiDocFileWithEmptyFirstLineInHeader); out.println(validHeaderWithEmptyFirstLine); - out.println("

This is a test with empty first line in header.

"); + out.println("

" + validHeaderWithEmptyFirstLineContent + "

"); out.close(); - validAsciiDocFileWithBlankFirstLineInHeader = folder.newFile("validwithblankfirstlineinheader.ad"); + validAsciiDocFileWithBlankFirstLineInHeader = folder.newFile("validAsciiDocFileWithBlankFirstLineInHeader.ad"); out = new PrintWriter(validAsciiDocFileWithBlankFirstLineInHeader); out.println(validHeaderWithBlankFirstLine); - out.println("

This is a test with blank first line in header.

"); + out.println("

" + validHeaderWithBlankFirstLineContent + "

"); out.close(); - validAsciiDocFileWithEmptyRandomLineInHeader = folder.newFile("validwithemptyrandomlineinheader.ad"); + validAsciiDocFileWithEmptyRandomLineInHeader = folder.newFile("validAsciiDocFileWithEmptyRandomLineInHeader.ad"); out = new PrintWriter(validAsciiDocFileWithEmptyRandomLineInHeader); out.println(validHeaderWithEmptyRandomLine); - out.println("

This is a test with empty random line in header.

"); + out.println("

" + validHeaderWithEmptyRandomLineContent + "

"); out.close(); - validAsciiDocFileWithBlankRandomLineInHeader = folder.newFile("validwithblankrandomlineinheader.ad"); + validAsciiDocFileWithBlankRandomLineInHeader = folder.newFile("validAsciiDocFileWithBlankRandomLineInHeader.ad"); out = new PrintWriter(validAsciiDocFileWithBlankRandomLineInHeader); out.println(validHeaderWithBlankRandomLine); - out.println("

This is a test with blank random line in header.

"); + out.println("

" + validHeaderWithBlankRandomLineContent + "

"); out.close(); - validAsciiDocFileWithSpacesAroundEqualInHeader = folder.newFile("validwithspacesaroundequalinheader.ad"); + validAsciiDocFileWithSpacesAroundEqualInHeader = folder.newFile("validAsciiDocFileWithSpacesAroundEqualInHeader.ad"); out = new PrintWriter(validAsciiDocFileWithSpacesAroundEqualInHeader); out.println(validHeaderWithSpacesAroundEqual); - out.println("

This is a test with spaces around equals in header.

"); + out.println("

" + validHeaderWithSpacesAroundEqualContent + "

"); + out.close(); + + validAsciiDocFileWithContinuedLineInHeader = folder.newFile("validAsciiDocFileWithContinuedLineInHeader.ad"); + out = new PrintWriter(validAsciiDocFileWithContinuedLineInHeader); + out.println(validHeaderWithContinuedLine); + out.println("

" + validHeaderWithContinuedLineContent + "

"); out.close(); } @@ -174,12 +208,12 @@ public void createSampleFile() throws Exception { public void parseValidHTMLFile() { Map map = parser.processFile(validHTMLFile); Assert.assertNotNull(map); - Assert.assertEquals("draft", map.get("status")); - Assert.assertEquals("post", map.get("type")); + Assert.assertEquals("draft", map.get(status.name())); + Assert.assertEquals("post", map.get(type.name())); Assert.assertEquals("This is a Title = This is a valid Title", map.get("title")); - Assert.assertNotNull(map.get("date")); + Assert.assertNotNull(map.get(date.name())); Calendar cal = Calendar.getInstance(); - cal.setTime((Date) map.get("date")); + cal.setTime((Date) map.get(date.name())); Assert.assertEquals(8, cal.get(Calendar.MONTH)); Assert.assertEquals(2, cal.get(Calendar.DAY_OF_MONTH)); Assert.assertEquals(2013, cal.get(Calendar.YEAR)); @@ -196,12 +230,12 @@ public void parseInvalidHTMLFile() { public void parseValidAsciiDocFile() { Map map = parser.processFile(validAsciiDocFile); Assert.assertNotNull(map); - Assert.assertEquals("draft", map.get("status")); - Assert.assertEquals("post", map.get("type")); - assertThat(map.get("body").toString()) + Assert.assertEquals("draft", map.get(status.name())); + Assert.assertEquals("post", map.get(type.name())); + assertThat(map.get(body.name()).toString()) .contains("class=\"paragraph\"") .contains("

JBake now supports AsciiDoc.

"); -// Assert.assertEquals("
\n
\n
\n

JBake now supports AsciiDoc.

\n
\n
\n
", map.get("body")); +// Assert.assertEquals("
\n
\n
\n

JBake now supports AsciiDoc.

\n
\n
\n
", map.get(body.name())); } @Test @@ -216,12 +250,12 @@ public void parseValidAsciiDocFileWithoutHeader() { Map map = parser.processFile(validAsciiDocFileWithoutHeader); Assert.assertNotNull(map); Assert.assertEquals("Hello: AsciiDoc!", map.get("title")); - Assert.assertEquals("published", map.get("status")); - Assert.assertEquals("page", map.get("type")); - assertThat(map.get("body").toString()) + Assert.assertEquals("published", map.get(status.name())); + Assert.assertEquals("page", map.get(type.name())); + assertThat(map.get(body.name()).toString()) .contains("class=\"paragraph\"") .contains("

JBake now supports AsciiDoc.

"); -// Assert.assertEquals("
\n
\n
\n

JBake now supports AsciiDoc.

\n
\n
\n
", map.get("body")); +// Assert.assertEquals("
\n
\n
\n

JBake now supports AsciiDoc.

\n
\n
\n
", map.get(body.name())); } @Test @@ -235,9 +269,9 @@ public void parseInvalidAsciiDocFileWithoutHeader() { public void parseValidAsciiDocFileWithExampleHeaderInContent() { Map map = parser.processFile(validAsciiDocFileWithHeaderInContent); Assert.assertNotNull(map); - Assert.assertEquals("published", map.get("status")); - Assert.assertEquals("page", map.get("type")); - assertThat(map.get("body").toString()) + Assert.assertEquals("published", map.get(status.name())); + Assert.assertEquals("page", map.get(type.name())); + assertThat(map.get(body.name()).toString()) .contains("class=\"paragraph\"") .contains("

JBake now supports AsciiDoc.

") .contains("class=\"listingblock\"") @@ -246,37 +280,58 @@ public void parseValidAsciiDocFileWithExampleHeaderInContent() { .contains("title=Example Header") .contains("date=2013-02-01") .contains("tags=tag1, tag2"); -// Assert.assertEquals("
\n
\n
\n

JBake now supports AsciiDoc.

\n
\n
\n
\n
title=Example Header\ndate=2013-02-01\ntype=post\ntags=tag1, tag2\nstatus=published\n~~~~~~
\n
\n
\n
\n
", map.get("body")); +// Assert.assertEquals("
\n
\n
\n

JBake now supports AsciiDoc.

\n
\n
\n
\n
title=Example Header\ndate=2013-02-01\ntype=post\ntags=tag1, tag2\nstatus=published\n~~~~~~
\n
\n
\n
\n
", map.get(body.name())); } @Test public void parseValidAsciiDocFileWithEmptyFirstLineInHeader() { Map map = parser.processFile(validAsciiDocFileWithEmptyFirstLineInHeader); Assert.assertNotNull(map); + assertThat(map.get(body.name()).toString()) + .contains(validHeaderWithEmptyFirstLineContent); } @Test public void parseValidAsciiDocFileWithBlankFirstLineInHeader() { Map map = parser.processFile(validAsciiDocFileWithBlankFirstLineInHeader); Assert.assertNotNull(map); + assertThat(map.get(body.name()).toString()) + .contains(validHeaderWithBlankFirstLineContent); } @Test public void parseValidAsciiDocFileWithEmptyRandomLineInHeader() { Map map = parser.processFile(validAsciiDocFileWithEmptyRandomLineInHeader); Assert.assertNotNull(map); + assertThat(map.get(body.name()).toString()) + .contains(validHeaderWithEmptyRandomLineContent); } @Test public void parseValidAsciiDocFileWithBlankRandomLineInHeader() { Map map = parser.processFile(validAsciiDocFileWithBlankRandomLineInHeader); Assert.assertNotNull(map); + assertThat(map.get(body.name()).toString()) + .contains(validHeaderWithBlankRandomLineContent); } @Test public void parseValidAsciiDocFileWithSpacesAroundEqualInHeader() { Map map = parser.processFile(validAsciiDocFileWithSpacesAroundEqualInHeader); Assert.assertNotNull(map); + assertThat(map.get(body.name()).toString()) + .contains(validHeaderWithSpacesAroundEqualContent); + } + + @Test + public void parseValidAsciiDocFileWithContinuedLineInHeader() { + Map map = parser.processFile(validAsciiDocFileWithContinuedLineInHeader); + Assert.assertNotNull(map); + Assert.assertEquals(continuedTitle, map.get("title")); + List headerTagsValues = Arrays.asList((String[]) map.get(tags.name())); + Assert.assertEquals(tagsEntryValue, StringUtils.join(headerTagsValues, ",")); + assertThat(map.get(body.name()).toString()) + .contains(validHeaderWithContinuedLineContent); } }