View Javadoc
1   /*
2    * Copyright (c) 2002-2020, City of Paris
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    *
9    *  1. Redistributions of source code must retain the above copyright notice
10   *     and the following disclaimer.
11   *
12   *  2. Redistributions in binary form must reproduce the above copyright notice
13   *     and the following disclaimer in the documentation and/or other materials
14   *     provided with the distribution.
15   *
16   *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17   *     contributors may be used to endorse or promote products derived from
18   *     this software without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30   * POSSIBILITY OF SUCH DAMAGE.
31   *
32   * License 1.0
33   */
34  package fr.paris.lutece.plugins.wordtemplate.service;
35  
36  import fr.paris.lutece.plugins.wordtemplate.exception.WordTemplateException;
37  import java.util.ArrayList;
38  import java.util.List;
39  import org.apache.poi.xwpf.usermodel.BodyElementType;
40  import org.apache.poi.xwpf.usermodel.IBody;
41  import org.apache.poi.xwpf.usermodel.IBodyElement;
42  import org.apache.poi.xwpf.usermodel.XWPFDocument;
43  import org.apache.poi.xwpf.usermodel.XWPFParagraph;
44  import org.apache.poi.xwpf.usermodel.XWPFRun;
45  import org.apache.poi.xwpf.usermodel.XWPFTable;
46  import org.apache.poi.xwpf.usermodel.XWPFTableCell;
47  import org.apache.poi.xwpf.usermodel.XWPFTableRow;
48  import org.apache.xmlbeans.XmlCursor;
49  import org.apache.xmlbeans.XmlException;
50  import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPPr;
51  import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;
52  import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRPr;
53  import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRow;
54  import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblPr;
55  import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTc;
56  import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;
57  import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTrPr;
58  
59  /**
60   * Service for manipulation of objects of a word document
61   */
62  public class WordService
63  {
64  
65      /***
66       * Get the content of body
67       * 
68       * @param body
69       * @return the content of the body
70       * @throws XmlException
71       */
72      public String getContent( IBody body ) throws XmlException
73      {
74          StringBuilder stringBuilder = new StringBuilder( );
75  
76          for ( IBodyElement bodyElement : body.getBodyElements( ) )
77          {
78              if ( bodyElement.getElementType( ).equals( BodyElementType.PARAGRAPH ) )
79              {
80                  for ( XWPFRun run : ( (XWPFParagraph) bodyElement ).getRuns( ) )
81                  {
82                      String text = run.getText( 0 );
83                      stringBuilder.append( text );
84                  }
85              }
86              if ( bodyElement.getElementType( ).equals( BodyElementType.TABLE ) )
87              {
88                  for ( XWPFTableRow row : ( (XWPFTable) bodyElement ).getRows( ) )
89                  {
90                      for ( XWPFTableCell cell : row.getTableCells( ) )
91                      {
92                          getContent( cell );
93                      }
94                  }
95              }
96          }
97  
98          return stringBuilder.toString( );
99      }
100 
101     /**
102      * Clone an IBodyElement
103      * 
104      * @param clone
105      *            the cloned IBodyElement
106      * @param source
107      *            the source for IBodyElement
108      */
109     public static void cloneBodyElement( IBodyElement clone, IBodyElement source )
110     {
111         if ( clone.getElementType( ).equals( BodyElementType.PARAGRAPH ) && source.getElementType( ).equals( BodyElementType.PARAGRAPH ) )
112         {
113             cloneParagraph( (XWPFParagraph) clone, (XWPFParagraph) source, false );
114             return;
115         }
116         if ( clone.getElementType( ).equals( BodyElementType.TABLE ) && source.getElementType( ).equals( BodyElementType.TABLE ) )
117         {
118             cloneTable( (XWPFTable) clone, (XWPFTable) source, false );
119             return;
120         }
121         throw new WordTemplateException( "Try to clone one BodyElement into another but with different type" );
122     }
123 
124     /**
125      * Clone a paragraph
126      * 
127      * @param clone
128      *            the cloned paragraph
129      * @param source
130      *            the source for paragraph
131      * @param isEmpty
132      */
133     public static void cloneParagraph( XWPFParagraph clone, XWPFParagraph source, boolean isEmpty )
134     {
135         CTPPr pPr = clone.getCTP( ).isSetPPr( ) ? clone.getCTP( ).getPPr( ) : clone.getCTP( ).addNewPPr( );
136         pPr.set( source.getCTP( ).getPPr( ) );
137 
138         if ( isEmpty )
139         {
140             return;
141         }
142 
143         for ( XWPFRun run : source.getRuns( ) )
144         {
145             XWPFRun newRun = clone.createRun( );
146             cloneRun( newRun, run, false );
147         }
148     }
149 
150     /**
151      * Clone a run
152      * 
153      * @param clone
154      *            the cloned run
155      * @param source
156      *            the source for run
157      * @param isEmpty
158      */
159     public static void cloneRun( XWPFRun clone, XWPFRun source, boolean isEmpty )
160     {
161         CTRPr rPr = clone.getCTR( ).isSetRPr( ) ? clone.getCTR( ).getRPr( ) : clone.getCTR( ).addNewRPr( );
162         rPr.set( source.getCTR( ).getRPr( ) );
163 
164         if ( isEmpty )
165         {
166             return;
167         }
168 
169         clone.setText( source.getText( 0 ) );
170     }
171 
172     /**
173      * Clone a table
174      * 
175      * @param clone
176      *            the cloned table
177      * @param source
178      *            the source for table
179      * @param isEmpty
180      */
181     public static void cloneTable( XWPFTable clone, XWPFTable source, boolean isEmpty )
182     {
183         CTTblPr tblPr = clone.getCTTbl( ).getTblPr( ) != null ? clone.getCTTbl( ).getTblPr( ) : clone.getCTTbl( ).addNewTblPr( );
184         tblPr.set( source.getCTTbl( ).getTblPr( ) );
185 
186         if ( isEmpty )
187         {
188             return;
189         }
190 
191         boolean first = true;
192         List<XWPFTableRow [ ]> newRows = new ArrayList<>( );
193 
194         for ( XWPFTableRow row : source.getRows( ) )
195         {
196             XWPFTableRow newRow;
197             if ( first && clone.getRow( 0 ) != null )
198             {
199                 newRow = clone.getRow( 0 );
200             }
201             else
202             {
203                 newRow = clone.createRow( );
204             }
205             XWPFTableRow [ ] twoRows = {
206                     newRow, row
207             };
208             newRows.add( twoRows );
209             first = false;
210         }
211 
212         for ( XWPFTableRow [ ] newRow : newRows )
213         {
214             cloneTableRow( newRow [0], newRow [1], false );
215         }
216     }
217 
218     /**
219      * Clone a table row
220      * 
221      * @param clone
222      *            the cloned table row
223      * @param source
224      *            the source for table row
225      * @param isEmpty
226      */
227     public static void cloneTableRow( XWPFTableRow clone, XWPFTableRow source, boolean isEmpty )
228     {
229         CTTrPr trPr = clone.getCtRow( ).getTrPr( ) != null ? clone.getCtRow( ).getTrPr( ) : clone.getCtRow( ).addNewTrPr( );
230         trPr.set( source.getCtRow( ).getTrPr( ) );
231 
232         if ( isEmpty )
233         {
234             return;
235         }
236 
237         boolean first = true;
238 
239         for ( XWPFTableCell cell : source.getTableCells( ) )
240         {
241             XWPFTableCell newCell;
242             if ( first && clone.getCell( 0 ) != null )
243             {
244                 newCell = clone.getCell( 0 );
245             }
246             else
247             {
248                 newCell = clone.createCell( );
249             }
250             cloneTableCell( newCell, cell );
251             first = false;
252         }
253     }
254 
255     /**
256      * Clone a table celle
257      * 
258      * @param clone
259      *            the cloned table celle
260      * @param source
261      *            the source for table celle
262      */
263     public static void cloneTableCell( XWPFTableCell clone, XWPFTableCell source )
264     {
265         cloneTableCell( clone, source, 0, source.getBodyElements( ).size( ) );
266     }
267 
268     /**
269      * Clone a table celle
270      * 
271      * @param clone
272      *            the cloned table celle
273      * @param source
274      *            the source for table celle
275      * @param fromIndex
276      * @param toIndex
277      */
278     public static void cloneTableCell( XWPFTableCell clone, XWPFTableCell source, int fromIndex, int toIndex )
279     {
280         CTTcPr tcPr = clone.getCTTc( ).getTcPr( ) != null ? clone.getCTTc( ).getTcPr( ) : clone.getCTTc( ).addNewTcPr( );
281         tcPr.set( source.getCTTc( ).getTcPr( ) );
282 
283         XWPFParagraph firstParagraph = clone.getParagraphs( ).get( 0 );
284         XmlCursor cursor = firstParagraph.getCTP( ).newCursor( );
285 
286         if ( !( fromIndex >= 0 && fromIndex <= toIndex && toIndex <= source.getBodyElements( ).size( ) ) )
287         {
288             return;
289         }
290 
291         for ( int i = fromIndex; i < toIndex; i++ )
292         {
293             IBodyElement bodyElement = source.getBodyElements( ).get( i );
294             if ( bodyElement.getElementType( ).equals( BodyElementType.PARAGRAPH ) )
295             {
296                 XWPFParagraph newParagraph = clone.insertNewParagraph( cursor );
297                 cloneParagraph( newParagraph, (XWPFParagraph) bodyElement, false );
298                 cursor.dispose( );
299                 cursor = newParagraph.getCTP( ).newCursor( );
300                 cursor.toNextSibling( );
301             }
302             if ( bodyElement.getElementType( ).equals( BodyElementType.TABLE ) )
303             {
304                 XWPFTable newTable = clone.insertNewTbl( cursor );
305                 cloneTable( newTable, (XWPFTable) bodyElement, false );
306                 cursor.dispose( );
307                 cursor = newTable.getCTTbl( ).newCursor( );
308                 cursor.toNextSibling( );
309             }
310         }
311         cursor.dispose( );
312         clone.removeParagraph( clone.getParagraphs( ).size( ) - 1 );
313     }
314 
315     /**
316      * Insert a table
317      * 
318      * @param body
319      * @param table
320      *            the table to insert
321      * @param posDest
322      * @return
323      */
324     public static XWPFTable insertTable( IBody body, XWPFTable table, int posDest )
325     {
326         IBodyElement bodyElement;
327 
328         if ( posDest > body.getBodyElements( ).size( ) || posDest < 0 )
329         {
330             return null;
331         }
332 
333         if ( posDest == body.getBodyElements( ).size( ) )
334         {
335             bodyElement = body.getBodyElements( ).get( posDest - 1 );
336         }
337         else
338         {
339             bodyElement = body.getBodyElements( ).get( posDest );
340         }
341 
342         XmlCursor cursor = getCursor( bodyElement );
343 
344         if ( posDest == body.getBodyElements( ).size( ) )
345         {
346             cursor.toParent( );
347             cursor.toEndToken( );
348         }
349 
350         XWPFTable newTable = insertTable( body, table, cursor );
351         return newTable;
352     }
353 
354     /**
355      * Insert a table
356      * 
357      * @param body
358      * @param table
359      *            the table to insert
360      * @param cursor
361      * @return
362      */
363     public static XWPFTable insertTable( IBody body, XWPFTable table, XmlCursor cursor )
364     {
365         XWPFTable newTable = body.insertNewTbl( cursor );
366         WordService.cloneTable( newTable, table, false );
367         return newTable;
368     }
369 
370     /**
371      * Insert a table row
372      * 
373      * @param table
374      * @param tableRow
375      * @param posDest
376      * @return
377      */
378     public static XWPFTableRow insertTableRow( XWPFTable table, XWPFTableRow tableRow, int posDest )
379     {
380         table.getCTTbl( ).insertNewTr( posDest );
381         table.getCTTbl( ).setTrArray( posDest, tableRow.getCtRow( ) );
382         XWPFTableRow newTableRow = new XWPFTableRow( table.getCTTbl( ).getTrArray( posDest ), table );
383 
384         table.getRows( ).add( posDest, newTableRow );
385 
386         return newTableRow;
387     }
388 
389     /**
390      * Insert a table row
391      * 
392      * @param table
393      * @param tableRow
394      * @param cursor
395      * @return
396      */
397     public static XWPFTableRow insertTableRow( XWPFTable table, XWPFTableRow tableRow, XmlCursor cursor )
398     {
399         XWPFTableRow cursorTableRow = table.getRow( (CTRow) cursor.getObject( ) );
400         int posDest = table.getRows( ).indexOf( cursorTableRow );
401         return insertTableRow( table, tableRow, posDest );
402     }
403 
404     /**
405      * Insert a table cell
406      * 
407      * @param tableRow
408      * @param tableCell
409      * @param posDest
410      * @return
411      */
412     public static XWPFTableCell insertTableCell( XWPFTableRow tableRow, XWPFTableCell tableCell, int posDest )
413     {
414         tableCell = addTableCell( tableRow, posDest );
415         cloneTableCell( tableRow.getCell( posDest ), tableCell );
416         return tableRow.getCell( posDest );
417     }
418 
419     /**
420      * Insert a table cell
421      * 
422      * @param tableRow
423      * @param tableCell
424      * @param cursor
425      * @return
426      */
427     public static XWPFTableCell insertTableCell( XWPFTableRow tableRow, XWPFTableCell tableCell, XmlCursor cursor )
428     {
429         XWPFTableCell cursorTableCell = tableRow.getTableCell( (CTTc) cursor.getObject( ) );
430         int posDest = tableRow.getTableCells( ).indexOf( cursorTableCell );
431         return insertTableCell( tableRow, tableCell, posDest );
432     }
433 
434     /**
435      * Insert a paragraph
436      * 
437      * @param body
438      * @param paragraph
439      * @param posDest
440      * @return
441      */
442     public static XWPFParagraph insertParagraph( IBody body, XWPFParagraph paragraph, int posDest )
443     {
444         IBodyElement bodyElement;
445 
446         if ( posDest > body.getBodyElements( ).size( ) || posDest < 0 )
447         {
448             return null;
449         }
450 
451         if ( posDest == body.getBodyElements( ).size( ) )
452         {
453             bodyElement = body.getBodyElements( ).get( posDest - 1 );
454         }
455         else
456         {
457             bodyElement = body.getBodyElements( ).get( posDest );
458         }
459 
460         XmlCursor cursor = getCursor( bodyElement );
461 
462         if ( posDest == body.getBodyElements( ).size( ) )
463         {
464             cursor.toParent( );
465             cursor.toEndToken( );
466         }
467 
468         return insertParagraph( body, paragraph, cursor );
469     }
470 
471     /**
472      * Insert a paragraph
473      * 
474      * @param body
475      * @param paragraph
476      * @param cursor
477      * @return
478      */
479     public static XWPFParagraph insertParagraph( IBody body, XWPFParagraph paragraph, XmlCursor cursor )
480     {
481         XWPFParagraph newParagraph = body.insertNewParagraph( cursor );
482         WordService.cloneParagraph( newParagraph, paragraph, false );
483         return newParagraph;
484     }
485 
486     /**
487      * Insert a run
488      * 
489      * @param paragraphe
490      * @param run
491      * @param posDest
492      * @return
493      */
494     public static XWPFRun insertRun( XWPFParagraph paragraphe, XWPFRun run, int posDest )
495     {
496         XWPFRun newRun = paragraphe.insertNewRun( posDest );
497         cloneRun( newRun, run, false );
498         return newRun;
499     }
500 
501     /**
502      * Insert a run
503      * 
504      * @param paragraphe
505      * @param run
506      * @param cursor
507      * @return
508      */
509     public static XWPFRun insertRun( XWPFParagraph paragraphe, XWPFRun run, XmlCursor cursor )
510     {
511         XWPFRun cursorRun = paragraphe.getRun( (CTR) cursor.getObject( ) );
512         int posDest = paragraphe.getRuns( ).indexOf( cursorRun );
513         return insertRun( paragraphe, run, posDest );
514     }
515 
516     /**
517      * Split a paragraph
518      *
519      * @param paragraph
520      * @param pos
521      */
522     public static void splitParagraph( XWPFParagraph paragraph, int pos )
523     {
524         if ( !( pos > 0 && pos < paragraph.getRuns( ).size( ) ) )
525         {
526             return;
527         }
528         XWPFParagraph beforeParagraph = WordService.insertParagraph( paragraph.getBody( ), paragraph, paragraph.getCTP( ).newCursor( ) );
529         while ( beforeParagraph.removeRun( pos ) )
530         {
531         }
532         while ( pos > 0 )
533         {
534             pos--;
535             paragraph.removeRun( pos );
536         }
537     }
538 
539     /**
540      * Split a run
541      *
542      * @param run
543      * @param pos
544      */
545     public static void splitRun( XWPFRun run, int pos )
546     {
547         if ( !( run.getParent( ) instanceof XWPFParagraph ) )
548         {
549             return;
550         }
551         if ( !( pos > 0 && pos < run.toString( ).length( ) ) )
552         {
553             return;
554         }
555         String beforeText = run.toString( ).substring( 0, pos );
556         String afterText = run.toString( ).substring( pos );
557         XWPFParagraph paragraph = (XWPFParagraph) run.getParent( );
558         int numRun = paragraph.getRuns( ).indexOf( run );
559         XWPFRun beforeRun = paragraph.insertNewRun( numRun );
560         WordService.cloneRun( beforeRun, run, true );
561         beforeRun.setText( beforeText, 0 );
562         run.setText( afterText, 0 );
563     }
564 
565     /**
566      * Split a table
567      *
568      * @param table
569      * @param pos
570      */
571     public static void splitTable( XWPFTable table, int pos )
572     {
573         if ( !( pos > 0 && pos < table.getRows( ).size( ) ) )
574         {
575             return;
576         }
577         XWPFTable beforeTable = WordService.insertTable( table.getBody( ), table, table.getCTTbl( ).newCursor( ) );
578         while ( beforeTable.removeRow( pos ) )
579         {
580         }
581         while ( pos > 0 )
582         {
583             pos--;
584             table.removeRow( pos );
585         }
586     }
587 
588     /**
589      * Split a table row
590      *
591      * @param tableRow
592      * @param pos
593      */
594     public static void splitTableRow( XWPFTableRow tableRow, int pos )
595     {
596         if ( !( pos > 0 && pos < tableRow.getTableCells( ).size( ) ) )
597         {
598             return;
599         }
600         XWPFTableRow beforeTableRow = WordService.insertTableRow( tableRow.getTable( ), tableRow, tableRow.getCtRow( ).newCursor( ) );
601         while ( WordService.removeTableCell( beforeTableRow, pos ) )
602         {
603         }
604         while ( pos > 0 )
605         {
606             pos--;
607             WordService.removeTableCell( tableRow, pos );
608         }
609     }
610 
611     /**
612      * Split a table cell
613      *
614      * @param tableCell
615      * @param pos
616      */
617     public static void splitTableCell( XWPFTableCell tableCell, int pos )
618     {
619         if ( !( pos > 0 && pos < tableCell.getBodyElements( ).size( ) ) )
620         {
621             return;
622         }
623         XWPFTableRow tableRow = tableCell.getTableRow( );
624         int posCell = tableRow.getTableCells( ).indexOf( tableCell );
625         XWPFTableCell beforeTableCell1 = WordService.addTableCell( tableRow, posCell );
626         XWPFTableCell beforeTableCell2 = WordService.addTableCell( tableRow, posCell );
627         WordService.cloneTableCell( beforeTableCell2, tableCell, 0, pos );
628         WordService.cloneTableCell( beforeTableCell1, tableCell, pos, tableCell.getBodyElements( ).size( ) );
629         WordService.removeTableCell( tableRow, posCell + 2 );
630     }
631 
632     /**
633      * Add a table cell
634      * 
635      * @param tableRow
636      * @param posDest
637      * @return
638      */
639     public static XWPFTableCell addTableCell( XWPFTableRow tableRow, int posDest )
640     {
641         CTTc cTTc = tableRow.getCtRow( ).insertNewTc( posDest );
642         XWPFTableCell tableCell = new XWPFTableCell( cTTc, tableRow, tableRow.getTable( ).getBody( ) );
643         tableRow.getTableCells( ).add( posDest, tableCell );
644         return tableRow.getCell( posDest );
645     }
646 
647     /**
648      * Remove a table cell
649      * 
650      * @param tableRow
651      * @param posDest
652      * @return
653      */
654     public static boolean removeTableCell( XWPFTableRow tableRow, int posDest )
655     {
656         if ( posDest >= 0 && posDest < tableRow.getTableCells( ).size( ) )
657         {
658             tableRow.getCtRow( ).removeTc( posDest );
659             tableRow.getTableCells( ).remove( posDest );
660             return true;
661         }
662         return false;
663     }
664 
665     /**
666      * Remove an IBodyElement
667      * 
668      * @param body
669      * @param posDest
670      */
671     public static void removeBodyElement( IBody body, int posDest )
672     {
673         IBodyElement bodyElement = body.getBodyElements( ).get( posDest );
674         XmlCursor cursor = getCursor( bodyElement );
675         removeElement( cursor );
676     }
677 
678     /**
679      * Remove an element
680      * 
681      * @param cursor
682      */
683     public static void removeElement( XmlCursor cursor )
684     {
685         cursor.removeXml( );
686     }
687 
688     /**
689      * Get a cursor at position the position of the IBodyElement
690      * 
691      * @param bodyElement
692      * @return
693      */
694     public static XmlCursor getCursor( IBodyElement bodyElement )
695     {
696         XmlCursor cursor = null;
697 
698         switch( bodyElement.getElementType( ) )
699         {
700             case PARAGRAPH:
701                 cursor = ( (XWPFParagraph) bodyElement ).getCTP( ).newCursor( );
702                 break;
703             case TABLE:
704                 cursor = ( (XWPFTable) bodyElement ).getCTTbl( ).newCursor( );
705                 break;
706         }
707 
708         return cursor;
709     }
710 }