itextpdf导出pdf

前提 :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的事件
      1. 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.
      1. onEndPage()—Triggered just before starting a new page. This is the best place to add a header, a footer, a watermark, and so on.
      1. 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.
      1. 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.
      1. 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.
      1. onParagraphEnd()—Differs from onParagraph() in that the Y position where the paragraph ends is provided, instead of the starting position.
      1. onChapter()—Similar to onParagraph(), but also gives you the title of the Chapter object (in the form of a Paragraph).
      1. onChapterEnd()—Similar to onParagraphEnd(), but for the Chapter object.
      1. onSection()—Similar to onChapter(), but for the Section object.
      1. onSectionEnd()—Similar to onChapterEnd(), but for the Section object.
      1. 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); // Step 1—Create a
        // Document.
        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); // 边距
          // 1 2 0 中右左
          // 对齐方式
          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);
          // groupsTable 问题组
          // 创建一个表格,1为一行有几栏
          PdfPTable table = new PdfPTable(1);
          table.setTotalWidth(Constants.FIVEHUNDRED);
          table.setLockedWidth(true); // 宽度算正确
          // 问卷描述
    //      table.addCell(drawPdfPCell(surveyDescription, bfChinese,
    //        Constants.TEN, 0, Constants.THIRTY, "description"));
          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)) {
                // 剔出<html>的标签
                question = question.replaceAll("</?[^>]+>", "");
                // 去除字符串中的空格,回车,换行符,制表
                question = question.replaceAll(
                  "<a>\\s*|\t|\r|\n</a>", "");
              }
              if (StringUtils.isNotEmpty(answer)) {
                // 剔出<html>的标签
                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();
    //          Boolean questionWithNumber = surveyQuestionAnswerVO.getQuestionWithNumber();
              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 {
        // 为null会报错 防止报错
        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垂直居中
        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;
     }
   
     /**
      * 重写PdfPageEventHelper中的onOpenDocument方法
      */
     @Override
     public void onOpenDocument(PdfWriter writer, Document document) {
       // 得到文档的内容并为该内容新建一个模板
       total = writer.getDirectContent().createTemplate(500, 500);
   
     }
   
     /**
      * 重写PdfPageEventHelper中的onEndPage方法
      */
     @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);
       // 3.计算前半部分的foot1的长度,后面好定位最后一部分的'Y页'这俩字的x轴坐标,字体长度也要计算进去 = len
       float len = bfChinese.getWidthPoint(text, 9);
       // 定位'X/'
       float x = (document.right() + document.left()) / 2-len;
       float y =20f;
       pdfContentByte.setTextMatrix(x, y);
       pdfContentByte.showText(text);
       pdfContentByte.endText();
   
       // 将模板加入到内容(content)中- // 定位'Y'
       //最后第几页的页怎么也和前面的
       // 第几对不齐,然后把y改为y-1,把下面onCloseDocument中的total.setTextMatrix(0, 0);
       // 改为//total.setTextMatrix(0, 1);
       pdfContentByte.addTemplate(total, (double)x + (double)textSize, (double)y-1);
   
       pdfContentByte.restoreState();
     }
   
     /**
      * 重写PdfPageEventHelper中的onCloseDocument方法
      */
     @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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值