Merge pull request #6756 from nwind/feat-ooxml-diagram

feat(office-viewer): 支持图片里的变量
This commit is contained in:
hsm-lv 2023-05-04 17:36:01 +08:00 committed by GitHub
commit 0f8842ffff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 207 additions and 3710 deletions

View File

@ -71,7 +71,7 @@ Word 渲染支持以下功能:
> 2.10.0 及以上版本
默认情况下 word 文档渲染使用流式布局,这样能更好融入到已有页面中,但展现上会和原先的文档有较大差异,且不支持页眉页脚,如果希望能看起来更像桌面端的效果,可以通过 `page` 配置开启分页渲染,只有在分页渲染
默认情况下 word 文档渲染使用流式布局,这样能更好融入到已有页面中,但展现上会和原先的文档有较大差异,且不支持页眉页脚,如果希望能看起来更像桌面端的效果,可以通过 `page` 配置开启分页渲染
```schema: scope="body"
{
@ -205,7 +205,7 @@ Word 渲染支持以下功能:
},
{
"type": "service",
"api": "/api/mock2/sample/mirror?json=%7B%22users%22%3A%5B%7B%22name%22%3A%22u1%22%2C%22age%22%3A10%7D%2C%7B%22name%22%3A%22u2%22%2C%22age%22%3A11%7D%5D%7D",
"api": "/api/mock2/sample/mirror?json=%7B%22users%22%3A%5B%7B%22name%22%3A%22u1%22%2C%22age%22%3A10%2C%22img%22%3A%22https%3A%2F%2Fsuda.cdn.bcebos.com%2Fimages%2Famis%2Fai-fake-face.jpg%22%7D%2C%7B%22name%22%3A%22u2%22%2C%22age%22%3A11%7D%5D%7D",
"body": [{
"type": "office-viewer",
"src": "/examples/static/table-list.docx",
@ -240,6 +240,41 @@ Word 渲染支持以下功能:
注意上面的例子用到了 `trackExpression`,默认情况下如果设置了 `enableVar`,每次上层数据变化都会重新渲染文档,如果文档较大可能会有性能问题,这时可以通过配置 `trackExpression` 来限制只有某个数据变化时才重新渲染。
### 图片中的变量
> 2.10 及以上版本
如果要将文档中的图片设置为变量,需要右键对应的图片,选择「查看可选文字」,然后填入类似 `{{img}}` 变量标识,在渲染时图片将替换为这个 `img` 变量的 url 地址
![word](../../../examples/static/word-alt.png)
下面是示例
```schema: scope="body"
{
"type": "form",
"title": "",
"wrapWithPanel": false,
"body": [
{
"type": "input-text",
"name": "img",
"value": "https://suda.cdn.bcebos.com/images/amis/ai-fake-face.jpg",
"label": "图片地址"
},
{
"type": "office-viewer",
"id": "office-viewer",
"src": "/examples/static/image-alt-var.docx",
"wordOptions": {
"enableVar": true,
"padding": "8px"
}
}
]
}
```
## 不渲染模式
通过配置 `display: false` 可以让文档不渲染,虽然不渲染,但还是可以使用后面的下载及打印功能

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

View File

@ -59,7 +59,7 @@
"mobx-state-tree": "^3.17.3",
"moment": "^2.19.4",
"mpegts.js": "^1.6.10",
"ooxml-viewer": "^0.1.2",
"ooxml-viewer": "^0.1.3",
"prop-types": "^15.6.1",
"qrcode.react": "^3.1.0",
"rc-overflow": "^1.2.4",

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
import {presetShape} from '../src/openxml/word/drawing/presetShape';
import {shapeToSVG} from '../src/openxml/word/drawing/svg/shapeToSVG';
import {presetShape} from '../src/openxml/drawing/presetShape';
import {shapeToSVG} from '../src/openxml/drawing/svg/shapeToSVG';
const container = document.getElementById('shapes')! as HTMLElement;

View File

@ -1,6 +1,6 @@
{
"name": "ooxml-viewer",
"version": "0.1.2",
"version": "0.1.3",
"description": "office 文档在线预览",
"main": "lib/index.js",
"module": "esm/index.js",

View File

@ -508,6 +508,14 @@ export default class Word {
return text;
}
loadWordRelXML(relation: Relationship): Document {
let path = relation.target;
if (relation.part === 'word') {
path = 'word/' + path;
}
return this.getXML(path);
}
/**
*
*/

View File

@ -1,5 +1,5 @@
import {Relationship} from '../../../parse/parseRelationship';
import Word from '../../../Word';
import {Relationship} from '../../parse/parseRelationship';
import Word from '../../Word';
export class Blip {
embled?: Relationship;

View File

@ -2,7 +2,7 @@
* http://webapp.docx4java.org/OnlineDemo/ecma376/DrawingML/blipFill_2.html
*/
import Word from '../../../Word';
import Word from '../../Word';
import {Blip} from './Blip';
export class BlipFill {

View File

@ -2,8 +2,8 @@
*
*/
import Word from '../../../Word';
import {parseShape} from '../../../parse/parseShape';
import Word from '../../Word';
import {parseShape} from '../../parse/parseShape';
import {Shape} from './Shape';
export class CustomGeom {

View File

@ -2,16 +2,16 @@
* textbox
*/
import {LengthUsage, convertLength} from './../../../parse/parseSize';
import {CSSStyle} from './../../Style';
import {LengthUsage, convertLength} from '../../parse/parseSize';
import {CSSStyle} from './../Style';
import {getAttrBoolean, getAttrNumber, getValBoolean} from '../../../OpenXML';
import Word from '../../../Word';
import {getAttrBoolean, getAttrNumber, getValBoolean} from '../../OpenXML';
import Word from '../../Word';
import {Pic} from './Pic';
import {parseSize} from '../../../parse/parseSize';
import {ST_RelFromH, ST_RelFromV} from '../../Types';
import {WPS} from '../wps/WPS';
import {behindIndex} from '../../../render/zindex';
import {parseSize} from '../../parse/parseSize';
import {ST_RelFromH, ST_RelFromV} from '../Types';
import {WPS} from '../word/wps/WPS';
import {Diagram} from './diagram/Diagram';
/**
* drawing child anchor
@ -47,6 +47,8 @@ export class Drawing {
pic?: Pic;
// 主要用于文本框
wps?: WPS;
// 主要用于 smartArt
diagram?: ConstrainDOMStringParameters;
// drawing 的位置配置
position: Position = Position.inline;
// 如果是 anchor描述具体配置
@ -169,6 +171,12 @@ export class Drawing {
drawing.wps = WPS.fromXML(word, graphicDataChild);
break;
case 'dgm:relIds':
// 这个是 diagram 的关系
// http://webapp.docx4java.org/OnlineDemo/ecma376/DrawingML/relIds.html
drawing.diagram = Diagram.fromXML(word, graphicDataChild);
break;
default:
console.warn(
'unknown graphicData child tag',

View File

@ -1,9 +1,9 @@
/**
*
*/
import {ST_ShapeType} from '../../Types';
import Word from '../../../Word';
import {parseShapeGuide} from '../../../parse/parseShape';
import {ST_ShapeType} from '../Types';
import Word from '../../Word';
import {parseShapeGuide} from '../../parse/parseShape';
import {ShapeGuide} from './Shape';
export class Geom {

View File

@ -1,4 +1,4 @@
import {ST_PathFillMode} from '../../Types';
import {ST_PathFillMode} from '../Types';
export interface IPath {
type: 'moveTo' | 'lnTo' | 'arcTo' | 'cubicBezTo' | 'quadBezTo' | 'close';

View File

@ -0,0 +1,44 @@
import {getAttrBoolean} from '../../OpenXML';
import Word from '../../Word';
import {BlipFill} from './BlipFill';
import {ShapePr} from './ShapeProperties';
export class Pic {
blipFill: BlipFill;
spPr: ShapePr;
/**
*
*/
alt?: string;
/**
* alt
*/
altVar?: string;
static fromXML(word: Word, element?: Element | null): Pic {
const pic = new Pic();
const cNvPr = element?.getElementsByTagName('pic:cNvPr').item(0);
if (cNvPr) {
pic.alt = cNvPr.getAttribute('descr') || '';
pic.altVar = cNvPr.getAttribute('descrVar') || '';
const hidden = getAttrBoolean(cNvPr, 'hidden', false);
if (hidden) {
return pic;
}
}
pic.blipFill = BlipFill.fromXML(
word,
element?.getElementsByTagName('pic:blipFill').item(0)
);
pic.spPr = ShapePr.fromXML(
word,
element?.getElementsByTagName('pic:spPr').item(0)
);
return pic;
}
}

View File

@ -2,13 +2,12 @@
* http://webapp.docx4java.org/OnlineDemo/ecma376/DrawingML/spPr_2.html
*/
import {ST_PresetLineDashVal, ST_ShapeType} from '../../Types';
import Word from '../../../Word';
import {ST_PresetLineDashVal, ST_ShapeType} from '../Types';
import Word from '../../Word';
import {Transform} from './Transform';
import {CSSStyle} from './../../Style';
import {parseSize, LengthUsage} from '../../../parse/parseSize';
import {parseSize, LengthUsage} from '../../parse/parseSize';
import {Geom} from './Geom';
import {parseChildColor} from '../../../parse/parseChildColor';
import {parseChildColor} from '../../parse/parseChildColor';
import {CustomGeom} from './CustomGeom';
function prstDashToCSSBorderType(prstDash: ST_PresetLineDashVal) {

View File

@ -2,8 +2,8 @@
* http://webapp.docx4java.org/OnlineDemo/ecma376/DrawingML/xfrm_2.html
*/
import {convertAngle, LengthUsage, parseSize} from '../../../parse/parseSize';
import Word from '../../../Word';
import {convertAngle, LengthUsage, parseSize} from '../../parse/parseSize';
import Word from '../../Word';
export interface Off {
x: string;

View File

@ -0,0 +1,23 @@
/**
*
*/
import Word from '../../../Word';
export class Diagram {
// 这里的输入是 dgm:relIds 元素
static fromXML(word: Word, relidsElement: Element) {
const diagram = new Diagram();
const dmId = relidsElement.getAttribute('r:dm');
if (dmId) {
const dmRel = word.getDocumentRels(dmId);
if (dmRel) {
// 对应的就是 digrams/data1.xml 文件
const dm = word.loadWordRelXML(dmRel);
console.log(dm);
}
}
return diagram;
}
}

View File

@ -0,0 +1 @@
export class Sp {}

View File

@ -0,0 +1,5 @@
/**
* Diagram spTree
*/
export class SpTree {}

View File

@ -4,9 +4,9 @@
* https://wiki.documentfoundation.org/Development/Improve_handles_of_DrawingML_shapes
*/
import {Color} from '../../../../util/color';
import {createSVGElement} from '../../../../util/dom';
import {WPSStyle} from '../../wps/WPSStyle';
import {Color} from '../../../util/color';
import {createSVGElement} from '../../../util/dom';
import {WPSStyle} from '../../word/wps/WPSStyle';
import {Shape, ShapeGuide} from '../Shape';
import {ShapePr} from '../ShapeProperties';
import {evalFmla} from './formulas';

View File

@ -3,7 +3,7 @@ import {parsePr} from '../../parse/parsePr';
import Word from '../../Word';
import {ST_FldCharType, ST_VerticalAlignRun} from '../Types';
import {Break} from './Break';
import {Drawing} from './drawing/Drawing';
import {Drawing} from '../drawing/Drawing';
import {InstrText} from './InstrText';
import {NoBreakHyphen} from './NoBreakHyphen';
import {Pict} from './Pict';

View File

@ -1,21 +0,0 @@
import Word from '../../../Word';
import {BlipFill} from './BlipFill';
import {ShapePr} from './ShapeProperties';
export class Pic {
blipFill: BlipFill;
spPr: ShapePr;
static fromXML(word: Word, element?: Element | null): Pic {
const pic = new Pic();
pic.blipFill = BlipFill.fromXML(
word,
element?.getElementsByTagName('pic:blipFill').item(0)
);
pic.spPr = ShapePr.fromXML(
word,
element?.getElementsByTagName('pic:spPr').item(0)
);
return pic;
}
}

View File

@ -1,5 +1,5 @@
import {Paragraph} from '../Paragraph';
import {ShapePr} from '../drawing/ShapeProperties';
import {ShapePr} from '../../drawing/ShapeProperties';
/**
* wps wordprocessingShape drawing word shape
* textbox

View File

@ -13,8 +13,8 @@ import {
Path,
IPath,
PathPoint
} from '../openxml/word/drawing/Path';
import {Rect, Shape, ShapeGuide} from '../openxml/word/drawing/Shape';
} from '../openxml/drawing/Path';
import {Rect, Shape, ShapeGuide} from '../openxml/drawing/Shape';
export function parsePts(element: Element) {
const pts: PathPoint[] = [];

View File

@ -1,7 +1,7 @@
import {ShapePr} from '../openxml/word/drawing/ShapeProperties';
import {shapeToSVG} from '../openxml/word/drawing/svg/shapeToSVG';
import {ShapePr} from '../openxml/drawing/ShapeProperties';
import {shapeToSVG} from '../openxml/drawing/svg/shapeToSVG';
import {WPSStyle} from '../openxml/word/wps/WPSStyle';
import {CustomGeom} from '../openxml/word/drawing/CustomGeom';
import {CustomGeom} from '../openxml/drawing/CustomGeom';
export function renderCustGeom(
geom: CustomGeom,

View File

@ -1,14 +1,13 @@
import {Paragraph} from './../openxml/word/Paragraph';
import Word from '../Word';
import {Drawing} from '../openxml/word/drawing/Drawing';
import {Pic} from '../openxml/word/drawing/Pic';
import {Drawing} from '../openxml/drawing/Drawing';
import {Pic} from '../openxml/drawing/Pic';
import {appendChild, applyStyle} from '../util/dom';
import renderParagraph from './renderParagraph';
import renderTable from './renderTable';
import {Table} from '../openxml/word/Table';
import {renderGeom} from './renderGeom';
import {renderCustGeom} from './renderCustGeom';
import {fixAbsolutePosition} from './fixAbsolutePosition';
/**
*
@ -18,9 +17,20 @@ function renderPic(pic: Pic, word: Word, drawing: Drawing) {
if (blip && blip.src) {
const img = document.createElement('img') as HTMLImageElement;
img.style.position = 'relative';
img.alt = pic.alt || '';
img.src = blip.src;
if (pic.alt && word.renderOptions.enableVar) {
if (pic.altVar) {
img.src = pic.altVar;
} else if (pic.alt.startsWith('{{')) {
const src = word.replaceText(pic.alt);
if (src) {
img.src = src;
}
}
}
const xfrm = pic.spPr?.xfrm;
if (xfrm) {

View File

@ -1,7 +1,7 @@
import {presetShape} from '../openxml/word/drawing/presetShape';
import {Geom} from '../openxml/word/drawing/Geom';
import {ShapePr} from '../openxml/word/drawing/ShapeProperties';
import {shapeToSVG} from '../openxml/word/drawing/svg/shapeToSVG';
import {presetShape} from '../openxml/drawing/presetShape';
import {Geom} from '../openxml/drawing/Geom';
import {ShapePr} from '../openxml/drawing/ShapeProperties';
import {shapeToSVG} from '../openxml/drawing/svg/shapeToSVG';
import {WPSStyle} from '../openxml/word/wps/WPSStyle';
export function renderGeom(

View File

@ -7,7 +7,7 @@ import {appendChild, createElement, applyStyle} from '../util/dom';
import Word from '../Word';
import {Run, Text} from '../openxml/word/Run';
import {Break} from '../openxml/word/Break';
import {Drawing} from '../openxml/word/drawing/Drawing';
import {Drawing} from '../openxml/drawing/Drawing';
import {renderDrawing} from './renderDrawing';
import {setElementStyle} from './setElementStyle';
import {Tab} from '../openxml/word/Tab';

View File

@ -11,12 +11,29 @@ import {createObject} from './createObject';
*/
export function replaceT(word: Word, t: Element, data: any) {
let text = t.textContent || '';
t.textContent = replaceText(word, text, data);
}
/**
*
*/
function replaceText(word: Word, text: string, data: any) {
const evalVar = word.renderOptions.evalVar;
if (text.startsWith('{{')) {
text = text.replace(/^{{/g, '').replace(/}}$/g, '');
const result = String(evalVar(text, data)) || '';
t.textContent = result;
const result = evalVar(text, data);
if (result !== undefined && result !== null) {
return String(result);
} else {
return '';
}
}
return text;
}
function replaceAlt(word: Word, cNvPr: Element, data: any) {
const alt = cNvPr.getAttribute('descr') || '';
cNvPr.setAttribute('descrVar', replaceText(word, alt, data));
}
/**
@ -68,6 +85,11 @@ function replaceTableRow(word: Word, tr: Element) {
replaceT(word, t, rowData);
}
// 替换图片里的变量
for (const cNvPr of newTr.getElementsByTagName('pic:cNvPr')) {
replaceAlt(word, cNvPr, rowData);
}
table.appendChild(newTr);
}
// 删除原来的行
@ -110,7 +132,6 @@ function removeAllAttr(node: Element) {
*
*/
function replaceTable(word: Word, documentData: Document) {
const evalVar = word.renderOptions.evalVar;
const trs = [].slice.call(documentData.getElementsByTagName('w:tr'));
for (const tr of trs) {
replaceTableRow(word, tr);

View File

@ -4,7 +4,7 @@
import {readFileSync, writeFileSync} from 'fs';
import {parseXML} from '../src/util/xml';
import {Shape} from '../src/openxml/word/drawing/Shape';
import {Shape} from '../src/openxml/drawing/Shape';
import {parseShape} from '../src/parse/parseShape';
import jsdom from 'jsdom';