pandoc API操作文件转换以及下载

Pandoc

Pandoc 是一个功能强大的文档转换工具,支持多种文档格式之间的转换。它由 John MacFarlane 开发,广泛应用于学术写作、出版和技术文档处理等领域。

支持的格式

Pandoc 支持多种输入和输出格式,包括但不限于:

  • 输入格式:Markdown、HTML、LaTeX、Word (.docx)、EPUB、reStructuredText 等。
  • 输出格式:PDF、HTML、Word (.docx)、LaTeX、EPUB、Markdown、reStructuredText 等。

基本用法

Pandoc 的基本命令格式如下:

pandoc input_file -o output_file

例如,将 Markdown 文件转换为 PDF:

pandoc example.md -o example.pdf

高级功能

Pandoc 提供了许多高级功能,如模板支持、元数据处理、自定义过滤器等。可以通过命令行选项或 YAML 元数据块来配置这些功能。

安装

Pandoc 可以通过多种方式安装:

  • 在 Linux 上,可以通过包管理器安装:
    sudo apt-get install pandoc
    
  • 在 macOS 上,可以通过 Homebrew 安装:
    brew install pandoc
    
  • 在 Windows 上,可以从 Pandoc 官网下载安装程序。

扩展性

Pandoc 支持通过 Lua 脚本编写自定义过滤器,进一步扩展其功能。这使得用户可以根据需要定制文档转换过程。

应用场景

Pandoc 适用于多种场景,如:

  • 学术写作:将 Markdown 转换为 LaTeX 或 PDF,方便生成学术论文。
  • 技术文档:将 reStructuredText 转换为 HTML 或 PDF,生成技术文档。
  • 电子书制作:将 Markdown 或 HTML 转换为 EPUB,制作电子书。

Pandoc 的灵活性和强大功能使其成为处理文档转换任务的理想工具。

Pandoc-api

https://github.com/alphakevin/pandoc-api

Pandoc API

一个简单的 RESTful 服务器,用于使用 pandoc 转换文档

作为 Docker 容器运行

pandoc-api可以从 Docker 开始,无需安装源代码或 npm:

docker run -d -p 4000:4000 --name=pandoc --restart=always alphakevin/pandoc-api

这是一个简单的 http 服务器,应该作为内部微服务运行,因此它不包含任何授权方法。请自行承担公开部署的风险。

文档格式和选项没有经过全面测试,它只是将它们传递给 pandoc。

修改pandoc-api代码,实现通过链接形式下载转换后的文件

要支持将转换后的文件通过链接形式下载,我们可以修改代码,使得服务器返回一个包含下载链接的响应,而不是直接发送文件。以下是具体的修改步骤和代码示例:

1. 修改 src/app.ts 文件

