added first support for lists

This commit is contained in:
jomu
2016-05-30 14:40:42 +00:00
parent 6262d07196
commit 969b583001
17 changed files with 450 additions and 84 deletions

View File

@ -0,0 +1,71 @@
package de.muehlencord.shared.pdf;
import com.google.gson.annotations.Expose;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author joern.muehlencord
*/
public class DefaultTableRow extends TableRow {
@Expose
private final List<Text> row;
@Expose
private Boolean isList;
@Expose
private String listName;
@Expose
private String varName;
public DefaultTableRow() {
this.row = new ArrayList<>();
this.isList = false;
this.listName = null;
this.varName = null;
}
public void add(Text text) {
row.add(text);
}
/* *** TableRow methods *** */
@Override
public int getColumnCount() {
return row.size();
}
@Override
public Text getColumnValue(int columnPos) {
return row.get(columnPos);
}
@Override
public boolean isList() {
return isList;
}
@Override
public void createList(String listName, String varName) {
this.listName = listName;
this.varName = varName;
this.isList = true;
}
@Override
public String getListName() {
return listName;
}
@Override
public String getVarName() {
return varName;
}
}

View File

@ -9,11 +9,12 @@ import com.google.gson.GsonBuilder;
*/
public class GsonUtil {
public final static Gson getGsonInstance() {
protected final static Gson getGsonInstance() {
return new GsonBuilder()
.setPrettyPrinting()
.excludeFieldsWithoutExposeAnnotation()
.registerTypeAdapter(Content.class, new InterfaceAdapter<>())
.registerTypeAdapter(TableRow.class, new InterfaceAdapter<>())
.create();
}

View File

@ -0,0 +1,17 @@
package de.muehlencord.shared.pdf;
/**
*
* @author joern.muehlencord
*/
public interface ListTemplate {
public void createList (String listName, String varName);
public boolean isList();
public String getListName();
public String getVarName();
}

View File

@ -5,6 +5,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import org.apache.commons.lang3.text.StrBuilder;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
@ -32,33 +34,110 @@ public class PDFDocument {
this.fontMap = null;
}
public String toJson() {
return GsonUtil.getGsonInstance().toJson(this);
}
public String fromJson() {
return GsonUtil.getGsonInstance().toJson(this);
}
public String getTemplateString() throws TemplateException {
ConcurrentLinkedDeque<Integer> bracketStack = new ConcurrentLinkedDeque<>();
int currentPosInTemplate = 0;
int currentStartOfSubelement = 0;
String templateString = GsonUtil.getGsonInstance().toJson(this);
String typeSearchString = " \"type\": \"de.muehlencord.shared.pdf.DefaultTableRow\"";
while (templateString.indexOf(typeSearchString, currentPosInTemplate) > 0) {
// get next element
currentPosInTemplate = templateString.indexOf(typeSearchString, currentPosInTemplate);
int posOpenBracket = templateString.substring(currentStartOfSubelement, currentPosInTemplate).lastIndexOf("{") + currentStartOfSubelement;
int posCloseBracket;
// store position of 1st open bracket
bracketStack.push(posOpenBracket);
// work until closing bracket for this element
while ((!bracketStack.isEmpty()) && (currentPosInTemplate < templateString.length())) {
currentPosInTemplate += 1;
char currentChar = templateString.charAt(currentPosInTemplate);
if (currentChar == '{') {
// new open bracket found
posOpenBracket = currentPosInTemplate;
bracketStack.push(posOpenBracket);
} else if (currentChar == '}') {
if (bracketStack.isEmpty()) {
throw new TemplateException("Found closing bracket, but missing open bracket");
}
// new open bracket found
posCloseBracket = currentPosInTemplate;
posOpenBracket = bracketStack.pop();
if (bracketStack.isEmpty()) {
// next element starts behing the closing bracket earliests
currentStartOfSubelement = posCloseBracket + 1;
String jsonSubString = templateString.substring(posOpenBracket, posCloseBracket + 1);
System.out.println(jsonSubString);
// insert the list values into the gson string
TableRow element = GsonUtil.getGsonInstance().fromJson(jsonSubString, TableRow.class);
if (element.isList()) {
String listStartString = "<#list ";
listStartString += element.getListName();
listStartString += " as ";
listStartString += element.getVarName();
listStartString += ">\n";
String listEndString = "<#if ("+element.getVarName()+"?has_next)>,</#if>";
listEndString += "</#list>\n";
String newString = templateString.substring(0, posOpenBracket);
newString += listStartString;
newString += templateString.substring(posOpenBracket, posCloseBracket+1);
newString += listEndString;
newString += templateString.substring(posCloseBracket+1, templateString.length());
templateString = newString;
currentPosInTemplate += listStartString.length();
currentPosInTemplate += listEndString.length();
System.out.println(templateString.substring(posOpenBracket-10, posCloseBracket + listStartString.length() + listEndString.length()+10));
}
}
}
}
if (!bracketStack.isEmpty()) {
throw new TemplateException("Exception - stack not empty but end of string reached");
}
} // for all types
return templateString;
}
public void addFont(String name, Font font) {
if (fontMap == null) {
fontMap = new ConcurrentHashMap<>();
}
fontMap.put(name, font);
}
protected PDFont getFont(String fontName) throws ConfigurationException {
if (fontName.equals(PDType1Font.HELVETICA.getBaseFont())) {
return PDType1Font.HELVETICA;
} else if (fontName.equals(PDType1Font.HELVETICA_BOLD.getBaseFont())) {
return PDType1Font.HELVETICA_BOLD;
}else {
} else {
throw new ConfigurationException("Font " + fontName + " not supported");
}
}
}
public Font getFontByAlias(String fontAlias) throws ConfigurationException {
if ((fontMap != null) && (fontMap.containsKey(fontAlias))) {
return fontMap.get(fontAlias);
return fontMap.get(fontAlias);
} else {
throw new ConfigurationException("Font " + fontAlias + " not found in mapping. ");
}
}
public PDFDocument addContent(Content content) {
contentList.add(content);
return this;

View File

@ -0,0 +1,9 @@
package de.muehlencord.shared.pdf;
/**
*
* @author joern.muehlencord
*/
public class PDFElement {
}

View File

@ -3,10 +3,12 @@ package de.muehlencord.shared.pdf;
import com.google.gson.Gson;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.io.FileUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
@ -32,9 +34,9 @@ public class PDFTemplate {
this.template = template;
this.dataModel = new ConcurrentHashMap<>();
}
public void addToDatamodel (String key, Object value) {
this.dataModel.put (key, value);
public void addToDatamodel(String key, Object value) {
this.dataModel.put(key, value);
}
public void create(String filenName) throws ConfigurationException, IOException {
@ -45,8 +47,8 @@ public class PDFTemplate {
throw new IOException("Error while processing template", ex);
}
String json = out.toString();
LOGGER.info(json);
LOGGER.debug(json);
Gson gson = GsonUtil.getGsonInstance();
PDFDocument pdfDoc = gson.fromJson(json, PDFDocument.class);
@ -60,16 +62,16 @@ public class PDFTemplate {
default:
throw new ConfigurationException("Papersize " + pdfDoc.getPaperSize().getLabel() + " not supported");
}
doc.addPage(page);
doc.addPage(page);
PDRectangle rect = page.getMediaBox();
PDPageContentStream cos = new PDPageContentStream(doc, page, AppendMode.APPEND, false);
PDPageContentStream cos = new PDPageContentStream(doc, page, AppendMode.APPEND, false);
Coordinate coord = null;
for (Content content : pdfDoc.getContentList()) {
content.setDocument (pdfDoc); // FIXME move to serialization
content.setCoordinate (coord);
content.setDocument(pdfDoc); // FIXME move to serialization
content.setCoordinate(coord);
coord = content.addContentToPdf(rect, cos);
}
}
cos.close();
doc.save(filenName);
}

View File

@ -19,17 +19,17 @@ public class TableContent extends Content {
private final Font headerFont;
@Expose
private TableHeader header;
private final TableHeader header;
@Expose
private List<List<Text>> data = null;
public TableContent (PDFDocument doc, Font hf) {
super (doc);
private List<TableRow> data = null;
public TableContent(PDFDocument doc, Font hf) {
super(doc);
this.header = new TableHeader();
this.headerFont = hf;
this.headerFont = hf;
this.data = new ArrayList<>();
}
public TableContent(PDFDocument doc, Font hf, int x, int y) {
@ -38,56 +38,66 @@ public class TableContent extends Content {
this.headerFont = hf;
this.data = new ArrayList<>();
}
public void addLine(String... values) {
addLine(Arrays.asList(values));
public DefaultTableRow addLine(String... values) {
return addLine(Arrays.asList(values));
}
public void addLine(List<String> values) {
List<Text> newLine = new ArrayList<>();
public DefaultTableRow addLine(List<String> values) {
DefaultTableRow newLine = new DefaultTableRow();
values.stream().forEach((cellText) -> {
newLine.add(new Text(cellText));
});
data.add(newLine);
return newLine;
}
protected TableRow getRow (int no) {
return data.get(no);
}
protected int getRowCount() {
return data.size();
}
@Override
protected Coordinate addContentToPdf(PDRectangle rect, PDPageContentStream cos) throws IOException, ConfigurationException {
cos.beginText();
PDFont hFont = document.getFont(headerFont.getFontName());
PDFont standardFont = document.getFont(document.getStandardFont().getFontName());
int xOffSet = 0;
for (int i = 0; i < getHeaders().size(); i++) {
xOffSet -= header.getColumnSize(i);
xOffSet -= header.getColumnSize(i);
}
int yOffset = document.getStandardFont().getFontSize() * -1 - document.getStandardFont().getPadding();
int currentX = x;
int currentY = y;
cos.setFont(hFont, headerFont.getFontSize());
cos.newLineAtOffset(x, y);
for (int i = 0; i < header.size(); i++) {
cos.showText(header.getHeader(i).getText());
cos.newLineAtOffset(header.getColumnSize(i), 0);
}
if (data.size() == 0) {
if (data.isEmpty()) {
currentY -= headerFont.getFontSize() - headerFont.getPadding();
}
cos.setFont(standardFont, document.getStandardFont().getFontSize());
for (int lineNo = 0; lineNo < data.size(); lineNo++) {
List<Text> currentRow = data.get(lineNo);
TableRow currentRow = data.get(lineNo);
cos.newLineAtOffset(xOffSet, yOffset);
currentY += yOffset;
for (int colNo = 0; colNo < currentRow.size(); colNo++) {
cos.showText(currentRow.get(colNo).getText());
currentY += yOffset;
for (int colNo = 0; colNo < currentRow.getColumnCount(); colNo++) {
cos.showText(currentRow.getColumnValue(colNo).getText());
cos.newLineAtOffset(header.getColumnSize(colNo), 0);
}
}
cos.endText();
return new Coordinate(currentX, currentY);
currentY += yOffset;
cos.endText();
return new Coordinate(currentX, currentY);
}
/* *** getter / setter *** */
@ -97,5 +107,5 @@ public class TableContent extends Content {
public TableHeader getHeaders() {
return header;
}
}
}

View File

@ -0,0 +1,21 @@
package de.muehlencord.shared.pdf;
/**
*
* @author joern.muehlencord
*/
public abstract class TableRow {
public abstract int getColumnCount();
public abstract void createList(String listName, String varName);
public abstract boolean isList();
public abstract Text getColumnValue(int columnPos);
public abstract String getListName();
public abstract String getVarName();
}

View File

@ -0,0 +1,25 @@
package de.muehlencord.shared.pdf;
/**
*
* @author joern.muehlencord
*/
public class TemplateException extends Exception {
/**
* Creates a new instance of <code>TemplateException</code> without detail
* message.
*/
public TemplateException() {
}
/**
* Constructs an instance of <code>TemplateException</code> with the
* specified detail message.
*
* @param msg the detail message.
*/
public TemplateException(String msg) {
super(msg);
}
}

View File

@ -6,7 +6,7 @@ import com.google.gson.annotations.Expose;
*
* @author joern.muehlencord
*/
public class Text {
public class Text extends PDFElement {
@Expose
private final String text;

View File

@ -0,0 +1,35 @@
package de.muehlencord.shared.pdf;
import org.junit.Test;
import static org.junit.Assert.*;
/**
*
* @author joern.muehlencord
*/
public class DefaultTableRowTest {
@Test
public void testFromJson() {
String jsonString = "{\n" +
" \"type\": \"de.muehlencord.shared.pdf.DefaultTableRow\",\n" +
" \"data\": {\n" +
" \"row\": [\n" +
" {\n" +
" \"text\": \"Rechnungs-Nr.:\"\n" +
" },\n" +
" {\n" +
" \"text\": \"${invoiceNumber}\"\n" +
" }\n" +
" ],\n" +
" \"isList\": false\n" +
" }\n" +
" }";
TableRow tableRow = GsonUtil.getGsonInstance().fromJson(jsonString, TableRow.class);
assertNotNull ("tableRowObject", tableRow);
assertEquals ("column count", 2, tableRow.getColumnCount());
assertFalse ("isList", tableRow.isList());
}
}

View File

@ -0,0 +1,25 @@
package de.muehlencord.shared.pdf;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author joern.muehlencord
*/
public class Invoice {
private final List<InvoiceLine> invoiceLines;
public Invoice() {
this.invoiceLines = new ArrayList<>();
}
public void addInvoiceLine(InvoiceLine il) {
this.invoiceLines.add(il);
}
public List<InvoiceLine> getInvoiceLines() {
return invoiceLines;
}
}

View File

@ -0,0 +1,58 @@
package de.muehlencord.shared.pdf;
/**
*
* @author joern.muehlencord
*/
public class InvoiceLine {
private String description;
private String price;
private String amount;
private String total;
public InvoiceLine(String description, String price, String amount, String total) {
this.description = description;
this.price = price;
this.amount = amount;
this.total = total;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
public String getAmount() {
return amount;
}
public void setAmount(String amount) {
this.amount = amount;
}
public String getTotal() {
return total;
}
public void setTotal(String total) {
this.total = total;
}
}

View File

@ -1,17 +1,13 @@
package de.muehlencord.shared.pdf;
import com.google.gson.Gson;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.commons.io.FileUtils;
import org.junit.BeforeClass;
import org.junit.FixMethodOrder;
import org.junit.Test;
@ -21,18 +17,11 @@ import org.junit.Test;
*/
@FixMethodOrder
public class PDFDocumentTest {
private static Gson gson;
private String jsonString = null;
@BeforeClass
public static void setUpClass() {
gson = GsonUtil.getGsonInstance();
}
@Test
public void testToJson() throws FileNotFoundException, IOException, ConfigurationException {
public void testToJson() throws FileNotFoundException, IOException, ConfigurationException, TemplateException {
System.out.println("testToJson");
PDFDocument doc = new PDFDocument();
doc.addFont("bold", new Font("Helvetica-Bold", 12, 2));
@ -72,19 +61,20 @@ public class PDFDocumentTest {
TableContent invoiceLines = new TableContent(doc, doc.getFontByAlias("bold"));
invoiceLines.getHeaders()
.add ("Menge", 100)
.add ("Beschreibung", 100)
.add ("Beschreibung", 300)
.add ("Einzelpreis", 100)
.add ("Summe", 100);
doc.addContent(invoiceLines);
invoiceLines.addLine("1","Anzeige Hövelhofer Rundschau", "10", "10");
invoiceLines.addLine ("${invoiceline.amount}", "${invoiceline.description}", "${invoiceline.price}", "${invoiceline.total}").createList("invoiceLines", "invoiceline");
invoiceLines.addLine("2","Anzeige Hövelhofer Rundschau", "10", "20");
doc.addContent(invoiceLines);
TextContent test = new TextContent (doc)
.addLine("Das ist ein Test");
doc.addContent (test);
jsonString = gson.toJson(doc);
System.out.println(jsonString);
jsonString = doc.getTemplateString();
File file = new File("c:/temp/test.ftlh");
FileUtils.writeStringToFile(file, jsonString, "UTF-8");
@ -97,9 +87,16 @@ public class PDFDocumentTest {
Template template = cfg.getTemplate("test.ftlh");
PDFTemplate pdfDoc = new PDFTemplate(template);
Invoice invoice = new Invoice();
invoice.addInvoiceLine(new InvoiceLine ("Product 1", "10", "1", "10"));
invoice.addInvoiceLine(new InvoiceLine ("Product 2", "5", "10", "50"));
invoice.addInvoiceLine(new InvoiceLine ("Product 3", "100", "20", "2000"));
pdfDoc.addToDatamodel("invoiceDate", new Date());
pdfDoc.addToDatamodel("customerNumber", "8755");
pdfDoc.addToDatamodel("invoiceNumber", "1234567");
pdfDoc.addToDatamodel("invoiceLines", invoice.getInvoiceLines());
pdfDoc.create("c:/temp/test.pdf");
}

View File

@ -0,0 +1,27 @@
package de.muehlencord.shared.pdf;
import org.junit.Test;
import static org.junit.Assert.*;
/**
*
* @author joern.muehlencord
*/
public class TextTest {
public TextTest() {
}
@Test
public void testFromJson() {
String jsonString = "{\n"
+ "\"text\": \"Rechnungs-Nr.:\"\n"
+ "}";
Text text = GsonUtil.getGsonInstance().fromJson(jsonString, Text.class);
assertNotNull ("text object", text);
assertEquals ("text value", "Rechnungs-Nr.:", text.getText());
}
}

View File

@ -12,6 +12,10 @@
<logger name="org.hibernate">
<level value="warn" />
</logger>
<logger name="org.gson">
<level value="DEBUG" />
</logger>
<category name="de.muehlencord">
<priority value="DEBUG"/>
@ -26,7 +30,7 @@
</category>
<root>
<level value="INFO" />
<level value="DEBUG" />
<appender-ref ref="consoleAppender" />
</root>