/** * Copyright 2011 Oliver Buchtala * * This file is part of ndogen. * * ndogen is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ndogen is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ndogen. If not, see <http://www.gnu.org/licenses/>. */ package org.ndogen.converter; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.net.MalformedURLException; import java.net.URL; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Map; import org.antlr.runtime.ANTLRStringStream; import org.antlr.runtime.CharStream; import org.antlr.runtime.CommonTokenStream; import org.antlr.runtime.RecognitionException; import org.antlr.runtime.tree.Tree; import org.ndogen.Fixture; import org.ndogen.TestUtils; import org.ndogen.antlr.DebugCharStream; import org.ndogen.markdown.MarkdownParser; import org.ndogen.markdown.MarkdownParser.document_return; import org.ndogen.markdown.MarkdownScanner; import org.ndogen.substance.Code; import org.ndogen.substance.Creator; import org.ndogen.substance.Document; import org.ndogen.substance.NestedNode; import org.ndogen.substance.Quote; import org.ndogen.substance.Section; import org.ndogen.substance.SubstanceNode; import org.ndogen.substance.SubstanceUtils; import org.ndogen.substance.Text; import org.ndogen.util.FileUtils; import org.ndogen.watch.task.ContentProvider; public class Markdown2Substance implements ContentProvider { ContentProvider markdownProvider; String id = null; String title = null; String author = null; Date createdAt = null; Date modifiedAt = null; public Markdown2Substance(ContentProvider markdownProvider) { super(); this.markdownProvider = markdownProvider; } @Override public String getContent() { String input = markdownProvider.getContent(); CharStream lexerInput; lexerInput = new ANTLRStringStream(input); lexerInput = new DebugCharStream(lexerInput); MarkdownScanner lexer = new MarkdownScanner(lexerInput); CommonTokenStream tokens = new CommonTokenStream(lexer); MarkdownParser parser = new MarkdownParser(tokens); try { document_return _doc = parser.document(); Tree tree = (Tree) _doc.getTree(); Document doc = new Document(); doc.setName(id); doc.setCreator(author); doc.setTitle(title); doc.setCreated_at(createdAt); doc.setUpdated_at(modifiedAt); processTree(tree, new State(doc)); Map<String, SubstanceNode> nodeMap = new Creator(author, doc).create(); return FileUtils.objectToJson(nodeMap); } catch (RecognitionException e) { throw new RuntimeException(e); } } private String extractHeader(String key, String text) { int colonIdx = text.indexOf(":"); if(colonIdx<0) { throw new RuntimeException("Illegal header line: " + text); } return text.substring(colonIdx).trim(); } protected void processTree(Tree node, State state) { int type = node.getType(); switch(type) { case 0: // if id and title are not set they have to be // specified in the first paragraph boolean firstIsHeader = false; if( id==null || title ==null || author == null || createdAt == null) { firstIsHeader = true; Tree header = node.getChild(0); for (int i = 0; i < header.getChildCount(); i++) { String text = header.getChild(i).getText(); if(author == null && text.toLowerCase().startsWith("author")) { author = extractHeader("author", text); } else if(id == null && text.toLowerCase().startsWith("id")) { id = extractHeader("id", text); } else if(title == null && text.toLowerCase().startsWith("title")) { title = extractHeader("title", text); } else if(title == null && text.toLowerCase().startsWith("createAt")) { String _createdAt = extractHeader("createAt", text); try { createdAt = new SimpleDateFormat().parse(_createdAt); } catch (ParseException e) { System.err.println("Could not parse createdAt date :" + createdAt); e.printStackTrace(); } } } } if(id==null || title ==null || author == null || createdAt == null || modifiedAt == null) { throw new RuntimeException("Meta information must be set on command line or in a header paragraph of document"); } Document doc = (Document) state.container; doc.setName(id); doc.setCreator(author); doc.setTitle(title); doc.setCreated_at(createdAt); doc.setUpdated_at(modifiedAt); for (int i = (firstIsHeader?1:0); i < node.getChildCount(); i++) { processTree(node.getChild(i), state); } break; case MarkdownParser.SECTION: { Section section = new Section(); String title = node.getChild(0).getText(); section.setName(title); State _state = new State(section); for (int i = 1; i < node.getChildCount(); i++) { processTree(node.getChild(i), _state); } state.container.addChild(section); break; } case MarkdownParser.TEXT_BLOCK: { Text text = new Text(); StringWriter sw = new StringWriter(); SimpleWriter out = new SimpleWriter(new PrintWriter(sw)); for (int i = 0; i < node.getChildCount(); i++) { processText(out, node.getChild(i), state); } text.setContent(sw.toString()); state.container.addChild(text); break; } case MarkdownParser.LIST: { Text text = new Text(); StringWriter sw = new StringWriter(); SimpleWriter out = new SimpleWriter(new PrintWriter(sw)); processText(out, node, state); text.setContent(sw.toString()); state.container.addChild(text); break; } case MarkdownParser.QUOTE_BLOCK: { Quote quote = new Quote(); StringWriter sw = new StringWriter(); SimpleWriter out = new SimpleWriter(new PrintWriter(sw)); State _state = new State(); _state.breakLine = true; for (int i = 0; i < node.getChildCount(); i++) { processText(out, node.getChild(i), _state); } quote.setContent(sw.toString()); state.container.addChild(quote); break; } case MarkdownParser.CODE_BLOCK: { Code code = new Code(); StringWriter sw = new StringWriter(); SimpleWriter out = new SimpleWriter(new PrintWriter(sw)); State _state = new State(); _state.insertNL = true; for (int i = 0; i < node.getChildCount(); i++) { processText(out, node.getChild(i), _state); } code.setContent(sw.toString()); state.container.addChild(code); break; } default: System.out.println("Skipping unhandled node: type="+ node.getType() + ", text=" + node.getText()); // skip } } protected void processText(SimpleWriter out, Tree node, State state) { int type = node.getType(); switch(type) { case MarkdownParser.NL: if(state.breakLine) { out.println("<br/>"); } else if (state.insertNL) { out.println(); } else { out.print(" "); } break; case MarkdownParser.EL: // skip this break; case MarkdownParser.TEXT: out.print(node.getText()); break; case MarkdownParser.LIST: out.println("<ul>"); for (int i = 0; i < node.getChildCount(); i++) { processText(out, node.getChild(i), state); } out.println("</ul>"); break; case MarkdownParser.LIST_ITEM: out.println("<li>"); for (int i = 0; i < node.getChildCount(); i++) { processText(out, node.getChild(i), state); } out.println(); out.println("</li>"); break; case MarkdownParser.EMPH: out.print("<em>"); for (int i = 0; i < node.getChildCount(); i++) { processText(out, node.getChild(i), state); } out.print("</em>"); break; case MarkdownParser.STRONG: out.print("<strong>"); for (int i = 0; i < node.getChildCount(); i++) { processText(out, node.getChild(i), state); } out.print("</strong>"); break; case MarkdownParser.INLINE_CODE: out.print("<code>"); for (int i = 0; i < node.getChildCount(); i++) { processText(out, node.getChild(i), state); } out.print("</code>"); break; case MarkdownParser.LINK: String text = node.getChild(0).getText(); String link = node.getChild(1).getText(); try { URL url = new URL(link); link = url.toString(); } catch (MalformedURLException e) { System.err.println("Malformed Url: " + link); out.print(text); break; } out.print("<a href=\""+link+"\">"+text+"</a>"); break; default: System.out.println("Skipping unhandled node: type="+ node.getType() + ", text=" + node.getText()); // skip } } public static class State { NestedNode container = null; boolean insertNL = false; boolean breakLine = false; public State() { } public State(NestedNode container) { this.container = container; } } public static void main(String[] args) throws IOException { // String fixtureId = "two_sections"; // Fixture fixture = TestUtils.getFixture("fixtures/markdown/parser", fixtureId); // File file = new File("fixtures/markdown/parser/"+fixtureId); // final String input = fixture.getInput(); String _file = "examples/Example1.markdown"; File file = new File(_file); String name = "Example1"; final String input = FileUtils.readFile(_file); Markdown2Substance markdown2Substance = new Markdown2Substance(new ContentProvider() { @Override public String getContent() { return input; } }); markdown2Substance.id = name; markdown2Substance.title = "Test: " + name; markdown2Substance.author = "oliver"; markdown2Substance.createdAt = new Date(file.lastModified()); markdown2Substance.modifiedAt = new Date(file.lastModified()); String substanceJson = markdown2Substance.getContent(); SubstanceUtils.push(substanceJson); } }