import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; import 'package:archive/archive.dart'; import 'package:xml/xml.dart'; import 'package:docx/replace.dart'; class DocxEditor { File fileDocx; /// [fileDocx] The Docx file... /// [replaceMap] All text and image to change DocxEditor(this.fileDocx, Map replaceMap) { if (!fileDocx.existsSync()) throw(PathNotFoundException(fileDocx.path, OSError('File not found', 404))); List documentsXml = editById(replaceMap); save(documentsXml, replaceMap); } /// This function search ID in [replaceMap] and replace by his value or by a image List editById(Map replaceMap) { Uint8List content = fileDocx.readAsBytesSync(); Archive archive = ZipDecoder().decodeBytes(content); ArchiveFile archiveFile = archive.firstWhere((f) => f.name == 'word/document.xml'); XmlDocument documentXml = XmlDocument.parse(utf8.decode(archiveFile.content as List)); XmlDocument documentXmlRels = addImageinArchive(archive, replaceMap); print(documentXml.toXmlString(pretty: true, indent: ' ')); // TODO: Rework this function, doesn't work with ambiguous ID to search, or multiple Id in one paragraphe, separator like '_' or '-' or space // TODO: Essayer de parcourir par rapport au paragraphe () et non les runs () for (XmlElement node in documentXml.findAllElements('w:r')) { String ndText = node.innerText.toLowerCase(); //Iterable replacesId = replaceMap.keys.where((rm) => rm.toLowerCase() == ndText); for (String key in replaceMap.keys) { Iterable replacesId = RegExp(key.toLowerCase()).allMatches(ndText).map((m) => m.group(0)!); XmlNode? parent; for (int i = 0; i < replacesId.length; i++) { ReplaceContent replaceCnt = replaceMap[key]!; if (replaceCnt.img != null) parent = putImage(node, replaceCnt, parent); else { /// Replace Text by other text int start = node.innerXml.indexOf(''); int end = node.innerXml.indexOf(''); node.innerXml = node.innerXml.replaceRange(start + 5, end, replaceCnt.value!); } } } } return [documentXml, documentXmlRels]; } /// Add all image in [replaceMap] to Archive in 'word/_rels/document.xml.rels' XmlDocument addImageinArchive(Archive archive, Map replaceMap) { ArchiveFile archiveFile = archive.firstWhere((f) => f.name == 'word/_rels/document.xml.rels'); XmlDocument documentXmlRels = XmlDocument.parse(utf8.decode(archiveFile.content as List)); String documentXmlStr = documentXmlRels.toXmlString(); RegExp exp = RegExp(r'Id="rId([0-9]+)"'); Iterable matches = exp.allMatches(documentXmlStr); int maxId = matches.isEmpty ? 1 : int.parse(matches.last.group(1)!); for (ReplaceContent replaceCnt in replaceMap.values.where((rm) => rm.img != null)) { replaceCnt.img!.id = ++maxId; String text = '\n'; int lastIndex = documentXmlStr.lastIndexOf('/>') + 3; documentXmlStr = documentXmlStr.replaceRange(lastIndex, lastIndex, text); } documentXmlRels = XmlDocument.parse(documentXmlStr); return documentXmlRels; } /// Replaces the text ([node]) with an image ([replaceContent]) XmlNode? putImage(XmlElement node, ReplaceContent replaceContent, XmlNode? newParent) { if (node.parent == null && newParent != null) node.attachParent(newParent); XmlNode? parent = node.parent; String nodeStr = node.toXmlString(); node.replace(XmlDocumentFragment.parse(""" $nodeStr """)); return parent; } /// Recreate a Archive to add modification and save it on [fileDocx] /// [documentsXml] is list -> ['word/document.xml', 'word/_rels/document.xml.rels'] /// [replaceMap] for upload file image in docx file void save(List documentsXml, Map replaceMap) { try { final originalBytes = fileDocx.readAsBytesSync(); final archive = ZipDecoder().decodeBytes(originalBytes); final Uint8List uListDocumentXml = utf8.encode(documentsXml[0].toXmlString()); final Uint8List uListDocumentXmlRels = utf8.encode(documentsXml[1].toXmlString()); final newArchive = Archive(); for (ArchiveFile file in archive.files) { if (file.name == 'word/document.xml') { newArchive.addFile(ArchiveFile( 'word/document.xml', uListDocumentXml.length, uListDocumentXml, )); } else if (file.name == 'word/_rels/document.xml.rels') { newArchive.addFile(ArchiveFile( 'word/_rels/document.xml.rels', uListDocumentXmlRels.length, uListDocumentXmlRels, )); } else { newArchive.addFile(file); } } for (ReplaceContent replaceCnt in replaceMap.values.where((rm) => rm.img != null)) { final imageBytes = replaceCnt.img!.file.readAsBytesSync(); newArchive.addFile( ArchiveFile( 'word/media/${replaceCnt.img!.file.path}', imageBytes.length, imageBytes, ) ); } final zipData = ZipEncoder().encode(newArchive); fileDocx.writeAsBytesSync(zipData); } catch (e) { throw StateError('DOCX BUILDER ERROR: $e'); } } }