app.post('/api/convert/:command(*)' 路由处理中,返回包含下载链接的 JSON 响应。

import * as os from 'os';
import * as path from 'path';
import * as express from 'express';
import * as mime from 'mime-types';
import * as multer from 'multer';
import * as uuid from 'uuid';
import { wrap } from 'async-middleware';
import * as contentDisposition from 'content-disposition';
import { Converter } from './converter';
import { ApiError, errorHandler } from './errors';
import { storage, uploadRaw } from './storage';

const packageJson = require('../package.json');

export function createApp() {
  const app = express();
  const converter = new Converter();
  const upload = multer({
    storage: storage,
  });

  app.use((req, res, next) => {
    console.log(`${req.method} ${req.path}`);
    res.set('X-Powered-By', `${packageJson.name}@${packageJson.version}`)
    next();
  });

  app.get('/', (req, res, next) => {
    res.redirect('/api');
  });

  app.get('/api/help', (req, res, next) => {
    res.set('Content-Type', 'text/plain');
    res.send(converter.getHelpText());
  });

  app.post('/api/convert/:command(*)',
    uploadRaw(),
    upload.single('file'),
    wrap(async (req, res, next) => {
      const { file } = req;
      if (!file) {
        throw new ApiError(400, 'cannot find input file');
      }
      const { command } = req.params;
      const options = converter.parseUrlCommand(command);
      const outputFile = await converter.convert(file.path, options);
      const extname = path.extname(outputFile);
      const basename = path.basename(file.originalname);
      const filename = `${basename}${extname}`;

      // 生成下载链接
      const downloadUrl = `/api/download/${path.basename(outputFile)}`;

      // 返回包含下载链接的 JSON 响应
      res.json({
        message: 'Conversion successful',
        downloadUrl: downloadUrl
      });
    })
  );

  // 添加下载文件的路由
  app.get('/api/download/:filename', async (req, res, next) => {
    const { filename } = req.params;
    const filePath = path.join(converter.getTmpDir(), filename);
    res.download(filePath, (err) => {
      if (err) {
        next(new ApiError(500, 'download_error', 'Failed to download the file'));
      }
    });
  });

  app.use('*', (req, res, next) => {
    throw new ApiError(404, 'route_not_found', 'the requested path does not exist');
  });

  app.use(errorHandler);

  return app;
}
2. 修改 src/converter.ts 文件

添加一个方法 getTmpDir 用于获取临时目录。

import * as childProcess from 'child_process';
import * as path from 'path';
import { promisify } from 'util';
import _ from 'lodash';
import { tmpDir, availableValues, extensions } from './constants';
import { ApiError } from './errors';
import { CommandOptionDefine, CommandOptions } from './types';

const exec = promisify(childProcess.exec);
const EXEC_NAME = 'pandoc';
const parameterPattern = /(-([a-zA-Z0-9]), )?(--([a-zA-Z0-9-]+)(=([^,]+))?)/g;

export class Converter {
  private helpText: string;
  private converterHelpText: string;
  private options: CommandOptionDefine[];
  private optionMap: Map<string, CommandOptionDefine>;
  private _ready: Promise<any>;

  constructor() {
    this.init();
  }

  init() {
    this._ready = exec(`${EXEC_NAME} --help`)
      .then((result) => {
        this.helpText = result.stdout;
        this.parseHelpInfo();
      });
  }

  ready() {
    return this._ready;
  }

  parseHelpInfo() {
    const lines = this.helpText.split('\n');
    const options: CommandOptionDefine[] = [];
    const help = [
      'pandoc-api, a RESTful wrapper for pandoc',
      '  please visit https://github.com/alphakevin/pandoc-api',
      '',
      'converting:',
      '  upload with multipart/form-data:',
      '    curl -F file=@example.docx http://127.0.0.1:4000/api/convert/from/docx/to/html > result.html',
      '  upload raw:',
      '    curl -X POST \\',
      '      -T "example.docx" \\',
      '      -H "Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document" \\',
      '      -H "Content-Disposition: attachment; filename=\"example.docx\"" \\',
      '      http://127.0.0.1:4000/api/convert/from/docx/to/html > result.html',
      '',
      'converter options:',
      '',
    ];
    const optionMap: Map<string, CommandOptionDefine> = new Map();
    lines.forEach(line => {
      const matches = line.match(parameterPattern);
      if (!matches) {
        return;
      };
      const option: CommandOptionDefine = {
        type: 'boolean',
        names: [],
        values: [],
        defaultName: '',
        optionalValue: false,
      };
      matches.forEach(match => {
        parameterPattern.lastIndex = 0;
        const result = parameterPattern.exec(match);
        let [, short, shortValue, long, longValue] = result;
        if (short) {
          optionMap.set(short, option);
          option.names.push(short);
          if (option.defaultName.length === 0) {
            option.defaultName = short;
          }
          if (shortValue) {
            option.type = shortValue.includes(':') ? 'meta' : 'string';
          }
        } else {
          optionMap.set(long, option);
          option.names.push(long);
          if (option.defaultName.length <= 1) {
            option.defaultName = long;
          }
          if (longValue) {
            if (/^\[/.test(longValue)) {
              option.type = 'string';
              option.optionalValue = true;
            } else if (longValue.includes('|')) {
              option.values = longValue.slice(1).split('|');
              option.type = /^[a-z]$/.test(option.values[0]) ? 'enum' : 'string';
            }
          }
        }
      });
      const values = availableValues[option.defaultName];
      if (values) {
        option.type = 'enum';
        option.values = values;
      }
      options.push(option);
    });
    help.push('');
    this.converterHelpText = help.join('\n') + this.helpText;
    this.options = options;
    this.optionMap = optionMap;
  }

  parseUrlCommand(commands: string): CommandOptions {
    const list = commands.split('/');
    const options = new CommandOptions(this.optionMap);
    const pairs = _.chunk(list, 2);
    pairs.forEach(([key, value]) => {
      options.set(key, value);
    });
    return options;
  }

  getHelpText() {
    return this.converterHelpText;
  }

  getFormatExtension(format: string) {
    return extensions[format] || 'txt';
  }

  convert(inputFile: string, options: CommandOptions): Promise<string> {
    const extension = this.getFormatExtension(options.get('to') as string);
    const outputFile = `${tmpDir}/${path.parse(inputFile).name}.${extension}`;
    const args = options.toArgs();
    args.push(`--output=${outputFile}`);
    args.push(inputFile);
    console.log('inputFile = ', inputFile);
    console.log(`pandoc ${args.join(' ')}`);
    return new Promise((resolve, reject) => {
      const handler = childProcess.spawn('pandoc', args);
      const errors = [];
      handler.stderr.on('data', error => {
        errors.push(error);
      });
      handler.on('exit', () => {
        if (errors.length) {
          return reject(new Error(Buffer.concat(errors).toString('utf8')));
        }
        resolve(outputFile);
      });
    })
  }

  getTmpDir() {
    return tmpDir;
  }
}
3. 测试

修改后的代码会在文件转换成功后返回一个包含下载链接的 JSON 响应。客户端可以通过访问这个链接来下载转换后的文件。

例如,使用 curl 进行测试:

$ curl -F file=@example.docx http://127.0.0.1:4000/api/convert/from/docx/to/html
{
  "message": "Conversion successful",
  "downloadUrl": "/api/download/example.html"
}

然后,客户端可以使用返回的下载链接来下载文件:

$ curl http://127.0.0.1:4000/api/download/example.html -o result.html

通过以上修改,服务器现在支持将转换后的文件通过链接形式下载。

OFDM(正交频分复用)是一种高效的多载波通信技术,它将高速数据流拆分为多个低速子流,并通过多个并行的低带宽子载波传输。这种技术具有高频谱效率、强抗多径衰落能力和灵活的带宽分配优势。 OFDM系统利用大量正交子载波传输数据,子载波间的正交性可有效避免码间干扰(ISI)。其数学表达为多个离散子载波信号的线性组合,调制和解调过程通过FFT(快速傅立叶变换)和IFFT(逆快速傅立叶变换)实现。其关键流程包括:数据符号映射到子载波、IFFT转换为时域信号、添加循环前缀以减少ISI、信道传输、接收端FFT恢复子载波数据和解调原始数据。 Matlab是一种广泛应用于科研、工程和数据分析的高级编程语言和交互式环境。在OFDM系统设计中,首先需掌握Matlab基础,包括编程语法、函数库和工具箱。接着,根据OFDM原理构建系统模型,实现IFFT/FFT变换、循环前缀处理和信道建模等关键算法,并通过改变参数(如信噪比、调制方式)评估系统性能。最后,利用Matlab的绘图功能展示仿真结果,如误码率(BER)曲线等。 无线通信中主要考虑加性高斯白噪声(AWGN),其在频带上均匀分布且统计独立。通过仿真OFDM系统,可在不同信噪比下测量并绘制BER曲线。分析重点包括:不同调制方式(如BPSK、QPSK)对BER的影响、循环前缀长度选择对性能的影响以及信道估计误差对BER的影响。 OFDM技术广泛应用于多个领域,如数字音频广播(DAB)、地面数字电视广播(DVB-T)、无线局域网(WLAN)以及4G/LTE和5G移动通信,是这些通信标准中的核心技术之一。 深入研究基于Matlab的OFDM系统设计与仿真,有助于加深对OFDM技术的理解,并提升解决实际通信问题的能力。仿真得到的关键性能指标(如BER曲线)对评估系统可靠性至关重要。未来可进一步探索复杂信道条件下的OFDM性能及系统优化,以适应不同应用场景
51单片机是电子工程领域常用的入门级微控制器,广泛应用于小型电子设备,例如电子时钟。本项目将介绍如何利用51单片机设计一款简单的电子时钟,并通过Keil软件进行程序开发,同时借助Proteus仿真工具进行电路模拟,帮助初学者掌握51单片机的基础应用。 51单片机基于Intel 8051内核,集成了CPU、RAM、ROM、定时器/计数器和I/O端口等功能模块,具有易于编程和性价比高的优势。在电子时钟项目中,主要利用其定时器实现时间的精确计算。Keil μVision是51单片机的常用开发环境,支持C语言和汇编语言编程。开发时,需编写代码以控制单片机显示和更新时间,包括初始化时钟硬件、设置定时器中断、编写中断服务程序以及与LCD显示屏交互等步骤。关键环节如下:一是初始化,配置时钟源(如外部晶振)设定工作频率;二是定时器设置,选择合适模式(如模式1或模式2),设置计数初值以获得所需时间分辨率;三是中断服务,编写定时器中断服务程序,定时器溢出时更新时间并触发中断;四是显示控制,通过I/O端口驱动LCD显示屏显示当前时间。 Proteus是一款虚拟原型设计软件,可用于模拟硬件电路,帮助开发者在编程前验证电路设计。在Proteus中,可搭建51单片机、LCD模块、晶振及电阻、电容等元件,形成电子时钟电路模型。运行仿真后,可观察程序在实际电路中的运行情况,及时发现并解决问题。 实际项目中,51单片机电子时钟还涉及以下知识点:一是时钟信号产生,定时器通过计数外部时钟脉冲实现时间累计,可通过调整晶振频率和定时器初始值设置不同时间间隔;二是LCD接口,需理解LCD的命令和数据传输协议,以及如何控制背光、显示模式、行列地址等;三是中断系统,了解中断概念、中断向量及程序中中断的启用和禁用方法;四是数码管显示,若使用数码管而非LCD,需了解其显示原理及段选、位选的驱动方式。 本项目融合了单片机基础、
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值