Skip to content

Commit

Permalink
Allow multi-lines value in header of content files.
Browse files Browse the repository at this point in the history
Any line starting with 2 spaces will be concatenated with the previous
line.

See point jbake-org#3 "the field tags is multi-line " of issue
"Make the parsing metadata more flexible(intelligent) jbake-org#180"
  • Loading branch information
atao60 committed Nov 8, 2015
1 parent 4b7b8f7 commit 7c58819
Show file tree
Hide file tree
Showing 2 changed files with 196 additions and 107 deletions.
188 changes: 111 additions & 77 deletions src/main/java/org/jbake/app/Parser.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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.
Expand All @@ -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);

Expand Down Expand Up @@ -67,13 +75,15 @@ public Map<String, Object> processFile(File file) {
IOUtils.closeQuietly(is);
}

boolean hasHeader = hasHeader(fileContents);
// read header from file
List<String> header = getHeaderFrom(fileContents);
boolean hasValidHeader = header != null;
ParserContext context = new ParserContext(
file,
fileContents,
config,
contentPath,
hasHeader,
hasValidHeader,
content
);

Expand All @@ -83,22 +93,21 @@ public Map<String, Object> 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;
Expand All @@ -115,103 +124,122 @@ public Map<String, Object> processFile(File file) {
return null;
}

if (content.get("tags") != null) {
String[] tags = (String[]) content.get("tags");
for( int i=0; i<tags.length; i++ ) {
tags[i]=tags[i].trim();
if (config.getBoolean(Keys.TAG_SANITIZE)) {
tags[i]=tags[i].replace(" ", "-");
}
}
content.put("tags", tags);
}
sanitizeTagsInside(content);

// TODO: post parsing plugins to hook in here?

return content;
}


private void sanitizeTagsInside(Map<String, Object> content) {
String[] tagValues = (String[]) content.get(tags.name());
if (tagValues == null) {
return;
}

for( int i=0; i<tagValues.length; i++ ) {
tagValues[i]=tagValues[i].trim();
if (config.getBoolean(Keys.TAG_SANITIZE)) {
tagValues[i]=tagValues[i].replace(" ", "-");
}
}
content.put(tags.name(), tagValues);
}

/**
* Checks if the file has a meta-data header.
*
* @param contents Contents of file
* @return true if header exists, false if not
* @return map if header exists and is valid, null if not
*/
private boolean hasHeader(List<String> contents) {
boolean headerValid = false;
private List<String> getHeaderFrom(final List<String> contents) {
boolean headerSeparatorFound = false;
boolean statusFound = false;
boolean typeFound = false;

List<String> header = new ArrayList<String>();


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<String> contents, final Map<String, Object> 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<String, Object> processHeader(final List<String> header, final Map<String, Object> content) {
for (String line : header) {
Entry<String, String> 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<String, String> getHeaderEntryFrom(final String line) {
String[] parts = line.split("\\s*=\\s*", 2);
return new AbstractMap.SimpleEntry<String, String>(parts[0], parts[1]);
}

/**
Expand All @@ -238,7 +266,13 @@ private void processBody(List<String> contents, final Map<String, Object> 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);
}

}
Loading

0 comments on commit 7c58819

Please sign in to comment.