I am creating a report with JasperReports, using iReport generated jrxml file.
My application is multilingual, (English (LTR) and Persian (RTL)). In tables generated, regarding to the direction of text I need to swap the whole page direction. Plus I use locale feature.
I googled a lot and finally found an attribute JRXlsAbstractExporter.PROPERTY_SHEET_DIRECTION, "RTL" but setting this attribute in excel generated formats don't have any impact on my report.
params.put(JRXlsAbstractExporter.PROPERTY_SHEET_DIRECTION, "RTL");
JasperPrint jasperPrint = JasperFillManager.fillReport(report,params,
dataSource != null ? new JRMapArrayDataSource(dataSource) : new JREmptyDataSource());
another thing I tried is setting this in exporter parameters as following:
JRExporter exporter = new JRXlsxExporter();
exporter.setParameter(JRXlsAbstractExporter.PROPERTY_SHEET_DIRECTION, "RTL");
exporter.exportReport();
but setting this parameter is not allowed and I get error.
If you have any experience for how to make a report page direction (or in other words mirror the whole report in specific locale) to change please help.
As far as I searched there is no property, you can use below util class:
package foo.bar.utils.export;
import java.util.Iterator;
import java.util.List;
import net.sf.jasperreports.engine.JRPrintElement;
import net.sf.jasperreports.engine.JRPrintFrame;
import net.sf.jasperreports.engine.JRPrintPage;
import net.sf.jasperreports.engine.JasperPrint;
/**
* Report utilities
* Please refer to: http://community.jaspersoft.com/questions/523041/right-left-arabic-reports
* There is another solution at: http://jaspermirror.sourceforge.net/
* which is not used here
* #author AFattahi
*
*/
public class ReportUtils {
private ReportUtils(){
}
/**
* mirror each page layout
* #param print
*/
public static void mirrorLayout(JasperPrint print) {
int pageWidth = print.getPageWidth();
for (Object element : print.getPages()) {
JRPrintPage page = (JRPrintPage) element;
mirrorLayout(page.getElements(), pageWidth);
}
}
/**
* mirror a list of elements
* #param print
*/
protected static void mirrorLayout(List<?> elements, int totalWidth) {
for (Iterator<?> it = elements.iterator(); it.hasNext();) {
JRPrintElement element = (JRPrintElement) it.next();
int mirrorX = totalWidth - element.getX() - element.getWidth();
element.setX(mirrorX);
if (element instanceof JRPrintFrame) {
JRPrintFrame frame = (JRPrintFrame) element;
mirrorLayout(frame.getElements(), frame.getWidth());
}
}
}
}
Please consider that the JRXlsxExporter does not support RTL (seems to be a bug in version 6) and you must JRXlsExporter
exporter = new JRXlsExporter();
exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(out));
SimpleXlsReportConfiguration xlsReportConfig = new SimpleXlsReportConfiguration();
xlsReportConfig.setSheetDirection(RunDirectionEnum.RTL);
exporter.setConfiguration(xlsReportConfig);
Related
Does anyone have an idea how we can add outlines to text (text outline) within powerpoint templates (ppxt) using Apache POI? What I have gathered so far is that the XSLFTextRun class does not have a method to get/ set the text outline for a given run element.
And as such, I could only persist the following font/ text styles:
def fontStyles(textBox: XSLFTextBox, textRun: XSLFTextRun): Unit = {
val fontFamily = textRun.getFontFamily
val fontColor = textRun.getFontColor
val fontSize = textRun.getFontSize
val fontBold = textRun.isBold
val fontItalic = textRun.isItalic
val textAlign = textRun.getParagraph.getTextAlign
textBox.getTextParagraphs.foreach { p =>
p.getTextRuns.foreach { tr =>
tr.setFontFamily(fontFamily)
tr.setFontColor(fontColor)
tr.setFontSize(fontSize)
tr.setBold(fontBold)
tr.setItalic(fontItalic)
tr.getParagraph.setTextAlign(textAlign)
}
}
}
Is it possible to add text outline?
Any assistance/ suggestions would be highly appreciated.
Apache poi uses underlying ooxml-schemas classes. Those are auto generated from Office Open XML standard. So they are more complete than the high level XSLF classes. Of course they are much less convenient.
So if somewhat is not implemented in high level XSLF classes, we can get the underlying CT classes and do it using those. In case of XSLFTextRun we can get the CTRegularTextRun object. Then we can look whether there are run properties already. If not, we add one. Then we look whether there is outline set already. If so, we unset it, because we want set it new. Then we set a new outline. This simply is a line having a special color. That line is represented by CTLineProperties object. So we need to have methods to create that CTLineProperties, to set CTLineProperties to the XSLFTextRun and get CTLineProperties from XSLFTextRun.
Complete example using Java code:
import java.io.FileOutputStream;
import java.io.FileInputStream;
import org.apache.poi.xslf.usermodel.*;
import org.apache.poi.sl.usermodel.*;
import java.awt.Rectangle;
public class PPTXTextRunOutline {
static org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties createSolidFillLineProperties(java.awt.Color color) {
// create new CTLineProperties
org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties lineProperties
= org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties.Factory.newInstance();
// set line solid fill color
lineProperties.addNewSolidFill().addNewSrgbClr().setVal(new byte[]{(byte)color.getRed(), (byte)color.getGreen(), (byte)color.getBlue()});
return lineProperties;
}
static void setOutline(XSLFTextRun run, org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties lineProperties) {
// get underlying CTRegularTextRun object
org.openxmlformats.schemas.drawingml.x2006.main.CTRegularTextRun ctRegularTextRun
= (org.openxmlformats.schemas.drawingml.x2006.main.CTRegularTextRun)run.getXmlObject();
// Are there run properties already? If not, add one.
if (ctRegularTextRun.getRPr() == null) ctRegularTextRun.addNewRPr();
// Is there outline set already? If so, unset it, because we are creating it new.
if (ctRegularTextRun.getRPr().isSetLn()) ctRegularTextRun.getRPr().unsetLn();
// set a new outline
ctRegularTextRun.getRPr().setLn(lineProperties);
}
static org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties getOutline(XSLFTextRun run) {
// get underlying CTRegularTextRun object
org.openxmlformats.schemas.drawingml.x2006.main.CTRegularTextRun ctRegularTextRun
= (org.openxmlformats.schemas.drawingml.x2006.main.CTRegularTextRun)run.getXmlObject();
// Are there run properties already? If not, return null.
if (ctRegularTextRun.getRPr() == null) return null;
// get outline, may be null
org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties lineProperties = ctRegularTextRun.getRPr().getLn();
// make a copy to avoid orphaned exceptions or value disconnected exception when set to its own XML parent
if (lineProperties != null) lineProperties = (org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties)lineProperties.copy();
return lineProperties;
}
// your method fontStyles taken to Java code
static void fontStyles(XSLFTextRun templateRun, XSLFTextShape textShape) {
String fontFamily = templateRun.getFontFamily();
PaintStyle fontColor = templateRun.getFontColor();
Double fontSize = templateRun.getFontSize();
boolean fontBold = templateRun.isBold();
boolean fontItalic = templateRun.isItalic();
TextParagraph.TextAlign textAlign = templateRun.getParagraph().getTextAlign();
org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties lineProperties = getOutline(templateRun);
for (XSLFTextParagraph paragraph : textShape.getTextParagraphs()) {
for (XSLFTextRun run : paragraph.getTextRuns()) {
run.setFontFamily(fontFamily);
if(run != templateRun) run.setFontColor(fontColor); // set PaintStyle has the issue which I am avoiding by using a copy of the underlying XML
run.setFontSize(fontSize);
run.setBold(fontBold);
run.setItalic(fontItalic);
run.getParagraph().setTextAlign(textAlign);
setOutline(run, lineProperties);
}
}
}
public static void main(String[] args) throws Exception {
XMLSlideShow slideShow = new XMLSlideShow(new FileInputStream("./PPTXIn.pptx"));
XSLFSlide slide = slideShow.getSlides().get(0);
//as in your code, get a template text run and set its font style to all other runs in text shape
if (slide.getShapes().size() > 0) {
XSLFShape shape = slide.getShapes().get(0);
if (shape instanceof XSLFTextShape) {
XSLFTextShape textShape = (XSLFTextShape) shape;
XSLFTextParagraph paragraph = null;
if(textShape.getTextParagraphs().size() > 0) paragraph = textShape.getTextParagraphs().get(0);
if (paragraph != null) {
XSLFTextRun run = null;
if(paragraph.getTextRuns().size() > 0) run = paragraph.getTextRuns().get(0);
if (run != null) {
fontStyles(run, textShape);
}
}
}
}
//new text box having outlined text from scratch
XSLFTextBox textbox = slide.createTextBox();
textbox.setAnchor(new Rectangle(100, 300, 570, 80));
XSLFTextParagraph paragraph = null;
if(textbox.getTextParagraphs().size() > 0) paragraph = textbox.getTextParagraphs().get(0);
if(paragraph == null) paragraph = textbox.addNewTextParagraph();
XSLFTextRun run = paragraph.addNewTextRun();
run.setText("Test text outline");
run.setFontSize(60d);
run.setFontColor(java.awt.Color.YELLOW);
setOutline(run, createSolidFillLineProperties(java.awt.Color.BLUE));
FileOutputStream out = new FileOutputStream("./PPTXOit.pptx");
slideShow.write(out);
out.close();
}
}
Tested and works using current apache poi 5.0.0.
So I've been browsing around the source code / documentation for POI (specifically XWPF) and I can't seem to find anything that relates to editing a hyperlink in a .docx. I only see functionality to get the information for the currently set hyperlink. My goal is to change the hyperlink in a .docx to link to "http://yahoo.com" from "http://google.com" as an example. Any help would be greatly appreciated. Thanks!
I found a way to edit the url of the link in a "indirect way" (copy the previous hyperlink, modify the url, delete the previous hyperlink and add the new one in the paragraph).
Code is shown below:
private void editLinksOfParagraph(XWPFParagraph paragraph, XWPFDocument document) {
for (int rIndex = 0; rIndex < paragraph.getRuns().size(); rIndex++) {
XWPFRun run = paragraph.getRuns().get(rIndex);
if (run instanceof XWPFHyperlinkRun) {
// get the url of the link to edit it
XWPFHyperlink link = ((XWPFHyperlinkRun) run).getHyperlink(document);
String linkURL = link.getURL();
//get the xml representation of the hyperlink that includes all the information
XmlObject xmlObject = run.getCTR().copy();
linkURL += "-edited-link"; //edited url of the link, f.e add a '-edited-link' suffix
//remove the previous link from the paragraph
paragraph.removeRun(rIndex);
//add the new hyperlinked with updated url in the paragraph, in place of the previous deleted
XWPFHyperlinkRun hyperlinkRun = paragraph.insertNewHyperlinkRun(rIndex, linkURL);
hyperlinkRun.getCTR().set(xmlObject);
}
}
}
This requirement needs knowledge about how hyperlinks referring to an external reference get stored in Microsoft Word documents and how this gets represented in XWPF of Apache POI.
The XWPFHyperlinkRun is the representation of a linked text run in a IRunBody. This text run, or even multiple text runs, is/are wrapped with a XML object of type CTHyperlink. This contains a relation ID which points to a relation in the package relations part. This package relation contains the URI which is the hyperlink's target.
Currently (apache poi 5.2.2) XWPFHyperlinkRun provides access to a XWPFHyperlink. But this is very rudimentary. It only has getters for the Id and the URI. It neither provides access to it's XWPFHyperlinkRun and it's IRunBody nor it provides a setter for the target URI in the package relations part. It not even has internally access to it's the package relations part.
So only using Apache POI classes the only possibility currently is to delete the old XWPFHyperlinkRun and create a new one pointing to the new URI. But as the text runs also contain the text formatting, deleting them will also delete the text formatting. It would must be copied from the old XWPFHyperlinkRun to the new before deleting the old one. That's uncomfortable.
So the rudimentary XWPFHyperlink should be extended to provide a setter for the target URI in the package relations part. A new class XWPFHyperlinkExtended could look like so:
import org.apache.poi.xwpf.usermodel.*;
import org.apache.poi.openxml4j.opc.PackageRelationship;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
/**
* Extended XWPF hyperlink class
* Provides access to it's Id, URI, XWPFHyperlinkRun, IRunBody.
* Provides setting target URI in PackageRelationship.
*/
public class XWPFHyperlinkExtended {
private String id;
private String uri;
private XWPFHyperlinkRun hyperlinkRun;
private IRunBody runBody;
private PackageRelationship rel;
public XWPFHyperlinkExtended(XWPFHyperlinkRun hyperlinkRun, PackageRelationship rel) {
this.id = rel.getId();
this.uri = rel.getTargetURI().toString();
this.hyperlinkRun = hyperlinkRun;
this.runBody = hyperlinkRun.getParent();
this.rel = rel;
}
public String getId() {
return this.id;
}
public String getURI() {
return this.uri;
}
public IRunBody getIRunBody() {
return this.runBody;
}
public XWPFHyperlinkRun getHyperlinkRun() {
return this.hyperlinkRun;
}
/**
* Provides setting target URI in PackageRelationship.
* The old PackageRelationship gets removed.
* A new PackageRelationship gets added using the same Id.
*/
public void setTargetURI(String uri) {
this.runBody.getPart().getPackagePart().removeRelationship(this.getId());
this.uri = uri;
PackageRelationship rel = this.runBody.getPart().getPackagePart().addExternalRelationship(uri, XWPFRelation.HYPERLINK.getRelation(), this.getId());
this.rel = rel;
}
}
It does not extend XWPFHyperlink as this is so rudimentary it's not worth it. Furthermore after setTargetURI the String uri needs to be updated. But there is no setter in XWPFHyperlink and the field is only accessible from inside the package.
The new XWPFHyperlinkExtended can be got from XWPFHyperlinkRun like so:
/**
* If this HyperlinkRun refers to an external reference hyperlink,
* return the XWPFHyperlinkExtended object for it.
* May return null if no PackageRelationship found.
*/
/*modifiers*/ XWPFHyperlinkExtended getHyperlink(XWPFHyperlinkRun hyperlinkRun) {
try {
for (org.apache.poi.openxml4j.opc.PackageRelationship rel : hyperlinkRun.getParent().getPart().getPackagePart().getRelationshipsByType(XWPFRelation.HYPERLINK.getRelation())) {
if (rel.getId().equals(hyperlinkRun.getHyperlinkId())) {
return new XWPFHyperlinkExtended(hyperlinkRun, rel);
}
}
} catch (org.apache.poi.openxml4j.exceptions.InvalidFormatException ifex) {
// do nothing, simply do not return something
}
return null;
}
Once we have that XWPFHyperlinkExtended we can set an new target URI using it's method setTargetURI.
A further problem results from the fact, that the XML object of type CTHyperlink can wrap around multiple text runs, not only one. Then multiple XWPFHyperlinkRun are in one CTHyperlink and point to one target URI. For example this could look like:
... [this is a link to example.com]->https://example.com ...
This results in 6 XWPFHyperlinkRuns in one CTHyperlink linking to https://example.com.
This leads to problems when link text needs to be changed when the link target changes. The text of all the 6 text runs is the link text. So which text run shall be changed?
The best I have found is a method which sets the text of the first text run in the CTHyperlink.
/**
* Sets the text of the first text run in the CTHyperlink of this XWPFHyperlinkRun.
* Tries solving the problem when a CTHyperlink contains multiple text runs.
* Then the String value is set in first text run only. All other text runs are set empty.
*/
/*modifiers*/ void setTextInFirstRun(XWPFHyperlinkRun hyperlinkRun, String value) {
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHyperlink ctHyperlink = hyperlinkRun.getCTHyperlink();
for (int r = 0; r < ctHyperlink.getRList().size(); r++) {
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR ctR = ctHyperlink.getRList().get(r);
for (int t = 0; t < ctR.getTList().size(); t++) {
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTText ctText = ctR.getTList().get(t);
if (r == 0 && t == 0) {
ctText.setStringValue(value);
} else {
ctText.setStringValue("");
}
}
}
}
There the String value is set in first text run only. All other text runs are set empty. The text formatting of the first text run remains.
This works, but need more some steps to get text formatting correctly:
try (var fis = new FileInputStream(fileName);
var doc = new XWPFDocument(fis)) {
var pList = doc.getParagraphs();
for (var p : pList) {
var runs = p.getRuns();
for (int i = 0; i < runs.size(); i++) {
var r = runs.get(i);
if (r instanceof XWPFHyperlinkRun) {
var run = (XWPFHyperlinkRun) r;
var link = run.getHyperlink(doc);
// To get text: link for checking
System.out.println(run.getText(0) + ": " + link.getURL());
// how i replace it
var run1 = p.insertNewHyperlinkRun(i, "http://google.com");
run1.setText(run.getText(0));
// remove the old link
p.removeRun(i + 1);
}
}
}
try (var fos = new FileOutputStream(outFileName)) {
doc.write(fos);
}
}
I'm using these libraries:
implementation 'org.apache.poi:poi:5.2.2'
implementation 'org.apache.poi:poi-ooxml:5.2.2'
I'm trying to read data from a docx file using java. The data is is tables. Is there a way to iterate through the table cells and extract the cell data?
You have two choices:
Write a library that can open, read, and manipulate Word documents, for some definitions of "open", "read", and "manipulate", and some definition of "Word document".
Find a third-party library that can do some or all of what is defined in (1).
Search "java docx library" in your favourite web search and see what comes up.
This should help you.
http://svn.apache.org/repos/asf/poi/trunk/src/examples/src/org/apache/poi/xwpf/usermodel/SimpleTable.java
The above was achieved using Apache POI
Here is the code from the link:
package org.apache.poi.xwpf.usermodel;
import java.io.FileOutputStream;
import java.math.BigInteger;
import java.util.List;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHeight;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTShd;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTString;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTrPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTVerticalJc;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STShd;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STVerticalJc;
/**
* This program creates a simple WordprocessingML table using POI XWPF API, and
* a more complex, styled table using both XWPF and ooxml-schema. It's possible
* that not all referenced wordprocessingml classes are defined in
* poi-ooxml-schemas-3.8-beta4. If this is the case, you'll need to use the full
* ooxml-schemas.jar library.
*
* #author gisella bronzetti (original)
* #author Gregg Morris (styled table)
*/
public class SimpleTable {
public static void main(String[] args) throws Exception {
try {
createSimpleTable();
}
catch(Exception e) {
System.out.println("Error trying to create simple table.");
throw(e);
}
try {
createStyledTable();
}
catch(Exception e) {
System.out.println("Error trying to create styled table.");
throw(e);
}
}
public static void createSimpleTable() throws Exception {
XWPFDocument doc = new XWPFDocument();
XWPFTable table = doc.createTable(3, 3);
table.getRow(1).getCell(1).setText("EXAMPLE OF TABLE");
// table cells have a list of paragraphs; there is an initial
// paragraph created when the cell is created. If you create a
// paragraph in the document to put in the cell, it will also
// appear in the document following the table, which is probably
// not the desired result.
XWPFParagraph p1 = table.getRow(0).getCell(0).getParagraphs().get(0);
XWPFRun r1 = p1.createRun();
r1.setBold(true);
r1.setText("The quick brown fox");
r1.setItalic(true);
r1.setFontFamily("Courier");
r1.setUnderline(UnderlinePatterns.DOT_DOT_DASH);
r1.setTextPosition(100);
table.getRow(2).getCell(2).setText("only text");
FileOutputStream out = new FileOutputStream("simpleTable.docx");
doc.write(out);
out.close();
}
/**
* Create a table with some row and column styling. I "manually" add the
* style name to the table, but don't check to see if the style actually
* exists in the document. Since I'm creating it from scratch, it obviously
* won't exist. When opened in MS Word, the table style becomes "Normal".
* I manually set alternating row colors. This could be done using Themes,
* but that's left as an exercise for the reader. The cells in the last
* column of the table have 10pt. "Courier" font.
* I make no claims that this is the "right" way to do it, but it worked
* for me. Given the scarcity of XWPF examples, I thought this may prove
* instructive and give you ideas for your own solutions.
* #throws Exception
*/
public static void createStyledTable() throws Exception {
// Create a new document from scratch
XWPFDocument doc = new XWPFDocument();
// -- OR --
// open an existing empty document with styles already defined
//XWPFDocument doc = new XWPFDocument(new FileInputStream("base_document.docx"));
// Create a new table with 6 rows and 3 columns
int nRows = 6;
int nCols = 3;
XWPFTable table = doc.createTable(nRows, nCols);
// Set the table style. If the style is not defined, the table style
// will become "Normal".
CTTblPr tblPr = table.getCTTbl().getTblPr();
CTString styleStr = tblPr.addNewTblStyle();
styleStr.setVal("StyledTable");
// Get a list of the rows in the table
List<XWPFTableRow> rows = table.getRows();
int rowCt = 0;
int colCt = 0;
for (XWPFTableRow row : rows) {
// get table row properties (trPr)
CTTrPr trPr = row.getCtRow().addNewTrPr();
// set row height; units = twentieth of a point, 360 = 0.25"
CTHeight ht = trPr.addNewTrHeight();
ht.setVal(BigInteger.valueOf(360));
// get the cells in this row
List<XWPFTableCell> cells = row.getTableCells();
// add content to each cell
for (XWPFTableCell cell : cells) {
// get a table cell properties element (tcPr)
CTTcPr tcpr = cell.getCTTc().addNewTcPr();
// set vertical alignment to "center"
CTVerticalJc va = tcpr.addNewVAlign();
va.setVal(STVerticalJc.CENTER);
// create cell color element
CTShd ctshd = tcpr.addNewShd();
ctshd.setColor("auto");
ctshd.setVal(STShd.CLEAR);
if (rowCt == 0) {
// header row
ctshd.setFill("A7BFDE");
}
else if (rowCt % 2 == 0) {
// even row
ctshd.setFill("D3DFEE");
}
else {
// odd row
ctshd.setFill("EDF2F8");
}
// get 1st paragraph in cell's paragraph list
XWPFParagraph para = cell.getParagraphs().get(0);
// create a run to contain the content
XWPFRun rh = para.createRun();
// style cell as desired
if (colCt == nCols - 1) {
// last column is 10pt Courier
rh.setFontSize(10);
rh.setFontFamily("Courier");
}
if (rowCt == 0) {
// header row
rh.setText("header row, col " + colCt);
rh.setBold(true);
para.setAlignment(ParagraphAlignment.CENTER);
}
else if (rowCt % 2 == 0) {
// even row
rh.setText("row " + rowCt + ", col " + colCt);
para.setAlignment(ParagraphAlignment.LEFT);
}
else {
// odd row
rh.setText("row " + rowCt + ", col " + colCt);
para.setAlignment(ParagraphAlignment.LEFT);
}
colCt++;
} // for cell
colCt = 0;
rowCt++;
} // for row
// write the file
FileOutputStream out = new FileOutputStream("styledTable.docx");
doc.write(out);
out.close();
}
}
I'm trying to create an PDF document using iText. I followed to THIS nice tutorial and tried to create single page pdf document which has a table. In the tutorial the author keeps table creation of table on separate method such as addMetaData, addTitlePage and addContent. I also would keep them separately, but I'm new to iText and currently I'm stuck. The current code is:
public static void main(String args[]) {
try {
Document document = new Document(PageSize.A4);
PdfWriter.getInstance(document, new FileOutputStream(FILE));
document.open();
addMetaData(document);
addTitlePage(document);
addContent(document);
document.close();
} catch (Exception e) {
e.printStackTrace();
}
}
private static void addTitlePage(Document document) throws DocumentException {
Paragraph preface = new Paragraph();
// Add one empty line
addEmptyLine(preface, 1);
// Header of the document
preface.add(new Paragraph("Title here", capFont));
addEmptyLine(preface, 1);
// Report generated by: _name, _date
preface.add(new Paragraph("Report generated by: " + System.getProperty("user.name") + ", " + new Date(), //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
normFont));
addEmptyLine(preface, 2);
preface.add(new Paragraph("This document describes some kind of price list which is unknown to me.", normFont));
document.add(preface);
}
private static void addContent(Document document) throws DocumentException {
Paragraph content = new Paragraph();
// Add one empty line
addEmptyLine(content, 1);
// Content of the document
content.add(new Paragraph(createTable(subPart), normFont)); // not working line
addEmptyLine(content, 5);
content.add(new Paragraph("This document is a preliminary version and not subject to the license agreement.", redFont));
document.add(content);
}
private static void createTable(Section subPart) throws BadElementException {
PdfPTable table = new PdfPTable(3);
table.setHorizontalAlignment(Element.ALIGN_CENTER);
// Data
table.addCell("1");
table.addCell("2");
table.addCell("3");
subPart.add(table);
}
Any help would be appreciated.
So, after one day literature reading and api homepage visiting I came to my solution:
Instead of: content.add(new Paragraph(createTable(subPart), normFont));
I have now: createTable(content);
and of course I changed the type of variable in createTable method to the paragraph in order to get it working.
package src.AutosysPolicyWriter.Utility;
import java.util.StringTokenizer;
import com.lowagie.text.Cell;
import com.lowagie.text.Document;
import com.lowagie.text.Element;
import com.lowagie.text.Font;
import com.lowagie.text.Phrase;
import com.lowagie.text.Rectangle;
import com.lowagie.text.Table;
/**
* Modification/itext 1.4 - <Modified : September 26, 2013>
* #author Oliver Lundag
* #date 2013-09-06
* Can handle table creation in the PDF
*/
public class PdfTableUtility {
/***
*
* #param titleFont - font of the title of the table
* #param fontHeader - font of the headers
* #param fontData - font of the data
* #param thisReport - Document
* #param tableTitle - Name of the table
* #param headerStrings - headers
* #param data - data
*
* How to use this function;
*
* Example:
*
* // Initialize PdfTableUtility object
* 1. PdfTableUtility tableUtility = new PdfTableUtility();
*
* // Declare the value for headers.
* // Note: Number of columns will depends on how many headers has been declared
* 2. String[] headers = {"Customer Name","Age","Plan","Amount"};
*
* // Declare the data that will be put inside
* // Note that the arrangement of the strings are the actual display in the pdf
* // ; - separator
* 3. String data = "1.Oliver Lundag;26;Plan A;250,000;"+
* "2.Oliver Lundag;26;Plan A;250,000;"+
* "3.Oliver Lundag;26;Plan A;250,000";
*
* //call the function with specified arguments
* //arguments will depend on developers perspective
* 4. tableUtility.displaytable(FontBold11, FontBold9, FontNormal9, thisReport, "Information", 4, headers, data);
*/
public void displaytable(Font titleFont, Font fontHeader, Font fontData, Document thisReport,String tableTitle, String[] headerStrings, String data) {
try{
//START-(Modification/itext 1.4) SR-CS-13035 - OLUND <Modified : September 26, 2013> - title
//1.create table
Table title = new Table(1);
title.setLastHeaderRow(1);
title.setOffset(12f);
title.setSpaceInsideCell(1f);
title.setBorder(Rectangle.NO_BORDER);
//2.create table
Cell celltitle = new Cell(new Phrase(tableTitle, titleFont));
celltitle.setLeading(12);
celltitle.setBorder(Rectangle.NO_BORDER);
celltitle.setHorizontalAlignment(Element.ALIGN_CENTER);
title.addCell(celltitle);
//3.add the title in document
thisReport.add(title);
//END -(Modification/itext 1.4) SR-CS-13035 - OLUND <Modified : September 26, 2013> - title
//START-(Modification/itext 1.4) SR-CS-13035 - OLUND <Modified : September 26, 2013> - data
//1. get the max number of columns
int numColumns = headerStrings.length;
//2. create a table
Table table = new Table(numColumns);
table.setOffset(12f);
table.setLastHeaderRow(1);
table.setSpaceInsideCell(1f);
table.setTableFitsPage(true);
table.setAutoFillEmptyCells(true);
//3.get headers and add it into cells
for (String header : headerStrings) {
Cell headerCell = new Cell(new Phrase(header, fontHeader));
headerCell.setLeading(12);
headerCell.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(headerCell);
}
//4.get data and add it into cells
String strToken;
if (data.length() > 0) {
StringTokenizer stStr = new StringTokenizer(data,";",false);
while(stStr.hasMoreTokens()){
strToken = stStr.nextToken().toString();
Cell cell = new Cell(new Phrase(strToken,fontData));
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
cell.setLeading(12);
table.addCell(cell);
}
}
//5. add the table in the document
thisReport.add(table);
//END - (Modification/itext 1.4) SR-CS-13035 - OLUND <Modified : September 26, 2013> - data
}catch (Exception e) {
e.printStackTrace();
}
}
}
Don't have to worry about linked style or hover style.
I want to automatically convert files like this
<html>
<body>
<style>
body{background:#FFC}
p{background:red}
body, p{font-weight:bold}
</style>
<p>...</p>
</body>
</html>
to files like this
<html>
<body style="background:red;font-weight:bold">
<p style="background:#FFC;font-weight:bold">...</p>
</body>
</html>
I would be even more interested if there was an HTML parser that would do this.
The reason I want to do this is so I can display emails that use global style sheets without their style sheets messing up the rest of my web page. I also would like to send the resulting style to web based rich text editor for reply and original message.
Here is a solution on java, I made it with the JSoup Library: http://jsoup.org/download
import java.io.IOException;
import java.util.StringTokenizer;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
public class AutomaticCssInliner {
/**
* Hecho por Grekz, http://grekz.wordpress.com
*/
public static void main(String[] args) throws IOException {
final String style = "style";
final String html = "<html>" + "<body> <style>"
+ "body{background:#FFC} \n p{background:red}"
+ "body, p{font-weight:bold} </style>"
+ "<p>...</p> </body> </html>";
// Document doc = Jsoup.connect("http://mypage.com/inlineme.php").get();
Document doc = Jsoup.parse(html);
Elements els = doc.select(style);// to get all the style elements
for (Element e : els) {
String styleRules = e.getAllElements().get(0).data().replaceAll(
"\n", "").trim(), delims = "{}";
StringTokenizer st = new StringTokenizer(styleRules, delims);
while (st.countTokens() > 1) {
String selector = st.nextToken(), properties = st.nextToken();
Elements selectedElements = doc.select(selector);
for (Element selElem : selectedElements) {
String oldProperties = selElem.attr(style);
selElem.attr(style,
oldProperties.length() > 0 ? concatenateProperties(
oldProperties, properties) : properties);
}
}
e.remove();
}
System.out.println(doc);// now we have the result html without the
// styles tags, and the inline css in each
// element
}
private static String concatenateProperties(String oldProp, String newProp) {
oldProp = oldProp.trim();
if (!newProp.endsWith(";"))
newProp += ";";
return newProp + oldProp; // The existing (old) properties should take precedence.
}
}
Using jsoup + cssparser:
private static final String STYLE_ATTR = "style";
private static final String CLASS_ATTR = "class";
public String inlineStyles(String html, File cssFile, boolean removeClasses) throws IOException {
Document document = Jsoup.parse(html);
CSSOMParser parser = new CSSOMParser(new SACParserCSS3());
InputSource source = new InputSource(new FileReader(cssFile));
CSSStyleSheet stylesheet = parser.parseStyleSheet(source, null, null);
CSSRuleList ruleList = stylesheet.getCssRules();
Map<Element, Map<String, String>> allElementsStyles = new HashMap<>();
for (int ruleIndex = 0; ruleIndex < ruleList.getLength(); ruleIndex++) {
CSSRule item = ruleList.item(ruleIndex);
if (item instanceof CSSStyleRule) {
CSSStyleRule styleRule = (CSSStyleRule) item;
String cssSelector = styleRule.getSelectorText();
Elements elements = document.select(cssSelector);
for (Element element : elements) {
Map<String, String> elementStyles = allElementsStyles.computeIfAbsent(element, k -> new LinkedHashMap<>());
CSSStyleDeclaration style = styleRule.getStyle();
for (int propertyIndex = 0; propertyIndex < style.getLength(); propertyIndex++) {
String propertyName = style.item(propertyIndex);
String propertyValue = style.getPropertyValue(propertyName);
elementStyles.put(propertyName, propertyValue);
}
}
}
}
for (Map.Entry<Element, Map<String, String>> elementEntry : allElementsStyles.entrySet()) {
Element element = elementEntry.getKey();
StringBuilder builder = new StringBuilder();
for (Map.Entry<String, String> styleEntry : elementEntry.getValue().entrySet()) {
builder.append(styleEntry.getKey()).append(":").append(styleEntry.getValue()).append(";");
}
builder.append(element.attr(STYLE_ATTR));
element.attr(STYLE_ATTR, builder.toString());
if (removeClasses) {
element.removeAttr(CLASS_ATTR);
}
}
return document.html();
}
After hours of trying different manual java code solutions and not being satisfied with results (responsive media query handling issues mostly), I stumbled upon https://github.com/mdedetrich/java-premailer-wrapper which works great as a java solution. Note that you might actually be better off running your own "premailer" server. While there is a public api to premailer, I wanted to have my own instance running that I can hit as hard as I want:
https://github.com/TrackIF/premailer-server
Easy to run on ec2 with just a few clicks: https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create_deploy_Ruby_sinatra.html
git clone https://github.com/Enalmada/premailer-server
cd premailer-server
eb init (choose latest ruby)
eb create premailer-server
eb deploy
curl --data "html=<your html>" http://your.eb.url
I haven't tried this but looks like you can use something like CSS parser to get a DOM tree corresponding to your CSS. So you can do something like:
Obtain cssDOM
Obtain htmlDOM (JAXP)
Iterate over each cssDOM element and use xpath to locate and insert the correct style in your htmlDOM.
Convert htmlDOM to string.
I can't yet comment but I wrote a gist that attempted to enhance the accepted answer to handle the cascading part of cascading stylesheets.
It doesn't work perfectly but it is almost there.
https://gist.github.com/moodysalem/69e2966834a1f79492a9
You can use HtmlUnit and Jsoup. You render the html page in the browser using HtmlUnit. Then you get the computed styles going through the elements thanks to HtmlUnit. Jsoup is just here to format the html output.
You can find here a simple implementation :
public final class CssInliner {
private static final Logger log = Logger.getLogger(CssInliner.class);
private CssInliner() {
}
public static CssInliner make() {
return new CssInliner();
}
/**
* Main method
*
* #param html html to inline
*
* #return inlined html
*/
public String inline(String html) throws IOException {
try (WebClient webClient = new WebClient()) {
HtmlPage htmlPage = getHtmlPage(webClient, html);
Window window = webClient.getCurrentWindow().getScriptableObject();
for (HtmlElement htmlElement : htmlPage.getHtmlElementDescendants()) {
applyComputedStyle(window, htmlElement);
}
return outputCleanHtml(htmlPage);
}
}
/**
* Output the HtmlUnit page to a clean html. Remove the old global style tag
* that we do not need anymore. This in order to simplify of the tests of the
* output.
*
* #param htmlPage
*
* #return
*/
private String outputCleanHtml(HtmlPage htmlPage) {
Document doc = Jsoup.parse(htmlPage.getDocumentElement().asXml());
Element globalStyleTag = doc.selectFirst("html style");
if (globalStyleTag != null) {
globalStyleTag.remove();
}
doc.outputSettings().syntax(Syntax.html);
return doc.html();
}
/**
* Modify the html elements by adding an style attribute to each element
*
* #param window
* #param htmlElement
*/
private void applyComputedStyle(Window window, HtmlElement htmlElement) {
HTMLElement pj = htmlElement.getScriptableObject();
ComputedCSSStyleDeclaration cssStyleDeclaration = window.getComputedStyle(pj, null);
Map<String, StyleElement> map = getStringStyleElementMap(cssStyleDeclaration);
// apply style element to html
if (!map.isEmpty()) {
htmlElement.writeStyleToElement(map);
}
}
private Map<String, StyleElement> getStringStyleElementMap(ComputedCSSStyleDeclaration cssStyleDeclaration) {
Map<String, StyleElement> map = new HashMap<>();
for (Definition definition : Definition.values()) {
String style = cssStyleDeclaration.getStyleAttribute(definition, false);
if (StringUtils.isNotBlank(style)) {
map.put(definition.getAttributeName(),
new StyleElement(definition.getAttributeName(),
style,
"",
SelectorSpecificity.DEFAULT_STYLE_ATTRIBUTE));
}
}
return map;
}
private HtmlPage getHtmlPage(WebClient webClient, String html) throws IOException {
URL url = new URL("http://tinubuinliner/" + Math.random());
StringWebResponse stringWebResponse = new StringWebResponse(html, url);
return HTMLParser.parseHtml(stringWebResponse, webClient.getCurrentWindow());
}
}
For a solution to this you're probably best using a battle hardened tool like the one from Mailchimp.
They've have opened up their css inlining tool in their API, see here: http://apidocs.mailchimp.com/api/1.3/inlinecss.func.php
Much more useful than a web form.
There's also an open source Ruby tool here: https://github.com/alexdunae/premailer/
Premailer also exposes an API and web form, see http://premailer.dialect.ca - it's sponsored by Campaign Monitor who are one of the other big players in the email space.
I'm guessing you could integrate Premailer into your Java app via [Jruby][1], although I have no experience with this.
The CSSBox + jStyleParser libraries can do the job as already answered here.
http://www.mailchimp.com/labs/inlinecss.php
Use that link above. It will save hours of your time and is made especially for email templates. It's a free tool by mailchimp
This kind of thing is often required for e-commerce applications where the bank/whatever doesn't allow linked CSS, e.g. WorldPay.
The big challenge isn't so much the conversion as the lack of inheritance. You have to explicitly set inherited properties on all descendant tags. Testing is vital as certain browsers will cause more grief than others. You will need to add a lot more inline code than you need for a linked stylesheet, for example in a linked stylesheet all you need is p { color:red }, but inline you have to explicitly set the color on all paragraphs.
From my experience, it's very much a manual process that requires a light touch and a lot of tweaking and cross-browser testing to get right.
I took the first two answers and adopted them to make use of the capabilities of the CSS parser library:
public String inline(String html, String styles) throws IOException {
Document document = Jsoup.parse(html);
CSSRuleList ruleList = getCssRules(styles);
for (int i = 0; i < ruleList.getLength(); i++) {
CSSRule rule = ruleList.item(i);
if (rule instanceof CSSStyleRule) {
CSSStyleRule styleRule = (CSSStyleRule) rule;
String selector = styleRule.getSelectorText();
Elements elements = document.select(selector);
for (final Element element : elements) {
applyRuleToElement(element, styleRule);
}
}
}
removeClasses(document);
return document.html();
}
private CSSRuleList getCssRules(String styles) throws IOException {
CSSOMParser parser = new CSSOMParser(new SACParserCSS3());
CSSStyleSheet styleSheet = parser.parseStyleSheet(new InputSource(new StringReader(styles)), null, null);
CSSRuleList list = styleSheet.getCssRules();
return list;
}
private void applyRuleToElement(Element element, CSSStyleRule rule){
String elementStyleString = element.attr("style");
CSSStyleDeclarationImpl elementStyleDeclaration = new CSSStyleDeclarationImpl();
elementStyleDeclaration.setCssText(elementStyleString);
CSSStyleDeclarationImpl ruleStyleDeclaration = (CSSStyleDeclarationImpl)rule.getStyle();
for(Property p : ruleStyleDeclaration.getProperties()){
elementStyleDeclaration.addProperty(p);
}
String cssText = elementStyleDeclaration.getCssText();
element.attr("style", cssText);
}
private void removeClasses(Document document){
Elements elements = document.getElementsByAttribute("class");
elements.removeAttr("class");
}
Maybe its possible to improve it further by using a CSS parser like https://github.com/phax/ph-css?