@rodewitsch/carbone
Version:
Fast, Simple and Powerful report generator. Injects JSON and produces PDF, DOCX, XLSX, ODT, PPTX, ODS, ...!
354 lines (282 loc) • 10.7 kB
JavaScript
const html2json = require('html2json').html2json;
const htmlEntities = require('./htmlentities');
const TAGS = {
docx : {
start_text : '<w:t>',
end_text : '</w:t>',
b : '<w:b/>',
u : '<w:u/>',
i : '<w:i/>',
color : (hex) => { return `<w:color w:val="${hex}"/>` },
}
}
let listId = 1
class html2xml {
constructor(html) {
this.html = html
this.result = []
this.TAGS = TAGS
this.hasError = false
this.json = {}
this.iter = 0
}
toJSON() {
let json = null
try {
this.html = htmlEntities.decode(this.html);
this.html = this.html.replace(/\n/g, "")
this.html = this.html.replace(/\t/g, "")
this.json = html2json(this.html);
}
catch(error) {
this.hasError = true
}
}
getXML() {
this.toJSON()
var temp = [`</w:t></w:r></w:p><w:p>`]
if(!this.hasError && this.json) {
this.convert(this.json)
temp.push(this.result.join(""))
}
else {
var html = this.html.replace(/<[\s\S]*?>/gi, "")
temp.push(`<w:r><w:t>${html}</w:t></w:r></w:p><w:p>`)
}
temp.push(`<w:r><w:t>`)
const xml = temp.join("")
return xml;
}
buildStyles(styles) {
const temp = Array.isArray(styles) ? styles : typeof styles === "string" ? [styles] : []
const _styles = temp.join("").split(";")
const props = {}
_styles.map( style => {
if( typeof style === "string" && style.length > 0) {
var arg_val = style.split(':')
if( Array.isArray(arg_val) && arg_val.length === 2) {
var arg = arg_val[0]
var val = arg_val[1]
props[arg] = val
}
}
})
return props
}
setProps(child, deep = 0) {
if(child.tag === "p") {
if(Array.isArray(child.child) && child.child.length>0) {
child.child[0].properties = child.child[0].properties || {}
child.child[0].properties.firstItem = true
child.child[child.child.length-1].properties = child.child[child.child.length-1].properties || {}
child.child[child.child.length-1].properties.newParagraph = true
}
}
else if(child.tag === "table") {
console.log(child)
child.properties.hasBorder = child.attr && child.attr.border
child.properties.cellpadding = child.attr && child.attr.cellpadding
child.properties.cellspacing = child.attr && child.attr.cellspacing
child.properties.tableStyle = child.attr && child.attr.style
child.properties.table = true
}
else if(child.tag === "tbody") {
child.properties.tbodyStyle = child.attr && child.attr.style
child.properties.tbody = true
child.child.map( (_child, i) => {
_child.properties = _child.properties || {}
_child.properties.row = i
if(i === child.child.length-1) _child.properties.lastRow = true
})
}
else if(child.tag === "tr") {
child.properties.trStyle = child.attr && child.attr.style
child.properties.numCols = child.child.length
child.child.map( (_child, i) => {
_child.properties = _child.properties || {}
_child.properties.col = i
if(i === child.child.length-1) _child.properties.lastCol = true
})
}
else if(child.tag === "td") {
child.properties.tdStyle = child.attr && child.attr.style
}
else if(child.tag === "h1") {
}
else if(child.tag === "h2") {
}
else if(child.tag === "strong") {
child.properties.bold = true
}
else if(child.tag === "b") {
child.properties.bold = true
}
else if(child.tag === "em") {
child.properties.italic = true
}
else if(child.tag === "u") {
child.properties.underline = true
}
else if(child.tag === "sub") {
child.properties.subScript = true
}
else if(child.tag === "sup") {
child.properties.supScript = true
}
else if(child.tag === "s") {
child.properties.strike = true
}
else if(child.tag === "span") {
}
else if(child.tag === "ul") {
child.properties.listType = "bullet"
child.properties.listId = child.properties.listId || listId++
}
else if(child.tag === "ol") {
child.properties.listType = "numbering"
child.properties.listId = child.properties.listId || listId++
}
else if(child.tag === "li") {
child.properties.list = true
child.properties.newParagraph = true
child.properties.deep = child.parent.properties.deep + 1 || deep
// child.properties.numId = child.properties.listId+"_"+child.properties.deep
if(child.properties.listType === "bullet") {
child.properties.listTypeXML = "Paragraphedeliste"
}
else if(child.properties.listType === "numbering") {
child.properties.listTypeXML = "ListParagraph"
}
else {
}
}
}
buildText(child) {
const props = child.properties || {}
var text = []
text.push(`<w:r>`)
if(props) {
if(props.list) {
let deep = 0
if(props.listTypeXML === "ListParagraph") {
deep = +props.deep + 1
}
const list = `
<w:pPr>
<w:pStyle w:val="${props.listTypeXML}"/>
<w:numPr>
<w:ilvl w:val="${deep}"/>
<w:numId w:val="${props.listId}"/>
</w:numPr>
<w:ind w:left="${720 + 360 * (+props.deep + 1)}"/>
</w:pPr>
`
text.push(list)
}
text.push('<w:rPr>')
if(props.strike) text.push('<w:strike w:val="true" />')
else if(props.dstrike) text.push('<w:dstrike w:val="true" />')
if(props.italic) text.push('<w:i w:val="true"/>')
if(props.bold) text.push('<w:b w:val="true"/>')
if(props.underline) text.push('<w:u w:val="single"/>')
if(props.style) {
let styles = this.buildStyles(props.style)
for (var arg in styles) {
if (styles.hasOwnProperty(arg)) {
const val = styles[arg]
// console.log(arg, val)
if(arg === "color") {
var rgb = val.startsWith("#") ? val.substring(1) : null
if (rgb !== null) {
text.push(`<w:color w:val="${rgb.toUpperCase()}"/>`)
}
}
else if(arg === "background-color") {
var rgb = val.startsWith("#") ? val.substring(1) : null
if (rgb !== null) {
text.push(`<w:shd w:val="clear" w:color="33FF49" w:fill="${rgb.toUpperCase()}"/>`)
}
}
else if(arg === "font-size") {
let size = val.toLowerCase()
size = size.endsWith("px") ? size.slice(0,-2) : null
if (size !== null) {
const factor = 1.25
const _size = +size * factor
text.push(`<w:sz w:val="${_size}"/><w:szCs w:val="${_size}"/>`)
}
}
else if(arg === "text-align") {
if(val === "right" | val === "left" | val === "center") {
text.push(`<w:jc w:val="${val}"/>`)
}
}
}
}
}
text.push('</w:rPr>')
let _text = child.text
text.push(`<w:t xml:space="preserve">${_text}</w:t>`)
text.push(`</w:r>`)
if(props.newParagraph) {
text.push(`</w:p><w:p>`)
}
/// Si jamais c'est dans un tableau
if(props.table) {
const tdStyles = this.buildStyles(props.tdStyle)
const tableStyles = this.buildStyles(props.tableStyle)
const table_total_width = 9000; // 100% de la largeur
// const table_width = tableStyles.width.endsWith("px") ? tableStyles.width.substring(-2) :
// const column_width = tdStyles.width.endsWith("px") ? tdStyles.width.substring(-2) :
// console.log(table_width, column_width)
if(props.col === 0 && props.row === 0) {
const wsz = 8;
this.result.push(`</w:p>\n<w:tbl><w:tblPr>`)
if(props.hasBorder) {
this.result.push(`
<w:tblBorders>
<w:top w:val="single" w:sz="${wsz}" w:space="0" w:color="000000" />
<w:start w:val="single" w:sz="${wsz}" w:space="0" w:color="000000" />
<w:bottom w:val="single" w:sz="${wsz}" w:space="0" w:color="000000" />
<w:end w:val="single" w:sz="${wsz}" w:space="0" w:color="000000" />
<w:insideH w:val="single" w:sz="${wsz-2}" w:space="0" w:color="000000" />
<w:insideV w:val="single" w:sz="${wsz-2}" w:space="0" w:color="000000" />
</w:tblBorders>
`)
}
this.result.push(`
<w:tblW w:w="0" w:type="auto"/>
</w:tblPr>
\n\t<w:tr>\n\t\t<w:tc><w:tcPr><w:tcW w:w="1000" w:type="dxa"/></w:tcPr>\n\t\t\t<w:p>`)
}
else if(props.col > 0) this.result.push(`</w:p>\n\t\t</w:tc>\n\t\t<w:tc><w:tcPr><w:tcW w:w="1000" w:type="dxa"/></w:tcPr>\n\t\t\t<w:p>`)
this.result.push(text.join(""))
if(props.lastCol && props.lastRow) this.result.push(`</w:p>\n\t\t</w:tc>\n\t</w:tr>\n</w:tbl>\n<w:p>`)
else if(props.lastCol) this.result.push(`</w:p>\n\t\t</w:tc>\n\t</w:tr>\n\t<w:tr>\n\t\t<w:tc><w:tcPr><w:tcW w:w="1000" w:type="dxa"/></w:tcPr>\n\t\t\t<w:p>`)
}
else {
this.result.push(text.join(""))
}
}
}
convert(item) {
const tableTags = ["table", "tbody", "tr", "td", "thead", "th", "tfoot"]
if(Array.isArray(item.child) && item.child.length>0) {
item.child.map( (child, i) => {
child.parent = item
child.properties = Object.assign(child.properties || {}, child.parent.properties) || {}
if(child.attr && child.attr.style && tableTags.indexOf(child.tag) < 0 ) {
child.properties.style = child.attr.style
}
if(child.node === "element") {
this.setProps(child)
this.convert(child)
}
else if(child.node === "text" && typeof child.text === "string") {
this.buildText(child)
}
})
}
}
}
module.exports = html2xml