前提 :limesurvey系统导出pdf,格式不符合需求
limesurvey为一个开源的php开发的调查问卷系统 limesurvey提供api地址:{ip/address}/survey/index.php?r=admin/remotecontrol limesurvey提供api的导出应答接口:export_responses_by_token 接口可以支持的文件类型 pdf, csv, xls, doc, json, html(api未展示,尝试可以) 接口返回的是json需要解析,是base64String需转码
原材料html(接口返回并转格式)
1. [上市公司股票代码][] 002264 1. [股票简称][] 新华都 1. [公司名称全称][] 新华都购物广场股份有限公司 1. [首发(重组)上市日期为][] 2008年7月31日 2.上市公司所属证监会行业门类 批发和零售业 上市公司所属证监会行业大类 零售业 3.所属辖区 厦门证监局 4.上市公司属性为 c.民营企业
html 具有很强规律性
1.可以获取题组信息: ‘data-gid=“25”’ 2.可以获取问题id : ‘data-qid=“684”’ 3.可以获取问题题干: ‘1.上市公司股票代码’ 4.可以获取问题答案: ‘新华都购物广场股份有限公司’
难点:在相应的问题前插入文本展示题
方案
筛选出需要在之前插入文本展示题的非文本展示类型的问题(具体为qid),且包含在解析html完成的对象里
查询出文本展示题的题干插入 错位(qid:需要在之前插入文本展示题的问题的id;question:文本展示题的题干) 查询出的文本展示题,之前还需插入文本展示题
递归查询同一题组的文本展示题的问题之前是否是文本展示题,体现为问题顺序减一(question_order-1) 答题逻辑控制文本展示
根据文本展示题之前的某题的答案展示 配置文件写固定逻辑,记录问题、问题答案和不展示的文本展示题的关联关系
技术点:java解析html,java生成pdf
jsoup:解析html itextpdf:生成pdf
maven 依赖
org.jsoup jsoup 1.12.1 com.itextpdf itextpdf 5.5.13.2 com.itextpdf itext-asian 5.2.0
jsoup解析html
jsoup是一个成熟的jar包,提供了丰富api接口 熟悉jQuery或者JS的同学并不陌生, getElementById(),getElementByName(),getElementsByTag()等 参考文档:https://www.cnblogs.com/zhangyinhua/p/8037599.html 搜索关键字:jsoup解析html
itextpdf生成pdf
导出的问卷pdf(问题和答案)
问卷标题(样式1) 题组(样式2)
文本展示题(样式3)
问题(样式3) 答案 (样式4)
itextpdf使用
基础对象
Document document = new Document(PageSize.A4) //文件纸张大小 PdfWriter writer; // 写对象–类似现实世界的钢笔,签字笔等 BaseFont bfChinese = BaseFont.createFont(“STSong-Light”, “UniGB-UCS2-H”, BaseFont.NOT_EMBEDDED);// 字体 Font textFont = new Font(bfChinese, Constants.TWENTY, Font.NORMAL);// 字号,样式 Paragraph paragraph ;//段落 Chunk chunk = new Chunk(surveyTitle); // document最小元素 PdfPTable table = new PdfPTable(1); // 表格:可以设置栏数,宽度 PdfPCell cell = new PdfPCell(); // 表格单元格 :可以设置文本内容,行高,字体,水平和垂直靠齐方式 生成pdf的事件
onStartPage() - 当一个新的页面开始时触发 Triggered when a new page is started. Don’t add content in this event, not even a header or footer . Use this event for initializing vari- ables or setting parameters that are page specific, such as the transition or duration parameters.
onEndPage()—Triggered just before starting a new page. This is the best place to add a header, a footer, a watermark, and so on.
onOpenDocument()—Triggered when a document is opened, just before onStartPage() is called for the first time. This is a good place to initialize variables that will be needed for all the pages of the document.
onCloseDocument()—Triggered just before the document is closed. This is the ideal place to release resources (if necessary) and to fill in the total number of pages in a page X of Y footer.
onParagraph()—In chapter 7, “Constructing columns,” you used get-VerticalPosition() to retrieve the current Y coordinate. With the onParagraph() method, you get this value automatically every time a new Paragraph is started.
onParagraphEnd()—Differs from onParagraph() in that the Y position where the paragraph ends is provided, instead of the starting position.
onChapter()—Similar to onParagraph(), but also gives you the title of the Chapter object (in the form of a Paragraph).
onChapterEnd()—Similar to onParagraphEnd(), but for the Chapter object.
onSection()—Similar to onChapter(), but for the Section object.
onSectionEnd()—Similar to onChapterEnd(), but for the Section object.
onGenericTag()—See section 4.6, “Generic Chunk functionality.” 部分代码
代码的对象分两类(可以根据业务自定义多层和各层的属性) GroupAndQuestion groupAndQuestion 题组信息
@Data
public class GroupAndQuestion {
private Integer gid;
private String title;
private List< QuestionAndAnswerDto> questionAndAnswerDtoList;
}
- QuestionAndAnswerDto 问题带答案
@Data
public class QuestionAndAnswerDto {
private Boolean show = Boolean. FALSE;
private Boolean subTitle = Boolean. FALSE;
private Boolean questionWithNumber = Boolean. FALSE;
private Boolean other = Boolean. FALSE;
private String question;
private String answer;
private Integer order;
private Integer qid;
}
private void exportMsgPDF ( HttpServletRequest request, HttpServletResponse response, String surveyTitle, List< GroupAndQuestion> groupAndQuestionList) {
try {
ByteArrayOutputStream byteArrayOutputStream = constructPDF ( surveyTitle, groupAndQuestionList) ;
response. setContentLength ( byteArrayOutputStream. size ( ) ) ;
OutputStream outputstream = response. getOutputStream ( ) ;
byteArrayOutputStream. writeTo ( outputstream) ;
byteArrayOutputStream. close ( ) ;
outputstream. flush ( ) ;
outputstream. close ( ) ;
} catch ( IOException e) {
logger. error ( "exportMsgPDF错误!" , e) ;
}
}
private ByteArrayOutputStream constructPDF ( String surveyTitle, List< GroupAndQuestion> groupAndQuestionList) {
ByteArrayOutputStream ba = new ByteArrayOutputStream ( ) ;
Document document = new Document ( PageSize. A4) ;
PdfWriter writer;
try {
BaseFont bfChinese = BaseFont. createFont ( "STSong-Light" ,
"UniGB-UCS2-H" , BaseFont. NOT_EMBEDDED) ;
writer = PdfWriter. getInstance ( document, ba) ;
PdfPageCustomEvent event = new PdfPageCustomEvent ( bfChinese) ;
writer. setPageEvent ( event) ;
document. open ( ) ;
Font textFont = new Font ( bfChinese, Constants. TWENTY, Font. NORMAL) ;
Font boldFont = new Font ( bfChinese, Constants. EIGHTEEN, Font. BOLD) ;
Paragraph paragraph = new Paragraph ( Constants. FORTY) ;
paragraph. setAlignment ( Constants. MIDLE_ALIGN) ;
Font font = new Font ( boldFont) ;
font. setSize ( Constants. THIRTY) ;
paragraph. setFont ( textFont) ;
Chunk chunk = new Chunk ( surveyTitle) ;
paragraph. add ( chunk) ;
document. add ( paragraph) ;
Paragraph p = new Paragraph ( Constants. FORTYFIVE) ;
Chunk c = new Chunk ( " " ) ;
p. add ( c) ;
document. add ( p) ;
PdfPTable table = new PdfPTable ( 1 ) ;
table. setTotalWidth ( Constants. FIVEHUNDRED) ;
table. setLockedWidth ( true ) ;
for ( GroupAndQuestion groupAndQuestion : groupAndQuestionList) {
table. addCell ( drawPdfPCell ( groupAndQuestion. getTitle ( ) ,
bfChinese, true , Constants. TWELVE, Constants. MIDLE_ALIGN, Constants. THIRTY, false ) ) ;
List< QuestionAndAnswerDto> questionAndAnswerDtoList = groupAndQuestion. getQuestionAndAnswerDtoList ( ) ;
for ( QuestionAndAnswerDto surveyQuestionAnswerVO : questionAndAnswerDtoList) {
String question = surveyQuestionAnswerVO. getQuestion ( ) ;
String answer = surveyQuestionAnswerVO. getAnswer ( ) ;
Integer qid = surveyQuestionAnswerVO. getQid ( ) ;
if ( StringUtils. isNotEmpty ( question) ) {
question = question. replaceAll ( "</?[^>]+>" , "" ) ;
question = question. replaceAll (
"<a>\\s*|\t|\r|\n</a>" , "" ) ;
}
if ( StringUtils. isNotEmpty ( answer) ) {
answer = answer. replaceAll ( "</?[^>]+>" , "" ) ;
answer = answer. replaceAll ( "<a>\\s*|\t|\r|\n</a>" ,
"" ) ;
}
Boolean other = surveyQuestionAnswerVO. getOther ( ) ;
if ( other) {
String otherStr = surveyRestTemplate. getQuestionProperties ( qid) ;
answer = otherStr + ":" + answer;
}
Boolean show = surveyQuestionAnswerVO. getShow ( ) ;
Boolean subTitle = surveyQuestionAnswerVO. getSubTitle ( ) ;
Integer align = subTitle ? Constants. MIDLE_ALIGN : Constants. LEFT_ALIGN;
table. addCell ( drawPdfPCell ( question, bfChinese, true ,
Constants. TEN, align, Constants. THIRTY, false ) ) ;
if ( ! show && StringUtils. isNotEmpty ( answer) ) {
table. addCell ( drawPdfPCell ( answer, bfChinese, false ,
Constants. TEN, 0 , Constants. THIRTY,
true ) ) ;
}
}
}
document. add ( table) ;
document. close ( ) ;
writer. close ( ) ;
ba. flush ( ) ;
ba. close ( ) ;
} catch ( DocumentException e) {
logger. debug ( e. getMessage ( ) ) ;
} catch ( IOException e) {
logger. debug ( e. getMessage ( ) ) ;
} catch ( Exception e) {
logger. debug ( e. getMessage ( ) ) ;
}
return ba;
}
private static PdfPCell drawPdfPCell ( String cellText, BaseFont baseFont, boolean bold,
float size, int alignment, int minimumHeight, boolean isAnswer)
throws Exception {
if ( cellText == null) {
cellText = " " ;
}
PdfPCell cell = new PdfPCell ( ) ;
Paragraph paragraph = new Paragraph ( ) ;
paragraph. setAlignment ( alignment) ;
Font font = new Font ( baseFont) ;
if ( bold) {
font = new Font ( baseFont, size, Font. BOLD) ;
} else {
font. setSize ( size) ;
}
if ( isAnswer) {
cellText = " " + cellText;
}
paragraph. setFont ( font) ;
Chunk chunk = new Chunk ( cellText) ;
paragraph. add ( chunk) ;
cell. disableBorderSide ( Constants. FIFTEEN) ;
cell. setUseAscender ( true ) ;
cell. setVerticalAlignment ( PdfPCell. ALIGN_MIDDLE) ;
cell. setMinimumHeight ( minimumHeight) ;
cell. addElement ( paragraph) ;
return cell;
}
每页都添加当前页和总页数(第?页/共N页)
参考文末文档 自定义PdfPageEvent,重写onOpenDocument,onEndPage 自定义PdfPageEvent代码如下
public class PdfPageCustomEvent extends PdfPageEventHelper {
private PdfTemplate total;
private BaseFont bfChinese;
public PdfPageCustomEvent ( BaseFont bfChinese) {
this . bfChinese = bfChinese;
}
@Override
public void onOpenDocument ( PdfWriter writer, Document document) {
total = writer. getDirectContent ( ) . createTemplate ( 500 , 500 ) ;
}
@Override
public void onEndPage ( PdfWriter writer, Document document) {
PdfContentByte pdfContentByte = writer. getDirectContent ( ) ;
pdfContentByte. saveState ( ) ;
String text = "第" + writer. getPageNumber ( ) + "页/" ;
float textSize = bfChinese. getWidthPoint ( text, 9 ) ;
pdfContentByte. beginText ( ) ;
pdfContentByte. setFontAndSize ( bfChinese, 9 ) ;
float len = bfChinese. getWidthPoint ( text, 9 ) ;
float x = ( document. right ( ) + document. left ( ) ) / 2 - len;
float y = 20f ;
pdfContentByte. setTextMatrix ( x, y) ;
pdfContentByte. showText ( text) ;
pdfContentByte. endText ( ) ;
pdfContentByte. addTemplate ( total, ( double ) x + ( double ) textSize, ( double ) y- 1 ) ;
pdfContentByte. restoreState ( ) ;
}
@Override
public void onCloseDocument ( PdfWriter writer, Document document) {
String text = "共" + String. valueOf ( writer. getPageNumber ( ) ) + "页" ;
total. beginText ( ) ;
total. setFontAndSize ( bfChinese, 9 ) ;
total. setTextMatrix ( 0 , 1 ) ;
total. showText ( text) ;
total. endText ( ) ;
}
}
参考文档1:https://www.cnblogs.com/liaojie970/p/7132475.html 参考文档2:https://blog.csdn.net/uper945/article/details/3314298 参考文档3:https://blog.csdn.net/qi923701/article/details/90477563 参考github代码:https://codeload.github.com/peichanglei109/multipleDemo/zip/master 官方api : https://api.itextpdf.com/iText5/java/5.5.13.2/ 搜索关键字:itextpdf