home *** CD-ROM | disk | FTP | other *** search
/ Practical Internet Web Designer 86 / PIWD86.iso / pc / contents / dreamweaver / software / dwmx2004.exe / Disk1 / data1.cab / Configuration_En / Commands / PMModules.js < prev    next >
Encoding:
JavaScript  |  2003-09-05  |  43.7 KB  |  1,516 lines

  1. //=========================================================================================================
  2. //
  3. // Copyright 2002 Macromedia, Inc. All rights reserved.
  4. //
  5. // Feature: Paste Fix
  6. // Author:  JDH
  7. // Module:  PMModules.js
  8. // Purpose:    The modules for the Paste Fix pipeline.
  9. // Updates:
  10. //    5/17/02 - Started file control
  11. //  5/30/02 - Added performance upgrade enhancements
  12. //  5/31/02 - Added extensive comments
  13. //  6/3/02 - Added support for STRONG and EM, as well as more comments
  14. //
  15. //=========================================================================================================
  16.  
  17.  
  18. // The main purpose of the the Paste Fix system is to provide an adaptable filter to take vendor specific
  19. // HTML and massage it into HTML that is reasonable, readable, effecient, and very importantly, editable in
  20. // the host MM application.  To that end we apply a series of scanners and filter to the HTML in a specific
  21. // series. The scanners analyze the document, looking for signatures, so that the filters can act more
  22. // specifically.  The filters both remove and alter the HTML to match needs of the application, and the current
  23. // security settings.
  24.  
  25. // Filters come in two main flavors; cleaning filters, and conversion filters.  Cleaning filters simply remove
  26. // tags, attributes, styles, directives, and content that is not editable, or required by, the host application.
  27. // Conversion filters analyze the tags and attempt to recreate the same effect as the vendor specific tag with
  28. // standard HTML.  For example, if a TD tag references a class, we look at the tag and the class, and create a
  29. // series of font, bold, italic, etc. tags to recreate the effect of the class. This makes the document more easily
  30. // editable by standard HTML editors.
  31.  
  32. // The key point here is that the whole system is designed to remove what it doesn't understand.  So to add new
  33. // items that the system will understand you will most likely have to add support in several places.  For example,
  34. // to add support for borders around tables you will need to make sure the style filter (if you implement the
  35. // borders as styles) does not filter out the styles you have just added.  To do this you add style specifications
  36. // to some of the global tables below.
  37.  
  38. // If your intent is to add a new security mode then you will most likely have to touch the run method of
  39. // every filter to either bring it into the stream when your mode is ON, or remove it from the stream when
  40. // your mode is on.  In addition you may have to create new sets of tag, attribute or style filters like those
  41. // in the globals section. The ETO mode is an example here.  In the basic case the 'NORMAL' tag retention
  42. // settings are used, and in ETO mode teh ETO tag retention set is used.
  43.  
  44.  
  45. //=========================================================================================================
  46. // Globals
  47. //=========================================================================================================
  48.  
  49. // Listed below are sets of tag, attributes and styles that are either to be remove or retained through
  50. // the operation of the filter.  Each associative array, or nested set of associative array, is used to
  51. // configure a filter.
  52.  
  53. // NOTE: Associative arrays were used here to speed up access in the filters.  The value of '1' is used
  54. // simply as a placeholder and has no semantic value in any case.
  55.  
  56.  
  57. // The list of tags that will be retained in the normal operating mode of the filter
  58.  
  59. var RETAIN_TAGS_NORMAL = {
  60.     html: 1, body: 1, table: 1, td: 1, tr: 1, thead: 1,
  61.     span: 1, b: 1, i: 1, p: 1,
  62.     ul: 1, li: 1, ol: 1,
  63.     h1: 1, h2: 1, h3: 1, h4: 1, h5: 1, h6: 1,
  64.     img: 1, a: 1, br: 1
  65. };
  66.  
  67. // The list of tags that will be retained in the low operating mode of the filter
  68.  
  69. var RETAIN_TAGS_LOW = {
  70.     html: 1, body: 1, table: 1, td: 1, tr: 1, thead: 1,
  71.     b: 1, i: 1, p: 1,
  72.     ul: 1, li: 1, ol: 1,
  73.     h1: 1, h2: 1, h3: 1, h4: 1, h5: 1, h6: 1,
  74.     img: 1, a: 1, br: 1
  75. };
  76.  
  77. // The list of tags that will be retained in the ETO mode of the filter
  78.  
  79. var RETAIN_TAGS_ETO = {
  80.     html: 1, body: 1, p: 1
  81. };
  82.  
  83. // Some tags will want to be retained through the midsection of the filter, but removed at the end.
  84. // Those tags are listed here.
  85.  
  86. var PRS_REMOVE_TAGS = {
  87.     thead: 1
  88. };
  89.  
  90. // These tags are to removed if they have no attributes.
  91.  
  92. var PRS_REMOVE_IF_NO_ATTRIBUTES = {
  93.     span: 1, font: 1, 'a': 1
  94. };
  95.  
  96. // Tags listed here are supposed to be removed if they contain no interior.
  97.  
  98. var PRS_REMOVE_IF_EMPTY = {
  99.     p: 1
  100. };
  101.  
  102. // The tags that the Decomposer should not inspect
  103.  
  104. var DC_IGNORE_TAGS = { table: 1, img: 1, col: 1, br: 1 };
  105.  
  106.  
  107. // These are the attributes to be retained for each type of tag.  The primary key is the tag name
  108. // (coerced to lower case), then within that entry there should be an associative array where the
  109. // keys are the attributes to be retained.
  110.  
  111. var RUA_TAG_SPECIFIC_ATTRIBUTES = {
  112.     span: { style: 1 },
  113.     font: { style: 1, face: 1, size: 1 },
  114.     ul: { style: 1 },
  115.     ol: { style: 1 },
  116.     td: { 'width': 1, 'bgcolor': 1, 'class': 1, 'align': 1, 'valign': 1, 'colspan': 1 },
  117.     th: { 'width': 1, 'bgcolor': 1, 'class': 1, 'align': 1, 'valign': 1, 'scope': 1 },
  118.     table: { cellspacing: 1, cellpadding: 1 },
  119.     p: { align: 1, 'class': 1, style: 1 },
  120.     img: { src: 1, height: 1, width: 1, alt: 1 },
  121.     a: { href: 1, name: 1 }
  122. };
  123.  
  124. // Within the style attribute of any tag you can specify what values are to be retained through the
  125. // filter.  If your attribute isn't listed here then it will be stripped by the filter, so you will
  126. // want to add an entry here for the tag and for the specific style attribute.
  127.  
  128. var RUA_TAG_SPECIFIC_STYLES = {
  129.     font: { "font-family": 1, "font-size": 1, "color": 1 },
  130.     span: { "font-family": 1, "font-size": 1, "color": 1 },
  131.     p: { }
  132. };
  133.  
  134.  
  135.  
  136. //=========================================================================================================
  137. // Filter modules
  138. //=========================================================================================================
  139.  
  140.  
  141. //---------------------------------------------------------------------------------------------------------
  142. // ParseMetaTags
  143. //---------------------------------------------------------------------------------------------------------
  144.  
  145. // The ParseMetaTags module finds all of the meta tags and puts that information
  146. // into the context.
  147.  
  148. function ParseMetaTags() { }
  149.  
  150. // The module API
  151.  
  152. ParseMetaTags.prototype.run = ParseMetaTags_run;
  153. ParseMetaTags.prototype.getPhase = ParseMetaTags_getPhase;
  154.  
  155. function ParseMetaTags_run( context )
  156. {
  157.     // Ignore non-HTML content
  158.     
  159.     if ( context.getContentType() != CONTENT_TYPE_HTML )
  160.         return true;
  161.  
  162.     context.debugInformation( "ParseMetaTags", ">> run" );
  163.  
  164.     // Build the meta tag scanner
  165.  
  166.     var metaParser = new GetMetaTagsScanner();
  167.     var metaTags = metaParser.scan( context.getClipText() );
  168.  
  169.     // Set the context with the current value of the meta tags
  170.  
  171.     for( var key in metaTags )
  172.     {
  173.         context.setMeta( key, metaTags[ key ] );
  174.     }
  175.  
  176.     context.debugInformation( "ParseMetaTags", "<< run" );
  177.  
  178.     return true;
  179. }
  180.  
  181. function ParseMetaTags_getPhase() { return PHASE_ANALYZE; }
  182.  
  183.  
  184. //---------------------------------------------------------------------------------------------------------
  185. // FixupMSGarbage
  186. //---------------------------------------------------------------------------------------------------------
  187.  
  188. // FixupMSGarbage fixes various problems with the HTML generated by the MSWordProcessingApplication
  189. // and the MSSpreadsheetApplication.
  190.  
  191. function FixupMSGarbage() { }
  192.  
  193. // The module API
  194.  
  195. FixupMSGarbage.prototype.run = FixupMSGarbage_run;
  196. FixupMSGarbage.prototype.getPhase = FixupMSGarbage_getPhase;
  197.  
  198. function FixupMSGarbage_run( context )
  199. {
  200.     // Dump out if we are not filter
  201.  
  202.     if ( context.settingDefined( SETTINGS_NO_FILTER ) )
  203.         return true;
  204.  
  205.     // Ignore non-HTML content
  206.     
  207.     if ( context.getContentType() != CONTENT_TYPE_HTML )
  208.         return true;
  209.  
  210.     context.debugInformation( "FixupMSGarbage", ">> run" );
  211.  
  212.     // Get the document
  213.     
  214.     var html = context.getClipText();
  215.  
  216.     // Turn MS specific list items into reasonable plain text with paragraph
  217.     // markup
  218.  
  219.     var listScanner = new ParseSupportListsScanner();
  220.     html = listScanner.scan( html, context );
  221.  
  222.     // Remove everything from the HTML except the section within the fragment
  223.  
  224.     var findClippingScanner = new FindClippingScanner();
  225.     html = findClippingScanner.scan( html, context );
  226.  
  227.     // Remove any conditionals, but retain images within the conditionals
  228.  
  229.     var conditionalScanner = new RemoveConditionalsScanner( { img: 1 } );
  230.     html = conditionalScanner.scan( html, context );
  231.  
  232.     if ( context.getOriginApplication() == "word" || context.getOriginApplication() == "excel" )
  233.     {
  234.         var remHiddenSpansScanner = new RemoveHiddenSpansScanner( );
  235.         html = remHiddenSpansScanner.scan( html, context );
  236.     }
  237.  
  238.     // In the case of an MSSpreadSheetApplication add the table element into the HTML
  239.  
  240.     var fixTable = false;
  241.  
  242.     if ( html.match( /\<\!\-\-(\s*)StartFragment(\s*)\-\-\>(\s*)<tr/i ) )
  243.         fixTable = true;
  244.  
  245.     if ( context.getOriginApplication() == "excel" )
  246.     {
  247.         if ( ! html.match( /\<\!\-\-(\s*)StartFragment(\s*)\-\-\>(\s*)<div/i ) )
  248.             fixTable = true;
  249.     }
  250.  
  251.     if ( fixTable )
  252.     {
  253.         var startFrag = "<!--StartFragment--><table cellspacing=0 cellpadding=0 class=\"";
  254.         startFrag += context.getOriginClasses().getTDClassName();
  255.         startFrag += "\">";
  256.  
  257.         html = html.replace( /\<\!\-\-(\s*)StartFragment(\s*)\-\-\>/, startFrag );
  258.         html = html.replace( /\<\!\-\-(\s*)EndFragment(\s*)\-\-\>/, "</table><!--EndFragment-->" );
  259.     }
  260.     
  261.     context.setClipText( html );
  262.  
  263.     context.debugInformation( "FixupMSGarbage", "<< run" );
  264.  
  265.     return true;
  266. }
  267.  
  268. function FixupMSGarbage_getPhase() { return PHASE_FIXUP; }
  269.  
  270.  
  271. //---------------------------------------------------------------------------------------------------------
  272. // IdentifyMSApplications
  273. //---------------------------------------------------------------------------------------------------------
  274.  
  275. // IdentifyMSApplications looks at the meta tag information and parses out where the information
  276. // came from.
  277.  
  278. function IdentifyMSApplications() { }
  279.  
  280. // The module API
  281.  
  282. IdentifyMSApplications.prototype.run = IdentifyMSApplications_run;
  283. IdentifyMSApplications.prototype.getPhase = IdentifyMSApplications_getPhase;
  284.  
  285. function IdentifyMSApplications_run( context )
  286. {
  287.     context.debugInformation( "IdentifyMSApplications", ">> run" );
  288.  
  289.     // Look for the generator meta tag
  290.  
  291.     var foundMSHTML = false;
  292.  
  293.     var name = context.getMeta( "generator" );
  294.     if ( name != null )
  295.     {
  296.         // Store the full application name
  297.  
  298.         context.setOriginApplicationFull( name );
  299.  
  300.         // Parse MSWordProcessingApplication signatures
  301.  
  302.         if ( name.match( /microsoft word/i ) )
  303.         {
  304.             context.setOriginApplication( "word" );
  305.             context.setOriginApplicationVersion( name.split( " " )[ 2 ] );
  306.             foundMSHTML = true;
  307.         }
  308.  
  309.         // Parse MSSpreadSheetApplication signatures
  310.  
  311.         if ( name.match( /microsoft excel/i ) )
  312.         {
  313.             context.setOriginApplication( "excel" );
  314.             context.setOriginApplicationVersion( name.split( " " )[ 2 ] );
  315.             foundMSHTML = true;
  316.         }
  317.  
  318.         context.debugInformation( "IDMS", "Origin Application: " + context.getOriginApplication() );
  319.         context.debugInformation( "IDMS", "Origin Application Version: " + context.getOriginApplicationVersion() );
  320.     }
  321.  
  322.     if( dreamweaver.appName == "Dreamweaver MX" )
  323.     {
  324.         if ( foundMSHTML == false )
  325.         {
  326.             context.setSetting( SETTINGS_NO_FILTER, 1 );
  327.         }
  328.     }
  329.  
  330.  
  331.     context.debugInformation( "IdentifyMSApplications", "<< run" );
  332.  
  333.     return true;
  334. }
  335.  
  336. function IdentifyMSApplications_getPhase() { return PHASE_IDENTIFICATION; }
  337.  
  338.  
  339.  
  340. //---------------------------------------------------------------------------------------------------------
  341. // RetainStructure
  342. //---------------------------------------------------------------------------------------------------------
  343.  
  344. // RetainStructure removes any tags from the HTML stream that are not required by the host
  345. // application.
  346.  
  347. function RetainStructure() { }
  348.  
  349. // The module API
  350.  
  351. RetainStructure.prototype.run = RetainStructure_run;
  352. RetainStructure.prototype.getPhase = RetainStructure_getPhase;
  353.  
  354. function RetainStructure_run( context )
  355. {
  356.     // Dump out if we are not filter
  357.  
  358.     if ( context.settingDefined( SETTINGS_NO_FILTER ) )
  359.         return true;
  360.  
  361.     // Ignore non-HTML content
  362.  
  363.     if ( context.getContentType() != CONTENT_TYPE_HTML )
  364.         return true;
  365.  
  366.     // Ignore this filter if we are not running in Contribute
  367.  
  368.     if ( ! context.settingDefined( SETTINGS_CONTRIBUTE ) )
  369.         return true;
  370.  
  371.     context.debugInformation( "RetainStructure", ">> run" );
  372.  
  373.     // Store a reference to the appropriate tag set
  374.  
  375.     var retainSet = RETAIN_TAGS_NORMAL;
  376.  
  377.     if ( context.settingDefined( SETTINGS_ETO ) )
  378.     {
  379.         context.debugInformation( "RetainStructure", "Using ETO tag set" );
  380.         retainSet = RETAIN_TAGS_ETO;
  381.     }
  382.  
  383.     if ( context.settingDefined( SETTINGS_LOW ) )
  384.     {
  385.         context.debugInformation( "RetainStructure", "Using low tag set" );
  386.         retainSet = RETAIN_TAGS_LOW;
  387.     }
  388.  
  389.     // Get the clipboard
  390.  
  391.     var html = context.getClipText();
  392.  
  393.     // Return the remove tag scanner with our set of tags
  394.     // to retain
  395.  
  396.     var remTags = new RemoveTagsScanner( retainSet, context );
  397.     html = remTags.scan( html, context );
  398.  
  399.     // Send the output back to the context
  400.  
  401.     context.setClipText( html );
  402.  
  403.     context.debugInformation( "RetainStructure", "<< run" );
  404.  
  405.     return true;
  406. }
  407.  
  408. function RetainStructure_getPhase() { return PHASE_CONFORM_STRUCTURE; }
  409.  
  410.  
  411.  
  412. //---------------------------------------------------------------------------------------------------------
  413. // RemoveUnsupportedAttributes
  414. //---------------------------------------------------------------------------------------------------------
  415.  
  416. // RemoveUnsupportedAttributes removes unwanted attributes and styles from the HTML
  417. // stream. 
  418.  
  419. function RemoveUnsupportedAttributes() { }
  420.  
  421. // The module API
  422.  
  423. RemoveUnsupportedAttributes.prototype.run = RemoveUnsupportedAttributes_run;
  424. RemoveUnsupportedAttributes.prototype.getPhase = RemoveUnsupportedAttributes_getPhase;
  425.  
  426. function RemoveUnsupportedAttributes_run( context )
  427. {
  428.     // Dump out if we are not filter
  429.  
  430.     if ( context.settingDefined( SETTINGS_NO_FILTER ) )
  431.         return true;
  432.  
  433.     // Ignore the content if it's not HTML
  434.  
  435.     if ( context.getContentType() != CONTENT_TYPE_HTML )
  436.         return true;
  437.  
  438.     // Ignore this filter if we are not running in Contribute
  439.  
  440.     if ( ! context.settingDefined( SETTINGS_CONTRIBUTE ) )
  441.         return true;
  442.  
  443.     context.debugInformation( "RemoveUnsupportedAttributes", ">> run" );
  444.  
  445.     // Get the clipboard text context
  446.  
  447.     var html = context.getClipText();
  448.  
  449.     var attributes = RUA_TAG_SPECIFIC_ATTRIBUTES;
  450.     if ( context.settingDefined( SETTINGS_CREATE_CLASSES ) &&
  451.          ! context.settingDefined( SETTINGS_LOW ) )
  452.     {
  453.         attributes[ 'p' ][ 'class' ] = 1;
  454.         attributes[ 'td' ][ 'class' ] = 1;
  455.         attributes[ 'table' ][ 'class' ] = 1;
  456.     }
  457.     if (  context.settingDefined( SETTINGS_LOW ) )
  458.     {
  459.         attributes[ 'p' ][ 'class' ] = 0;
  460.     }
  461.  
  462.     // Run the attribute remove scanner with our set of attributes and styles
  463.     // to retain
  464.  
  465.     var attributeRemover = new RemoveAttributesScanner( attributes, RUA_TAG_SPECIFIC_STYLES );
  466.     html = attributeRemover.scan( html, context );
  467.  
  468.     // Send the text back to the clipboard
  469.  
  470.     context.setClipText( html );
  471.  
  472.     context.debugInformation( "RemoveUnsupportedAttributes", "<< run" );
  473.  
  474.     return true;
  475. }
  476.  
  477. function RemoveUnsupportedAttributes_getPhase() { return PHASE_CONFORM_OTHER; }
  478.  
  479.  
  480. //---------------------------------------------------------------------------------------------------------
  481. // RemoveCSSClasses
  482. //---------------------------------------------------------------------------------------------------------
  483.  
  484. // RemoveCSSClasses removes class attributes from all tags if we are runing without CSS
  485. // or running in ETO mode.
  486.  
  487. function RemoveCSSClasses() { }
  488.  
  489. // The module API
  490.  
  491. RemoveCSSClasses.prototype.run = RemoveCSSClasses_run;
  492. RemoveCSSClasses.prototype.getPhase = RemoveCSSClasses_getPhase;
  493.  
  494. function RemoveCSSClasses_run( context )
  495. {
  496.     // Dump out if we are not filter
  497.  
  498.     if ( context.settingDefined( SETTINGS_NO_FILTER ) )
  499.         return true;
  500.  
  501.     // Ignore non-HTML content
  502.  
  503.     if ( context.getContentType() != CONTENT_TYPE_HTML )
  504.         return true;
  505.  
  506.     // Ignore this filter if we are not in Contribute
  507.  
  508.     if ( ! context.settingDefined( SETTINGS_CONTRIBUTE ) )
  509.         return true;
  510.  
  511.     // Ignore this filter if we are using CSS
  512.  
  513.     if ( context.settingDefined( SETTINGS_ETO ) == false ||
  514.          context.settingDefined( SETTINGS_LOW ) == false )
  515.         return true;
  516.  
  517.     context.debugInformation( "RemoveCSSClasses", ">> run" );
  518.  
  519.     // Get the clipboard HTML
  520.  
  521.     var html = context.getClipText();
  522.  
  523.     // Run the scanner that removes specific attributes.  In this case, remove the
  524.     // class attributes.
  525.  
  526.     var attributeRemover = new RemoveOnlyTheseAttributesScanner( { 'class': 1 } );
  527.     html = attributeRemover.scan( html, context );
  528.  
  529.     // Put back the HTML
  530.  
  531.     context.setClipText( html );
  532.  
  533.     context.debugInformation( "RemoveCSSClasses", "<< run" );
  534.  
  535.     return true;
  536. }
  537.  
  538. function RemoveCSSClasses_getPhase() { return PHASE_FIXUP; }
  539.  
  540.  
  541. //---------------------------------------------------------------------------------------------------------
  542. // RemoveParsingRequiredStructuralTags
  543. //---------------------------------------------------------------------------------------------------------
  544.  
  545. // RemoveParsingRequiredStructuralTags removes any tags from the HTML stream that were required
  546. // during the parsing (like THEAD) but are not required by the host application.
  547.  
  548. function RemoveParsingRequiredStructuralTags() {}
  549.  
  550. RemoveParsingRequiredStructuralTags.prototype = new StructureScanner();
  551.  
  552. // The module API
  553.  
  554. RemoveParsingRequiredStructuralTags.prototype.run = RemoveParsingRequiredStructuralTags_run;
  555. RemoveParsingRequiredStructuralTags.prototype.getPhase = RemoveParsingRequiredStructuralTags_getPhase;
  556.  
  557. // The structure scanner override methods
  558.  
  559. RemoveParsingRequiredStructuralTags.prototype.createTag = RemoveParsingRequiredStructuralTags_createTag;
  560. RemoveParsingRequiredStructuralTags.prototype.finalizeTag = RemoveParsingRequiredStructuralTags_finalizeTag;
  561.  
  562. function RemoveParsingRequiredStructuralTags_run( context )
  563. {
  564.     // Dump out if we are not filter
  565.  
  566.     if ( context.settingDefined( SETTINGS_NO_FILTER ) )
  567.         return true;
  568.  
  569.     // Ignore non-HTML content
  570.  
  571.     if ( context.getContentType() != CONTENT_TYPE_HTML )
  572.         return true;
  573.  
  574.     context.debugInformation( "RemoveParsingRequiredStructuralTags", ">> run" );
  575.  
  576.     // Get the clipboard HTML
  577.  
  578.     var html = context.getClipText();
  579.  
  580.     // Remove specific tags from the HTML
  581.  
  582.     var remTags = new RemoveOnlyTheseTagsScanner( PRS_REMOVE_TAGS );
  583.     html = remTags.scan( html, context );
  584.  
  585.     // Now use ourselves to remove specific tags that have specific problems,
  586.     // like no content or no attributes.
  587.  
  588.     html = this.scan( html );
  589.  
  590.     // Set the clipboard HTML
  591.  
  592.     context.setClipText( html );
  593.  
  594.     context.debugInformation( "RemoveParsingRequiredStructuralTags", "<< run" );
  595.  
  596.     return true;
  597. }
  598.  
  599. function RemoveParsingRequiredStructuralTags_getPhase() { return PHASE_FINALIZE; }
  600.  
  601. function RemoveParsingRequiredStructuralTags_createTag( tag, attributes, closed )
  602. {
  603.     // If you look at this method and the method below you will think to yourself,
  604.     // "Why not merge the two?"  Well, the requirements are different.  In the case
  605.     // of no attributes you just want to remove the tag, not the interior of the tag.
  606.     // For example, this:
  607.     //
  608.     //    <p><font>My Text</font></p>
  609.     //
  610.     // Should become:
  611.     //
  612.     //    <p>My Text</p>
  613.     //
  614.     // In the case of removing empty tags we are looking to do this:
  615.     //
  616.     //    <p>Some text</p><p></p><p>Some more text</p> 
  617.     //
  618.     // Should become:
  619.     //
  620.     //    <p>Some text</p><p>Some more text</p>
  621.     //
  622.     // But you can only do that if you know what the child HTML is, and you only
  623.     // know that in the finalize phase.
  624.  
  625.     // If this tag is on the check list then run the check
  626.  
  627.     if ( PRS_REMOVE_IF_NO_ATTRIBUTES[ tag.toLowerCase() ] > 0 )
  628.     {
  629.         // Create a 0 or 1 count of attributes
  630.         var count = 0;
  631.         for( var key in attributes )
  632.         {
  633.             count = 1;
  634.             break;
  635.         }
  636.  
  637.         // If the count is zero then return a blank tag text output
  638.         if ( count == 0 )
  639.             return { postfix: "", prefix: "" };
  640.     }
  641.  
  642.     // Otherwise, let the base class handle it
  643.  
  644.     return StructureScanner_createTag( tag, attributes, closed );
  645. }
  646.  
  647. function RemoveParsingRequiredStructuralTags_finalizeTag( tag, attributes, closed, childHTML )
  648. {
  649.     // If this tag is in the check list the check it
  650.  
  651.     if ( PRS_REMOVE_IF_EMPTY[ tag ] > 0 )
  652.     {
  653.         // If there is no interior, then kill the tag.
  654.         
  655.         if ( Utils_StripWhitespace( childHTML ).length < 1 )
  656.             return false;
  657.     }
  658.  
  659.     return true;
  660. }
  661.  
  662.  
  663.  
  664.  
  665. //---------------------------------------------------------------------------------------------------------
  666. // DecomposeClasses
  667. //---------------------------------------------------------------------------------------------------------
  668.  
  669. // DecomposeClasses turns CSS style information into inline HTML formatting.  For example, the
  670. // HTML:
  671. //
  672. //   <html><head><style><!-- MsoNormal { font-family: Arial } --></style>
  673. //   <body><p class=MsoNormal>Hello</p></body></html>
  674. //
  675. // Should become:
  676. //
  677. //   <html><head><style><!-- MsoNormal { font-family: Arial } --></style>
  678. //   <body><p><font face="Arial">Hello</p></body></html>
  679. //
  680. // This allows Contribute users to edit the HTML using Contribute.
  681.  
  682. function DecomposeClasses() {}
  683.  
  684. DecomposeClasses.prototype = new StructureScanner ();
  685.  
  686. // Methods to make use a filter component
  687.  
  688. DecomposeClasses.prototype.run = DecomposeClasses_run;
  689. DecomposeClasses.prototype.getPhase = DecomposeClasses_getPhase;
  690.  
  691. // Local methods
  692.  
  693. DecomposeClasses.prototype.parseClass = DecomposeClasses_parseClass;
  694. DecomposeClasses.prototype.buildNewTags = DecomposeClasses_buildNewTags;
  695. DecomposeClasses.prototype.buildTagSpecifications = DecomposeClasses_buildTagSpecifications;
  696. DecomposeClasses.prototype.shouldUseTargetClass = DecomposeClasses_shouldUseTargetClass;
  697.  
  698. // StructureScanner overrides
  699.  
  700. DecomposeClasses.prototype.startTag = DecomposeClasses_startTag;
  701. DecomposeClasses.prototype.endTag = DecomposeClasses_endTag;
  702. DecomposeClasses.prototype.createTag = DecomposeClasses_createTag;
  703.  
  704. function DecomposeClasses_run( context )
  705. {
  706.     // Dump out if we are not filter
  707.  
  708.     if ( context.settingDefined( SETTINGS_NO_FILTER ) )
  709.         return true;
  710.  
  711.     // Initialize the decomposition class and the other member variables
  712.  
  713.     this._cache = {};
  714.     this._isHeader = false;
  715.  
  716.     // Ignore this if the content is not HTML
  717.  
  718.     if ( context.getContentType() != CONTENT_TYPE_HTML )
  719.         return true;
  720.  
  721.     // Ignore this filter if we are not in Contribute
  722.  
  723.     if ( ! context.settingDefined( SETTINGS_CONTRIBUTE ) )
  724.         return true;
  725.  
  726.     // Ignore this filter if we are not in ETO mode
  727.     if ( context.settingDefined( SETTINGS_ETO ) )
  728.         return true;
  729.  
  730.     // Ignore this filter if we are not in LOW mode
  731.     if ( context.settingDefined( SETTINGS_LOW ) )
  732.         return true;
  733.  
  734.     context.debugInformation( "DecomposeClasses", ">> run" );
  735.  
  736.     this._context = context;
  737.  
  738.     // Decide on whether we are building fonts or spans
  739.  
  740.     this._buildingFonts = false;
  741.     if ( context.settingDefined( SETTINGS_CHANGE_SPAN_TO_FONT ) )
  742.         this._buildingFonts = true;
  743.  
  744.     // Get the HTML
  745.  
  746.     var html = context.getClipText();
  747.  
  748.     // Use ourselves to scan
  749.  
  750.     html = this.scan( html );
  751.  
  752.     // Set the HTML
  753.  
  754.     context.setClipText( html );
  755.  
  756.     context.debugInformation( "DecomposeClasses", "<< run" );
  757.  
  758.     return true;
  759. }
  760.  
  761. function DecomposeClasses_getPhase() { return PHASE_CONFORM_OTHER; }
  762.  
  763. function DecomposeClasses_parseClass( classDef, tagStruct )
  764. {
  765.     // Handle the different CSS style elements and stuff them into the
  766.     // right tagStruct item.
  767.  
  768.     if ( classDef[ "font-size" ] )
  769.         tagStruct.fontSize = classDef[ "font-size" ];
  770.  
  771.     if ( classDef[ "font-family" ] )
  772.     {
  773.         if ( this._context.settingDefined( SETTINGS_NO_FONT_MAP ) )
  774.             tagStruct.fontName = classDef[ "font-family" ];
  775.         else
  776.             tagStruct.fontName = Utils_MapFont( classDef[ "font-family" ] );
  777.     }
  778.  
  779.     if ( classDef[ "font-style" ] == "italic" )
  780.         tagStruct.isItalic = true;
  781.  
  782.     if ( classDef[ "font-weight" ] > 400 )
  783.         tagStruct.isBold = true;
  784.  
  785.     if ( classDef[ "font-weight" ] == "bold" )
  786.         tagStruct.isBold = true;
  787.  
  788.     if ( classDef[ "text-decoration" ] == "underline" )
  789.         tagStruct.isUnderline = true;
  790.  
  791.     if ( classDef[ "text-align" ] == "right" || classDef[ "text-align" ] == "center" )
  792.         tagStruct.textAlign = classDef[ "text-align" ];
  793.  
  794.     // For indents we give one indent for every half inch of margin
  795.  
  796.     if ( classDef[ "mso-tab-count" ] != null )
  797.         tagStruct.tabCount = classDef[ "mso-tab-count" ];
  798.  
  799.     if ( classDef[ "margin-left" ] != null )
  800.     {
  801.         var value = classDef[ "margin-left" ];
  802.         // grab numerical portion
  803.         var floatVal = parseFloat (value);
  804.          
  805.         // test if the format is in pica not inches
  806.         if ( value.match( /pt$/ ) )
  807.         {
  808.             // convert to inches
  809.             floatVal = floatVal * .16;
  810.         }
  811.  
  812.         // Convert inches to blockquotes, there are two blockquotes per inch.
  813.         tagStruct.indent = Math.floor( floatVal * 2 );
  814.     }
  815.  
  816.     // Parse the colors, but don't allow vendor specific colors.
  817.  
  818.     if ( classDef[ "color" ] != null )
  819.     {
  820.         if( classDef[ "color" ].match( /windowtext/ig ) )
  821.             classDef[ "color" ] = "black";
  822.  
  823.         tagStruct.textColor = classDef[ "color" ];
  824.     }
  825.  
  826.     if ( classDef[ "background" ] != null )
  827.     {
  828.         if( ! classDef[ "background" ].match( /windowtext/ig ) )
  829.             tagStruct.bgColor = classDef[ "background" ];
  830.     }
  831. }
  832.  
  833. function DecomposeClasses_buildNewTags( tag, attributes, tagStruct )
  834. {
  835.     // The various attributes of the font/span tag
  836.  
  837.     var faceAttribute = "";
  838.     var styleAttribute = "";
  839.     var sizeAttribute = "";
  840.  
  841.     // Initialize the starting and ending tag
  842.  
  843.     var startTag = "";
  844.     var endTag = "";
  845.     var replaceTag = false;
  846.  
  847.     // Setup the FACE, STYLE and SIZE attributes of the font/span
  848.     // tag depending on the fontName, fontSize, and textColor attributes
  849.     // in the tagStruct.
  850.  
  851.     if ( tagStruct.fontName && tagStruct.fontName.length > 1 )
  852.     {
  853.         tagStruct.fontName = tagStruct.fontName.replace( /\"/g, "" );
  854.         if ( this._buildingFonts )
  855.             faceAttribute = tagStruct.fontName;
  856.         else
  857.             styleAttribute += "font-family:" + tagStruct.fontName + ";";
  858.     }
  859.     if ( tagStruct.fontSize != null )
  860.     {
  861.         if ( this._buildingFonts )
  862.             sizeAttribute += Utils_ConvertPointsToFontSizes( tagStruct.fontSize );
  863.         else
  864.             styleAttribute += "font-size:" + tagStruct.fontSize + ";";
  865.     }
  866.     if ( tagStruct.textColor != null )
  867.         styleAttribute += "color:" + tagStruct.textColor + ";";
  868.     if ( tag == "p" && tagStruct.textAlign != null )
  869.     {
  870.         startTag = "<p style=\"text-align:" + tagStruct.textAlign + ";\">";
  871.         endTag = "</p>";
  872.         replaceTag = true;
  873.     }
  874.  
  875.     // Indent the paragraph
  876.  
  877.     if ( tagStruct.indent > 0 && tag == "p" )
  878.     {
  879.         for( var indent = 0; indent < tagStruct.indent; indent++ )
  880.         {
  881.             startTag += "<blockquote>"; endTag = "</blockquote>" + endTag;
  882.         }
  883.         replaceTag = true;
  884.     }
  885.  
  886.     // Build the font/span tag
  887.  
  888.     if ( faceAttribute.length > 0 || styleAttribute.length > 0 || sizeAttribute.length > 0 )
  889.     {
  890.         var baseTagType = "font";
  891.         if ( !this._buildingFonts )
  892.             baseTagType = "span";
  893.  
  894.         startTag += "<" + baseTagType;
  895.     
  896.         if ( faceAttribute.length > 0 )
  897.             startTag += " face=\"" + faceAttribute + "\"";
  898.  
  899.         if ( sizeAttribute.length > 0 )
  900.             startTag += " size=\"" + sizeAttribute + "\"";
  901.  
  902.         if ( styleAttribute.length > 0 )
  903.             startTag += " style=\"" + styleAttribute + "\"";
  904.     
  905.         startTag += ">";
  906.     
  907.         endTag = "</" + baseTagType + ">" + endTag;
  908.     }
  909.  
  910.     // Add in italics and bolding
  911.  
  912.     if ( tagStruct.isItalic ) { startTag += "<i>"; endTag = "</i>" + endTag; }
  913.     if ( tagStruct.isBold ) { startTag += "<b>"; endTag = "</b>" + endTag; }
  914.     if ( tagStruct.isUnderline ) { startTag += "<u>"; endTag = "</u>" + endTag; }
  915.  
  916.     // Add in any tabs
  917.  
  918.     if ( tagStruct.tabCount != null )
  919.         for( var tab = 0; tab < parseInt( tagStruct.tabCount ); tab++ )
  920.             startTag += "  ";
  921.  
  922.     // If this is a span tag then we are replacing it because we are
  923.     // creating the font/span tag.
  924.  
  925.     if ( tag == "span" )
  926.         replaceTag = true;
  927.  
  928.     // Check for text alignment
  929.  
  930.     if ( tag == "td" && tagStruct.textAlign != null )
  931.         attributes[ "align" ] = tagStruct.textAlign;
  932.  
  933.     // Turn TDs into THs if this TD is in the THEAD section
  934.  
  935.     if ( tag == "td" && this._isHeader )
  936.     {
  937.         // We need to copy the attributes
  938.  
  939.         var attributeStr = "";
  940.  
  941.         for ( var key in attributes )
  942.         {
  943.             if ( attributes[ key ] )
  944.                 attributeStr += " " + key + "=\"" + attributes[ key ] + "\"";
  945.         }
  946.  
  947.         // BGCOLOR is special because it doesn't exist yet, so we need to add it
  948.  
  949.         if ( tagStruct.bgColor != null )
  950.             attributeStr += " bgcolor=\"" + tagStruct.bgColor + "\"";
  951.  
  952.         // This is hard wired for accesibility.  No Word version that I know of allows you
  953.         // to turn columns into headers.  So we are assuming that rows are always the header
  954.         // and thus the scope of the header is the column.
  955.         //
  956.         // This assumption should be checked against new versions of Word as they are released.
  957.     
  958.         attributeStr += ' scope="col"';
  959.  
  960.         // Put together the new TH tag
  961.  
  962.         startTag = "<th" + attributeStr + ">" + startTag;
  963.         endTag = "</th>" + endTag;
  964.         replaceTag = true;
  965.     }
  966.  
  967.     // Return the fixup structure if we are fixing something up
  968.  
  969.     return { tag: tag, prefix: startTag, postfix: endTag, replace: replaceTag };
  970. }
  971.  
  972. function DecomposeClasses_buildTagSpecifications( tag, attributes, tagStruct )
  973. {
  974.     // It takes a while to figure out just what font, italic, etc. combination maps to
  975.     // any combination of tag name, class and style attributes.  On the assumption that
  976.     // most people use the same combination over and over we cache the result of the
  977.     // combination of tag name, class name and style text.
  978.  
  979.  
  980.     // Create the cache names for the class and style.  Since it's a lookup we need
  981.     // to actually have a value, so we replace null with an empty string
  982.  
  983.     var cacheClassName = new String( attributes[ 'class' ] );
  984.     if ( cacheClassName == null )
  985.         cacheClassName = "";
  986.  
  987.     var cacheStyleName = new String( attributes[ 'style' ] );
  988.     if ( cacheStyleName == null )
  989.         cacheStyleName = "";
  990.  
  991.     // Make sure that the cache hierachy exists for teh tag and the class name
  992.  
  993.     if ( this._cache[ tag ] == null )
  994.         this._cache[ tag ] = {};
  995.     if ( this._cache[ tag ][ cacheClassName ] == null )
  996.         this._cache[ tag ][ cacheClassName ] = {}
  997.  
  998.     // Get value of the cache for this combination of tag, clas name and style
  999.     // name
  1000.  
  1001.     var cacheValue = this._cache[ tag ][ cacheClassName ][ cacheStyleName ];
  1002.     if ( cacheValue )
  1003.     {
  1004.         attributes[ 'class' ] = null;
  1005.         for( var key in cacheValue )
  1006.             tagStruct[ key ] = cacheValue[ key ];
  1007.         return;
  1008.     }
  1009.  
  1010.     // Here we are populating the tagStruct from the CSS stuff. This is really in three sections;
  1011.     // first we look at the class for the tag.  Then we look at the class specified by the this tag
  1012.     // in particular. Then we look at the style data.  We do it in that order because that is the 
  1013.     // order in which CSS cascades.  If you change the ordering then you will be altering the
  1014.     // cascading behaviour of CSS.
  1015.  
  1016.  
  1017.     // First look to see if this tag has an associated class.  For example, if this
  1018.     // is a TD tag then this looks for a TD class.
  1019.  
  1020.     if ( this._context.getOriginClasses().get( tag ) )
  1021.         this.parseClass( this._context.getOriginClasses().get( tag ), tagStruct );
  1022.  
  1023.     // Now we look for the specific CLASS specified in the attribute, maybe
  1024.  
  1025.     var tagClassName = attributes[ 'class' ];
  1026.     if ( tagClassName )
  1027.     {
  1028.         var classDef = null;
  1029.  
  1030.         // First look for <tagName>.<className>
  1031.  
  1032.         var className = tag.toLowerCase() + "." + tagClassName;
  1033.         if ( this._context.getOriginClasses().get( className ) )
  1034.             classDef = this._context.getOriginClasses().get( className );
  1035.  
  1036.         // Then look for .<className>
  1037.  
  1038.         if ( classDef == null )
  1039.         {
  1040.             className = "." + tagClassName;
  1041.             if ( this._context.getOriginClasses().get( className ) )
  1042.                 classDef = this._context.getOriginClasses().get( className );
  1043.         }
  1044.  
  1045.         // If we find it then parse it
  1046.  
  1047.         if ( classDef )
  1048.             this.parseClass( classDef, tagStruct );
  1049.  
  1050.         attributes[ 'class' ] = null;
  1051.     }
  1052.  
  1053.     // Last, bring in the STYLE data
  1054.  
  1055.     if ( attributes.style )
  1056.     {
  1057.         this.parseClass( Utils_ParseStyle( attributes.style ), tagStruct );
  1058.     }
  1059.  
  1060.     var cacheValue = {};
  1061.     for( var key in tagStruct )
  1062.         cacheValue[ key ] = tagStruct[ key ];
  1063.     this._cache[ tag ][ cacheClassName ][ cacheStyleName ] = cacheValue;
  1064. }
  1065.  
  1066. function DecomposeClasses_shouldUseTargetClass( tag, attributes )
  1067. {
  1068.     // If we aren't allowing CSS then don't allow the user to reuse target
  1069.     // document classes
  1070.     
  1071.     if ( this._context.settingDefined( SETTINGS_NO_CSS ) )
  1072.         return false;
  1073.  
  1074.     // Check to see if we have a class that is defined by the target document.
  1075.  
  1076.     if ( attributes[ 'class' ] )
  1077.     {
  1078.         var name = this._context.checkClasses( tag, attributes[ 'class' ] );
  1079.         if ( name != null )
  1080.         {
  1081.             attributes[ 'class' ] = name;
  1082.             return true;
  1083.         }
  1084.     }
  1085.  
  1086.     return false;
  1087. }
  1088.  
  1089. function DecomposeClasses_startTag( tag )
  1090. {
  1091.     // Mark if we are in a header
  1092.  
  1093.     if ( tag.tag == "thead" )
  1094.         this._isHeader = true;
  1095. }
  1096.  
  1097. function DecomposeClasses_endTag( tag )
  1098. {
  1099.     // Mark when we leave a table header
  1100.  
  1101.     if ( tag.tag == "thead" )
  1102.         this._isHeader = false;
  1103. }
  1104.  
  1105. function DecomposeClasses_createTag( tag, attributes, closed )
  1106. {
  1107.     // Ignore this tag if it is in the ignore list
  1108.  
  1109.     if ( DC_IGNORE_TAGS[ tag ] > 0 )
  1110.         return null;
  1111.  
  1112.     if ( ! this.shouldUseTargetClass( tag, attributes ) )
  1113.     {
  1114.  
  1115.         // This is the finalized structure that should be populated after the class
  1116.         // and style attributes are analyzed.
  1117.  
  1118.         var tagStruct = {
  1119.             fontName: "",
  1120.             isItalic: false,
  1121.             isBold: false,
  1122.             tabCount: null,
  1123.             isUnderline: false,
  1124.             textAlign: null,
  1125.             fontSize: null,
  1126.             indent: null,
  1127.             textColor: null,
  1128.             bgColor: null
  1129.         };
  1130.  
  1131.         // Analyze the tag and populate the tag specifications structures
  1132.  
  1133.         this.buildTagSpecifications( tag, attributes, tagStruct );
  1134.  
  1135.         // Build a set of tags that represent the specification structure
  1136.  
  1137.         outStruct = this.buildNewTags( tag, attributes, tagStruct );
  1138.  
  1139.         // If we aren't replacing the current tag then look for any alterations
  1140.         // to the tag itself.
  1141.  
  1142.         var prefix = "";
  1143.         var postfix = "";
  1144.  
  1145.         // So there are two ways to handle a tag, we either add onto it, or replace it.
  1146.         // For example, here is an add to:
  1147.         //
  1148.         //    <p class=MsoNormal>text</p>
  1149.         //
  1150.         // Becomes:
  1151.         //
  1152.         //    <p><font face="Times New Roman">text</font></p>
  1153.         //
  1154.         // Or replacing, like:
  1155.         //
  1156.         //    <p style="mso-indent-level:2>text</p>
  1157.         //
  1158.         // Is replaced by:
  1159.         //
  1160.         //    <blockquote><blockquote>text</blockquote></blockquote>
  1161.         //
  1162.         // Another case is replacing <span> tags with <font> tags.
  1163.  
  1164.         if ( outStruct.replace )
  1165.         {
  1166.             prefix = outStruct.prefix;
  1167.             postfix = outStruct.postfix;
  1168.         }
  1169.         else
  1170.         {
  1171.             if ( tagStruct.bgColor != null )
  1172.                 attributes.bgcolor = tagStruct.bgColor;
  1173.  
  1174.             var retVal = StructureScanner_createTag( tag, attributes, closed );
  1175.  
  1176.             prefix = retVal.prefix + outStruct.prefix;
  1177.             postfix = outStruct.postfix + retVal.postfix;
  1178.         }
  1179.  
  1180.         return { prefix: prefix, postfix: postfix };
  1181.     }
  1182.  
  1183.     return null;
  1184. }
  1185.  
  1186.  
  1187.  
  1188. //---------------------------------------------------------------------------------------------------------
  1189. // DemoteToParagraphs
  1190. //---------------------------------------------------------------------------------------------------------
  1191.  
  1192. // DemoteToParagraphs turns and <H1>, <H2>, <H3>, etc. tag into a 
  1193. // <P> tag if the SETTINGS_DEMOTE_TO_PARAGRAPHS setting is on.
  1194.  
  1195. function DemoteToParagraphs() { }
  1196.  
  1197. // The module API
  1198.  
  1199. DemoteToParagraphs.prototype.run = DemoteToParagraphs_run;
  1200. DemoteToParagraphs.prototype.getPhase = DemoteToParagraphs_getPhase;
  1201.  
  1202. function DemoteToParagraphs_run( context )
  1203. {
  1204.     // Dump out if we are not filter
  1205.  
  1206.     if ( context.settingDefined( SETTINGS_NO_FILTER ) )
  1207.         return true;
  1208.  
  1209.     // Ignore non-HTML content
  1210.  
  1211.     if ( context.getContentType() != CONTENT_TYPE_HTML )
  1212.         return true;
  1213.  
  1214.     // Ignore this filter if we are not in Contribute
  1215.  
  1216.     if ( ! context.settingDefined( SETTINGS_CONTRIBUTE ) )
  1217.         return true;
  1218.  
  1219.     // Ignore this filter if we are not in demoting to paragraphs
  1220.  
  1221.     if ( ! context.settingDefined( SETTINGS_DEMOTE_TO_PARAGRAPHS ) )
  1222.         return true;
  1223.  
  1224.     context.debugInformation( "DemoteToParagraphs", ">> run" );
  1225.  
  1226.     // Get the HTML
  1227.  
  1228.     var html = context.getClipText();
  1229.  
  1230.     // Put together the regular expression of what to find
  1231.  
  1232.     var mapRE = /^h[123456]$/i;
  1233.  
  1234.     // And then send it in with what we want it replaced with
  1235.  
  1236.     var tagNameMapper = new MapTagNamesScanner( mapRE, "p" );
  1237.  
  1238.     // Run the scanner
  1239.  
  1240.     html = tagNameMapper.scan( html, context );
  1241.  
  1242.     // Replace the HTML
  1243.  
  1244.     context.setClipText( html );
  1245.  
  1246.     context.debugInformation( "DemoteToParagraphs", "<< run" );
  1247.  
  1248.     return true;
  1249. }
  1250.  
  1251. function DemoteToParagraphs_getPhase() { return PHASE_FIXUP; }
  1252.  
  1253.  
  1254.  
  1255. //---------------------------------------------------------------------------------------------------------
  1256. // SingleSpaceParagraphs
  1257. //---------------------------------------------------------------------------------------------------------
  1258.  
  1259. // If the SETTINGS_SINGLE_SPACE_P setting is on then SingleSpaceParagraphs adds
  1260. // the margin-top:0 and margin-bottom:0 style attributes to each paragraph.
  1261.  
  1262. function SingleSpaceParagraphs() { }
  1263.  
  1264. // The module API
  1265.  
  1266. SingleSpaceParagraphs.prototype.run = SingleSpaceParagraphs_run;
  1267. SingleSpaceParagraphs.prototype.getPhase = SingleSpaceParagraphs_getPhase;
  1268.  
  1269. function SingleSpaceParagraphs_run( context )
  1270. {
  1271.     // Dump out if we are not filter
  1272.  
  1273.     if ( context.settingDefined( SETTINGS_NO_FILTER ) )
  1274.         return true;
  1275.  
  1276.     // Ignore non-HTML content
  1277.  
  1278.     if ( context.getContentType() != CONTENT_TYPE_HTML )
  1279.         return true;
  1280.  
  1281.     // Ignore this filter if we are not in Contribute
  1282.  
  1283.     if ( ! context.settingDefined( SETTINGS_CONTRIBUTE ) )
  1284.         return true;
  1285.  
  1286.     // Ignore this filter if we are not single spacing paragraphs
  1287.  
  1288.     if ( ! context.settingDefined( SETTINGS_SINGLE_SPACE_P ) )
  1289.         return true;
  1290.  
  1291.     context.debugInformation( "SingleSpaceParagraphs", ">> run" );
  1292.  
  1293.  
  1294.     var html = context.getClipText();
  1295.  
  1296.     var styleAdder = new AddStylesScanner( "p", { 'margin-top': 0, 'margin-bottom': 0 } );
  1297.     html = styleAdder.scan( html, context );
  1298.  
  1299.     context.setClipText( html );
  1300.  
  1301.  
  1302.     context.debugInformation( "SingleSpaceParagraphs", "<< run" );
  1303.  
  1304.     return true;
  1305. }
  1306.  
  1307. function SingleSpaceParagraphs_getPhase() { return PHASE_FINALIZE; }
  1308.  
  1309.  
  1310. //---------------------------------------------------------------------------------------------------------
  1311. // MergeRedundantFontTags
  1312. //---------------------------------------------------------------------------------------------------------
  1313.  
  1314. // This handler fixes a problem where the system creates font tags within font tags.  For
  1315. // example:
  1316. //
  1317. //     <font name="Times New Roman" size="7"><b><font size="3">Example</font></b></font>
  1318. //
  1319. // Should become:
  1320. //
  1321. //     <font name="Times New Roman" size="3"><b>Example</b></font>
  1322. //
  1323. // What actually happens is that the tags become:
  1324. //
  1325. //     <font name="Times New Roman" size="7"><b><font>Example</font></b></font>
  1326. //
  1327. // And the empty font tags are removed in the finalization process.
  1328.  
  1329. function MergeRedundantFontTags() {}
  1330.  
  1331. MergeRedundantFontTags.prototype = new StructureScanner ();
  1332.  
  1333. // The module API
  1334.  
  1335. MergeRedundantFontTags.prototype.run = MergeRedundantFontTags_run;
  1336. MergeRedundantFontTags.prototype.getPhase = MergeRedundantFontTags_getPhase;
  1337.  
  1338. // The structure scanner override methods
  1339.  
  1340. MergeRedundantFontTags.prototype.inspectTag = MergeRedundantFontTags_inspectTag;
  1341. MergeRedundantFontTags.prototype.findFontTag = MergeRedundantFontTags_findFontTag;
  1342.  
  1343. function MergeRedundantFontTags_run( context )
  1344. {
  1345.     // Dump out if we are not filter
  1346.  
  1347.     if ( context.settingDefined( SETTINGS_NO_FILTER ) )
  1348.         return true;
  1349.  
  1350.     // Ignore non-HTML content
  1351.  
  1352.     if ( context.getContentType() != CONTENT_TYPE_HTML )
  1353.         return true;
  1354.  
  1355.     // Ignore this filter if we are not in Contribute
  1356.  
  1357.     if ( ! context.settingDefined( SETTINGS_CONTRIBUTE ) )
  1358.         return true;
  1359.  
  1360.     context.debugInformation( "MergeRedundantFontTags", ">> run" );
  1361.  
  1362.     // Get the HTML
  1363.  
  1364.     var html = context.getClipText();
  1365.  
  1366.     // Use ourselves to scan the HTML
  1367.  
  1368.     html = this.scan( html );
  1369.  
  1370.     // Replace the HTML
  1371.  
  1372.     context.setClipText( html );
  1373.  
  1374.     context.debugInformation( "MergeRedundantFontTags", "<< run" );
  1375.  
  1376.     return true;
  1377. }
  1378.  
  1379. function MergeRedundantFontTags_getPhase() { return PHASE_OPTIMIZE; }
  1380.  
  1381. function MergeRedundantFontTags_findFontTag( tag )
  1382. {
  1383.     // We ignore interior text
  1384.  
  1385.     if ( tag.type == "text" )
  1386.         return null;
  1387.  
  1388.     // We check to see if there are children, there must be only one child
  1389.     // at each level
  1390.  
  1391.     if ( tag.children.length == 1 )
  1392.     {
  1393.         // If the child is a font tag, then we have a qualifying tag/subTag
  1394.         // combination.  Otherwise we delve further.
  1395.  
  1396.         if ( tag.children[ 0 ].tag == "font" )
  1397.             return tag.children[ 0 ];
  1398.         else
  1399.             return this.findFontTag( tag.children[ 0 ] );
  1400.     }
  1401.  
  1402.     return null;
  1403. }
  1404.  
  1405. function MergeRedundantFontTags_inspectTag( tag )
  1406. {
  1407.     // In the inspection phase of the StructureScanner process we can actually alter the
  1408.     // tag before it's output.  In this case we want to take interior font tags and merge
  1409.     // them into the parent font tag.
  1410.  
  1411.     // Just to make things more clear, in the example above:
  1412.     //
  1413.     //     <font name="Times New Roman" size="7"><b><font size="3">Example</font></b></font>
  1414.     //
  1415.     // At the point we find them:
  1416.     //
  1417.     // 'tag' = <font name="Times New Roman" size="7">
  1418.     // 'subTag' = <font size="3">
  1419.     //
  1420.     // Then after we filter it:
  1421.     //
  1422.     // 'tag' = <font name="Times New Roman" size="3">
  1423.     // 'subTag' = <font>
  1424.     //
  1425.  
  1426.  
  1427.     if ( tag.tag == "font" && tag.children.length > 0 )
  1428.     {
  1429.         // Find a qualifying interior font tag
  1430.  
  1431.         var subTag = this.findFontTag( tag );
  1432.  
  1433.         // If we found one then migrate all of the attributes from that tag into the parent
  1434.         // tag and mark them as null in the sub tag.
  1435.  
  1436.         if ( subTag )
  1437.         {
  1438.             for ( var key in subTag.attributes )
  1439.             {
  1440.                 tag.attributes[ key ] = subTag.attributes[ key ];
  1441.                 subTag.attributes[ key ] = null;
  1442.             }
  1443.         }
  1444.     }
  1445.  
  1446.     return tag;
  1447. }
  1448.  
  1449.  
  1450. //---------------------------------------------------------------------------------------------------------
  1451. // ChangeToStrongAndEm
  1452. //---------------------------------------------------------------------------------------------------------
  1453.  
  1454. // ChangeToStrongAndEm changes <b> tags to <strong> tags, and <i> tags to <em>
  1455. // tags if the SETTINGS_USE_EMPHASIS setting is on.
  1456.  
  1457. function ChangeToStrongAndEm() { }
  1458.  
  1459. // The module API
  1460.  
  1461. ChangeToStrongAndEm.prototype.run = ChangeToStrongAndEm_run;
  1462. ChangeToStrongAndEm.prototype.getPhase = ChangeToStrongAndEm_getPhase;
  1463.  
  1464. function ChangeToStrongAndEm_run( context )
  1465. {
  1466.     // Dump out if we are not filter
  1467.  
  1468.     if ( context.settingDefined( SETTINGS_NO_FILTER ) )
  1469.         return true;
  1470.  
  1471.     // Ignore non-HTML content
  1472.  
  1473.     if ( context.getContentType() != CONTENT_TYPE_HTML )
  1474.         return true;
  1475.  
  1476.     // Ignore this filter if we are not in Contribute
  1477.  
  1478.     if ( ! context.settingDefined( SETTINGS_CONTRIBUTE ) )
  1479.         return true;
  1480.  
  1481.     // Ignore this filter if we are not changing to <strong> and <em>
  1482.  
  1483.     if ( ! context.settingDefined( SETTINGS_USE_EMPHASIS ) )
  1484.         return true;
  1485.  
  1486.     context.debugInformation( "ChangeToStrongAndEm", ">> run" );
  1487.  
  1488.     // Get the HTML
  1489.  
  1490.     var html = context.getClipText();
  1491.  
  1492.     // Setup regexps for <b> and <i> and turn them into <strong>
  1493.     // and <em>.
  1494.  
  1495.     var mapB = /^b$/i;
  1496.     var tagNameMapperB = new MapTagNamesScanner( mapB, "strong" );
  1497.     html = tagNameMapperB.scan( html, context );
  1498.  
  1499.     var mapI = /^i$/i;
  1500.     var tagNameMapperI = new MapTagNamesScanner( mapI, "em" );
  1501.     html = tagNameMapperI.scan( html, context );
  1502.  
  1503.     // Replace the HTML
  1504.  
  1505.     context.setClipText( html );
  1506.  
  1507.     context.debugInformation( "ChangeToStrongAndEm", "<< run" );
  1508.  
  1509.     return true;
  1510. }
  1511.  
  1512. function ChangeToStrongAndEm_getPhase() { return PHASE_FINALIZE; }
  1513.  
  1514.  
  1515.  
  1516.