2121use DOMDocument ;
2222use DOMNode ;
2323use DOMXPath ;
24- use Exception ;
2524use PhpOffice \PhpWord \Element \AbstractContainer ;
2625use PhpOffice \PhpWord \Element \Row ;
2726use PhpOffice \PhpWord \Element \Table ;
27+ use PhpOffice \PhpWord \Exception \InvalidImageException ;
2828use PhpOffice \PhpWord \Settings ;
2929use PhpOffice \PhpWord \SimpleType \Jc ;
3030use PhpOffice \PhpWord \SimpleType \NumberFormat ;
@@ -48,6 +48,15 @@ class Html
4848 */
4949 protected static $ css ;
5050
51+ protected static $ userDefinedNodeMappings = [];
52+
53+ protected static $ contentTypeFileExtensionMap = [
54+ 'image/svg+xml ' => 'svg ' ,
55+ 'image/jpeg ' => 'jpg ' ,
56+ 'image/png ' => 'png ' ,
57+ 'image/gif ' => 'gif ' ,
58+ ];
59+
5160 /**
5261 * Add HTML parts.
5362 *
@@ -201,39 +210,22 @@ protected static function parseNode($node, $element, $styles = [], $data = []):
201210 }
202211 }
203212
204- // Node mapping table
205- $ nodes = [
206- // $method $node $element $styles $data $argument1 $argument2
207- 'p ' => ['Paragraph ' , $ node , $ element , $ styles , null , null , null ],
208- 'h1 ' => ['Heading ' , null , $ element , $ styles , null , 'Heading1 ' , null ],
209- 'h2 ' => ['Heading ' , null , $ element , $ styles , null , 'Heading2 ' , null ],
210- 'h3 ' => ['Heading ' , null , $ element , $ styles , null , 'Heading3 ' , null ],
211- 'h4 ' => ['Heading ' , null , $ element , $ styles , null , 'Heading4 ' , null ],
212- 'h5 ' => ['Heading ' , null , $ element , $ styles , null , 'Heading5 ' , null ],
213- 'h6 ' => ['Heading ' , null , $ element , $ styles , null , 'Heading6 ' , null ],
214- '#text ' => ['Text ' , $ node , $ element , $ styles , null , null , null ],
215- 'strong ' => ['Property ' , null , null , $ styles , null , 'bold ' , true ],
216- 'b ' => ['Property ' , null , null , $ styles , null , 'bold ' , true ],
217- 'em ' => ['Property ' , null , null , $ styles , null , 'italic ' , true ],
218- 'i ' => ['Property ' , null , null , $ styles , null , 'italic ' , true ],
219- 'u ' => ['Property ' , null , null , $ styles , null , 'underline ' , 'single ' ],
220- 'sup ' => ['Property ' , null , null , $ styles , null , 'superScript ' , true ],
221- 'sub ' => ['Property ' , null , null , $ styles , null , 'subScript ' , true ],
222- 'span ' => ['Span ' , $ node , null , $ styles , null , null , null ],
223- 'font ' => ['Span ' , $ node , null , $ styles , null , null , null ],
224- 'table ' => ['Table ' , $ node , $ element , $ styles , null , null , null ],
225- 'tr ' => ['Row ' , $ node , $ element , $ styles , null , null , null ],
226- 'td ' => ['Cell ' , $ node , $ element , $ styles , null , null , null ],
227- 'th ' => ['Cell ' , $ node , $ element , $ styles , null , null , null ],
228- 'ul ' => ['List ' , $ node , $ element , $ styles , $ data , null , null ],
229- 'ol ' => ['List ' , $ node , $ element , $ styles , $ data , null , null ],
230- 'li ' => ['ListItem ' , $ node , $ element , $ styles , $ data , null , null ],
231- 'img ' => ['Image ' , $ node , $ element , $ styles , null , null , null ],
232- 'br ' => ['LineBreak ' , null , $ element , $ styles , null , null , null ],
233- 'a ' => ['Link ' , $ node , $ element , $ styles , null , null , null ],
234- 'input ' => ['Input ' , $ node , $ element , $ styles , null , null , null ],
235- 'hr ' => ['HorizRule ' , $ node , $ element , $ styles , null , null , null ],
236- ];
213+ $ nodes = self ::getNodeMappingTable ($ node , $ element , $ styles , $ data );
214+ array_map (
215+ function ($ argumentList , $ markTag ) use ($ node , $ element , $ styles , $ data , &$ nodes ): void {
216+ $ nodes [$ markTag ] = [
217+ 0 => $ argumentList ['method ' ],
218+ 1 => $ argumentList ['withNode ' ] ? $ node : null ,
219+ 2 => $ argumentList ['withElement ' ] ? $ element : null ,
220+ 3 => $ argumentList ['withStyles ' ] ? $ styles : null ,
221+ 4 => $ argumentList ['withData ' ] ? $ data : null ,
222+ 5 => $ argumentList ['argument1 ' ],
223+ 6 => $ argumentList ['argument2 ' ],
224+ ];
225+ },
226+ self ::$ userDefinedNodeMappings ,
227+ array_keys (self ::$ userDefinedNodeMappings )
228+ );
237229
238230 $ newElement = null ;
239231 $ keys = ['node ' , 'element ' , 'styles ' , 'data ' , 'argument1 ' , 'argument2 ' ];
@@ -249,8 +241,9 @@ protected static function parseNode($node, $element, $styles = [], $data = []):
249241 $ arguments [$ keys [$ i ]] = &$ args [$ i ];
250242 }
251243 }
252- $ method = "parse {$ method }" ;
253- $ newElement = call_user_func_array (['PhpOffice\PhpWord\Shared\Html ' , $ method ], array_values ($ arguments ));
244+
245+ $ method = is_string ($ method ) ? ['PhpOffice\PhpWord\Shared\Html ' , "parse {$ method }" ] : $ method ;
246+ $ newElement = call_user_func_array ($ method , array_values ($ arguments ));
254247
255248 // Retrieve back variables from arguments
256249 foreach ($ keys as $ key ) {
@@ -957,6 +950,17 @@ protected static function parseImage($node, $element)
957950 $ tmpDir = Settings::getTempDir () . '/ ' ;
958951 $ match = [];
959952 preg_match ('/.+\.(\w+)$/ ' , $ src , $ match );
953+
954+ if (empty ($ match ) || !isset ($ match [1 ])) {
955+ $ contentType = get_headers ($ src , 1 )['Content-Type ' ];
956+
957+ if (!array_key_exists ($ contentType , self ::$ contentTypeFileExtensionMap )) {
958+ return $ element ->addText ("Error: Could not load image: {$ src }" );
959+ }
960+
961+ $ match [1 ] = self ::$ contentTypeFileExtensionMap [$ contentType ];
962+ }
963+
960964 $ src = $ tmpDir . uniqid () . '. ' . $ match [1 ];
961965
962966 $ ifp = fopen ($ src , 'wb ' );
@@ -969,12 +973,13 @@ protected static function parseImage($node, $element)
969973 }
970974
971975 if (is_file ($ src )) {
972- $ newElement = $ element ->addImage ($ src , $ style );
973- } else {
974- throw new Exception ("Could not load image $ originSrc " );
976+ try {
977+ return $ element ->addImage ($ src , $ style );
978+ } catch (InvalidImageException $ ex ) {
979+ }
975980 }
976981
977- return $ newElement ;
982+ return $ element -> addText ( " Error: Could not load image: { $ src }" ) ;
978983 }
979984
980985 /**
@@ -1155,4 +1160,67 @@ protected static function parseHorizRule($node, $element): void
11551160 // - line - that is a shape, has different behaviour
11561161 // - repeated text, e.g. underline "_", because of unpredictable line wrapping
11571162 }
1163+
1164+ /**
1165+ * Add a custom mapping for HTML tag.
1166+ *
1167+ * @param string $htmlTag
1168+ * @param bool $withNode
1169+ * @param bool $withElement
1170+ * @param bool $withStyles
1171+ * @param bool $withData
1172+ * @param string $argument1
1173+ * @param string $argument2
1174+ * @param callable|string $method
1175+ */
1176+ public static function addUserDefinedNodeMapping ($ htmlTag , $ withNode , $ withElement , $ withStyles , $ withData , $ argument1 , $ argument2 , $ method ): void
1177+ {
1178+ $ args = compact (
1179+ 'withNode ' ,
1180+ 'withElement ' ,
1181+ 'withStyles ' ,
1182+ 'withData ' ,
1183+ 'argument1 ' ,
1184+ 'argument2 ' ,
1185+ 'method '
1186+ );
1187+ self ::$ userDefinedNodeMappings [$ htmlTag ] = $ args ;
1188+ }
1189+
1190+ protected static function getNodeMappingTable ($ node , $ element , $ styles , $ data )
1191+ {
1192+ // Node mapping table
1193+ return [
1194+ // $method $node $element $styles $data $argument1 $argument2
1195+ 'p ' => ['Paragraph ' , $ node , $ element , $ styles , null , null , null ],
1196+ 'h1 ' => ['Heading ' , null , $ element , $ styles , null , 'Heading1 ' , null ],
1197+ 'h2 ' => ['Heading ' , null , $ element , $ styles , null , 'Heading2 ' , null ],
1198+ 'h3 ' => ['Heading ' , null , $ element , $ styles , null , 'Heading3 ' , null ],
1199+ 'h4 ' => ['Heading ' , null , $ element , $ styles , null , 'Heading4 ' , null ],
1200+ 'h5 ' => ['Heading ' , null , $ element , $ styles , null , 'Heading5 ' , null ],
1201+ 'h6 ' => ['Heading ' , null , $ element , $ styles , null , 'Heading6 ' , null ],
1202+ '#text ' => ['Text ' , $ node , $ element , $ styles , null , null , null ],
1203+ 'strong ' => ['Property ' , null , null , $ styles , null , 'bold ' , true ],
1204+ 'b ' => ['Property ' , null , null , $ styles , null , 'bold ' , true ],
1205+ 'em ' => ['Property ' , null , null , $ styles , null , 'italic ' , true ],
1206+ 'i ' => ['Property ' , null , null , $ styles , null , 'italic ' , true ],
1207+ 'u ' => ['Property ' , null , null , $ styles , null , 'underline ' , 'single ' ],
1208+ 'sup ' => ['Property ' , null , null , $ styles , null , 'superScript ' , true ],
1209+ 'sub ' => ['Property ' , null , null , $ styles , null , 'subScript ' , true ],
1210+ 'span ' => ['Span ' , $ node , null , $ styles , null , null , null ],
1211+ 'font ' => ['Span ' , $ node , null , $ styles , null , null , null ],
1212+ 'table ' => ['Table ' , $ node , $ element , $ styles , null , null , null ],
1213+ 'tr ' => ['Row ' , $ node , $ element , $ styles , null , null , null ],
1214+ 'td ' => ['Cell ' , $ node , $ element , $ styles , null , null , null ],
1215+ 'th ' => ['Cell ' , $ node , $ element , $ styles , null , null , null ],
1216+ 'ul ' => ['List ' , $ node , $ element , $ styles , $ data , null , null ],
1217+ 'ol ' => ['List ' , $ node , $ element , $ styles , $ data , null , null ],
1218+ 'li ' => ['ListItem ' , $ node , $ element , $ styles , $ data , null , null ],
1219+ 'img ' => ['Image ' , $ node , $ element , $ styles , null , null , null ],
1220+ 'br ' => ['LineBreak ' , null , $ element , $ styles , null , null , null ],
1221+ 'a ' => ['Link ' , $ node , $ element , $ styles , null , null , null ],
1222+ 'input ' => ['Input ' , $ node , $ element , $ styles , null , null , null ],
1223+ 'hr ' => ['HorizRule ' , $ node , $ element , $ styles , null , null , null ],
1224+ ];
1225+ }
11581226}
0 commit comments