upgrade compability and code to have only export different between web and desktop

This commit is contained in:
Xamora 2025-07-10 09:53:33 +02:00
parent d09241852c
commit f487132af9
6 changed files with 150 additions and 284 deletions

View file

@ -1,4 +1,5 @@
export 'src/replace.dart';
export 'src/docx.dart'
if (dart.library.js_interop) 'src/docx_web.dart'
if (dart.library.io) 'src/docx.dart';
export 'src/docx.dart';
export 'src/export.dart'
if (dart.library.js_interop) 'src/export_web.dart';

View file

@ -12,6 +12,7 @@ class DocxEditor {
Uint8List? binaryInDocx;
String? pathOutDocx;
String? name;
Map<String, ReplaceContent> replaceMap;
/// [fileInDocx] The Docx file
/// [binaryInDocx] The Docx binary (for web generaly)
@ -22,7 +23,7 @@ class DocxEditor {
this.binaryInDocx,
this.name,
this.pathOutDocx,
required Map<String, ReplaceContent> replaceMap
required this.replaceMap
}) {
if (fileInDocx != null) {
@ -34,7 +35,7 @@ class DocxEditor {
throw(ArgumentError('file or binary is mandatory to be define'));
List<XmlDocument> documentsXml = editById(replaceMap);
save(documentsXml, replaceMap);
save(documentsXml, this);
}
/// This function search ID in [replaceMap] and replace by his value or by a image
@ -45,8 +46,6 @@ class DocxEditor {
XmlDocument documentXmlRels = addImageinArchive(archive, replaceMap);
//print(documentXml.toXmlString(pretty: true, indent: ' '));
// sort descending orderz
final keys = replaceMap.keys.toList()..sort((a, b) => b.length.compareTo(a.length));
@ -167,59 +166,4 @@ class DocxEditor {
return documentXmlRels;
}
/// Recreate a Archive to add modification and save it on [fileInDocx]
/// [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 archive = ZipDecoder().decodeBytes(binaryInDocx!);
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!.bytes;
newArchive.addFile(
ArchiveFile(
'word/media/${replaceCnt.img!.name}',
imageBytes.length,
imageBytes,
)
);
}
final zipData = ZipEncoder().encode(newArchive);
if (fileInDocx != null) {
File newFile = pathOutDocx == null ? fileInDocx! : File(pathOutDocx!);
newFile.writeAsBytesSync(zipData);
}
else {
File newFile = pathOutDocx == null ? File('.') : File(pathOutDocx!);
newFile.writeAsBytesSync(zipData);
}
} catch (e) {
throw StateError('DOCX BUILDER ERROR: $e');
}
}
}

View file

@ -1,222 +0,0 @@
import 'dart:convert';
// Web
import 'package:web/web.dart' as web;
import 'dart:js_interop';
import 'dart:typed_data';
import 'package:archive/archive.dart';
import 'package:xml/xml.dart';
import 'package:docx/docx.dart';
class DocxEditor {
Uint8List binaryInDocx;
String? pathOutDocx;
String? name;
/// [binaryInDocx] The Docx binary (required)
/// [pathOutDocx] The path where new file is save (optional)
/// [replaceMap] All text and image to change (required)
DocxEditor({
required this.binaryInDocx,
this.name,
this.pathOutDocx,
required Map<String, ReplaceContent> replaceMap
}) {
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) {
Archive archive = ZipDecoder().decodeBytes(binaryInDocx);
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: ' '));
// sort descending orderz
final keys = replaceMap.keys.toList()..sort((a, b) => b.length.compareTo(a.length));
final pattern = RegExp(
keys.map((key) => '(?<=^|\\s)${RegExp.escape(key)}(?=\\s|\$)').join('|'),
);
// replace image
for (XmlElement node in documentXml.findAllElements('w:t')) {
String ndText = node.innerText.toLowerCase();
if (ndText.isEmpty)
continue;
XmlElement? parent;
for (String key in replaceMap.keys) {
ReplaceContent replaceCnt = replaceMap[key]!;
if (replaceCnt.img == null)
continue;
// ignore: unused_local_variable
for (RegExpMatch regMatch in RegExp('(?<=^|\\s)${key.toLowerCase()}(?=\\s|\$)').allMatches(ndText))
parent = putImage(node.parentElement, replaceCnt, parent);
}
}
// Only replace text!
for (XmlElement paragraph in documentXml.findAllElements('w:p')) {
List<XmlElement> texts = paragraph.findAllElements('w:t').toList();
if (texts.isEmpty)
continue;
String original = texts.map((t) {
return t.innerText;
}).join();
String replaced = original.replaceAllMapped(pattern, (match) {
ReplaceContent rc = replaceMap[match[0]]!;
return rc.img == null ? rc.value! : '';
});
if (original == replaced)
continue;
final firstRun = paragraph.findElements('w:r').first;
final newRun = firstRun.copy();
newRun.findAllElements('w:t').first.innerText = replaced; // Update with new text
// replace the first w:r with the new
firstRun.replace(newRun);
}
return [documentXml, documentXmlRels];
}
// TODO: Find a solution to put multiple image in only one <w:r>
/// Replaces the text ([node]) with an image ([replaceContent])
XmlElement? putImage(XmlElement? node, ReplaceContent replaceContent, XmlElement? newParent) {
if (node!.parentElement == null && newParent != null)
node.attachParent(newParent);
XmlElement? parent = node.parentElement;
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;
}
/// 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!.name}"/>\n';
int lastIndex = documentXmlStr.lastIndexOf('/>') + 3;
documentXmlStr = documentXmlStr.replaceRange(lastIndex, lastIndex, text);
}
documentXmlRels = XmlDocument.parse(documentXmlStr);
return documentXmlRels;
}
/// Recreate a Archive to add modification and save it on [fileInDocx]
/// [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 archive = ZipDecoder().decodeBytes(binaryInDocx);
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!.bytes;
newArchive.addFile(
ArchiveFile(
'word/media/${replaceCnt.img!.name}',
imageBytes.length,
imageBytes,
)
);
}
String url = web.URL.createObjectURL(
web.Blob(
<JSUint8Array>[binaryInDocx.toJS].toJS,
web.BlobPropertyBag(type: 'application/octet-stream'),
),
);
web.Document htmlDocument = web.document;
web.HTMLAnchorElement anchor = htmlDocument.createElement('a') as web.HTMLAnchorElement;
anchor.href = url;
anchor.style.display = '${name ?? 'template'}.docx';
anchor.download = '${name ?? 'template'}.docx';
web.document.body!.add(anchor);
anchor.click();
anchor.remove();
} catch (e) {
throw StateError('DOCX BUILDER ERROR: $e');
}
}
}

