PageRenderTime 53ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/src/org/apache/poi/hwpf/converter/WordToHtmlConverter.java

https://github.com/minstrelsy/SimpleAndroidDocView
Java | 746 lines | 591 code | 101 blank | 54 comment | 66 complexity | 26fefb68f3e9024bc39626c516856a5c MD5 | raw file
Possible License(s): Apache-2.0
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.hwpf.converter;
  16. import java.io.File;
  17. import java.io.FileWriter;
  18. import java.util.List;
  19. import java.util.Stack;
  20. import javax.xml.parsers.DocumentBuilderFactory;
  21. import javax.xml.transform.OutputKeys;
  22. import javax.xml.transform.Transformer;
  23. import javax.xml.transform.TransformerFactory;
  24. import javax.xml.transform.dom.DOMSource;
  25. import javax.xml.transform.stream.StreamResult;
  26. import org.apache.poi.hpsf.SummaryInformation;
  27. import org.apache.poi.hwpf.HWPFDocument;
  28. import org.apache.poi.hwpf.HWPFDocumentCore;
  29. import org.apache.poi.hwpf.converter.FontReplacer.Triplet;
  30. import org.apache.poi.hwpf.usermodel.Bookmark;
  31. import org.apache.poi.hwpf.usermodel.CharacterRun;
  32. import org.apache.poi.hwpf.usermodel.OfficeDrawing;
  33. import org.apache.poi.hwpf.usermodel.Paragraph;
  34. import org.apache.poi.hwpf.usermodel.Picture;
  35. import org.apache.poi.hwpf.usermodel.Range;
  36. import org.apache.poi.hwpf.usermodel.Section;
  37. import org.apache.poi.hwpf.usermodel.Table;
  38. import org.apache.poi.hwpf.usermodel.TableCell;
  39. import org.apache.poi.hwpf.usermodel.TableRow;
  40. import org.apache.poi.util.Beta;
  41. import org.apache.poi.util.POILogFactory;
  42. import org.apache.poi.util.POILogger;
  43. import org.w3c.dom.Document;
  44. import org.w3c.dom.Element;
  45. import org.w3c.dom.Text;
  46. import static org.apache.poi.hwpf.converter.AbstractWordUtils.TWIPS_PER_INCH;
  47. /**
  48. * Converts Word files (95-2007) into HTML files.
  49. * <p>
  50. * This implementation doesn't create images or links to them. This can be
  51. * changed by overriding {@link #processImage(Element, boolean, Picture)}
  52. * method.
  53. *
  54. * @author Sergey Vladimirov (vlsergey {at} gmail {dot} com)
  55. */
  56. @Beta
  57. public class WordToHtmlConverter extends AbstractWordConverter
  58. {
  59. /**
  60. * Holds properties values, applied to current <tt>p</tt> element. Those
  61. * properties shall not be doubled in children <tt>span</tt> elements.
  62. */
  63. private static class BlockProperies
  64. {
  65. final String pFontName;
  66. final int pFontSize;
  67. public BlockProperies( String pFontName, int pFontSize )
  68. {
  69. this.pFontName = pFontName;
  70. this.pFontSize = pFontSize;
  71. }
  72. }
  73. private static final POILogger logger = POILogFactory
  74. .getLogger( WordToHtmlConverter.class );
  75. private static String getSectionStyle( Section section )
  76. {
  77. float leftMargin = section.getMarginLeft() / TWIPS_PER_INCH;
  78. float rightMargin = section.getMarginRight() / TWIPS_PER_INCH;
  79. float topMargin = section.getMarginTop() / TWIPS_PER_INCH;
  80. float bottomMargin = section.getMarginBottom() / TWIPS_PER_INCH;
  81. String style = "margin: " + topMargin + "in " + rightMargin + "in "
  82. + bottomMargin + "in " + leftMargin + "in;";
  83. if ( section.getNumColumns() > 1 )
  84. {
  85. style += "column-count: " + ( section.getNumColumns() ) + ";";
  86. if ( section.isColumnsEvenlySpaced() )
  87. {
  88. float distance = section.getDistanceBetweenColumns()
  89. / TWIPS_PER_INCH;
  90. style += "column-gap: " + distance + "in;";
  91. }
  92. else
  93. {
  94. style += "column-gap: 0.25in;";
  95. }
  96. }
  97. return style;
  98. }
  99. /**
  100. * Java main() interface to interact with {@link WordToHtmlConverter}
  101. *
  102. * <p>
  103. * Usage: WordToHtmlConverter infile outfile
  104. * </p>
  105. * Where infile is an input .doc file ( Word 95-2007) which will be rendered
  106. * as HTML into outfile
  107. */
  108. public static void main( String[] args )
  109. {
  110. if ( args.length < 2 )
  111. {
  112. System.err
  113. .println( "Usage: WordToHtmlConverter <inputFile.doc> <saveTo.html>" );
  114. return;
  115. }
  116. System.out.println( "Converting " + args[0] );
  117. System.out.println( "Saving output to " + args[1] );
  118. try
  119. {
  120. Document doc = WordToHtmlConverter.process( new File( args[0] ) );
  121. FileWriter out = new FileWriter( args[1] );
  122. DOMSource domSource = new DOMSource( doc );
  123. StreamResult streamResult = new StreamResult( out );
  124. TransformerFactory tf = TransformerFactory.newInstance();
  125. Transformer serializer = tf.newTransformer();
  126. // TODO set encoding from a command argument
  127. serializer.setOutputProperty( OutputKeys.ENCODING, "UTF-8" );
  128. serializer.setOutputProperty( OutputKeys.INDENT, "yes" );
  129. serializer.setOutputProperty( OutputKeys.METHOD, "html" );
  130. serializer.transform( domSource, streamResult );
  131. out.close();
  132. }
  133. catch ( Exception e )
  134. {
  135. e.printStackTrace();
  136. }
  137. }
  138. static Document process( File docFile ) throws Exception
  139. {
  140. final HWPFDocumentCore wordDocument = WordToHtmlUtils.loadDoc( docFile );
  141. WordToHtmlConverter wordToHtmlConverter = new WordToHtmlConverter(
  142. DocumentBuilderFactory.newInstance().newDocumentBuilder()
  143. .newDocument() );
  144. wordToHtmlConverter.processDocument( wordDocument );
  145. return wordToHtmlConverter.getDocument();
  146. }
  147. private final Stack<BlockProperies> blocksProperies = new Stack<BlockProperies>();
  148. private final HtmlDocumentFacade htmlDocumentFacade;
  149. private Element notes = null;
  150. /**
  151. * Creates new instance of {@link WordToHtmlConverter}. Can be used for
  152. * output several {@link HWPFDocument}s into single HTML document.
  153. *
  154. * @param document
  155. * XML DOM Document used as HTML document
  156. */
  157. public WordToHtmlConverter( Document document )
  158. {
  159. this.htmlDocumentFacade = new HtmlDocumentFacade( document );
  160. }
  161. public WordToHtmlConverter( HtmlDocumentFacade htmlDocumentFacade )
  162. {
  163. this.htmlDocumentFacade = htmlDocumentFacade;
  164. }
  165. @Override
  166. protected void afterProcess()
  167. {
  168. if ( notes != null )
  169. htmlDocumentFacade.getBody().appendChild( notes );
  170. htmlDocumentFacade.updateStylesheet();
  171. }
  172. public Document getDocument()
  173. {
  174. return htmlDocumentFacade.getDocument();
  175. }
  176. @Override
  177. protected void outputCharacters( Element pElement,
  178. CharacterRun characterRun, String text )
  179. {
  180. Element span = htmlDocumentFacade.document.createElement( "span" );
  181. pElement.appendChild( span );
  182. StringBuilder style = new StringBuilder();
  183. BlockProperies blockProperies = this.blocksProperies.peek();
  184. Triplet triplet = getCharacterRunTriplet( characterRun );
  185. if ( WordToHtmlUtils.isNotEmpty( triplet.fontName )
  186. && !WordToHtmlUtils.equals( triplet.fontName,
  187. blockProperies.pFontName ) )
  188. {
  189. style.append( "font-family:" + triplet.fontName + ";" );
  190. }
  191. if ( characterRun.getFontSize() / 2 != blockProperies.pFontSize )
  192. {
  193. style.append( "font-size:" + characterRun.getFontSize() / 2 + "pt;" );
  194. }
  195. if ( triplet.bold )
  196. {
  197. style.append( "font-weight:bold;" );
  198. }
  199. if ( triplet.italic )
  200. {
  201. style.append( "font-style:italic;" );
  202. }
  203. WordToHtmlUtils.addCharactersProperties( characterRun, style );
  204. if ( style.length() != 0 )
  205. htmlDocumentFacade.addStyleClass( span, "s", style.toString() );
  206. Text textNode = htmlDocumentFacade.createText( text );
  207. span.appendChild( textNode );
  208. }
  209. @Override
  210. protected void processBookmarks( HWPFDocumentCore wordDocument,
  211. Element currentBlock, Range range, int currentTableLevel,
  212. List<Bookmark> rangeBookmarks )
  213. {
  214. Element parent = currentBlock;
  215. for ( Bookmark bookmark : rangeBookmarks )
  216. {
  217. Element bookmarkElement = htmlDocumentFacade
  218. .createBookmark( bookmark.getName() );
  219. parent.appendChild( bookmarkElement );
  220. parent = bookmarkElement;
  221. }
  222. if ( range != null )
  223. processCharacters( wordDocument, currentTableLevel, range, parent );
  224. }
  225. @Override
  226. protected void processDocumentInformation(
  227. SummaryInformation summaryInformation )
  228. {
  229. if ( WordToHtmlUtils.isNotEmpty( summaryInformation.getTitle() ) )
  230. htmlDocumentFacade.setTitle( summaryInformation.getTitle() );
  231. if ( WordToHtmlUtils.isNotEmpty( summaryInformation.getAuthor() ) )
  232. htmlDocumentFacade.addAuthor( summaryInformation.getAuthor() );
  233. if ( WordToHtmlUtils.isNotEmpty( summaryInformation.getKeywords() ) )
  234. htmlDocumentFacade.addKeywords( summaryInformation.getKeywords() );
  235. if ( WordToHtmlUtils.isNotEmpty( summaryInformation.getComments() ) )
  236. htmlDocumentFacade
  237. .addDescription( summaryInformation.getComments() );
  238. }
  239. @Override
  240. public void processDocumentPart( HWPFDocumentCore wordDocument, Range range )
  241. {
  242. super.processDocumentPart( wordDocument, range );
  243. afterProcess();
  244. }
  245. @Override
  246. protected void processDropDownList( Element block,
  247. CharacterRun characterRun, String[] values, int defaultIndex )
  248. {
  249. Element select = htmlDocumentFacade.createSelect();
  250. for ( int i = 0; i < values.length; i++ )
  251. {
  252. select.appendChild( htmlDocumentFacade.createOption( values[i],
  253. defaultIndex == i ) );
  254. }
  255. block.appendChild( select );
  256. }
  257. @Override
  258. protected void processDrawnObject( HWPFDocument doc,
  259. CharacterRun characterRun, OfficeDrawing officeDrawing,
  260. String path, Element block )
  261. {
  262. Element img = htmlDocumentFacade.createImage( path );
  263. block.appendChild( img );
  264. }
  265. @Override
  266. protected void processEndnoteAutonumbered( HWPFDocument wordDocument,
  267. int noteIndex, Element block, Range endnoteTextRange )
  268. {
  269. processNoteAutonumbered( wordDocument, "end", noteIndex, block,
  270. endnoteTextRange );
  271. }
  272. @Override
  273. protected void processFootnoteAutonumbered( HWPFDocument wordDocument,
  274. int noteIndex, Element block, Range footnoteTextRange )
  275. {
  276. processNoteAutonumbered( wordDocument, "foot", noteIndex, block,
  277. footnoteTextRange );
  278. }
  279. @Override
  280. protected void processHyperlink( HWPFDocumentCore wordDocument,
  281. Element currentBlock, Range textRange, int currentTableLevel,
  282. String hyperlink )
  283. {
  284. Element basicLink = htmlDocumentFacade.createHyperlink( hyperlink );
  285. currentBlock.appendChild( basicLink );
  286. if ( textRange != null )
  287. processCharacters( wordDocument, currentTableLevel, textRange,
  288. basicLink );
  289. }
  290. protected void processImage( Element currentBlock, boolean inlined,
  291. Picture picture, String imageSourcePath )
  292. {
  293. final int aspectRatioX = picture.getHorizontalScalingFactor();
  294. final int aspectRatioY = picture.getVerticalScalingFactor();
  295. StringBuilder style = new StringBuilder();
  296. final float imageWidth;
  297. final float imageHeight;
  298. final float cropTop;
  299. final float cropBottom;
  300. final float cropLeft;
  301. final float cropRight;
  302. if ( aspectRatioX > 0 )
  303. {
  304. imageWidth = picture.getDxaGoal() * aspectRatioX / 1000
  305. / TWIPS_PER_INCH;
  306. cropRight = picture.getDxaCropRight() * aspectRatioX / 1000
  307. / TWIPS_PER_INCH;
  308. cropLeft = picture.getDxaCropLeft() * aspectRatioX / 1000
  309. / TWIPS_PER_INCH;
  310. }
  311. else
  312. {
  313. imageWidth = picture.getDxaGoal() / TWIPS_PER_INCH;
  314. cropRight = picture.getDxaCropRight() / TWIPS_PER_INCH;
  315. cropLeft = picture.getDxaCropLeft() / TWIPS_PER_INCH;
  316. }
  317. if ( aspectRatioY > 0 )
  318. {
  319. imageHeight = picture.getDyaGoal() * aspectRatioY / 1000
  320. / TWIPS_PER_INCH;
  321. cropTop = picture.getDyaCropTop() * aspectRatioY / 1000
  322. / TWIPS_PER_INCH;
  323. cropBottom = picture.getDyaCropBottom() * aspectRatioY / 1000
  324. / TWIPS_PER_INCH;
  325. }
  326. else
  327. {
  328. imageHeight = picture.getDyaGoal() / TWIPS_PER_INCH;
  329. cropTop = picture.getDyaCropTop() / TWIPS_PER_INCH;
  330. cropBottom = picture.getDyaCropBottom() / TWIPS_PER_INCH;
  331. }
  332. Element root;
  333. if ( cropTop != 0 || cropRight != 0 || cropBottom != 0 || cropLeft != 0 )
  334. {
  335. float visibleWidth = Math
  336. .max( 0, imageWidth - cropLeft - cropRight );
  337. float visibleHeight = Math.max( 0, imageHeight - cropTop
  338. - cropBottom );
  339. root = htmlDocumentFacade.createBlock();
  340. htmlDocumentFacade.addStyleClass( root, "d",
  341. "vertical-align:text-bottom;width:" + visibleWidth
  342. + "in;height:" + visibleHeight + "in;" );
  343. // complex
  344. Element inner = htmlDocumentFacade.createBlock();
  345. htmlDocumentFacade.addStyleClass( inner, "d",
  346. "position:relative;width:" + visibleWidth + "in;height:"
  347. + visibleHeight + "in;overflow:hidden;" );
  348. root.appendChild( inner );
  349. Element image = htmlDocumentFacade.createImage( imageSourcePath );
  350. htmlDocumentFacade.addStyleClass( image, "i",
  351. "position:absolute;left:-" + cropLeft + ";top:-" + cropTop
  352. + ";width:" + imageWidth + "in;height:"
  353. + imageHeight + "in;" );
  354. inner.appendChild( image );
  355. style.append( "overflow:hidden;" );
  356. }
  357. else
  358. {
  359. root = htmlDocumentFacade.createImage( imageSourcePath );
  360. root.setAttribute( "style", "width:" + imageWidth + "in;height:"
  361. + imageHeight + "in;vertical-align:text-bottom;" );
  362. }
  363. currentBlock.appendChild( root );
  364. }
  365. @Override
  366. protected void processImageWithoutPicturesManager( Element currentBlock,
  367. boolean inlined, Picture picture )
  368. {
  369. // no default implementation -- skip
  370. currentBlock.appendChild( htmlDocumentFacade.document
  371. .createComment( "Image link to '"
  372. + picture.suggestFullFileName() + "' can be here" ) );
  373. }
  374. @Override
  375. protected void processLineBreak( Element block, CharacterRun characterRun )
  376. {
  377. block.appendChild( htmlDocumentFacade.createLineBreak() );
  378. }
  379. protected void processNoteAutonumbered( HWPFDocument doc, String type,
  380. int noteIndex, Element block, Range noteTextRange )
  381. {
  382. final String textIndex = String.valueOf( noteIndex + 1 );
  383. final String textIndexClass = htmlDocumentFacade.getOrCreateCssClass(
  384. "a", "vertical-align:super;font-size:smaller;" );
  385. final String forwardNoteLink = type + "note_" + textIndex;
  386. final String backwardNoteLink = type + "note_back_" + textIndex;
  387. Element anchor = htmlDocumentFacade.createHyperlink( "#"
  388. + forwardNoteLink );
  389. anchor.setAttribute( "name", backwardNoteLink );
  390. anchor.setAttribute( "class", textIndexClass + " " + type
  391. + "noteanchor" );
  392. anchor.setTextContent( textIndex );
  393. block.appendChild( anchor );
  394. if ( notes == null )
  395. {
  396. notes = htmlDocumentFacade.createBlock();
  397. notes.setAttribute( "class", "notes" );
  398. }
  399. Element note = htmlDocumentFacade.createBlock();
  400. note.setAttribute( "class", type + "note" );
  401. notes.appendChild( note );
  402. Element bookmark = htmlDocumentFacade.createBookmark( forwardNoteLink );
  403. bookmark.setAttribute( "href", "#" + backwardNoteLink );
  404. bookmark.setTextContent( textIndex );
  405. bookmark.setAttribute( "class", textIndexClass + " " + type
  406. + "noteindex" );
  407. note.appendChild( bookmark );
  408. note.appendChild( htmlDocumentFacade.createText( " " ) );
  409. Element span = htmlDocumentFacade.getDocument().createElement( "span" );
  410. span.setAttribute( "class", type + "notetext" );
  411. note.appendChild( span );
  412. this.blocksProperies.add( new BlockProperies( "", -1 ) );
  413. try
  414. {
  415. processCharacters( doc, Integer.MIN_VALUE, noteTextRange, span );
  416. }
  417. finally
  418. {
  419. this.blocksProperies.pop();
  420. }
  421. }
  422. @Override
  423. protected void processPageBreak( HWPFDocumentCore wordDocument, Element flow )
  424. {
  425. flow.appendChild( htmlDocumentFacade.createLineBreak() );
  426. }
  427. protected void processPageref( HWPFDocumentCore hwpfDocument,
  428. Element currentBlock, Range textRange, int currentTableLevel,
  429. String pageref )
  430. {
  431. Element basicLink = htmlDocumentFacade.createHyperlink( "#" + pageref );
  432. currentBlock.appendChild( basicLink );
  433. if ( textRange != null )
  434. processCharacters( hwpfDocument, currentTableLevel, textRange,
  435. basicLink );
  436. }
  437. protected void processParagraph( HWPFDocumentCore hwpfDocument,
  438. Element parentElement, int currentTableLevel, Paragraph paragraph,
  439. String bulletText )
  440. {
  441. final Element pElement = htmlDocumentFacade.createParagraph();
  442. parentElement.appendChild( pElement );
  443. StringBuilder style = new StringBuilder();
  444. WordToHtmlUtils.addParagraphProperties( paragraph, style );
  445. final int charRuns = paragraph.numCharacterRuns();
  446. if ( charRuns == 0 )
  447. {
  448. return;
  449. }
  450. {
  451. final String pFontName;
  452. final int pFontSize;
  453. final CharacterRun characterRun = paragraph.getCharacterRun( 0 );
  454. if ( characterRun != null )
  455. {
  456. Triplet triplet = getCharacterRunTriplet( characterRun );
  457. pFontSize = characterRun.getFontSize() / 2;
  458. pFontName = triplet.fontName;
  459. WordToHtmlUtils.addFontFamily( pFontName, style );
  460. WordToHtmlUtils.addFontSize( pFontSize, style );
  461. }
  462. else
  463. {
  464. pFontSize = -1;
  465. pFontName = WordToHtmlUtils.EMPTY;
  466. }
  467. blocksProperies.push( new BlockProperies( pFontName, pFontSize ) );
  468. }
  469. try
  470. {
  471. if ( WordToHtmlUtils.isNotEmpty( bulletText ) )
  472. {
  473. if ( bulletText.endsWith( "\t" ) )
  474. {
  475. /*
  476. * We don't know how to handle all cases in HTML, but at
  477. * least simplest case shall be handled
  478. */
  479. final float defaultTab = TWIPS_PER_INCH / 2;
  480. float firstLinePosition = paragraph.getIndentFromLeft()
  481. + paragraph.getFirstLineIndent() + 20; // char have
  482. // some space
  483. float nextStop = (float) ( Math.ceil( firstLinePosition
  484. / defaultTab ) * defaultTab );
  485. final float spanMinWidth = nextStop - firstLinePosition;
  486. Element span = htmlDocumentFacade.getDocument()
  487. .createElement( "span" );
  488. htmlDocumentFacade
  489. .addStyleClass( span, "s",
  490. "display: inline-block; text-indent: 0; min-width: "
  491. + ( spanMinWidth / TWIPS_PER_INCH )
  492. + "in;" );
  493. pElement.appendChild( span );
  494. Text textNode = htmlDocumentFacade.createText( bulletText
  495. .substring( 0, bulletText.length() - 1 )
  496. + UNICODECHAR_ZERO_WIDTH_SPACE
  497. + UNICODECHAR_NO_BREAK_SPACE );
  498. span.appendChild( textNode );
  499. }
  500. else
  501. {
  502. Text textNode = htmlDocumentFacade.createText( bulletText
  503. .substring( 0, bulletText.length() - 1 ) );
  504. pElement.appendChild( textNode );
  505. }
  506. }
  507. processCharacters( hwpfDocument, currentTableLevel, paragraph,
  508. pElement );
  509. }
  510. finally
  511. {
  512. blocksProperies.pop();
  513. }
  514. if ( style.length() > 0 )
  515. htmlDocumentFacade.addStyleClass( pElement, "p", style.toString() );
  516. WordToHtmlUtils.compactSpans( pElement );
  517. return;
  518. }
  519. protected void processSection( HWPFDocumentCore wordDocument,
  520. Section section, int sectionCounter )
  521. {
  522. Element div = htmlDocumentFacade.createBlock();
  523. htmlDocumentFacade.addStyleClass( div, "d", getSectionStyle( section ) );
  524. htmlDocumentFacade.body.appendChild( div );
  525. processParagraphes( wordDocument, div, section, Integer.MIN_VALUE );
  526. }
  527. @Override
  528. protected void processSingleSection( HWPFDocumentCore wordDocument,
  529. Section section )
  530. {
  531. htmlDocumentFacade.addStyleClass( htmlDocumentFacade.body, "b",
  532. getSectionStyle( section ) );
  533. processParagraphes( wordDocument, htmlDocumentFacade.body, section,
  534. Integer.MIN_VALUE );
  535. }
  536. protected void processTable( HWPFDocumentCore hwpfDocument, Element flow,
  537. Table table )
  538. {
  539. Element tableHeader = htmlDocumentFacade.createTableHeader();
  540. Element tableBody = htmlDocumentFacade.createTableBody();
  541. final int[] tableCellEdges = WordToHtmlUtils
  542. .buildTableCellEdgesArray( table );
  543. final int tableRows = table.numRows();
  544. int maxColumns = Integer.MIN_VALUE;
  545. for ( int r = 0; r < tableRows; r++ )
  546. {
  547. maxColumns = Math.max( maxColumns, table.getRow( r ).numCells() );
  548. }
  549. for ( int r = 0; r < tableRows; r++ )
  550. {
  551. TableRow tableRow = table.getRow( r );
  552. Element tableRowElement = htmlDocumentFacade.createTableRow();
  553. StringBuilder tableRowStyle = new StringBuilder();
  554. WordToHtmlUtils.addTableRowProperties( tableRow, tableRowStyle );
  555. // index of current element in tableCellEdges[]
  556. int currentEdgeIndex = 0;
  557. final int rowCells = tableRow.numCells();
  558. for ( int c = 0; c < rowCells; c++ )
  559. {
  560. TableCell tableCell = tableRow.getCell( c );
  561. if ( tableCell.isVerticallyMerged()
  562. && !tableCell.isFirstVerticallyMerged() )
  563. {
  564. currentEdgeIndex += getNumberColumnsSpanned(
  565. tableCellEdges, currentEdgeIndex, tableCell );
  566. continue;
  567. }
  568. Element tableCellElement;
  569. if ( tableRow.isTableHeader() )
  570. {
  571. tableCellElement = htmlDocumentFacade
  572. .createTableHeaderCell();
  573. }
  574. else
  575. {
  576. tableCellElement = htmlDocumentFacade.createTableCell();
  577. }
  578. StringBuilder tableCellStyle = new StringBuilder();
  579. WordToHtmlUtils.addTableCellProperties( tableRow, tableCell,
  580. r == 0, r == tableRows - 1, c == 0, c == rowCells - 1,
  581. tableCellStyle );
  582. int colSpan = getNumberColumnsSpanned( tableCellEdges,
  583. currentEdgeIndex, tableCell );
  584. currentEdgeIndex += colSpan;
  585. if ( colSpan == 0 )
  586. continue;
  587. if ( colSpan != 1 )
  588. tableCellElement.setAttribute( "colspan",
  589. String.valueOf( colSpan ) );
  590. final int rowSpan = getNumberRowsSpanned( table,
  591. tableCellEdges, r, c, tableCell );
  592. if ( rowSpan > 1 )
  593. tableCellElement.setAttribute( "rowspan",
  594. String.valueOf( rowSpan ) );
  595. processParagraphes( hwpfDocument, tableCellElement, tableCell,
  596. table.getTableLevel() );
  597. if ( !tableCellElement.hasChildNodes() )
  598. {
  599. tableCellElement.appendChild( htmlDocumentFacade
  600. .createParagraph() );
  601. }
  602. if ( tableCellStyle.length() > 0 )
  603. htmlDocumentFacade.addStyleClass( tableCellElement,
  604. tableCellElement.getTagName(),
  605. tableCellStyle.toString() );
  606. tableRowElement.appendChild( tableCellElement );
  607. }
  608. if ( tableRowStyle.length() > 0 )
  609. tableRowElement.setAttribute( "class", htmlDocumentFacade
  610. .getOrCreateCssClass( "r", tableRowStyle.toString() ) );
  611. if ( tableRow.isTableHeader() )
  612. {
  613. tableHeader.appendChild( tableRowElement );
  614. }
  615. else
  616. {
  617. tableBody.appendChild( tableRowElement );
  618. }
  619. }
  620. final Element tableElement = htmlDocumentFacade.createTable();
  621. tableElement
  622. .setAttribute(
  623. "class",
  624. htmlDocumentFacade
  625. .getOrCreateCssClass( "t",
  626. "table-layout:fixed;border-collapse:collapse;border-spacing:0;" ) );
  627. if ( tableHeader.hasChildNodes() )
  628. {
  629. tableElement.appendChild( tableHeader );
  630. }
  631. if ( tableBody.hasChildNodes() )
  632. {
  633. tableElement.appendChild( tableBody );
  634. flow.appendChild( tableElement );
  635. }
  636. else
  637. {
  638. logger.log( POILogger.WARN, "Table without body starting at [",
  639. Integer.valueOf( table.getStartOffset() ), "; ",
  640. Integer.valueOf( table.getEndOffset() ), ")" );
  641. }
  642. }
  643. }