175 lines
7.1 KiB
Dart
175 lines
7.1 KiB
Dart
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<String, ReplaceContent> replaceMap) {
|
|
if (!fileDocx.existsSync())
|
|
throw(PathNotFoundException(fileDocx.path, OSError('File not found', 404)));
|
|
|
|
List<XmlDocument> documentsXml = editById(replaceMap);
|
|
save(documentsXml, replaceMap);
|
|
}
|
|
|
|
/// This function search ID in [replaceMap] and replace by his value or by a image
|
|
List<XmlDocument> editById(Map<String, ReplaceContent> 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<int>));
|
|
|
|
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 (<w:p>) et non les runs (<w:r>)
|
|
for (XmlElement node in documentXml.findAllElements('w:r')) {
|
|
String ndText = node.innerText.toLowerCase();
|
|
//Iterable<String> replacesId = replaceMap.keys.where((rm) => rm.toLowerCase() == ndText);
|
|
for (String key in replaceMap.keys) {
|
|
Iterable<String> 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('<w:t>');
|
|
int end = node.innerXml.indexOf('</w:t>');
|
|
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<String, ReplaceContent> replaceMap) {
|
|
|
|
ArchiveFile archiveFile = archive.firstWhere((f) => f.name == 'word/_rels/document.xml.rels');
|
|
XmlDocument documentXmlRels = XmlDocument.parse(utf8.decode(archiveFile.content as List<int>));
|
|
String documentXmlStr = documentXmlRels.toXmlString();
|
|
RegExp exp = RegExp(r'Id="rId([0-9]+)"');
|
|
Iterable<RegExpMatch> 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 = '<Relationship Id="rId$maxId" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/${replaceCnt.img!.file.path}"/>\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
|
|
<w:r xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
|
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
|
xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
|
|
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
|
|
xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
|
|
<w:drawing>
|
|
<wp:inline distT="0" distB="0" distL="0" distR="0">
|
|
<wp:extent cx="${replaceContent.img!.sizeX!}" cy="${replaceContent.img!.sizeY!}"/>
|
|
<wp:docPr id="1" name="Image1"/>
|
|
<wp:cNvGraphicFramePr/>
|
|
<a:graphic>
|
|
<a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
|
|
<pic:pic>
|
|
<pic:blipFill>
|
|
<a:blip r:embed="rId${replaceContent.img!.id}"/>
|
|
<a:stretch>
|
|
<a:fillRect/>
|
|
</a:stretch>
|
|
</pic:blipFill>
|
|
<pic:spPr>
|
|
<a:xfrm>
|
|
<a:off x="0" y="0"/>
|
|
<a:ext cx="${replaceContent.img!.sizeX!}" cy="${replaceContent.img!.sizeY!}"/>
|
|
</a:xfrm>
|
|
<a:prstGeom prst="rect">
|
|
<a:avLst/>
|
|
</a:prstGeom>
|
|
</pic:spPr>
|
|
</pic:pic>
|
|
</a:graphicData>
|
|
</a:graphic>
|
|
</wp:inline>
|
|
</w:drawing>
|
|
</w:r>
|
|
"""));
|
|
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<XmlDocument> documentsXml, Map<String, ReplaceContent> 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');
|
|
}
|
|
}
|
|
} |