View Javadoc
1   /*
2    * Copyright (c) 2002-2017 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * http://www.apache.org/licenses/LICENSE-2.0
8    *
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 com.gargoylesoftware.htmlunit.html;
16  
17  import java.util.Iterator;
18  import java.util.List;
19  import java.util.regex.Pattern;
20  
21  import org.apache.commons.lang3.StringUtils;
22  
23  import com.gargoylesoftware.htmlunit.Page;
24  import com.gargoylesoftware.htmlunit.SgmlPage;
25  import com.gargoylesoftware.htmlunit.javascript.host.Element;
26  
27  /**
28   * Utility to handle conversion from HTML code to string.
29   *
30   * @author Marc Guillemot
31   * @author Ahmed Ashour
32   * @author Ronald Brill
33   * @author Rob Kodey
34   */
35  public class HtmlSerializer {
36      /** Indicates a block. Will be rendered as line separator (multiple block marks are ignored) */
37      protected static final String AS_TEXT_BLOCK_SEPARATOR = "§bs§";
38      private static final int AS_TEXT_BLOCK_SEPARATOR_LENGTH = AS_TEXT_BLOCK_SEPARATOR.length();
39  
40      /** Indicates a new line. Will be rendered as line separator. */
41      protected static final String AS_TEXT_NEW_LINE = "§nl§";
42      private static final int AS_TEXT_NEW_LINE_LENGTH = AS_TEXT_NEW_LINE.length();
43  
44      /** Indicates a non blank that can't be trimmed or reduced. */
45      protected static final String AS_TEXT_BLANK = "§blank§";
46      /** Indicates a tab. */
47      protected static final String AS_TEXT_TAB = "§tab§";
48  
49      private static final Pattern TEXT_AREA_PATTERN = Pattern.compile("\r?\n");
50  
51      private boolean appletEnabled_;
52      private boolean ignoreMaskedElements_ = true;
53  
54      /**
55       * Converts an HTML node to text.
56       * @param node a node
57       * @return the text representation according to the setting of this serializer
58       */
59      public String asText(final DomNode node) {
60          appletEnabled_ = node.getPage().getWebClient().getOptions().isAppletEnabled();
61  
62          final StringBuilder builder = new StringBuilder();
63          appendNode(builder, node);
64          final String response = builder.toString();
65          return cleanUp(response);
66      }
67  
68      /**
69       * Reduce the whitespace and do some more cleanup.
70       * @param text the text to clean up
71       * @return the new text
72       */
73      protected String cleanUp(String text) {
74          // ignore <br/> at the end of a block
75          text = reduceWhitespace(text);
76          text = StringUtils.replace(text, AS_TEXT_BLANK, " ");
77          final String ls = System.lineSeparator();
78          text = StringUtils.replace(text, AS_TEXT_NEW_LINE, ls);
79          text = StringUtils.replace(text, AS_TEXT_BLOCK_SEPARATOR, ls);
80          text = StringUtils.replace(text, AS_TEXT_TAB, "\t");
81  
82          return text;
83      }
84  
85      private static String reduceWhitespace(String text) {
86          text = trim(text);
87  
88          // remove white spaces before or after block separators
89          text = reduceWhiteSpaceAroundBlockSeparator(text);
90  
91          // remove leading block separators
92          while (text.startsWith(AS_TEXT_BLOCK_SEPARATOR)) {
93              text = text.substring(AS_TEXT_BLOCK_SEPARATOR_LENGTH);
94          }
95  
96          // remove trailing block separators
97          while (text.endsWith(AS_TEXT_BLOCK_SEPARATOR)) {
98              text = text.substring(0, text.length() - AS_TEXT_BLOCK_SEPARATOR_LENGTH);
99          }
100         text = trim(text);
101 
102         final StringBuilder builder = new StringBuilder(text.length());
103 
104         boolean whitespace = false;
105         for (final char ch : text.toCharArray()) {
106 
107             // Translate non-breaking space to regular space.
108             if (ch == (char) 160) {
109                 builder.append(' ');
110                 whitespace = false;
111             }
112             else {
113                 if (whitespace) {
114                     if (!isSpace(ch)) {
115                         builder.append(ch);
116                         whitespace = false;
117                     }
118                 }
119                 else {
120                     if (isSpace(ch)) {
121                         whitespace = true;
122                         builder.append(' ');
123                     }
124                     else {
125                         builder.append(ch);
126                     }
127                 }
128             }
129         }
130         return builder.toString();
131     }
132 
133     private static boolean isSpace(final char ch) {
134         return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\f' || ch == '\r';
135     }
136 
137     private static String trim(String string) {
138         int length = string.length();
139 
140         int start = 0;
141         while (start != length && isSpace(string.charAt(start))) {
142             start++;
143         }
144         if (start != 0) {
145             string = string.substring(start);
146             length = string.length();
147         }
148 
149         if (length != 0) {
150             int end = length;
151             while (end != 0 && isSpace(string.charAt(end - 1))) {
152                 end--;
153             }
154             if (end != length) {
155                 string = string.substring(0, end);
156             }
157         }
158 
159         return string;
160     }
161 
162     private static String reduceWhiteSpaceAroundBlockSeparator(final String text) {
163         int p0 = text.indexOf(AS_TEXT_BLOCK_SEPARATOR);
164         if (p0 == -1) {
165             return text;
166         }
167 
168         final int length = text.length();
169         if (length <= AS_TEXT_BLOCK_SEPARATOR_LENGTH) {
170             return text;
171         }
172 
173         final StringBuilder result = new StringBuilder(length);
174         int start = 0;
175         while (p0 != -1) {
176             int p1 = p0 + AS_TEXT_BLOCK_SEPARATOR_LENGTH;
177             while (p0 != start && isSpace(text.charAt(p0 - 1))) {
178                 p0--;
179             }
180             if (p0 >= AS_TEXT_NEW_LINE_LENGTH && text.startsWith(AS_TEXT_NEW_LINE, p0 - AS_TEXT_NEW_LINE_LENGTH)) {
181                 p0 = p0 - AS_TEXT_NEW_LINE_LENGTH;
182             }
183             result.append(text.substring(start, p0)).append(AS_TEXT_BLOCK_SEPARATOR);
184 
185             while (p1 < length && isSpace(text.charAt(p1))) {
186                 p1++;
187             }
188             start = p1;
189 
190             // ignore duplicates
191             p0 = text.indexOf(AS_TEXT_BLOCK_SEPARATOR, start);
192             while (p0 != -1 && p0 == start) {
193                 start += AS_TEXT_BLOCK_SEPARATOR_LENGTH;
194                 p0 = text.indexOf(AS_TEXT_BLOCK_SEPARATOR, start);
195             }
196         }
197         if (start < length) {
198             result.append(text.substring(start));
199         }
200         return result.toString();
201     }
202 
203     /**
204      * Iterate over all Children and call appendNode() for every.
205      *
206      * @param builder the StringBuilder to add to
207      * @param node the node to process
208      */
209     protected void appendChildren(final StringBuilder builder, final DomNode node) {
210         for (final DomNode child : node.getChildren()) {
211             appendNode(builder, child);
212         }
213     }
214 
215     /**
216      * The core distribution method call the different appendXXX
217      * methods depending on the type of the given node.
218      *
219      * @param builder the StringBuilder to add to
220      * @param node the node to process
221      */
222     protected void appendNode(final StringBuilder builder, final DomNode node) {
223         if (node instanceof DomText) {
224             appendText(builder, (DomText) node);
225         }
226         else if (node instanceof DomComment) {
227             appendComment(builder, (DomComment) node);
228         }
229         else if (node instanceof HtmlApplet && appletEnabled_) {
230             appendApplet(builder, (HtmlApplet) node);
231         }
232         else if (node instanceof HtmlBreak) {
233             appendBreak(builder, (HtmlBreak) node);
234         }
235         else if (node instanceof HtmlHiddenInput) {
236             appendHiddenInput(builder, (HtmlHiddenInput) node);
237         }
238         else if (node instanceof HtmlScript) {
239             appendScript(builder, (HtmlScript) node);
240         }
241         else if (node instanceof HtmlStyle) {
242             appendStyle(builder, (HtmlStyle) node);
243         }
244         else if (node instanceof HtmlNoFrames) {
245             appendNoFrames(builder, (HtmlNoFrames) node);
246         }
247         else if (node instanceof HtmlTextArea) {
248             appendTextArea(builder, (HtmlTextArea) node);
249         }
250         else if (node instanceof HtmlTitle) {
251             appendTitle(builder, (HtmlTitle) node);
252         }
253         else if (node instanceof HtmlTableRow) {
254             appendTableRow(builder, (HtmlTableRow) node);
255         }
256         else if (node instanceof HtmlSelect) {
257             appendSelect(builder, (HtmlSelect) node);
258         }
259         else if (node instanceof HtmlSubmitInput) {
260             appendSubmitInput(builder, (HtmlSubmitInput) node);
261         }
262         else if (node instanceof HtmlResetInput) {
263             appendResetInput(builder, (HtmlResetInput) node);
264         }
265         else if (node instanceof HtmlCheckBoxInput) {
266             doAppendCheckBoxInput(builder, (HtmlCheckBoxInput) node);
267         }
268         else if (node instanceof HtmlRadioButtonInput) {
269             doAppendRadioButtonInput(builder, (HtmlRadioButtonInput) node);
270         }
271         else if (node instanceof HtmlInput) {
272             appendInput(builder, (HtmlInput) node);
273         }
274         else if (node instanceof HtmlTable) {
275             appendTable(builder, (HtmlTable) node);
276         }
277         else if (node instanceof HtmlOrderedList) {
278             appendOrderedList(builder, (HtmlOrderedList) node);
279         }
280         else if (node instanceof HtmlUnorderedList) {
281             appendUnorderedList(builder, (HtmlUnorderedList) node);
282         }
283         else if (node instanceof HtmlPreformattedText) {
284             appendPreformattedText(builder, (HtmlPreformattedText) node);
285         }
286         else if (node instanceof HtmlInlineFrame) {
287             appendInlineFrame(builder, (HtmlInlineFrame) node);
288         }
289         else if (node instanceof HtmlNoScript && node.getPage().getWebClient().getOptions().isJavaScriptEnabled()) {
290             appendNoScript(builder, (HtmlNoScript) node);
291         }
292         else {
293             appendDomNode(builder, node);
294         }
295     }
296 
297     /**
298      * Process {@link HtmlHiddenInput}.
299      *
300      * @param builder the StringBuilder to add to
301      * @param domNode the target to process
302      */
303     protected void appendDomNode(final StringBuilder builder, final DomNode domNode) {
304         final boolean block;
305         final Object scriptableObject = domNode.getScriptableObject();
306         if (domNode instanceof HtmlBody) {
307             block = false;
308         }
309         else if (scriptableObject instanceof Element) {
310             final Element element = (Element) scriptableObject;
311             final String display = element.getWindow().getComputedStyle(element, null).getDisplay(true);
312             block = "block".equals(display);
313         }
314         else {
315             block = false;
316         }
317 
318         if (block) {
319             builder.append(AS_TEXT_BLOCK_SEPARATOR);
320         }
321         appendChildren(builder, domNode);
322         if (block) {
323             builder.append(AS_TEXT_BLOCK_SEPARATOR);
324         }
325     }
326 
327     /**
328      * Process {@link HtmlHiddenInput}.
329      *
330      * @param builder the StringBuilder to add to
331      * @param htmlHiddenInput the target to process
332      */
333     protected void appendHiddenInput(final StringBuilder builder, final HtmlHiddenInput htmlHiddenInput) {
334         // nothing to do
335     }
336 
337     /**
338      * Process {@link HtmlScript}.
339      *
340      * @param builder the StringBuilder to add to
341      * @param htmlScript the target to process
342      */
343     protected void appendScript(final StringBuilder builder, final HtmlScript htmlScript) {
344         // nothing to do
345     }
346 
347     /**
348      * Process {@link HtmlStyle}.
349      *
350      * @param builder the StringBuilder to add to
351      * @param htmlStyle the target to process
352      */
353     protected void appendStyle(final StringBuilder builder, final HtmlStyle htmlStyle) {
354         // nothing to do
355     }
356 
357     /**
358      * Process {@link HtmlNoScript}.
359      *
360      * @param builder the StringBuilder to add to
361      * @param htmlNoScript the target to process
362      */
363     protected void appendNoScript(final StringBuilder builder, final HtmlNoScript htmlNoScript) {
364         // nothing to do
365     }
366 
367     /**
368      * Process {@link HtmlNoFrames}.
369      *
370      * @param builder the StringBuilder to add to
371      * @param htmlNoFrames the target to process
372      */
373     protected void appendNoFrames(final StringBuilder builder, final HtmlNoFrames htmlNoFrames) {
374         // nothing to do
375     }
376 
377     /**
378      * Process {@link HtmlSubmitInput}.
379      *
380      * @param builder the StringBuilder to add to
381      * @param htmlSubmitInput the target to process
382      */
383     protected void appendSubmitInput(final StringBuilder builder, final HtmlSubmitInput htmlSubmitInput) {
384         builder.append(htmlSubmitInput.asText());
385     }
386 
387     /**
388      * Process {@link HtmlInput}.
389      *
390      * @param builder the StringBuilder to add to
391      * @param htmlInput the target to process
392      */
393     protected void appendInput(final StringBuilder builder, final HtmlInput htmlInput) {
394         builder.append(htmlInput.getValueAttribute());
395     }
396 
397     /**
398      * Process {@link HtmlResetInput}.
399      *
400      * @param builder the StringBuilder to add to
401      * @param htmlResetInput the target to process
402      */
403     protected void appendResetInput(final StringBuilder builder, final HtmlResetInput htmlResetInput) {
404         builder.append(htmlResetInput.asText());
405     }
406 
407     /**
408      * Process {@link HtmlUnorderedList}.
409      * @param builder the StringBuilder to add to
410      * @param htmlUnorderedList the target to process
411      */
412     protected void appendUnorderedList(final StringBuilder builder, final HtmlUnorderedList htmlUnorderedList) {
413         builder.append(AS_TEXT_BLOCK_SEPARATOR);
414         boolean first = true;
415         for (final DomNode item : htmlUnorderedList.getChildren()) {
416             if (!first) {
417                 builder.append(AS_TEXT_BLOCK_SEPARATOR);
418             }
419             first = false;
420             appendNode(builder, item);
421         }
422         builder.append(AS_TEXT_BLOCK_SEPARATOR);
423     }
424 
425     /**
426      * Process {@link HtmlTitle}.
427      * @param builder the StringBuilder to add to
428      * @param htmlTitle the target to process
429      */
430     protected void appendTitle(final StringBuilder builder, final HtmlTitle htmlTitle) {
431         // optimized version
432         // for the title there is no need to check the visibility
433         // of the containing dom text;
434         // this optimization defers the load of the style sheets
435         final DomNode child = htmlTitle.getFirstChild();
436         if (child instanceof DomText) {
437             builder.append(((DomText) child).getData());
438             builder.append(AS_TEXT_BLOCK_SEPARATOR);
439         }
440     }
441 
442     /**
443      * Process {@link HtmlTableRow}.
444      *
445      * @param builder the StringBuilder to add to
446      * @param htmlTableRow the target to process
447      */
448     protected void appendTableRow(final StringBuilder builder, final HtmlTableRow htmlTableRow) {
449         boolean first = true;
450         for (final HtmlTableCell cell : htmlTableRow.getCells()) {
451             if (!first) {
452                 builder.append(AS_TEXT_TAB);
453             }
454             else {
455                 first = false;
456             }
457             appendChildren(builder, cell); // trim?
458         }
459     }
460 
461     /**
462      * Process {@link HtmlTextArea}.
463      *
464      * @param builder the StringBuilder to add to
465      * @param htmlTextArea the target to process
466      */
467     protected void appendTextArea(final StringBuilder builder, final HtmlTextArea htmlTextArea) {
468         if (isVisible(htmlTextArea)) {
469             String text = htmlTextArea.getText();
470             text = StringUtils.stripEnd(text, null);
471             text = TEXT_AREA_PATTERN.matcher(text).replaceAll(AS_TEXT_NEW_LINE);
472             text = StringUtils.replace(text, "\r", AS_TEXT_NEW_LINE);
473             text = StringUtils.replace(text, " ", AS_TEXT_BLANK);
474             builder.append(text);
475         }
476     }
477 
478     /**
479      * Process {@link HtmlTable}.
480      *
481      * @param builder the StringBuilder to add to
482      * @param htmlTable the target to process
483      */
484     protected void appendTable(final StringBuilder builder, final HtmlTable htmlTable) {
485         builder.append(AS_TEXT_BLOCK_SEPARATOR);
486         final String caption = htmlTable.getCaptionText();
487         if (caption != null) {
488             builder.append(caption);
489             builder.append(AS_TEXT_BLOCK_SEPARATOR);
490         }
491 
492         boolean first = true;
493 
494         // first thead has to be displayed first and first tfoot has to be displayed last
495         final HtmlTableHeader tableHeader = htmlTable.getHeader();
496         if (tableHeader != null) {
497             first = appendTableRows(builder, tableHeader.getRows(), true, null, null);
498         }
499         final HtmlTableFooter tableFooter = htmlTable.getFooter();
500 
501         final List<HtmlTableRow> tableRows = htmlTable.getRows();
502         first = appendTableRows(builder, tableRows, first, tableHeader, tableFooter);
503 
504         if (tableFooter != null) {
505             first = appendTableRows(builder, tableFooter.getRows(), first, null, null);
506         }
507         else if (tableRows.isEmpty()) {
508             final DomNode firstChild = htmlTable.getFirstChild();
509             if (firstChild != null) {
510                 appendNode(builder, firstChild);
511             }
512         }
513 
514         builder.append(AS_TEXT_BLOCK_SEPARATOR);
515     }
516 
517     /**
518      * Process {@link HtmlTableRow}.
519      *
520      * @param builder the StringBuilder to add to
521      * @param rows the rows
522      * @param first if true this is the first one
523      * @param skipParent1 skip row if the parent is this
524      * @param skipParent2 skip row if the parent is this
525      * @return true if this was the first one
526      */
527     protected boolean appendTableRows(final StringBuilder builder,
528             final List<HtmlTableRow> rows, boolean first, final TableRowGroup skipParent1,
529             final TableRowGroup skipParent2) {
530         for (final HtmlTableRow row : rows) {
531             if (row.getParentNode() == skipParent1 || row.getParentNode() == skipParent2) {
532                 continue;
533             }
534             if (!first) {
535                 builder.append(AS_TEXT_BLOCK_SEPARATOR);
536             }
537             first = false;
538             appendTableRow(builder, row);
539         }
540         return first;
541     }
542 
543     /**
544      * Process {@link HtmlSelect}.
545      *
546      * @param builder the StringBuilder to add to
547      * @param htmlSelect the target to process
548      */
549     protected void appendSelect(final StringBuilder builder, final HtmlSelect htmlSelect) {
550         final List<HtmlOption> options;
551         if (htmlSelect.isMultipleSelectEnabled()) {
552             options = htmlSelect.getOptions();
553         }
554         else {
555             options = htmlSelect.getSelectedOptions();
556         }
557 
558         for (final Iterator<HtmlOption> i = options.iterator(); i.hasNext();) {
559             final HtmlOption currentOption = i.next();
560             appendNode(builder, currentOption);
561             if (i.hasNext()) {
562                 builder.append(AS_TEXT_BLOCK_SEPARATOR);
563             }
564         }
565     }
566 
567     /**
568      * Process {@link HtmlOrderedList} taking care to numerate it.
569      *
570      * @param builder the StringBuilder to add to
571      * @param htmlOrderedList the OL element
572      */
573     protected void appendOrderedList(final StringBuilder builder, final HtmlOrderedList htmlOrderedList) {
574         builder.append(AS_TEXT_BLOCK_SEPARATOR);
575         boolean first = true;
576         int i = 1;
577         for (final DomNode item : htmlOrderedList.getChildren()) {
578             if (!first) {
579                 builder.append(AS_TEXT_BLOCK_SEPARATOR);
580             }
581             first = false;
582             if (item instanceof HtmlListItem) {
583                 builder.append(Integer.toString(i++));
584                 builder.append(". ");
585                 appendChildren(builder, item);
586             }
587             else {
588                 appendNode(builder, item);
589             }
590         }
591         builder.append(AS_TEXT_BLOCK_SEPARATOR);
592     }
593 
594     /**
595      * Process {@link HtmlPreformattedText}.
596      *
597      * @param builder the StringBuilder to add to
598      * @param htmlPreformattedText the target to process
599      */
600     protected void appendPreformattedText(final StringBuilder builder,
601             final HtmlPreformattedText htmlPreformattedText) {
602         if (isVisible(htmlPreformattedText)) {
603             builder.append(AS_TEXT_BLOCK_SEPARATOR);
604             String text = htmlPreformattedText.getTextContent();
605             text = StringUtils.replace(text, "\t", AS_TEXT_TAB);
606             text = StringUtils.replace(text, " ", AS_TEXT_BLANK);
607             text = TEXT_AREA_PATTERN.matcher(text).replaceAll(AS_TEXT_NEW_LINE);
608             text = StringUtils.replace(text, "\r", AS_TEXT_NEW_LINE);
609             builder.append(text);
610             builder.append(AS_TEXT_BLOCK_SEPARATOR);
611         }
612     }
613 
614     /**
615      * Process {@link HtmlInlineFrame}.
616      *
617      * @param builder the StringBuilder to add to
618      * @param htmlInlineFrame the target to process
619      */
620     protected void appendInlineFrame(final StringBuilder builder,
621             final HtmlInlineFrame htmlInlineFrame) {
622         if (isVisible(htmlInlineFrame)) {
623             builder.append(AS_TEXT_BLOCK_SEPARATOR);
624             final Page page = htmlInlineFrame.getEnclosedPage();
625             if (page instanceof SgmlPage) {
626                 builder.append(((SgmlPage) page).asText());
627             }
628             builder.append(AS_TEXT_BLOCK_SEPARATOR);
629         }
630     }
631 
632     /**
633      * Process {@link DomText}.
634      *
635      * @param builder the StringBuilder to add to
636      * @param domText the target to process
637      */
638     protected void appendText(final StringBuilder builder, final DomText domText) {
639         final DomNode parent = domText.getParentNode();
640         if (parent == null || parent instanceof HtmlTitle || isVisible(parent)) {
641             builder.append(domText.getData());
642         }
643     }
644 
645     /**
646      * Process {@link DomComment}.
647      *
648      * @param builder the StringBuilder to add to
649      * @param domComment the target to process
650      */
651     protected void appendComment(final StringBuilder builder, final DomComment domComment) {
652         // nothing to do
653     }
654 
655     /**
656      * Process {@link HtmlApplet}.
657      *
658      * @param builder the StringBuilder to add to
659      * @param htmlApplet the target to process
660      */
661     protected void appendApplet(final StringBuilder builder, final HtmlApplet htmlApplet) {
662         // nothing to do
663     }
664 
665     /**
666      * Process {@link HtmlBreak}.
667      *
668      * @param builder the StringBuilder to add to
669      * @param htmlBreak the target to process
670      */
671     protected void appendBreak(final StringBuilder builder, final HtmlBreak htmlBreak) {
672         builder.append(AS_TEXT_NEW_LINE);
673     }
674 
675     /**
676      * Process {@link HtmlCheckBoxInput}.
677      *
678      * @param builder the StringBuilder to add to
679      * @param htmlCheckBoxInput the target to process
680      */
681     protected void doAppendCheckBoxInput(final StringBuilder builder, final HtmlCheckBoxInput htmlCheckBoxInput) {
682         if (htmlCheckBoxInput.isChecked()) {
683             builder.append("checked");
684         }
685         else {
686             builder.append("unchecked");
687         }
688     }
689 
690     /**
691      * Process {@link HtmlRadioButtonInput}.
692      *
693      * @param builder the StringBuilder to add to
694      * @param htmlRadioButtonInput the target to process
695      */
696     protected void doAppendRadioButtonInput(final StringBuilder builder,
697             final HtmlRadioButtonInput htmlRadioButtonInput) {
698         if (htmlRadioButtonInput.isChecked()) {
699             builder.append("checked");
700         }
701         else {
702             builder.append("unchecked");
703         }
704     }
705 
706     private boolean isVisible(final DomNode node) {
707         return !ignoreMaskedElements_ || node.isDisplayed();
708     }
709 
710     /**
711      * Indicates if element that are not displayed due to style settings
712      * (visibility or display) should be visible in generated text.
713      * @param ignore indicates if masked elements should be ignored or not
714      */
715     public void setIgnoreMaskedElements(final boolean ignore) {
716         ignoreMaskedElements_ = ignore;
717     }
718 }