65
lib/src/export.dart Normal file
View file

@ -0,0 +1,65 @@
import 'package:xml/xml.dart';
import 'package:archive/archive.dart';
import 'package:docx/docx.dart';
import 'dart:typed_data';
import 'dart:io';
import 'dart:convert';
/// Recreate a Archive to add modification and save it on [fileInDocx]
/// [documentsXml] is list -> ['word/document.xml', 'word/_rels/document.xml.rels']
/// [docxEditor] Instance of docxEditor for Bytes, path,...
void save(List<XmlDocument> documentsXml, DocxEditor docxEditor) {
try {
final archive = ZipDecoder().decodeBytes(docxEditor.binaryInDocx!);
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 docxEditor.replaceMap.values.where((rm) => rm.img != null)) {
final imageBytes = replaceCnt.img!.bytes;
newArchive.addFile(
ArchiveFile(
'word/media/${replaceCnt.img!.name}',
imageBytes.length,
imageBytes,
)
);
}
List<int> zipData = ZipEncoder().encode(newArchive);
File? fileInDocx = docxEditor.fileInDocx;
String? pathOutDocx = docxEditor.pathOutDocx;
if (fileInDocx != null) {
File newFile = pathOutDocx == null ? fileInDocx : File(pathOutDocx);
newFile.writeAsBytesSync(zipData);
}
else {
File newFile = pathOutDocx == null ? File('.') : File(pathOutDocx);
newFile.writeAsBytesSync(zipData);
}
} catch (e) {
throw StateError('DOCX BUILDER ERROR: $e');
}
}

73
lib/src/export_web.dart Normal file
View file

@ -0,0 +1,73 @@
import 'dart:convert';
// Web
import 'package:web/web.dart' as web;
import 'dart:js_interop';
import 'dart:typed_data';
import 'package:archive/archive.dart';
import 'package:xml/xml.dart';
import 'package:docx/docx.dart';
/// Recreate a Archive to add modification and save it on [fileInDocx]
/// [documentsXml] is list -> ['word/document.xml', 'word/_rels/document.xml.rels']
/// [docxEditor] Instance of docxEditor for Bytes, path,...
void save(List<XmlDocument> documentsXml, DocxEditor docxEditor) {
try {
final archive = ZipDecoder().decodeBytes(docxEditor.binaryInDocx!);
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 docxEditor.replaceMap.values.where((rm) => rm.img != null)) {
final imageBytes = replaceCnt.img!.bytes;
newArchive.addFile(
ArchiveFile(
'word/media/${replaceCnt.img!.name}',
imageBytes.length,
imageBytes,
)
);
}
Uint8List zipData = ZipEncoder().encodeBytes(newArchive);
String url = web.URL.createObjectURL(
web.Blob(
<JSUint8Array>[zipData.toJS].toJS,
web.BlobPropertyBag(type: 'application/octet-stream'),
),
);
web.Document htmlDocument = web.document;
web.HTMLAnchorElement anchor = htmlDocument.createElement('a') as web.HTMLAnchorElement;
anchor.href = url;
anchor.style.display = '${docxEditor.name ?? 'template'}.docx';
anchor.download = '${docxEditor.name ?? 'template'}.docx';
web.document.body!.add(anchor);
anchor.click();
anchor.remove();
} catch (e) {
throw StateError('DOCX BUILDER ERROR: $e');
}
}

View file

@ -11,6 +11,11 @@ class ReplaceContent {
if (img == null && value == null)
throw StateError('You need define value or image');
}
@override
String toString() {
return value ?? img!.name;
}
}
/// 16cm * 360 000 = 5760000 EMU