source: spip-zone/_plugins_/convertisseur/extract/postoffice/class.DOCX-HTML.php @ 56851

Last change on this file since 56851 was 56851, checked in by fil@…, 8 years ago

ajout de docx2spip dans le convertisseur de formats

File size: 25.6 KB
Line 
1<?php
2/**CLASS DOCXtoHTML Premium will convert a .docx file to (x)html.
3 *
4 * This class uses the dUnzip2 class from phpclasses.org and requires extension ZLib
5 * This class can only handle 1 single file per instance.
6 * This class might overrun the execution time limit.
7 *
8 * Variables that can be set:
9 * @see *$docxPath
10 * @see $paragraphs
11 * @see *$image_max_width
12 * @see $mediaDir
13 * @see $imagePathPrefix
14 *
15 * Variables Returned by Class:
16 * @see $status
17 * @see $output
18 * @see $error
19 * @see $time
20 */
21class DOCXtoHTML {
22    /**
23     * @var String This is the path to the file that should be read
24     * @since 1.0
25     */
26    var $docxPath = "";
27    /**
28     * @var String This is where the ziped contents will be extracted to to process
29     * @since 1.0
30     */
31    var $tempDir = "";
32    /**
33     *
34     * @var String This is the html data that is returned from this class
35     * @since 1.0
36     */
37    var $output = array();
38    /**
39     *
40     * @var Bool Should colored text be allowed or not
41     * @since 1.0
42     */
43    var $allowColor = false;
44    /**
45     * @var Int This is the maximum width of an image after the process
46     * @since 1.0
47     * @update 1.2
48     */
49    var $image_max_width = 0;
50    /**
51     * @var String The path to where the content is extracted
52     * @since 1.0
53     */
54    var $content_folder = "";
55    /**
56     * @var String The current Status of the class
57     * @since 1.0
58     */
59    var $status = "";
60    /**
61     * @var String The path to where the media files of the document should be extracted
62     * @since 1.0
63     */
64    var $mediaDir = "";
65    /**
66     * @var String The value of this variable will be prefixed to the path of the image. This class will create a folder 2 levels up, inside an 'upload' folder and this value should go to there.
67     * @since 1.0
68     */
69    var $imagePathPrefix = "";
70    /**
71     * @var Array This contains the relationships of different elements inside the word document and is used to link to the correct image.
72     * @since 1.1
73     */
74    var $rels = array();
75    /**
76     * @val String The error number generated and the meaning of the error
77     * @since 1.0
78     */
79    var $error = NULL;
80    /**
81     * @val String This will contain the closing tag of a paragraph level opened tag that can't be specified explicitly
82     * @since 1.1
83     */
84    var $tagclosep = "";
85    /**
86     * @val String This will contain the closing tag of a text opened tag that can't be specified explicitly
87     * @since 1.1
88     */
89     var $tagcloset = "";
90    /**
91     * @val Bool SWhould a thumbnail be created as well as to keep the original image in the folder
92     * @since 1.3
93     */
94     var $keepOriginalImage = false;
95    /**
96     * @val Bool Split the document into multiple posts/pages
97     * @since 1.3
98     */
99     var $split = false;
100
101    /**
102     * This function will set the status to Ready when the class is called. The Constructor Method.
103     * @return Bool True when ready
104     * @since 1.0
105     */
106    function __construct(){
107        $this->status = "Ready";
108        return true;
109    }
110    /**
111     * This function call the Constructor Method
112     * @return Bool True when ready
113     * @since 1.0
114     */
115    function DOCXtoHTML(){
116        return __construct();
117    }
118    /**
119     * This function will initialize the process as well as handle the process automatically.
120     * This requires that the vars be set to start
121     * @return Bool True when successfully completed
122     * @since 1.0
123     * @modified 1.2.3
124     */
125    function Init(){
126        global $PostOffice;
127        if($this->extractRelXML()==false){
128            $this->DeleteTemps();
129            $PostOffice->__destruct();
130            $this->error = "12. The file data could not be found or read.";
131            return false;
132        }
133        if($this->extractMedia()==false){
134            $this->DeleteTemps();
135            $PostOffice->__destruct();
136            $this->error = "13. The Media could not be found.";
137            return false;
138        }
139        if($this->extractXML()==false){
140            $this->DeleteTemps();
141            $PostOffice->__destruct();
142            $this->error = "14. The file data could not be found or read.";
143            return false;
144        }
145        if($this->DeleteTemps()==false){
146            $PostOffice->__destruct();
147            $this->error = "15. The temporary files created during the process could not be deleted.
148                The contents, however, might still have been extracted.";
149            return false;
150        }
151        return true;
152    }
153    /**
154     * This function handles the extraction of the XML building the Rels array
155     * @return Bool True on success
156     * @since 1.1
157     * @modified 1.2.3
158     */
159    function extractRelXML(){
160        $xmlFile = $this->tempDir."/word/_rels/document.xml.rels";
161        if(is_file($xmlFile)){
162            $xml = file_get_contents($xmlFile);
163        } else {
164            echo "the file ".$xmlFile." could not be found<br /><br />";
165            return false;
166        }
167        if($xml == false){
168            return false;
169        }
170        $xml = mb_convert_encoding($xml, 'UTF-8', mb_detect_encoding($xml));
171        $parser = xml_parser_create('UTF-8');
172        $data = array();
173        xml_parse_into_struct($parser, $xml, $data);
174        foreach($data as $value){
175            if($value['tag']=="RELATIONSHIP"){
176                //it is an relationship tag, get the ID attr as well as the TARGET and (if set, the targetmode)set into var.
177                if(isset($value['attributes']['TARGETMODE'])){
178                    $this->rels[$value['attributes']['ID']] = array(0 => $value['attributes']['TARGET'], 3=> $value['attributes']['TARGETMODE']);
179                } else {
180                    $this->rels[$value['attributes']['ID']] = array(0 => $value['attributes']['TARGET']);
181                }
182            }
183        }
184        return true;
185    }
186    /**
187     * This function handles the extraction of the Media
188     * @return Bool True on success
189     * @since 1.1
190     * @modified 1.2.3
191     */
192    function extractMedia(){
193        $wordFolder = $this->tempDir."/word/";
194        if(!is_dir($wordFolder."media")){
195            return true;
196            //there are no images to extract
197        }
198        $this->getMediaFolder();
199        $i = false;
200        foreach($this->rels as $key => $value){
201            if(strtolower(pathinfo($value[0],PATHINFO_EXTENSION))=="png" || strtolower(pathinfo($value[0],PATHINFO_EXTENSION))=="gif" || strtolower(pathinfo($value[0],PATHINFO_EXTENSION))=="jpg"
202                || strtolower(pathinfo($value[0],PATHINFO_EXTENSION))=="jpeg"){
203                //this really is an image that we are working with
204                $fileType = strtolower(pathinfo($value[0],PATHINFO_EXTENSION));
205                //set the file type so that the correct image creation function can be called
206                if(is_file($wordFolder.$value[0])){
207                    if($this->keepOriginalImage == true){
208                        $image = $this->processImage($wordFolder.$value[0], $this->image_max_width);
209                        $imageorr = $this->processImage($wordFolder.$value[0]);
210                        /*if( imageSx( $imageorr ) == $this->image_max_width ){
211                            //they are the extact same width, so it is not needed to create it again
212                            imageDestroy ( $imageorr );
213                            $imageorr = false;
214                        }*/
215                    } else {
216                        $image = $this->processImage($wordFolder.$value[0], $this->image_max_width);
217                        $imageorr = false;
218                    }
219                    if($image){
220                        $i = true;//this have been resourceful, do not return false
221                        //the image was successfully created, now write to file
222                        $filename = pathinfo($value[0],PATHINFO_BASENAME);
223                        if($fileType=="png"){
224                            if(imagePng($image,$this->mediaDir."/".$filename,0,PNG_NO_FILTER)){
225                                imagedestroy($image);
226                                $this->rels[$key][1] = $this->mediaDir."/".$filename;
227                            }
228                        } elseif($fileType=="gif"){
229                            if(imageGif($image,$this->mediaDir."/".$filename,0)){
230                                imagedestroy($image);
231                                $this->rels[$key][1] = $this->mediaDir."/".$filename;
232                            }
233                        } else {
234                            if(imageJpeg($image,$this->mediaDir."/".$filename,100)){
235                                imagedestroy($image);
236                                $this->rels[$key][1] = $this->mediaDir."/".$filename;
237                            }
238                        }
239                    }
240                    if($imageorr){
241                        $i = true;//this have been resourceful, do not return false
242                        //the image was successfully created, now write to file
243                        $pathinfo = pathinfo($value[0]);
244                        $filename = $pathinfo['filename']."_big.".$pathinfo['extension'];
245                        if($fileType=="png"){
246                            if(imagePng($imageorr,$this->mediaDir."/".$filename,0,PNG_NO_FILTER)){
247                                imagedestroy($imageorr);
248                                $this->rels[$key][2] = $this->mediaDir."/".$filename;
249                            }
250                        } elseif($fileType=="gif"){
251                            if(imageGif($imageorr,$this->mediaDir."/".$filename,0)){
252                                imagedestroy($imageorr);
253                                $this->rels[$key][2] = $this->mediaDir."/".$filename;
254                            }
255                        } else {
256                            if(imageJpeg($imageorr,$this->mediaDir."/".$filename,100)){
257                                imagedestroy($imageorr);
258                                $this->rels[$key][2] = $this->mediaDir."/".$filename;
259                            }
260                        }
261                    }
262                }
263            }
264        }
265        return $i;
266    }
267    /**
268     * This function creates the folder that will contain the media after the move
269     * @return Bool True on success
270     * @since 1.0
271     */
272    function getMediaFolder(){
273        if(empty($this->content_folder)){
274            $mediaFolder = pathinfo($this->docxPath,PATHINFO_BASENAME);
275            $ext = pathinfo($this->docxPath,PATHINFO_EXTENSION);
276            $MediaFolder = strtolower(str_replace(".".$ext,"",str_replace(" ","-",$mediaFolder)));
277            $this->mediaDir = "../../uploads/media/".$MediaFolder;
278        } else {
279            $this->mediaDir = "../../uploads/media/".$this->content_folder;
280        }
281        if($this->mkdir_p($this->mediaDir)){
282            return true;
283        } else {
284            return false;
285        }
286    }
287    /**
288     * This function handles the image proccessing
289     * @param String $url Path to the file to proccess
290     * @param Int $thumb The maximum width of an proccessed image
291     * @return String The binary of the image that was created
292     * @since 1.0
293     */
294    function processImage($url, $thumb=0) {
295        $tmp0 = imageCreateFromString(fread(fopen($url, "rb"), filesize( $url )));
296        if ($tmp0) {
297            if($thumb == 0) {
298                $dim = Array ('w' => imageSx($tmp0), 'h' => imageSy($tmp0));
299            } else {
300                if(imagesx($tmp0)<=$thumb){
301                    if (imageSy($tmp0) > imageSx($tmp0)){
302                        $dim = Array ('w' => imageSx($tmp0), 'h' => imageSy($tmp0));
303                    } else {
304                        $dim = Array ('w' => imageSx($tmp0), 'h' => imageSy($tmp0));
305                    }
306                } else {
307                    $dim = Array ('w' => $thumb, 'h' => round(imageSy($tmp0)*$thumb/imageSx($tmp0)));
308                }
309            }
310            $tmp1 = imageCreateTrueColor ( $dim [ 'w' ], $dim [ 'h' ] );
311            if ( imagecopyresized  ( $tmp1 , $tmp0, 0, 0, 0, 0, $dim [ 'w' ], $dim [ 'h' ], imageSx ( $tmp0 ), imageSy ( $tmp0 ) ) ) {
312                imageDestroy ( $tmp0 );
313                return $tmp1;
314            } else {
315                imageDestroy ( $tmp0 );
316                imageDestroy ( $tmp1 );
317                return $this -> null;
318            }
319        } else {
320            return $this -> null;
321        }
322    }
323    /**
324     * This function handles the extraction of the XML file data used to construct the HTML
325     * @return Bool True on success
326     * @since 1.0
327     * @modified 1.2.3
328     */
329    function extractXML(){
330        $xmlFile = $this->tempDir."/word/document.xml";
331        $xml = file_get_contents($xmlFile);
332        if($xml == false){
333            return false;
334        }
335        $xml = mb_convert_encoding($xml, 'UTF-8', mb_detect_encoding($xml));
336        $parser = xml_parser_create('UTF-8');
337        $data = array();
338        xml_parse_into_struct($parser, $xml, $data);
339        //echo "<pre>";
340        //print_r($data);
341        //echo "</pre>";
342        $html4output = "";
343        $i = 0;//var of post part id
344        $h1 = 0;//var of heading 1s done
345        foreach($data as $key => $value){
346            if(is_array($value)){
347                switch ($value['tag']) {
348                    case "W:P"://the paragrah begins
349                        if($value['type'] == "open"){
350                            $html4output .= "<p>";
351                        } elseif($value['type'] == "close"){
352                            $html4output .= $this->tagclosep."</p>";
353                            $this->tagclosep = "";
354                        }
355                        break;
356                    case "W:TBL"://the table is initiated
357                        if($value['type'] == "open"){
358                            $html4output .= "<table border='1'>";
359                        } elseif($value['type'] == "close"){
360                            $html4output .= "</table>";
361                        }
362                        break;
363                    case "W:TR"://the table row is initiated
364                        if($value['type'] == "open"){
365                            $html4output .= "<tr>";
366                        } elseif($value['type'] == "close"){
367                            $html4output .= "</tr>";
368                        }
369                        break;
370                    case "W:TC"://the table cell is initiated
371                        if($value['type'] == "open"){
372                            $html4output .= "<td>";
373                        } elseif($value['type'] == "close"){
374                            $html4output .= "</td>";
375                        }
376                        break;
377                    case "W:HYPERLINK"://the hyperlink is initiated
378                        if($value['type'] == "open"){
379                            if(array_key_exists('R:ID', $value['attributes'])){
380                                $rid = $value['attributes']['R:ID'];
381                                $path = $this->rels[$rid][0];
382                                $target = $this->rels[$rid][3];
383                            } else {
384                                $target = false;
385                            }
386                            //now determine which type of link it is
387                            if(strtolower($target) == "external"){
388                                //this is an external link to a website
389                                $html4output .= "<a href='".$path."'>";
390                            } elseif(isset($value['attributes']['W:ANCHOR'])){
391                                $html4output .= "<a href='#".$value['attributes']['W:ANCHOR']."'>";
392                            }
393                        } elseif($value['type'] == "close"){
394                            $html4output .= "</a>";
395                        }
396                        break;
397                    case "A:BLIP"://the image data
398                        if($value['type'] == "open" || $value['type'] == "complete"){
399                            $rid = $value['attributes']['R:EMBED'];
400                            $imagepath = $this->rels[$rid][1];
401                            if(array_key_exists(2,$this->rels[$rid])){
402                                $imagebigpath = $this->rels[$rid][2];
403                            } else {
404                                $imagebigpath = false;
405                            }
406                            if($this->keepOriginalImage == true && $imagebigpath){
407                                $html4output .= "<a href='".$this->imagePathPrefix.$imagebigpath."' target='_blank' >
408                                    <img style='display:inline;' src='".$this->imagePathPrefix.$imagepath."' alt='' />
409                                    </a>";
410                            } else {
411                                $html4output .= "<img style='display:inline;' src='".$this->imagePathPrefix.$imagepath."' alt='' />";
412                            }
413                        }
414                        break;
415                    case "W:T":
416                        if($value['type'] == "complete"){
417                            $html4output .= $value['value'].$this->tagcloset;//return the text (add spaces after)
418                            $this->tagcloset = "";
419                        }
420                        break;
421                    case "W:COLOR":
422                        if($value['type'] == "complete" && $this->allowColor){
423                            $html4output .= "<span style='color:#".$value['attributes']['W:VAL'].";'>";//add colored text
424                            $this->tagcloset = "</span>";
425                        }
426                        break;
427                    case "V:TEXTPATH":
428                        if($value['type'] == "complete"){
429                            if(array_key_exists('STRING',$value['attributes'])){
430                                $html4output .= $value['attributes']['STRING'];//add word art text (this is also important)
431                            }
432                        }
433                        break;
434                    case "W:PSTYLE"://word styles used for headings etc.
435                        if($value['type'] == "complete"){
436                            if($value['attributes']['W:VAL'] == "Heading1"){
437                                // -- || Determine if should split or not here || -- //
438                                if($this->split){
439                                    //the document may be split
440                                    if($h1 > 0){
441                                        //it should split if there already have been an heading 1
442                                        $this->output[$i] = $html4output;
443                                        $i++;
444                                        $html4output = "<p>";//it should start on a paragraph
445                                    }
446                                }//then just continue an add the H1 tag
447                                $html4output .= "<h1>";
448                                $h1++;
449                                $this->tagclosep = "</h1>";
450                            }elseif($value['attributes']['W:VAL'] == "Heading2"){
451                                $html4output .= "<h2>";
452                                $this->tagclosep = "</h2>";
453                            }elseif($value['attributes']['W:VAL'] == "Heading3"){
454                                $html4output .= "<h3>";
455                                $this->tagclosep = "</h3>";
456                            }
457                        }
458                        break;
459                    case "W:B"://word style for bold
460                        if($value['type'] == "complete"){
461                            if($this->tagcloset == "</strong>"){
462                                break;
463                            }
464                            $html4output .= "<strong>";//return the text (add spaces after)
465                            $this->tagcloset = "</strong>";
466                        }
467                        break;
468                    case "W:I"://word style for italics
469                        if($value['type'] == "complete"){
470                            if($this->tagcloset == "</em>"){
471                                break;
472                            }
473                            $html4output .= "<em>";//return the text (add spaces after)
474                            $this->tagcloset = "</em>";
475                        }
476                        break;
477                    case "W:U"://word style for underline
478                        if($value['type'] == "complete"){
479                            if($this->tagcloset == "</span>"){
480                                break;
481                            }
482                            $html4output .= "<span style='text-decoration:underline;'>";//return the text (add spaces after)
483                            $this->tagcloset = "</span>";
484                        }
485                        break;
486                    case "W:STRIKE"://word style for strike-throughs
487                        if($value['type'] == "complete"){
488                            if($this->tagcloset == "</span>"){
489                                break;
490                            }
491                            $html4output .= "<span style='text-decoration:line-through;'>";//return the text (add spaces after)
492                            $this->tagcloset = "</span>";
493                        }
494                        break;
495                    case "W:VERTALIGN": //word style for super- and subscripts
496                        if($value['type'] == "complete"){
497                            if($value['attributes']['W:VAL'] == "subscript"){
498                                $html4output .= "<sub>";
499                                $this->tagcloset = "</sub>";
500                            }elseif($value['attributes']['W:VAL'] == "superscript"){
501                                $html4output .= "<sup>";
502                                $this->tagcloset = "</sup>";
503                            }
504                        }
505                        break;
506                    case "W:BOOKMARKSTART": //word style for bookmarks/internal links
507                        if($value['type'] == "complete"){
508                            $html4output .= "<a id='".$value['attributes']['W:NAME']."'></a>";
509                        }
510                        break;
511                    default:
512                        break;
513                }
514            }
515        }
516        $this->output[] = $html4output;//this should output the last part to the output var
517        $this->status = "Contents Extracted...";
518        if(empty($html4output)){
519            return false;
520        }
521        return true;
522    }
523    /**
524     * Recursive directory creation based on full path.
525     * Will attempt to set permissions on folders.
526     * @param string $target Full path to attempt to create.
527     * @return bool Whether the path was created or not. True if path already exists.
528     * @since 1.0
529     */
530    function mkdir_p( $target ) {
531        // from php.net/mkdir user contributed notes
532        $target = str_replace( '//', '/', $target );
533        if ( file_exists( $target ) ){
534            return @is_dir( $target );
535        }
536        // Attempting to create the directory may clutter up our display.
537        if ( @mkdir( $target ) ) {
538            $stat = @stat( dirname( $target ) );
539            $dir_perms = $stat['mode'] & 0007777;  // Get the permission bits.
540            @chmod( $target, $dir_perms );
541            return true;
542        } elseif ( is_dir( dirname( $target ) ) ) {
543            return false;
544        }
545        // If the above failed, attempt to create the parent node, then try again.
546        if ( ( $target != '/' ) && ( $this->mkdir_p( dirname( $target ) ) ) ){
547            return $this->mkdir_p( $target );
548        }
549        return false;
550    }
551    /**
552     * This function concludes the class by removing all te temporary files and folders as well as unsetting all variables not required
553     * @return Bool True on success
554     * @since 1.0
555     */
556    function DeleteTemps(){
557        //this function will delete all the temp files except the word document
558        //(.docx) itself. If this was uploaded it will be removed when the
559        //script terminates
560        if(is_dir($this->tempDir)){
561            //the temp directory still exist
562            $this->rrmdir($this->tempDir);
563            unset($this->content_folder);
564            unset($this->docxPath);
565            unset($this->imagePathPrefix);
566            unset($this->image_max_width);
567            unset($this->tempDir);
568            unset($this->rels);
569            unset($this->tagclosep);
570            unset($this->tagcloset);
571            return true;
572        }
573        return false;
574    }
575    /**
576     * This function will remove files and directories recursivly
577     * @param String $dir The path to the folder to be removed
578     */
579    function rrmdir($dir) {
580        if (is_dir($dir)) {
581            $objects = scandir($dir);
582            foreach ($objects as $object) {
583                if ($object != "." && $object != "..") {
584                    if (filetype($dir."/".$object) == "dir"){
585                        $this->rrmdir($dir."/".$object);
586                    } else {
587                        unlink($dir."/".$object);
588                    }
589                }
590            }
591            reset($objects);
592            rmdir($dir);
593        }
594    }
595}
596#EOF-----------
Note: See TracBrowser for help on using the repository browser.