Contents
- prettify_MATLAB_html(inputhtmlFile, verbose, overloadPublish, addCommandsToToolbar)
- ==========================================================================================================================================================
- Documentation :
- Version history :
- Notes :
- Authorship:
- ==========================================================================================================================================================
- Constants
- Check sufficient inputs
- Clear show_warning and write_fstrm_to_file sub-functions
- Overload publish
- Execute toolbar functions
- Some input checking
- Open input file
- Find [cssClass] tags
- Themes
- Define extra css
- Define javascript
- Insert extra css and javascript
- Fix/tweak publish's css
- html validation fixes
- Insert floating "return" link
- Find start and end of page body source
- Move any style</style> section to before the body
- Process [h2] tags
- Process [targetn] and [jumpton] tags
- Process colour, scale, and class tags
- Find [dtls] and [smry] tags
- Process [dtls] and [smry] tags
- Remove excess line breaks after any tables in details sections
- Remove excess spacing after any lists at the end of details sections
- Add breaks between tables and images
- Process [bottomMarginx] tags
- Assign image-fit class to all images
- Process undocumented [png2svg] tags
- Process undocumented [removepng] tags
- Process [darkAlt] tags
- Replace [br] with br
- Process undocumented [rembr] and [delbr] tags
- Insert breaks of specified pixel height ([brx] tags)
- Remove excess spacing after codeoutput sections
- Remove erroneous p</p> tags
- Insert link to FEX page
- Remove/modify remaining prettify_MATLAB_html tags
- Final bit of javascript
- Write the output file
- Subroutines
- Index updater
- Show warning dialogue if warnings were generated
- ==========================================================================================================================================================
- Helper functions
- Tag processing
- Get path of this .m file
- Save handle to built-in Publish function
- Warning functions
- Toolbar button functions
- File read & write
- New line constant
- ==========================================================================================================================================================
- Version history:
function prettify_MATLAB_html(inputhtmlFile, verbose, overloadPublish, addCommandsToToolbar)
prettify_MATLAB_html(inputhtmlFile, verbose, overloadPublish, addCommandsToToolbar)
Given a MATLAB-generated HTML file as an input, this function adds additional features such as disclosure boxes and expand-all/collapse-all links. This makes the document more closely resemble "official" MATLAB help files. It also gives extended formatting options, provides an optional dark theme, and automatically enhances the formatting of inline code and images.
% Version 6.5 % % Inputs: % % inputhtmlFile: Character vector. The name or full path of the MATLAB-generated % HTML file to be processed. The file is automatically over- % written by the processed file. % % verbose: Logical. Optional input to suppress the begin and end messages % that are printed to the command line when prettify_MATLAB_html % is run. Set to "false" to suppress the messages. % % overloadPublish: Logical. When used, the first two inputs are ignored. This is an % optional input to configure an overload for MATLAB's publish % function, so that when you press "publish" in the toolbar of a % MATLAB editor window, the generated HTML will automatically be % processed by prettify_MATLAB_html. This only needs to be done % once per MATLAB session. If you put a call to: % prettify_MATLAB_html([], [], true); % in your startup.m file, publish will get overloaded every time % you run MATLAB. To stop overloading the built-in publish % function, set overloadPublish to false: % prettify_MATLAB_html([], [], false); % % addCommandsToToolbar: % Logical. When used, the first two inputs are ignored. This is % an optional input used to add shortcuts to the quick-access % toolbar that then allow you to easily add prettify_MATLAB_html % tags to your .m files. Set to true to add the commands. % In MATLAB versions prior to 2018a, the buttons are added to your % shortcuts, and you have to restart to get them to be added to the % Quick-Access Toolbar. If anyone knows how to add shortcuts to the % Quick-Access Toolbar programatically, without requiring a % restart, please let me know: harry.dymond@bristol.ac.uk % % Most prettify_MATLAB_html features require you to use additional markup "tags" in % the original source .m file, for example to indicate where you want the disclosure % boxes (the "expand"/"collapse" links are automatically generated). % % See the <a href="matlab:ans=dir(which('prettify_MATLAB_html.m')); % open([ans.folder filesep 'prettify documentation' filesep 'html' filesep 'prettify_MATLAB_html_helpdoc.html']); % clear ans">help document</a> for more information
==========================================================================================================================================================
Documentation :
Please see the help document: /prettify documentation/html/prettify_MATLAB_html_helpdoc.html for more information (type "help prettify_MATLAB_html" at the MATLAB command line to get a clickable link to the help document).
%============================================================================================================================================================ %
Version history :
Version history is at the end of this file
%============================================================================================================================================================ %
Notes :
This file uses "sections" to help segment the code for readability. It is recommended to enable folding of sections in MATLAB's preferences, so that sections can be collapsed/expanded. Lines are 157 characters wide. The position of the vertical text-limit line (if shown) may be moved by going to MATLAB preferences -> Editor/Debugger -> Display -> Right-hand text limit
The code uses the following naming convention : _____________________________________________ | Item(s) | Naming convention __________________|__________________________ Variables | initialLowerTitleCase ------------------+-------------------------- Functions | lower_case(...) ------------------+-------------------------- constants | UPPER_CASE __________________|__________________________
%============================================================================================================================================================ %
Authorship:
Written by Harry Dymond, Electrical Energy Management Group, University of Bristol, UK; harry.dymond@bris.ac.uk. If you find any bugs, please email me! Developed with MATLAB R2018a running on Windows 10 and macOS Mojave.
==========================================================================================================================================================
Constants
HELP_URL = [' Click <a href="matlab:ans=dir(which(''prettify_MATLAB_html.m''));' ... 'open([ans.folder filesep ''prettify documentation'' filesep ''html'' filesep ''prettify_MATLAB_html_helpdoc.html'']);' ... 'clear ans">here</a> to see the help document for more information.']; NO_START_ERR = 'ERROR: couldn''t find start of content in html file'; DEBUG = getappdata(0,'PRETTY_DEBUG'); if isempty(DEBUG), DEBUG = false; end
Check sufficient inputs
assert(nargin>=1,[' Call syntax error. Call syntax is:' NL ... ' prettify_MATLAB_html(inputhtmlFile, verbose, overloadPublish, addCommandsToToolbar)' NL ... ' Where inputs verbose, overloadPublish, and addCommandsToToolbar are optional.' NL HELP_URL NL]); try
Clear show_warning and write_fstrm_to_file sub-functions
show_warning(true) write_fstrm_to_file([], [], [])
Overload publish
if nargin>=3 && islogical(overloadPublish) PUBLISH_OVERLOAD_FOLDER_NAME = 'publish overload'; ERROR_MSG_START = 'Not installed correctly. In the folder that contains the prettify_MATLAB_html.m file, there should be a '; if ~exist([my_path PUBLISH_OVERLOAD_FOLDER_NAME],'dir'), error([ERROR_MSG_START 'folder named "' PUBLISH_OVERLOAD_FOLDER_NAME '"']); end pathList = strsplit(path,':'); overloadPath = pathList(cellfun(@(str)contains(str,PUBLISH_OVERLOAD_FOLDER_NAME),pathList)); if ~isempty(overloadPath) for i=1:length(overloadPath) overloadPath{i} = strsplit(overloadPath{i},';'); overloadPath{i} = overloadPath{i}{1}; rmpath(overloadPath{i}); end end if overloadPublish save_publish_handle; if ~isempty(overloadPath), for i=1:length(overloadPath), addpath(overloadPath{i}); end else , addpath([my_path PUBLISH_OVERLOAD_FOLDER_NAME]); end end if nargin == 3, return; end end
Execute toolbar functions
if nargin==4 if islogical(addCommandsToToolbar) && addCommandsToToolbar add_pretty_commands_to_toolbar; elseif ischar(addCommandsToToolbar) document = matlab.desktop.editor.getActive; if isempty(document), return, end tagName = addCommandsToToolbar; switch tagName case {'[dtls]','[smry]','[cssClasses]'}, wrap_text_in_tag(document, tagName); case 'target' , insert_target(document); case 'jumpto' , jumpton_wrap(document); case 'class' , class_wrap(document); case 'scale' , wrap_text_in_tag(document, '[scale', 'x'); case 'colour' , colour_wrap(document); case 'table' , insert_table(document); end end return end
Some input checking
assert(ischar(inputhtmlFile),['ERROR: First input to prettify_MATLAB_html should be a character vector specifying '... 'the html file to be processed, either by just file name, or by full path.' NL HELP_URL NL]); if nargin < 2 || ~islogical(verbose), verbose = true; end if verbose, fprintf(1,'\nReading input file...\n'); end fInfo = dir(inputhtmlFile); assert(~isempty(fInfo),['ERROR: Specified html file could not be found. Make sure the html file '... 'is in the present working directory, or otherwise on the MATLAB path']);
Open input file
fstrm = read_file_to_mem(inputhtmlFile); if verbose, fprintf(1,'Processing:\n'); end
Find [cssClass] tags
[fstrm, classNames, userCSS] = process_cssClasses_tags(fstrm); % add built-in classes as valid class names (not done in process_cssClasses_tags so as not to pollute class list when "class" Toolbar button is used) classNames = [classNames {'codeinput', 'codeoutput', 'error', 'keyword', 'comment', 'string', 'untermstring', 'syscmd'}];
Themes
themeSwitchPos = strfind(fstrm,'<div class="content">'); assert (~isempty(themeSwitchPos), NO_START_ERR); themeSwitchPos = themeSwitchPos(1) + 20; if ~strcmp(char(fstrm(themeSwitchPos+1:themeSwitchPos+4)),'<h1>') ... || strcmp(char(fstrm(themeSwitchPos+1:themeSwitchPos+9)),'<h1></h1>'), extraStr = '<div> </div>'; %#ok<ALIGN> else , extraStr = '' ; end themeSwitch = ''; if contains(char(fstrm),'[themesEnabled]') classNames = [classNames {'show-if-light', 'show-if-dark'}]; themeSwitch = ['<div><p onclick="toggle_theme()" style="text-align:right; float:right; padding-left:10px; margin:0;">'... '<a href="javascript:void(0);" id="ToggleTheme"> </a></p></div>'... '<script>set_theme(null)</script>' extraStr]; fstrm = [fstrm(1:themeSwitchPos) themeSwitch fstrm(themeSwitchPos+1:end)]; fstrm = strrep(fstrm,'[themesEnabled]',''); else fstrm = [fstrm(1:themeSwitchPos) '<script>document.getElementById("dark-theme").sheet.disabled = true;</script>' fstrm(themeSwitchPos+1:end)]; end
Define extra css
extraCSS = [userCSS NL ... '#tooltiptext {' NL ... ' visibility: hidden;' NL ... ' padding: 5px 10px;' NL ... ' font-size: 75%;' NL ... ' line-height:110%;' NL ... ' text-align: center;' NL ... ' background-color: black;' NL ... ' color: #ddd;' NL ... ' border-radius: 6px;' NL ... ' position: fixed;' NL ... ' bottom: 11px;' NL ... ' right: 62px;' NL ... ' z-index: 2;' NL ... '}' NL ... '#tooltiptext::after {' NL ... ' content: " ";' NL ... ' position: absolute;' NL ... ' top: 50%;' NL ... ' left: 100%;' NL ... ' margin-top: -5px;' NL ... ' border-width: 5px;' NL ... ' border-style: solid;' NL ... ' border-color: transparent transparent transparent black;' NL ... '}' NL ... '.tooltip:hover #tooltiptext {' NL ... ' visibility: visible;' NL ... '}' NL ... '#return-link {' NL ... ' position: fixed;' NL ... ' bottom: 10px;' NL ... ' right: 10px;' NL ... ' overflow: visible;' NL ... ' font-size:120%;' NL ... ' background: rgba(0, 0, 0, 0.75);' NL ... ' border-style: solid;' NL ... ' border-width: 3pt;' NL ... ' border-color: #202020;' NL ... ' border-radius: 4px;' NL ... ' cursor: pointer;' NL ... ' }' NL ... '#return-link > p { padding:3px; margin:0; color:#C0C0C0;}' NL ... '.MATLAB-Help {' NL ... 'width: 100%;' NL ... 'margin-bottom: 12px;' NL ... 'border: 1px solid #ccc;' NL ... 'border-right: none;' NL ... 'border-bottom: none;' NL ... 'font-size: 96%;' NL ... 'line-height: 1.4;' NL ... 'table-layout: fixed;' NL ... 'overflow:hidden;}' NL ... NL ... '.MATLAB-Help > thead > tr > th {' NL ... 'padding: 6px 5px;' NL ... 'border: none;' NL ... 'border-right: 1px solid #ccc;' NL ... 'border-bottom: 1px solid #ccc;' NL ... 'background: #F2F2F2;' NL ... 'color: #000;' NL ... 'font-weight: bold;' NL ... 'text-align: left;' NL ... 'vertical-align: middle;}' NL ... NL ... '.MATLAB-Help td{padding: 5px 5px;' NL ... 'border: none;' NL ... 'border-right: 1px solid #ccc;' NL ... 'border-bottom: 1px solid #ccc;' NL ... 'vertical-align: middle;}' NL ... NL... '.language-matlab { line-height:135% }' NL NL... '.collapse-link {float:right; line-height:200%; padding-left:10px; margin:0}' NL NL ... NL ... 'details > summary,' NL ... '.details-div {' NL ... ' padding: 8px 20px;' NL ... ' border-style: solid;' NL ... ' border-width: 1.2pt;' NL ... ' border-color: #E0E0E0;' NL ... '}' NL ... 'details > summary {' NL ... ' border-radius:6px 6px 0 0;' NL ... ' background-color: #F2F2F2;' NL ... ' cursor: pointer;' NL ... '}' NL ... '.details-div {' NL ... ' border-top-style: none;' NL ... ' border-radius: 0 0 6px 6px;' NL ... '}' NL ... '.image-fit-svg,' NL ... '.image-fit {' NL ... ' max-width: 95%;' NL ... ' max-height: 100%;' NL ... ' margin: auto;' NL ... '}' NL ... '.image-fit-svg{ padding:0px; max-width:500px; }' NL ... 'details > img.image-fit-svg{ padding: 0px 0px 10px; }' NL ... '@media (max-width: 580px) {' NL ... ' .image-fit-svg { max-width: 95%; }' NL ... '}' NL ... '.pretty-link { color:#001188 !important; }' NL ]; darkTheme = [NL '<style id="dark-theme">' NL ... ' h2, h3 { color: #B0B0B0; }' NL ... ' html body { background-color: #101010; color: #B0B0B0; }' NL ... ' .pretty-link { color: #C46313 !important; }' NL ... ' a, a:visited { color: #C46313 }' NL ... ' a:hover { color: orange; }' NL ... ' details > summary,' NL ... ' .details-div { border-color: #505050; }' NL ... ' details > summary { background-color: #202020; }' NL ... ' pre.codeinput { border-width: 1.2pt; border-color:#001B33; background:#001129; color:#F0F0F0; }' NL ... ' pre.codeoutput { color:#A5A5A5; }' NL ... ' span.keyword { color:#FF9D00; }' NL ... ' span.comment { color:#808080; }' NL ... ' span.string { color:#3AD900; }' NL ... ' span.untermstring { color:#FFEE80; }' NL ... ' span.syscmd { color:#CCCCCC; }' NL ... ' .MATLAB-Help, .MATLAB-Help > thead > tr > th, .MATLAB-Help td { border-color:#505050; }' NL ... ' .MATLAB-Help > thead > tr > th { background: #202020; color: #B0B0B0; }' NL ... ' .summary-sub-heading { color:#909090; }' NL ... ' .show-if-light { display:none }' NL ... '</style>' NL ... '<style id="hide-dark">' NL ... ' .show-if-dark { display:none }' NL ... '</style>'... NL]; jumpShift = [NL '<style id="anchor-offsets">' NL ... ' h2::before, a[id]::before{' NL ... ' content: "";' NL ... ' display: block;' NL ... ' height: 100px;' NL ... ' margin: -100px 0 0;' NL ... ' visibility: hidden;' NL ... ' width:10%;' NL ... ' z-index: -1;' NL ... '}' NL ... '</style>' ... NL];
Define javascript
javaScript = ['<script>' NL ... ' var returnElem = null;' NL ... ' var skipCheck = false;' NL NL ... ' function hide_back_link()' NL ... ' {' NL ... ' returnButton.style.display = "none";' NL ... ' try{' NL ... ' window.removeEventListener("scroll", update_back_position, true);' NL ... ' window.removeEventListener("resize", update_back_position, true);' NL ... ' parent.window.removeEventListener("scroll", update_back_position, true);' NL ... ' parent.window.removeEventListener("resize", update_back_position, true);}' NL ... ' catch(e){}' NL ... ' }' NL NL ... ' function get_offset(element)' NL ... ' {' NL ... ' if (!element.getClientRects().length){ return { top: 0, left: 0 }; }' NL ... ' var rect = element.getBoundingClientRect();' NL ... ' var win = element.ownerDocument.defaultView;' NL ... ' return ( {top: rect.top + win.pageYOffset,' NL ... ' left: rect.left + win.pageXOffset} );' NL ... ' }' NL NL ... ' function jump_to()' NL ... ' {' NL ... ' var clickedElem = event.target;' NL ... ' var clickedID = clickedElem.closest("span");' NL ... ' if (clickedID){' NL ... ' clickedID = clickedID.getAttribute("id");' NL ... ' if (clickedID.localeCompare("jump-close")===0) { return };}' NL ... ' clickedID = clickedElem.closest("div").getAttribute("id");' NL ... ' if (clickedID && clickedID.localeCompare("return-link")===0)' NL ... ' {' NL ... ' if (returnElem)' NL ... ' {' NL ... ' event.preventDefault();' NL ... ' hide_back_link();' NL ... ' returnElem.scrollIntoView();' NL ... ' if (contentDiv.getAttribute("data-isHelpBrowser")){' NL ... ' contentDiv.scrollTop = contentDiv.scrollTop-100; }' NL ... ' if (contentDiv.getAttribute("data-isMATLABCentral")){' NL ... ' parent.window.scrollBy(0,-100)}' NL ... ' returnElem = null;' NL ... ' }' NL ... ' }' NL ... ' else' NL ... ' {' NL ... ' var href = clickedElem.closest("a").getAttribute("href");' NL ... ' if ( href && href[0] == "#" )' NL ... ' {' NL ... ' var target = document.getElementById(href.substring(1));' NL ... ' var enclosingBox = target;' NL ... ' while ( enclosingBox )' NL ... ' {' NL ... ' prevBox = enclosingBox;' NL ... ' enclosingBox = enclosingBox.closest("details");' NL ... ' if ( enclosingBox===prevBox ){' NL ... ' enclosingBox = enclosingBox.parentElement' NL ... ' if ( enclosingBox ) { enclosingBox = enclosingBox.closest("details"); } }' NL ... ' if (enclosingBox && !enclosingBox.open) { open_details(enclosingBox.id) }' NL ... ' }' NL ... ' if (target && in_iFrame() && !contentDiv.getAttribute("data-isHelpBrowser") ){' NL ... ' event.preventDefault();' NL ... ' target.scrollIntoView(); }' NL ... ' var nextElem = target.nextElementSibling;' NL ... ' var nextNode = target.nextSibling;' NL ... ' while ( nextNode && nextNode.nodeType==Node.TEXT_NODE && nextNode.data.trim().length == 0 ){' NL ... ' nextNode = nextNode.nextSibling;}' NL ... ' if ( nextElem && nextElem===nextNode && nextElem.localName.localeCompare("details")===0 && '... '!nextElem.open){' NL ... ' open_details(nextElem.id);}' NL ... ' }' NL ... ' else { return }' NL ... ' if (!contentDiv.getAttribute("data-isHelpBrowser"))' NL ... ' {' NL ... ' update_back_position();' NL ... ' returnButton.style.display = "block";' NL ... ' var linkTop = clickedElem.offsetTop;' NL ... ' var targetTop = target.offsetTop;' NL ... ' if (targetTop>linkTop){' NL ... ' document.getElementById("down").style.display = "none";' NL ... ' document.getElementById("up").style.display = "inline"; }' NL ... ' else{' NL ... ' document.getElementById("up").style.display = "none";' NL ... ' document.getElementById("down").style.display = "inline"; }' NL ... ' returnElem = clickedElem;' NL ... ' }' NL ... ' }' NL ... ' }' NL NL ... ' function open_details(detailsID)' NL ... ' {' NL ... ' var details = document.getElementById(detailsID);' NL ... ' skipCheck = true;' NL ... ' state_check(details.id);' NL ... ' details.open = true;' NL ... ' }' NL NL ... ' function update_back_position()' NL ... ' {' NL ... ' try' NL ... ' {' NL ... ' window.addEventListener("scroll", update_back_position, true);' NL ... ' window.addEventListener("resize", update_back_position, true);' NL ... ' var scrollPos;' NL ... ' if (in_iFrame())' NL ... ' {' NL ... ' parent.window.addEventListener("scroll", update_back_position, true);' NL ... ' parent.window.addEventListener("resize", update_back_position, true);' NL ... ' var iFrame = window.frameElement;' NL ... ' var frameOffset = get_offset(iFrame);' NL ... ' var documentBottom = parent.window.innerHeight + parent.window.scrollY;' NL ... ' var extHeight = Math.round(frameOffset.top + iFrame.getBoundingClientRect().height - documentBottom);' NL ... ' if (extHeight<0) { extHeight = 0; }' NL ... ' returnButton.style.bottom = (10+extHeight) + "px";' NL ... ' document.getElementById("tooltiptext").style.bottom = (11+extHeight) + "px";' NL ... ' scrollPos = contentDiv.scrollTop - 25 + iFrame.getBoundingClientRect().height - extHeight;' NL ... ' }' NL ... ' else{' NL ... ' scrollPos = window.scrollY + window.innerHeight - 25;}' NL ... ' if (returnElem.offsetTop>scrollPos){' NL ... ' document.getElementById("down").style.display = "inline";' NL ... ' document.getElementById("up").style.display = "none"; }' NL ... ' else{' NL ... ' document.getElementById("down").style.display = "none";' NL ... ' document.getElementById("up").style.display = "inline"; }' NL ... ' }' NL ... ' catch(e){}' NL ... ' }' NL ... ' function set_theme(themePref)' NL ... ' {' NL ... ' var themeSwitch = document.getElementById("ToggleTheme");' NL ... ' var themeSwitchText = "switch to";' NL ... ' var switchToText = null;' NL ... ' if (!themePref){ themePref = get_theme_pref(); }' NL ... ' if (themePref.localeCompare("light")===0){' NL ... ' document.getElementById("dark-theme").sheet.disabled = true;' NL ... ' document.getElementById("hide-dark").sheet.disabled = false;' NL ... ' switchToText = " dark theme";}' NL ... ' else{' NL ... ' document.getElementById("dark-theme").sheet.disabled = false;' NL ... ' document.getElementById("hide-dark").sheet.disabled = true;' NL ... ' switchToText = " light theme";}' NL ... ' themeSwitch.innerHTML = themeSwitchText + switchToText;' NL ... ' set_theme_pref(themePref);' NL ... ' }' NL NL ... ' function toggle_theme()' NL ... ' {' NL ... ' if (document.getElementById("dark-theme").sheet.disabled) { set_theme("dark"); }' NL ... ' else { set_theme("light"); }' NL ... ' }' NL NL ... ' function set_theme_pref(themePref)' NL ... ' {' NL ... ' var d = new Date();' NL ... ' d.setTime(d.getTime() + (2*365*24*60*60*1000));' NL ... ' var expires = "expires="+ d.toUTCString();' NL ... ' document.cookie = "themepref=" + themePref + ";" + expires + "path=/";' NL ... ' localStorage.setItem("PRETTY_THEME", themePref);' NL ... ' }' NL ... NL ... ' function get_theme_pref() {' NL ... ' var name = "themepref=";' NL ... ' var decodedCookie = decodeURIComponent(document.cookie);' NL ... ' var ca = decodedCookie.split('';'');' NL ... ' for(var i = 0; i < ca.length; i++) {' NL ... ' var c = ca[i];' NL ... ' while (c.charAt(0) == '' '') {' NL ... ' c = c.substring(1);' NL ... ' }' NL ... ' if (c.indexOf(name) == 0) {' NL ... ' return c.substring(name.length, c.length);' NL ... ' }' NL ... ' }' NL ... ' var docTheme = localStorage.getItem("PRETTY_THEME");' NL ... ' if (docTheme) { return docTheme }' NL ... ' else { return "light" }' NL ... ' }' NL ... NL ... ' function toggle_details(section)' NL ... ' {' NL ... ' var link;' NL ... ' var subSection;' NL ... ' var details;' NL ... ' var linkText;' NL ... ' var i;' NL ... ' var openState = true;' NL ... ' var border = "6px 6px 0 0;"' NL ... ' if (section===0)' NL ... ' {' NL ... ' link = document.getElementById("Toggle"+section.toString());' NL ... ' if (link.innerHTML.localeCompare("collapse all on page")===0){' NL ... ' openState = false;' NL ... ' border = "6px;"' NL ... ' linkText = "expand all";}' NL ... ' else{' NL ... ' linkText = "collapse all";}' NL ... ' link.innerHTML = linkText + " on page";' NL ... ' for (i = 0; i < allDetails.length; i++){' NL ... ' allDetails[i].open = openState;' NL ... ' allDetails[i].children[0].setAttribute( ''style'', "border-radius:"+border );' NL ... ' link = document.getElementById("Toggle"+allDetails[i].id.split(".", 1));' NL ... ' if (allDetails[i].id.charAt(0).localeCompare("0") && link){link.innerHTML = linkText;}}' NL ... ' }' NL ... ' else' NL ... ' {' NL ... ' link = document.getElementById("Toggle"+section.toString());' NL ... ' subSection = 1;' NL ... ' if (link.innerHTML.localeCompare("collapse all")===0){' NL ... ' openState = false;' NL ... ' border = "6px;"' NL ... ' link.innerHTML = "expand all";}' NL ... ' else{' NL ... ' link.innerHTML = "collapse all";}' NL ... ' details = document.getElementById(section.toString()+"."+subSection.toString());' NL ... ' while (details){' NL ... ' details.open = openState;' NL ... ' details.children[0].setAttribute( ''style'', "border-radius:"+border );' NL ... ' subSection++;' NL ... ' details = document.getElementById(section.toString()+"."+subSection.toString());}' NL ... ' var allCollapsed = true;' NL ... ' var allExpanded = true;' NL ... ' for (i = 0; i < allDetails.length; i++){' NL ... ' check_if_open(allDetails[i]);}' NL ... ' link = document.getElementById("Toggle0");' NL ... ' if (allExpanded) {link.innerHTML = "collapse all on page";}' NL ... ' if (allCollapsed){link.innerHTML = "expand all on page";}' NL ... ' }' NL ... ' function check_if_open(details)' NL ... ' {' NL ... ' if (details.open){allCollapsed = false;}' NL ... ' else {allExpanded = false;}' NL ... ' }' NL ... ' }' NL NL ... ' function state_check(detailsID)' NL ... ' {' NL ... ' // first deal with just the section' NL ... ' if (event.detail){document.activeElement.blur();}' NL ... ' var clickedElem = event.target;' NL ... ' if (!skipCheck && clickedElem.localName.localeCompare("summary"))' NL ... ' { ' NL ... ' if (!(clickedElem.closest("summary"))) { return };' NL ... ' };' NL ... ' var details = document.getElementById(detailsID);' NL ... ' if ( !skipCheck ) {' NL ... ' var parentID = clickedElem.closest("details").id;' NL ... ' if (details.id.localeCompare(parentID)) { return };}' NL ... ' skipCheck = false;' NL ... ' var clickedStatus = details.open;' NL ... ' var section = detailsID.split(".", 1);' NL ... ' var subSection = 1;' NL ... ' var allCollapsed = true;' NL ... ' var allExpanded = true;' NL ... ' var link = document.getElementById("Toggle"+section);' NL ... ' if (clickedStatus) { details.children[0].setAttribute( ''style'', "border-radius:6px;" ); }' NL ... ' else { details.children[0].setAttribute( ''style'', "border-radius:6px 6px 0 0;" ); }' NL ... ' if (link)' NL ... ' {' NL ... ' details = document.getElementById(section+"."+subSection.toString());' NL ... ' while (details){' NL ... ' check_if_open(details);' NL ... ' subSection++;' NL ... ' details = document.getElementById(section+"."+subSection.toString());}' NL ... ' if (allExpanded) {link.innerHTML = "collapse all";}' NL ... ' if (allCollapsed){link.innerHTML = "expand all";}' NL ... ' }' NL ... ' // then the whole page' NL ... ' allCollapsed = true;' NL ... ' allExpanded = true;' NL ... ' for (var i = 0; i < allDetails.length; i++){' NL ... ' check_if_open(allDetails[i]);}' NL ... ' link = document.getElementById("Toggle0");' NL ... ' if (allExpanded) {link.innerHTML = "collapse all on page";}' NL ... ' if (allCollapsed){link.innerHTML = "expand all on page";}' NL NL ... ' function check_if_open(details)' NL ... ' {' NL ... ' var openStatus' NL ... ' if (detailsID.localeCompare( details.id )===0 ){openStatus = !clickedStatus;}' NL ... ' else {openStatus = details.open;}' NL ... ' if (openStatus){allCollapsed = false;}' NL ... ' else {allExpanded = false;}' NL ... ' }' NL ... ' }' NL ... NL ... ' function in_iFrame ()' NL ... ' {' NL ... ' try {' NL ... ' return window.self !== window.top;' NL ... ' } catch (e) {' NL ... ' return true;' NL ... ' }' NL ... ' }' NL ... '</script>' NL];
Insert extra css and javascript
STYLE_CLOSE = '</style>'; cssClose = strfind(fstrm,STYLE_CLOSE); cssClose = cssClose(1); rewindCount = 0; while fstrm(cssClose)~=NL, rewindCount = rewindCount+1; cssClose = cssClose-1; end fstrm = [fstrm(1:cssClose-1+length(STYLE_CLOSE)+rewindCount) NL javaScript fstrm(cssClose+length(STYLE_CLOSE)+rewindCount:end)]; fstrm = [fstrm(1:cssClose-1) extraCSS '</style>' darkTheme jumpShift fstrm(cssClose+length(STYLE_CLOSE)+rewindCount:end)];
Fix/tweak publish's css
fstrm = strrep(fstrm, '.content { font-size:1.2em; line-height:140%;', '.content { font-size:1.2em; line-height:160%;'); fstrm = strrep(fstrm, 'pre { margin:0px 0px 20px; }', 'pre { margin:0px 0px 15px; overflow-x:auto; }'); fstrm = strrep(fstrm, 'pre, code { font-size:12px; }', 'pre { font-size:12px; }'); fstrm = strrep(fstrm, 'tt { font-size: 1.2em; }','code { font-size: 1.15em; }'); fstrm = strrep(fstrm, 'pre.codeoutput { padding:10px 11px; margin:0px 0px 20px;','pre.codeoutput { padding:10px 11px; margin:0px 0px 15px;');
html validation fixes
fstrm = strrep(fstrm, ['<!DOCTYPE html' NL ' PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">'],'<!DOCTYPE html>'); fstrm = strrep(fstrm, '<html>', '<html lang="en">'); fstrm = strrep(fstrm, '<style type="text/css">','<style>'); fstrm = strrep(fstrm, ':focus{outine:0}',''); fstrm = strrep(fstrm, '<tt','<code'); fstrm = strrep(fstrm, '</tt>','</code>');
Insert floating "return" link
fstrm = strrep(fstrm,'<div class="content">',... ['<div class="content">' NL ... '<div id="return-link" style="display:none;" class="tooltip">' NL ... '<p onclick="jump_to()">' NL ... ' <span onclick="jump_to()"><span id="up">⇧</span><span id="down">⇩</span>' NL ... ' <span onclick="hide_back_link()" style="padding:2px; font-size:120%;" id="jump-close">' ... '<b onclick="hide_back_link()">×</b></span></span>' NL ... '</p>' NL ... '<div id="tooltiptext">click to return<br>(click <b>×</b> to hide)</div>' NL ... '</div>']);
Find start and end of page body source
[sourceStart, htmlEnd] = find_source(fstrm); if DEBUG, write_fstrm_to_file(fstrm(sourceStart:htmlEnd), 'input.html', 1); end
Move any style</style> section to before the body
styleStart = strfind(fstrm(sourceStart:htmlEnd),'<style'); if ~isempty(styleStart) styleStart = styleStart + sourceStart - 1; styleEnd = strfind(fstrm(sourceStart:htmlEnd),'</style>'); assert(length(styleStart)==length(styleEnd),'ERROR: Imbalanced <style></style> tags; there are %i opening tags and %i closing tags', ... length(styleStart), length(styleEnd)); styleEnd = styleEnd + sourceStart - 1; for i = 1:length(styleStart) sectionLength = 1+styleEnd(i)+7-styleStart(i); fstrm = [fstrm(1:sourceStart-1) fstrm(styleStart(i):styleEnd(i)+7) fstrm(sourceStart:end)]; styleStart(i) = styleStart(i) + sectionLength; styleEnd(i) = styleEnd(i) + sectionLength; sourceStart = sourceStart + sectionLength; fstrm(styleStart(i):styleEnd(i)+7) = []; end end
Process [h2] tags
if verbose, fprintf(1,' [h2] tags...\n'); end userHeadingCounter = 0; fstrm = strrep(fstrm,'[h2]' ,'<h2>' ); fstrm = strrep(fstrm,'[/h2]','</h2>'); userHeadings = strfind(fstrm(sourceStart:htmlEnd),'[h2.CElink]'); if ~isempty(userHeadings) userHeadings = userHeadings + sourceStart - 1; for i = 1:length(userHeadings) userHeadingCounter = userHeadingCounter+1; headingTag = ['<h2 id="userHeading' num2str(userHeadingCounter) '">']; fstrm = [fstrm(1:userHeadings(i)-1) headingTag fstrm(userHeadings(i)+11:end)]; userHeadings = userHeadings + length(headingTag)-11; htmlEnd = htmlEnd + length(headingTag)-11; end end if DEBUG, write_fstrm_to_file(fstrm(sourceStart:htmlEnd), 'after processing [h2] tags.html', 1); end
Process [targetn] and [jumpton] tags
if verbose, fprintf(1,' target and jump tags...\n'); end [fstrm, htmlEnd] = process_target_and_jump_tags(fstrm, htmlEnd); if DEBUG, write_fstrm_to_file(fstrm(sourceStart:htmlEnd), 'after processing internal links.html', 1); end
Process colour, scale, and class tags
if verbose, fprintf(1,' colour, scale, and class tags...\n'); end tagList = {'colour','scale','class'}; validValList = {[],[],classNames}; for i = 1:length(tagList) tagName = tagList{i}; [tagOpens, tagCloses] = get_tag_pairs(['[' tagName ']'], fstrm, htmlEnd); [fstrm, htmlEnd, tagOpens, tagCloses, vals, tagEnds] = find_valid_prettify_tags(tagName, tagOpens, tagCloses, validValList{i}, fstrm, htmlEnd); [fstrm, htmlEnd] = prettyTag2htmlTag(tagName, tagOpens, tagCloses, vals, tagEnds, fstrm, sourceStart, htmlEnd, DEBUG); if DEBUG, write_fstrm_to_file(fstrm(sourceStart:htmlEnd), ['after processing ' tagName '.html'], 1); end end
Find [dtls] and [smry] tags
if verbose, fprintf(1,' dtls tags...\n'); end [detailsOpens, detailsCloses] = get_tag_pairs('[dtls]', fstrm, htmlEnd); [summaryOpens, summaryCloses] = get_tag_pairs('[smry]', fstrm, htmlEnd); assert(length(summaryOpens)==length(detailsOpens), 'ERROR: Number of [smry] tags does not match the number of [dtls] tags'); detailsTags = {detailsOpens, detailsCloses}; wrapTags = {'<span','<pre'}; for i = 1:2 if i == 1, slash = ''; else , slash = '/'; end for j = 1:length(detailsOpens) for k = 1:length(wrapTags) wrapTag = wrapTags{k}; wrapIdxs = strfind(fstrm(sourceStart:detailsTags{i}(j)), wrapTag) + sourceStart - 1; if ~isempty(wrapIdxs) wrapIdxs = wrapIdxs(end); if ~contains(fstrm(wrapIdxs:detailsTags{i}(j)),['</' wrapTag(2:end) '>']) if k == 1, warnStr1 = 'appears inside a comment section of'; warnStr2 = 'forgotten a space'; else , warnStr1 = 'is wrapped in <pre>...</pre> tags in'; warnStr2 = 'put too many spaces'; end show_warning(sprintf(['[%cdtls] tag %i %s the html document - this may cause an error.\n' ... 'Possibly you have forgotten to start a new section in the source .m file, or %s ' ... 'between the start-of-line "%%" and the [%cdtls] tag.'], slash, j, warnStr1, warnStr2, slash)); break end end end end end [countOfDtlsTags, detailsCloses] = sort_tag_closes(detailsOpens, detailsCloses); for i = 1:countOfDtlsTags assert(summaryOpens (i)>detailsOpens (i),'ERROR: [smry] tag %i opened before [dtls] tag %i', i, i); assert(summaryCloses(i)<detailsCloses(i),'ERROR: [dtls] tag pair %i closed before [smry] tag pair %i', i, i); end imgTags = [];
Process [dtls] and [smry] tags
if ~isempty(detailsOpens)
dummySections = 0; revertedCount = false; revertAt = 0; subSectionCounterBak = NaN; collapseAllStyle = 'class="collapse-link"'; collapseAllPos = strfind(fstrm(sourceStart:htmlEnd),'<h2>Contents</h2>') + sourceStart - 1; noSections = false; insertPoint = detailsOpens(1)-1; if isempty(collapseAllPos) || any(detailsOpens<collapseAllPos(1)) if isempty(collapseAllPos), noSections = true; collapseAllPos=0; end firstP = strfind(fstrm(sourceStart:htmlEnd),'<div class="content">') + sourceStart - 1; firstP = strfind(fstrm(firstP(1):htmlEnd),'<p>') + firstP(1) - 1; assert (~isempty(firstP),[NO_START_ERR ' - is entire source .m file comment-only? Did you forget to start a section?']); if isempty(themeSwitch) collapseAll = ['<p style="margin:0px; line-height:0;"> </p><p onclick="toggle_details(0)"'... ' style="float:right; padding-left:10px; margin:0;"><a href="javascript:void(0);" id="Toggle0">collapse all on page</a></p>']; fstrm = strrep(fstrm,'<script>document.getElementById("dark-theme")',[collapseAll '<script>document.getElementById("dark-theme")']); else newThemeSwitch = strrep(themeSwitch,['<p onclick="toggle_theme()" style="text-align:right; float:right; padding-left:10px; margin:0;">'... '<a '],... ['<p style="text-align:right; float:right; padding-left:10px; margin:0;"><span ' ... 'onclick="toggle_theme()" style="line-height:100%; padding-bottom:1px; border-bottom:1px ' ... 'solid #d6d4d4;"><a ']); newThemeSwitch = strrep(newThemeSwitch,'</a>','</a></span>'); fstrm = strrep(fstrm,themeSwitch,newThemeSwitch); update_idxs(length(newThemeSwitch)-length(themeSwitch)); collapseAll = '<br><span onclick="toggle_details(0)"><a href="javascript:void(0);" id="Toggle0">collapse all on page</a></span> '; fstrm = strrep(fstrm,'</p></div><script>set_theme(null)</script>',[collapseAll '</p></div><script>set_theme(null)</script>']); end else collapseAllPos = collapseAllPos(1); collapseAll = ['<p style="margin:0px; line-height:0;"> </p><p onclick="toggle_details(0)" ' collapseAllStyle '>'... '<a href="javascript:void(0);" id="Toggle0">collapse all on page</a></p>']; fstrm = [fstrm(1:collapseAllPos-1) collapseAll fstrm(collapseAllPos:end)]; end update_idxs(length(collapseAll)); collapseAllPos = collapseAllPos(1)+length(collapseAll); sectionsCounter = 0; subSectionCounter = 1; detailsBeforeContents = detailsOpens<collapseAllPos; for detailsSectionCounter = 1:countOfDtlsTags if revertAt && detailsOpens(detailsSectionCounter)>detailsCloses(revertAt) sectionsCounter = sectionsCounterBak; subSectionCounter = subSectionCounterBak; subSectionCounterBak = NaN; revertedCount = true; revertAt = 0; end if ~noSections if detailsBeforeContents(detailsSectionCounter) sectionsCounter=-1; if subSectionCounter==1 sectionCollapseState = ['<div style="display:none;"><p id="Toggle' num2str(sectionsCounter) '">collapse all</p></div>']; fstrm = [fstrm(1:detailsOpens(detailsSectionCounter)-1) sectionCollapseState fstrm(detailsOpens(detailsSectionCounter):end)]; insertPoint = detailsOpens(detailsSectionCounter)-1; update_idxs(length(sectionCollapseState)); end elseif sectionsCounter==-1 sectionsCounter = 0; end elseif sectionsCounter == 0 sectionsCounter = 1; end if detailsSectionCounter==1, headingSearchStart = 1; else , headingSearchStart = detailsOpens(detailsSectionCounter-1); end headingIdx = strfind( fstrm(headingSearchStart:detailsOpens(detailsSectionCounter)), '<h2 id="' ); if ~isempty(headingIdx) skipHeading = false; headingIdx = headingSearchStart-1+headingIdx(end); idEnd = strfind(fstrm(headingIdx+9:detailsOpens(detailsSectionCounter)),'"'); if isempty(idEnd), error('ERROR: malformed <h2> tag (no closing " for id)'); end idEnd = idEnd(1) + headingIdx + 7; headingNum = str2double(fstrm(headingIdx+8:idEnd)); if isnan(headingNum), isDummySection = true; else , isDummySection = false; end headingNestLevel = sum(headingIdx<detailsCloses(1:detailsSectionCounter-1)); if detailsSectionCounter>1 && headingNestLevel if ~isDummySection error('ERROR: Section heading %i is inside a [dtls] box: this is not supported', headingNum); elseif headingNestLevel > sum(detailsOpens(detailsSectionCounter)<detailsCloses(1:detailsSectionCounter-1)) skipHeading = true; elseif ~noSections revertAt = find(headingIdx<detailsCloses(1:detailsSectionCounter-1),1,'last'); end end if ~skipHeading sectionsCounter = sectionsCounter + 1; if ~isDummySection subSectionCounterBak = NaN; else if isnan(subSectionCounterBak) sectionsCounterBak = sectionsCounter - 1; subSectionCounterBak = subSectionCounter; end end if revertedCount, sectionsCounter = sectionsCounter + dummySections; end revertedCount = false; subSectionCounter = 1; headingClose = strfind(fstrm(headingIdx:detailsOpens(detailsSectionCounter)),'</h2>'); headingClose = headingClose(1) + headingIdx - 1; if ~contains(fstrm(headingIdx:headingClose),'display:none') sectionHeader = ['<p style="margin:0px; line-height:0;"> </p><p onclick="toggle_details(' num2str(sectionsCounter)... ')" class="collapse-link"><a href="javascript:void(0);" id="Toggle' num2str(sectionsCounter) ... '">collapse all</a></p>']; fstrm = [fstrm(1:headingIdx-1) sectionHeader fstrm(headingIdx:end)]; insertPoint = headingIdx-1; update_idxs(length(sectionHeader)); end if isDummySection, dummySections = dummySections+1; end end end % <p>...[dtls] -> ...[dtls][smry]...[/smry]<p> % look for unbalanced <p></p> tags before the [dtls] tag pBeforeDtls = strfind(fstrm(sourceStart:detailsOpens(detailsSectionCounter)),'<p') + sourceStart - 1; if ~isempty(pBeforeDtls) closepBeforeDtls = strfind(fstrm(sourceStart:detailsOpens(detailsSectionCounter)),'</p>') + sourceStart - 1; if length(pBeforeDtls) > length(closepBeforeDtls) while closepBeforeDtls(end)>pBeforeDtls(end) closepBeforeDtls(end) = []; pBeforeDtls(end) = []; end % move pBeforeDtls(end) to after the [/smry] tag associated with this [dtls] tag, but only if the pBeforeDtls is a plain "<p>" if strcmp(fstrm(pBeforeDtls(end):pBeforeDtls(end)+2),'<p>') fstrm(pBeforeDtls(end):pBeforeDtls(end)+2) = []; detailsOpens ( detailsSectionCounter ) = detailsOpens ( detailsSectionCounter ) - 3; summaryCloses( detailsSectionCounter ) = summaryCloses( detailsSectionCounter ) - 3; fstrm = [fstrm(1:summaryCloses(detailsSectionCounter)+6) '<p>' fstrm(summaryCloses(detailsSectionCounter)+7:end)]; end end end % [/dtls]...</p> -> [/dtls]<p>...</p> + delete the previous <p> % or [/dtls]</p> -> [/dtls] + delete the previous <p> closepAfterDtls = strfind(fstrm(detailsCloses(detailsSectionCounter):htmlEnd),'</p>') + detailsCloses(detailsSectionCounter) - 1; if ~isempty(closepAfterDtls) closepAfterDtls = closepAfterDtls(1); pAfterDtls = strfind(fstrm(detailsCloses(detailsSectionCounter):htmlEnd),'<p') + detailsCloses(detailsSectionCounter) - 1; if isempty(pAfterDtls) || pAfterDtls(1)>closepAfterDtls if strcmp(fstrm(detailsCloses(detailsSectionCounter)+7:detailsCloses(detailsSectionCounter)+10),'</p>') fstrm(detailsCloses(detailsSectionCounter)+7:detailsCloses(detailsSectionCounter)+10) = []; insertPoint = detailsCloses(detailsSectionCounter); update_idxs(-4); else fstrm = [fstrm(1:detailsCloses(detailsSectionCounter)+6) '<p>' fstrm(detailsCloses(detailsSectionCounter)+7:end)]; insertPoint = detailsCloses(detailsSectionCounter); update_idxs(3); end pInDtls = strfind(fstrm(detailsOpens(detailsSectionCounter):detailsCloses(detailsSectionCounter)),'<p>'); if ~isempty(pInDtls) pInDtls = pInDtls(end) + detailsOpens(detailsSectionCounter) - 1; fstrm(pInDtls:pInDtls+2) = []; insertPoint = pInDtls; update_idxs(-3); end end end % [dtls] -> <details open onclick="state_check(<ID>)" id="<ID>"> detailsId = [num2str(sectionsCounter) '.' num2str(subSectionCounter)]; detailsOpener = ['<details open onclick="state_check(''' detailsId ''')" id="' detailsId '">']; fstrm = [fstrm(1:detailsOpens(detailsSectionCounter)-1) detailsOpener fstrm(detailsOpens(detailsSectionCounter)+6:end)]; insertPoint = detailsOpens(detailsSectionCounter)-1; update_idxs(length(detailsOpener)-6); % [/dtls] -> </div></details> addBreak = ''; % add line break after [dtls] section, if necessary if ~strcmp(fstrm(detailsCloses(detailsSectionCounter)+7:detailsCloses(detailsSectionCounter)+13),'<p></p>') ... && ~strcmp(fstrm(detailsCloses(detailsSectionCounter)+7:detailsCloses(detailsSectionCounter)+14),'<p> </p>') ... && ~strcmp(fstrm(detailsCloses(detailsSectionCounter)+7:detailsCloses(detailsSectionCounter)+14),['<p>' char(160) '</p>']) ... && ~strcmp(fstrm(detailsCloses(detailsSectionCounter)+7:detailsCloses(detailsSectionCounter)+24),'<p class="footer">') ... && ~strcmp(fstrm(detailsCloses(detailsSectionCounter)+7:detailsCloses(detailsSectionCounter)+12),'<p><br') ... && ~strcmp(fstrm(detailsCloses(detailsSectionCounter)+7:detailsCloses(detailsSectionCounter)+12),'<p>[br') ... && ~strcmp(fstrm(detailsCloses(detailsSectionCounter)+7:detailsCloses(detailsSectionCounter)+9) ,'<br') ... && ~strcmp(fstrm(detailsCloses(detailsSectionCounter)+7:detailsCloses(detailsSectionCounter)+9) ,'[br') addBreak = '<br>'; end dtlsClose = ['</div></details>' addBreak]; fstrm = [fstrm(1:detailsCloses(detailsSectionCounter)-1) dtlsClose fstrm(detailsCloses(detailsSectionCounter)+7:end)]; insertPoint = detailsCloses(detailsSectionCounter)-1; update_idxs(length(dtlsClose)-7); subSectionCounter = subSectionCounter + 1; if DEBUG, write_fstrm_to_file(fstrm(sourceStart:htmlEnd), ['after replacing [dtls] tag set ' num2str(detailsSectionCounter) '.html'], 1); end end fstrm = strrep(fstrm,'[smry]','<summary>'); fstrm = strrep(fstrm,'[/smry]','</summary><div class="details-div">'); if DEBUG, write_fstrm_to_file(fstrm(sourceStart:htmlEnd), 'after replacing [smry] tags.html', 1); end insertPoint = detailsOpens(1)-1; update_idxs(31*countOfDtlsTags);
Remove excess line breaks after any tables in details sections
detailsOpens = strfind(fstrm(sourceStart:htmlEnd),'<details open ') + sourceStart - 1; detailsCloses = strfind(fstrm(sourceStart:htmlEnd),'</details>') + sourceStart - 1; [countOfDtlsTags, detailsCloses] = sort_tag_closes(detailsOpens, detailsCloses); for detailsCounter = 1:countOfDtlsTags tableCloseIdxs = strfind(fstrm(detailsOpens(detailsCounter):detailsCloses(detailsCounter)),'</table>') + detailsOpens(detailsCounter) - 1; for i = 1:length(tableCloseIdxs) brIdx = strfind(fstrm(tableCloseIdxs(i)+8:detailsCloses(detailsCounter)-5),'<br>'); if isempty(brIdx), continue, end brIdx = brIdx(1) + tableCloseIdxs(i) + 7; if strcmp(sscanf( fstrm(tableCloseIdxs(i)+8:brIdx+3),'%s' ),'<br>') fstrm(brIdx:brIdx+3) = []; tableCloseIdxs = tableCloseIdxs-4; insertPoint = brIdx; update_idxs(-4); end end end
Remove excess spacing after any lists at the end of details sections
for listTag = {'ul','ol'} for detailsCounter = 1:countOfDtlsTags listIdxs = strfind(fstrm(detailsOpens(detailsCounter):detailsCloses(detailsCounter)),['<' listTag{:} '>']); listIdxs = sort( [listIdxs strfind(fstrm(detailsOpens(detailsCounter):detailsCloses(detailsCounter)),['<' listTag{:} ' style="'])] ); if ~isempty(listIdxs) listIdxs = listIdxs(end) + detailsOpens(detailsCounter) - 1; listTagEnd = strfind(fstrm(listIdxs:detailsCloses(detailsCounter)),'>'); listTagEnd = listTagEnd(1) + listIdxs - 1; listEnd = strfind(fstrm(listIdxs:detailsCloses(detailsCounter)),['</' listTag{:} '>']) + listIdxs - 1; if ~isempty(listEnd) && ~contains(fstrm(listIdxs:listTagEnd),'margin') if strcmp(fstrm(listEnd:detailsCloses(detailsCounter)),['</' listTag{:} '></div></span></div><']) ... || strcmp(fstrm(listEnd:detailsCloses(detailsCounter)),['</' listTag{:} '></div></div><']) ... || strcmp(fstrm(listEnd:detailsCloses(detailsCounter)),['</' listTag{:} '></span></div><']) ... || strcmp(fstrm(listEnd:detailsCloses(detailsCounter)),['</' listTag{:} '></div><']) if fstrm(listIdxs+3)=='>' fstrm = [fstrm(1:listIdxs+2) ' style="margin-bottom:0px;"' fstrm(listIdxs+3:end)]; insertPoint = listIdxs+2; update_idxs(27); else % <ul style="blah -> <ul style="margin-bottome:0px; blah fstrm = [fstrm(1:listIdxs+10) 'margin-bottom:0px; ' fstrm(listIdxs+11:end)]; insertPoint = listIdxs+10; update_idxs(19); end end end end end end if DEBUG, write_fstrm_to_file(fstrm(sourceStart:htmlEnd), 'before adding breaks between tables and images.html', 1); end
Add breaks between tables and images
for detailsCounter = 1:countOfDtlsTags tableThenImage = strfind(fstrm(detailsOpens(detailsCounter):detailsCloses(detailsCounter)),'</table><img ') + detailsOpens(detailsCounter) - 1; for i = 1:length(tableThenImage) fstrm = [fstrm(1:tableThenImage(i)+7) '[br15]' fstrm(tableThenImage(i)+8:end)]; insertPoint = tableThenImage(i)+7; update_idxs(6); tableThenImage = tableThenImage+6; end end if DEBUG, write_fstrm_to_file(fstrm(sourceStart:htmlEnd), 'after adding [br15].html', 1); end
end
Process [bottomMarginx] tags
if verbose, fprintf(1,' bottom margin tags...\n'); end marginTagList = strfind(fstrm(sourceStart:htmlEnd),'[bottomMargin') + sourceStart - 1; [fstrm, htmlEnd, marginTagList, ~ , margins, marginTagEnds] = find_valid_prettify_tags('bottomMargin', marginTagList, [], [], fstrm, htmlEnd); for i = 1:length(marginTagList) tagLength = 1+marginTagEnds(i)-marginTagList(i); if strcmp(fstrm(marginTagList(i)-3:marginTagList(i)-1),'<p>') style = [' style="margin-bottom: ' num2str(margins(i)) 'px;"']; if strcmp(fstrm(marginTagEnds(i)+1:marginTagEnds(i)+9),'</p><div>') % <p>[bottomMarginx]</p><div> -> <div style="margin-bottom:xpx;"> fstrm = [fstrm(1:marginTagEnds(i)+8) style fstrm(marginTagEnds(i)+9:end)]; fstrm(marginTagList(i)-3:marginTagEnds(i)+4) = []; charsAdded = length(style) - (tagLength+7); else % <p>[bottomMarginx] -> <p style="margin-bottom:xpx;"> fstrm = [fstrm(1:marginTagList(i)-2) style fstrm(marginTagList(i)-1:end)]; fstrm(marginTagList(i)+length(style):marginTagEnds(i)+length(style)) = []; charsAdded = length(style) - tagLength; end else fstrm(marginTagList(i):marginTagEnds(i)) = []; charsAdded = -tagLength; end marginTagList = marginTagList + charsAdded; marginTagEnds = marginTagEnds + charsAdded; htmlEnd = htmlEnd + charsAdded; end if DEBUG && ~isempty(marginTagList), write_fstrm_to_file(fstrm(sourceStart:htmlEnd), 'after processing bottomMarginx.html', 1); end
Assign image-fit class to all images
imgTags = strfind(fstrm(sourceStart:htmlEnd),'<img ') + sourceStart - 1; for i = 1:length(imgTags) srcIdx = strfind(fstrm(imgTags(i):htmlEnd),' src='); srcIdx = srcIdx(1) + imgTags(i) - 1; imgTagEnd = strfind(fstrm(imgTags(i):htmlEnd),'>'); imgTagEnd = imgTagEnd(1) + imgTags(i) - 1; assert(~isempty(srcIdx) && ~isempty(imgTagEnd),'ERROR: malformed image tags found'); if contains(fstrm(srcIdx:imgTagEnd),'.svg'), fitClass = 'image-fit-svg'; else , fitClass = 'image-fit'; end classStrs = {'class="','class ="','class= "','class = "'}; for classStr = classStrs classIdx = strfind(fstrm(imgTags(i):imgTagEnd),classStr{:}); if ~isempty(classIdx) classIdx = classIdx(1) + imgTags(i) - 1; if contains(fstrm(imgTags(i):imgTagEnd),'image-fit') break end fstrm = [fstrm(1:classIdx-1) classStr{:} fitClass ' ' fstrm(classIdx+length(classStr{:}):end)]; insertPoint = classIdx-1; update_idxs(length(fitClass)+1); break end end if isempty(classIdx) classToAdd = ['class="' fitClass '"']; if contains(fstrm(imgTags(i):imgTagEnd),'vspace="5" hspace="5"') charsToRemove = srcIdx - imgTags(i) - 5; fstrm = [fstrm(1:imgTags(i)+4) classToAdd fstrm(srcIdx:end)]; else charsToRemove = -1; fstrm = [fstrm(1:imgTags(i)+4) classToAdd ' ' fstrm(imgTags(i)+5:end)]; end insertPoint = imgTags(i)+4; update_idxs(length(classToAdd)-charsToRemove); end end
Process undocumented [png2svg] tags
WARNING - MATLAB central does not support .svg images, so they have to be hosted externally
[png2svgOpens, png2svgCloses] = get_tag_pairs('[png2svg]', fstrm, htmlEnd); imageFolder = strfind(fstrm(1:htmlEnd),'[imageFolder='); if ~isempty(imageFolder) imageFolder = imageFolder(1); imageFolderEnd = strfind(fstrm(imageFolder:htmlEnd),']'); imageFolder = fstrm(imageFolder+13:imageFolder+imageFolderEnd(1)-2); end for i = 1:length(png2svgOpens) imgTags = strfind(fstrm(png2svgOpens(i):png2svgCloses(i)),'<img ') + png2svgOpens(i) - 1; for j = 1:length(imgTags) srcIdx = strfind(fstrm(imgTags(j):png2svgCloses(i)),' src='); srcIdx = srcIdx(1) + imgTags(j) - 1; imgTagEnd = strfind(fstrm(srcIdx:png2svgCloses(i)),'>'); imgTagEnd = imgTagEnd(1) + srcIdx - 1; if contains(fstrm(srcIdx:imgTagEnd),'.png" alt="">') fstrm = [fstrm(1:srcIdx-1) strrep(fstrm(srcIdx:imgTagEnd),'.png" alt="">','_.svg" alt="">') fstrm(imgTagEnd+1:end)]; imgTags(j+1:end) = imgTags(j+1:end) + 1; imgTagEnd = imgTagEnd + 1; % image-fit -> image-fit-svg fstrm = [fstrm(1:imgTags(j)-1) strrep(fstrm(imgTags(j):imgTagEnd),'image-fit','image-fit-svg') fstrm(imgTagEnd+1:end)]; insertPoint = imgTags(j)-1; update_idxs(5); png2svgOpens = png2svgOpens + 5; png2svgCloses = png2svgCloses + 5; if ~isempty(imageFolder) fstrm = [fstrm(1:srcIdx+9) imageFolder '/' fstrm(srcIdx+10:end)]; insertPoint = srcIdx+9; update_idxs(length(imageFolder)+1); png2svgOpens = png2svgOpens + length(imageFolder)+1; png2svgCloses = png2svgCloses + length(imageFolder)+1; end end end end
Process undocumented [removepng] tags
[removepngOpens, removepngCloses] = get_tag_pairs('[removepng]', fstrm, htmlEnd); for i = 1:length(removepngOpens) imgTags = strfind(fstrm(removepngOpens(i):removepngCloses(i)),'<img ') + removepngOpens(i) - 1; for j = 1:length(imgTags) srcIdx = strfind(fstrm(imgTags(j):removepngCloses(i)),' src='); srcIdx = srcIdx(1) + imgTags(j) - 1; imgTagEnd = strfind(fstrm(srcIdx:removepngCloses(i)),'>'); imgTagEnd = imgTagEnd(1) + srcIdx - 1; if contains(fstrm(srcIdx:imgTagEnd),'.png" alt="">') fstrm(imgTags(j):imgTagEnd) = []; imgTagLength = (imgTagEnd+1-imgTags(j)); insertPoint = imgTags(j); update_idxs(-imgTagLength); removepngOpens = removepngOpens - imgTagLength; removepngCloses = removepngCloses - imgTagLength; end end end
Process [darkAlt] tags
if verbose, fprintf(1,' darkAlt tags...\n'); end IMG_EXTS = {'png','jpg','jpeg','svg'}; IMG_EXTS = [IMG_EXTS upper(IMG_EXTS)]; [darkAltOpens, darkAltCloses] = get_tag_pairs('[darkAlt]', fstrm, htmlEnd); for i = 1:length(darkAltOpens) imgTags = strfind(fstrm(darkAltOpens(i):darkAltCloses(i)),'<img ') + darkAltOpens(i) - 1; if ~isempty(imgTags) fstrm = [fstrm(1:darkAltOpens(i)-1) ... strrep(fstrm(darkAltOpens(i):darkAltCloses(i)),'image-fit"','image-fit show-if-light"') ... fstrm(darkAltCloses(i)+1:end)]; fstrm = [fstrm(1:darkAltOpens(i)-1) ... strrep(fstrm(darkAltOpens(i):darkAltCloses(i)),'image-fit-svg','image-fit-svg show-if-light') ... fstrm(darkAltCloses(i)+1:end)]; insertPoint = darkAltOpens(i)-1; update_idxs(14*length(imgTags)) darkAltOpens(i+1:end) = darkAltOpens(i+1:end) + 14*length(imgTags); darkAltCloses = darkAltCloses + 14*length(imgTags); end imgTags = strfind(fstrm(darkAltOpens(i):darkAltCloses(i)),'<img ') + darkAltOpens(i) - 1; for j = 1:length(imgTags) srcIdx = strfind(fstrm(imgTags(j):darkAltCloses(i)),' src='); srcIdx = srcIdx(1) + imgTags(j) - 1; imgTagEnd = strfind(fstrm(srcIdx:darkAltCloses(i)),'>'); imgTagEnd = imgTagEnd(1) + srcIdx - 1; duplicate = fstrm(imgTags(j):imgTagEnd); for ext = IMG_EXTS if contains(fstrm(srcIdx:imgTagEnd),['.' ext{:} '" alt="">']) fstrm = [fstrm(1:imgTagEnd) ... strrep(strrep(duplicate,['.' ext{:} '" alt="">'],['_dark.' ext{:} '" alt="">']),'show-if-light','show-if-dark') ... fstrm(imgTagEnd+1:end)]; insertPoint = imgTagEnd; update_idxs(length(duplicate)+4); darkAltOpens(i+1:end) = darkAltOpens(i+1:end) + length(duplicate)+4; darkAltCloses = darkAltCloses + length(duplicate)+4; end end end end
Replace [br] with br
if verbose, fprintf(1,' [br] tags...\n'); end fstrm = strrep(fstrm,'[br]','<br>');
Process undocumented [rembr] and [delbr] tags
rembrTags = strfind(fstrm(sourceStart:htmlEnd),'[rembr]') + sourceStart - 1; for i = 1:length(rembrTags) brTag = strfind(fstrm(sourceStart:rembrTags(i)),'<br>'); brTag = brTag(end) + sourceStart - 1; if strcmp(sscanf(fstrm(brTag:rembrTags(i)-1),'%s'),'<br>') fstrm(brTag:brTag+3) = []; rembrTags = rembrTags - 4; htmlEnd = htmlEnd - 4; end end delbrTags = strfind(fstrm(sourceStart:htmlEnd),'[delbr]') + sourceStart - 1; for i = 1:length(delbrTags) brTag = strfind(fstrm(delbrTags(i):htmlEnd),'<br>'); brTag = brTag(1) + delbrTags(i) - 1; if strcmp(sscanf(fstrm(delbrTags(i)+7:brTag+3),'%s'),'<br>') fstrm(brTag:brTag+3) = []; delbrTags = delbrTags - 4; htmlEnd = htmlEnd - 4; end end if verbose, fprintf(1,' [delsp] tags...\n'); end delspTags = strfind(fstrm(sourceStart:htmlEnd),'[delsp]') + sourceStart - 1; for i = 1:length(delspTags) if fstrm(delspTags(i)+7) == ' ' fstrm(delspTags(i)+7) = []; delspTags = delspTags - 1; htmlEnd = htmlEnd - 1; end end
Insert breaks of specified pixel height ([brx] tags)
if verbose, fprintf(1,' [brx] tags...\n'); end brxTagList = strfind(fstrm(sourceStart:htmlEnd),'[br') + sourceStart - 1; [fstrm, htmlEnd, brxTagList, ~ , ~, brTagEnds] = find_valid_prettify_tags('br', brxTagList, [], [], fstrm, htmlEnd); for j = 1:length(brxTagList) %[brx] -> <br style="display:block; content:''; margin-top:xpx;"> fstrm = [fstrm(1:brxTagList(j)-1) '<br style="display:block; content:''''; margin-top:' fstrm(brxTagList(j)+3:brTagEnds(j)-1) ... 'px;">' fstrm(brTagEnds(j)+1:end)]; brxTagList = brxTagList + 50; brTagEnds = brTagEnds + 50; htmlEnd = htmlEnd + 50; end
Remove excess spacing after codeoutput sections
codeoutLoc = strfind(fstrm(sourceStart:htmlEnd),'<pre class="codeoutput">') + sourceStart - 1; for i = 1:length(codeoutLoc) codeoutEnd = strfind(fstrm(codeoutLoc(i):htmlEnd),'</pre>'); charsDeleted = 0; if ~isempty(codeoutEnd) codeoutEnd = codeoutEnd(1) + codeoutLoc(i) - 1; while fstrm(codeoutEnd-1-charsDeleted)==NL fstrm(codeoutEnd-1-charsDeleted)=[]; charsDeleted = charsDeleted + 1; end end htmlEnd = htmlEnd - charsDeleted; codeoutLoc(i+1:end) = codeoutLoc(i+1:end) - charsDeleted; if strcmp(fstrm(codeoutEnd-charsDeleted+6:codeoutEnd-charsDeleted+21),'</div></details>') fstrm = [fstrm(1:codeoutLoc(i)+4) 'style=margin-bottom:0px; ' fstrm(codeoutLoc(i)+5:end)]; htmlEnd = htmlEnd + 25; codeoutLoc(i+1:end) = codeoutLoc(i+1:end) + 25; end end
Remove erroneous p</p> tags
pOpens = strfind(fstrm(sourceStart:htmlEnd),'<p ') + sourceStart - 1; unstyledP = strfind(fstrm(sourceStart:htmlEnd),'<p>') + sourceStart - 1; pOpens = sort([pOpens unstyledP]); pCloses = strfind(fstrm(sourceStart:htmlEnd),'</p>') + sourceStart - 1; assert(length(pOpens)==length(pCloses),'ERROR: Imbalanced <p></p> tags detected. There are %i opening tags and %i closing tags',... length(pOpens),length(pCloses)) [~, pCloses] = sort_tag_closes(pOpens, pCloses); pCloses = pCloses(ismember(pOpens,unstyledP)); pOpens = pOpens (ismember(pOpens,unstyledP)); for i = 1:length(pOpens) if contains(fstrm(pOpens(i):pCloses(i)), {'</p>','</table>','</ol>','</ul>','</pre>','</div>','</img>','</dl>','</h1>','</h2>','</h3>','</h4>', ... '</h5>','</h6>'}) fstrm(pOpens(i):pOpens(i)+2) = []; pCloses(pCloses>pOpens(i)) = pCloses(pCloses>pOpens(i)) - 3; pOpens (pCloses>pOpens(i)) = pOpens (pCloses>pOpens(i)) - 3; fstrm(pCloses(i):pCloses(i)+3) = []; pOpens (pCloses>pCloses(i)) = pOpens (pCloses>pCloses(i)) - 4; pCloses(pCloses>pCloses(i)) = pCloses(pCloses>pCloses(i)) - 4; end end
Insert link to FEX page
handle defunct [frameBufferx] tag
frameBufferTag = strfind(fstrm(sourceStart:htmlEnd),'[frameBuffer') + sourceStart - 1; if ~isempty(frameBufferTag) assert(length(frameBufferTag)==1,'ERROR: only one [frameBufferx] tag is allowed'); frameBufferTagEnd = strfind(fstrm(frameBufferTag:htmlEnd),']') + frameBufferTag - 1; fstrm(frameBufferTag:frameBufferTagEnd) = []; end frameBuffer = '<p id="iFrameBuf"> </p>'; finalA = strfind(fstrm(sourceStart:htmlEnd),'<a'); finalA = finalA(end) + sourceStart - 1; if strcmp(fstrm(finalA-4:finalA-1),'<br>'), fstrm(finalA-4:finalA-1) = []; end finalCloseA = strfind(fstrm(sourceStart:htmlEnd),'</a>'); finalCloseA = finalCloseA(end) + sourceStart - 1; % get version number from opening comments fid = fopen([mfilename('fullpath') '.m'],'r'); line = ''; while ~contains(line,'Version'), line = fgetl(fid); end verStr = sscanf(line,'%% Version %s'); fclose(fid); % add link and frame buffer to footer fstrm = [fstrm(1:finalCloseA+3) ' and subsequently processed by ' ... '<a class="pretty-link" href="https://www.mathworks.com/matlabcentral/fileexchange/78059-prettify-matlab-html">prettify_MATLAB_html</a>' ... ' V' verStr '</p>' frameBuffer fstrm(finalCloseA+12:end)]; % skip over <br></p> in original source
Remove/modify remaining prettify_MATLAB_html tags
tagsToRemove = {'[rembr]','[delbr]','[delsp]','[png2svg]','[/png2svg]','[removepng]','[/removepng]','[darkAlt]','[/darkAlt]','[imageFolder='}; for tag = tagsToRemove if tag{:}(end)~=']' tagStarts = strfind(fstrm,tag{:}); for tagStart = tagStarts tagEnd = strfind(fstrm(tagStart:end),']'); fstrm(tagStart:tagStart+tagEnd(1)-1)=[]; end else fstrm = strrep(fstrm,tag{:},''); end end fstrm = strrep(fstrm,'[ targetn]','[target<i>n</i>]'); fstrm = strrep(fstrm,'[ jumpton]','[jumpto<i>n</i>]'); fstrm = strrep(fstrm,'[ scalex]' ,'[scale<i>x</i>]'); fstrm = strrep(fstrm,'[ class.class-name]' ,'[class.<i>class-name</i>]'); fstrm = strrep(fstrm,'[ brx]' ,'[br<i>x</i>]'); fstrm = strrep(fstrm,'[ bottomMarginx]' ,'[bottomMargin<i>x</i>]'); tagsToModify = {'[ br','[ bottomMargin','[ target','[ rembr]','[ delbr]','[ delsp]','[ frameBufferx]'}; for tag = tagsToModify fstrm = strrep(fstrm,tag{:},[tag{:}(1) tag{:}(3:end)]); end tagsToModify = {'[ dtls]','[ smry]','[ jumpto','[ cssClasses','[ colour','[ scale','[ class','[ themesEnabled]','[ darkAlt]','[ h2]','[ h2.CElink]'}; for tag = tagsToModify fstrm = strrep(fstrm,tag{:},[tag{:}(1) tag{:}(3:end)]); fstrm = strrep(fstrm,[tag{:}(1:2) '/' tag{:}(3:end)],[tag{:}(1) '/' tag{:}(3:end)]); end
Final bit of javascript
fstrm = strrep(fstrm,'</body>',['<script>' NL ... 'var allDetails = document.getElementsByTagName(''details'');' NL ... 'var contentDiv = document.getElementsByClassName("content"); contentDiv = contentDiv[0];' NL ... 'var returnButton = document.getElementById("return-link");' NL ... 'document.getElementById("iFrameBuf").style.display = "none";' NL ... 'if(in_iFrame())' NL ... '{' NL ... ' try{' NL ... ' var footerNav = parent.document.getElementsByClassName("footernav");' NL ... ' var tabPane = parent.document.getElementsByClassName("tab-pane");}' NL ... ' catch(err) { var footerNav = []; var tabPane = [];};' NL ... ' if(!(footerNav.length) || tabPane.length)' NL ... we are embedded in a frame that's not a MATLAB help browser frame ' {' NL ... ' contentDiv.style.overflowY = "scroll";' NL ... ' contentDiv.style.overflowX = "hidden";' NL ... ' contentDiv.style.position = "absolute";' NL ... ' contentDiv.style.width = "95%";' NL ... ' contentDiv.style.top = 0;' NL ... ' contentDiv.style.bottom = 0;' NL ... ' if (tabPane.length){' NL ... ' contentDiv.setAttribute("data-isMATLABCentral","1");' NL ... ' returnButton.style.right = "40px";' NL ... ' document.getElementById("tooltiptext").style.right = "92px"; }' NL ... ' document.getElementById("iFrameBuf").style.display = "block";' NL ... ' }' NL ... ' else { contentDiv.setAttribute("data-isHelpBrowser","1"); }' NL ... '}' NL ... 'if (!contentDiv.getAttribute("data-isHelpBrowser") && !contentDiv.getAttribute("data-isMATLABCentral") ){' NL ... ' document.getElementById("anchor-offsets").sheet.disabled = true; }' NL ... 'var jumpLinks = document.getElementsByTagName("a");' NL ... 'for (var i = 0; i < jumpLinks.length; i++){' NL ... ' href = jumpLinks[i].getAttribute("href");' NL ... ' if (href && href[0] == "#") { jumpLinks[i].onclick = jump_to;}}' NL ... '</script></body>']);
Write the output file
if verbose, fprintf(1,'Writing output file...\n'); end write_fstrm_to_file(fstrm, inputhtmlFile); if DEBUG, write_fstrm_to_file(fstrm(sourceStart:htmlEnd), 'processing complete.html', 1); end if verbose, fprintf(1,'Processing complete.\n'); end show_warning_dialogue;
catch MEx show_warning_dialogue; throwError = false; if ~isempty(MEx.identifier) fprintf(2,'\nPLEASE REPORT BUGS TO: <a href="mailto:harry.dymond@bristol.ac.uk">harry.dymond@bristol.ac.uk</a>\n\n'); errorMsg = ['Error!' NL 'Please see MATLAB command line for more information']; throwError = true; else errorMsg = MEx.message; end callStack = dbstack; if length(callStack)>1 && strcmp(callStack(2).file,'publish.m') uiwait(msgbox(errorMsg,'','error','modal')); else fprintf(2,errorMsg); end if throwError fprintf(2,'Error in %s on <a href="matlab:opentoline(''%s'', %i)">line %i</a>:\n', ... MEx.stack(1).name, MEx.stack(1).file, MEx.stack(1).line, MEx.stack(1).line) throw(MEx) end end
Subroutines
function update_idxs(insertionLength)
Index updater
detailsOpens ( detailsOpens > insertPoint ) = detailsOpens ( detailsOpens > insertPoint ) + insertionLength; detailsCloses( detailsCloses > insertPoint ) = detailsCloses( detailsCloses > insertPoint ) + insertionLength; summaryCloses( summaryCloses > insertPoint ) = summaryCloses( summaryCloses > insertPoint ) + insertionLength; imgTags ( imgTags > insertPoint ) = imgTags ( imgTags > insertPoint ) + insertionLength; htmlEnd = htmlEnd + insertionLength;
end function show_warning_dialogue
Show warning dialogue if warnings were generated
callStack = dbstack; if length(callStack)>1 && ismember('publish.m',{callStack.file}) && ~isempty(show_warning([])) uiwait(msgbox([num2str(show_warning([])) ' warnings generated' NL 'Please see MATLAB command line for more information'],'','warn','modal')); end
end
end
==========================================================================================================================================================
Helper functions
Tag processing
function [fstrm, classNames, userCSS] = process_cssClasses_tags(fstrm, getClassNamesOnly) if nargin<2, getClassNamesOnly = false; end if getClassNamesOnly, htmlEnd = length(fstrm); else , [~, htmlEnd] = find_source(fstrm); end [cssOpens, cssCloses] = get_tag_pairs('[cssClasses]', fstrm, htmlEnd); userCSS = ''; if isempty(cssOpens), classNames = {}; return, end if length(cssOpens)>1 throwAsCaller(MException('prettify:too many cssClass blocks','There should only be one pair of [cssClasses][/cssClasses] tags in your source file')) end openBraces = strfind(fstrm(cssOpens:cssCloses),'{') + cssOpens - 1; closeBraces = strfind(fstrm(cssOpens:cssCloses),'}') + cssOpens - 1; classNames{length(openBraces)} = ''; fileIdx = cssOpens + 12; for classCounter = 1:length(openBraces) % check first char after [cssClass] % [/cssClass] warnMsg = ''; WARN_MSG_START = ['css class ' num2str(classCounter) ' is malformed: ']; while fileIdx<cssCloses && char(fstrm(fileIdx))~='.' fileIdx = fileIdx + 1; end if fstrm(fileIdx) ~= '.' warnMsg = [WARN_MSG_START 'css class declarations should start with a dot']; else if isempty(warnMsg) && openBraces(classCounter)>closeBraces(classCounter) warnMsg = [WARN_MSG_START 'opening ''{'' found after closing ''}''']; end classNames{classCounter} = sscanf(char(fstrm(fileIdx:openBraces(classCounter)-1)),'.%s'); if any( cellfun( @(x)isequal(x,classNames{classCounter}), classNames( 1:length(classNames)~=classCounter ) ) ) warnMsg = [WARN_MSG_START classNames{classCounter} ' has already been declared']; classNames{classCounter} = ''; end end if ~isempty(warnMsg) show_warning(warnMsg) elseif ~getClassNamesOnly userCSS = [userCSS '.' classNames{classCounter} ' ' fstrm(openBraces(classCounter):closeBraces(classCounter)) NL]; %#ok<AGROW> end fileIdx = closeBraces(classCounter) + 1; if ~isempty(warnMsg), openBraces(classCounter) = 0; end end classNames(openBraces==0)=[]; if getClassNamesOnly clear userCSS; else fstrm(cssOpens:cssCloses+12) = []; end end function [sourceStart, sourceEnd] = find_source(fstrm) sourceStart = strfind(fstrm,'</head>'); sourceStart = sourceStart(1); sourceEnd = strfind(fstrm,'##### SOURCE BEGIN #####'); try assert(~isempty(sourceEnd),'ERROR: Format of MATLAB-generated html file has changed.'); catch MEx throwAsCaller(MEx); end sourceEnd = sourceEnd(1); end function [fstrm, htmlEnd] = process_target_and_jump_tags(fstrm, htmlEnd) targetTagList = strfind(fstrm(1:htmlEnd),'[target'); [fstrm, htmlEnd, targetTagList, ~ , targetIDList, targetTagEnds] = find_valid_prettify_tags('target', targetTagList, [], [], fstrm, htmlEnd); for j = 1:length(targetTagList) % [targetn] -> <a id="targetn"></a> fstrm = [fstrm(1:targetTagList(j)-1) '<a id="' fstrm(targetTagList(j)+1:targetTagEnds(j)-1) '"></a>' fstrm(targetTagEnds(j)+1:end)]; targetTagList = targetTagList + 11; targetTagEnds = targetTagEnds + 11; htmlEnd = htmlEnd + 11; end [jumpOpens, jumpCloses] = get_tag_pairs('[jumpto]', fstrm, htmlEnd); [fstrm,htmlEnd,jumpOpens,jumpCloses,jumpIDList,jumpTagEnds] = find_valid_prettify_tags('jumpto', jumpOpens, jumpCloses, targetIDList, fstrm, htmlEnd); for j = 1:length(jumpOpens) % [jumpton] -> <a href="#targetn"> fstrm = [fstrm(1:jumpOpens(j)-1) '<a href="#target' num2str(jumpIDList(j)) '">' fstrm(jumpTagEnds(j)+1:end)]; jumpCloses = jumpCloses + 10; % [/jumpto] -> "</a>" fstrm = [fstrm(1:jumpCloses(j)-1) '</a>' fstrm(jumpCloses(j)+9:end)]; jumpCloses = jumpCloses - 5; jumpOpens = jumpOpens + 5; jumpTagEnds = jumpTagEnds + 5; htmlEnd = htmlEnd + 5; end end function [fstrm, htmlEnd, tagList, tagCloseList, valList, tagEnds] = find_valid_prettify_tags(type, tagList, tagCloseList, validValList, fstrm, htmlEnd) if isempty(tagList), valList = []; tagEnds = []; return, end switch type case {'target','jumpto'}, sscanfFormat = ['[' type '%i']; generic = 'n'; valDescriptor = 'target identifier'; valType = 'integer'; long = 3; case 'br' , sscanfFormat = ['[' type '%g']; generic = 'x'; valDescriptor = 'padding'; valType = 'number'; long = 3; case 'scale' , sscanfFormat = ['[' type '%g']; generic = 'x'; valDescriptor = 'scaling'; valType = 'positive number'; long = 5; case 'colour' , sscanfFormat = ['[' type '%s']; generic = '#'; valDescriptor = 'colour code'; valType = 'str'; long = 6; case 'class' , sscanfFormat = ['[' type '.%s']; generic = '' ; valDescriptor = 'class name'; valType = 'str'; long = 100; case 'bottomMargin' , sscanfFormat = ['[' type '%g']; generic = 'x'; valDescriptor = 'margin'; valType = 'number'; long = 6; end WARN_MSG_START = ['[' type '] tag %i is malformed: ']; if strcmp(valType, 'str'), valList{length(tagList)} = ''; else , valList(length(tagList)) = NaN; end tagEnds{length(tagList)} = []; for i = 1:length(tagList) warningMsg = ''; tagEnds{i} = strfind(fstrm(tagList(i):htmlEnd),']'); if isempty(tagEnds{i}), error(['ERROR: ' sprintf(WARN_MSG_START,i) 'there is no closing bracket.']); end tagEnds{i} = tagEnds{i}(1) + tagList(i) - 1; if (tagEnds{i}-tagList(i)-1-length(type))>long show_warning(['The ' valDescriptor ' for ' type generic ' tag ' num2str(i) ' is more than '... num2str(long) ' characters long. Possible malformed tag?']); end val = sscanf(fstrm(tagList(i):tagEnds{i}-1),sscanfFormat); if strcmp(valType, 'str') && isempty(val) warningMsg = sprintf([WARN_MSG_START '%c should be a valid %s.'], i, generic, valDescriptor); elseif ~strcmp(valType, 'str') && ( isempty(val) || (contains(valType,'positive') && val<=0) ) warningMsg = sprintf([WARN_MSG_START '%c should be a %s.'], i, generic, valType); else switch type case 'target' , if any(valList == val) warningMsg = sprintf([WARN_MSG_START '%i has already been used as a target.'], i, val); end case 'jumpto' , if ~any(validValList == val) %#ok<ALIGN> warningMsg = sprintf([WARN_MSG_START '%i has not been specified as a jump target (no [target%i] tag in source).'],... i, val, val); end case 'colour' valid = true; if length(val)~=6 , valid = false; end if valid, try hex2dec(val); catch, valid = false; end, end if ~valid, warningMsg = sprintf([WARN_MSG_START val ' is not a valid colour code.']); end case 'class', if ~any(cellfun(@(x)isequal(x,val),validValList)) warningMsg = sprintf([WARN_MSG_START '%s has not been defined as a CSS class.'], i, val); end end end if ~isempty(warningMsg) show_warning(warningMsg); elseif ischar(val) valList{i} = val; else valList(i) = val; end end tagEnds = cell2mat(tagEnds); if strcmp(valType, 'str'), invalidIdxs = find(cellfun(@isempty,valList)); else , invalidIdxs = find(isnan(valList)); end for i = invalidIdxs fstrm(tagList(i):tagEnds(i)) = []; noOfCharsDeleted = (tagEnds(i)-tagList(i)+1); tagList(i+1:end) = tagList(i+1:end) - noOfCharsDeleted; tagEnds(i+1:end) = tagEnds(i+1:end) - noOfCharsDeleted; htmlEnd = htmlEnd - noOfCharsDeleted; if ismember(type,{'jumpto','scale','colour','class'}) noOfCharsToRemove = 3 + length(type); tagCloseList(i:end) = tagCloseList(i:end) - noOfCharsDeleted; fstrm(tagCloseList(i):tagCloseList(i)+2+length(type)) = []; tagCloseList(i) = 0; tagCloseList(i+1:end) = tagCloseList(i+1:end) - noOfCharsToRemove; tagList (i+1:end) = tagList (i+1:end) - noOfCharsToRemove; tagEnds (i+1:end) = tagEnds (i+1:end) - noOfCharsToRemove; htmlEnd = htmlEnd - noOfCharsToRemove; end tagList(i) = 0; end valList(tagList==0)=[]; tagEnds(tagList==0)=[]; tagList(tagList==0)=[]; tagCloseList(tagCloseList==0)=[]; end function [fstrm, htmlEnd] = prettyTag2htmlTag(type, tagOpens, tagCloses, vals, tagEnds, fstrm, sourceStart, htmlEnd, DEBUG) switch type case 'scale' , kind = 'style'; get_val = @(x, idx)sprintf('font-size: %.3g%%;',x(idx)*100); case 'colour', kind = 'style'; get_val = @(x, idx)['color: #' x{idx} ';']; case 'class' , kind = 'class'; get_val = @(x, idx)x{idx}; end if strcmp(type,'class'), [countOfTags, tagCloses] = sort_tag_closes(tagOpens, tagCloses); else , countOfTags = length(tagOpens); end for i = 1:countOfTags % <p>[/type]</p> -> [/type] if strcmp(fstrm(tagCloses(i)-3:tagCloses(i)-1),'<p>') ... && strcmp(fstrm(tagCloses(i)+length(type)+3:tagCloses(i)+length(type)+6),'</p>') fstrm([tagCloses(i)-3:tagCloses(i)-1 tagCloses(i)+length(type)+3:tagCloses(i)+length(type)+6]) = []; update_idxs(tagCloses(i), -7); tagCloses(i) = tagCloses(i)-3; end end % Deal with [type] ... [/type] tags enclosing non-compatible html tags such as <p></p>, <table></table>, <ul></ul>, etc.: % [type] ... [/type] fully encloses non-enclosable section: % [type] ... <nonEnclosable>...</nonEnclosable> ... [/type] -> [type] ... [/type]<nonEnclosable kind="">...</nonEnclosable>[type] ... [/type] (note kind % added to <nonEnclosable>) % % [type] ... [/type] encloses opening non-enclosable tag only: % [type] ... <nonEnclosable>... [/type] ... </nonEnclosable> -> [type] ... [/type]<nonEnclosable>[type] ... [/type] ...</nonEnclosable> (note kind NOT % added to <nonEnclosable>) % % [type] ... [/type] encloses closing non-enclosable tag only: % <nonEnclosable> ... [type] ... </nonEnclosable> ... [/type] -> <nonEnclosable>...[type] ... [/type]</nonEnclosable>[type]... [/type] (note kind NOT % added to <nonEnclosable>) % % nested [type] tags -> what might happen? % 1o 2o 2c 1c % [type] ... <nonEnclosable>... [type] ... </nonEnclosable> ... [/type][/type] type pair 1 processed gives -> % 1o 1c (new) 3o (shift) 2o (new) 3c (shift) 2c (shift) % [type] ... [/type]<nonEnclosable kind="kind1">... [type] ... </nonEnclosable>[type] ... [/type][/type] type pair 2 processed gives no change, type % pair 3 processed gives -> % 1o 1c 3o 3c (new) 4o (new) 2o 4c (shift) 2c % [type] ... [/type]<nonEnclosable kind="kind1">... [type] ... [/type]</nonEnclosable>[type][type] ... [/type][/type] -> changesMade = true; while changesMade changesMade = false; i = 1; while i<=length(tagOpens) for nonEnclosableTag = {'<p>','<table>','<ol>','<ul>','<pre>','<div>','<img>','<dl>','<h1>','<h2>','<h3>','<h4>','<h5>','<h6>'} nonEnclosableTagAlt = [nonEnclosableTag{:}(1:end-1) ' ']; nonEnclosableTagCloser = [nonEnclosableTag{:}(1) '/' nonEnclosableTag{:}(2:end)]; if contains(fstrm(tagOpens(i):tagCloses(i)), nonEnclosableTag{:}) ... || contains(fstrm(tagOpens(i):tagCloses(i)), nonEnclosableTagAlt) ... || contains(fstrm(tagOpens(i):tagCloses(i)), nonEnclosableTagCloser) nonEnclosableTagOpens = strfind(fstrm(tagOpens(i):tagCloses(i)), nonEnclosableTag{:}) + tagOpens(i) - 1; nonEnclosableTagOpens = sort( [ nonEnclosableTagOpens strfind(fstrm(tagOpens(i):tagCloses(i)), nonEnclosableTagAlt)+tagOpens(i)-1 ] ); nonEnclosableTagCloses = strfind(fstrm(tagOpens(i):tagCloses(i)), nonEnclosableTagCloser) + tagOpens(i) - 1; addKindToTag = false; if ~isempty(nonEnclosableTagOpens) && ~isempty(nonEnclosableTagCloses) && nonEnclosableTagOpens(1)<nonEnclosableTagCloses(end) if ~strcmp(nonEnclosableTag{:},'<img>'), addKindToTag = true; end newTagCloseInsertPos = nonEnclosableTagOpens(1)-1; newTagOpenInsertPos = nonEnclosableTagCloses(end)+length(nonEnclosableTagCloser) + length(type)+2; elseif ~isempty(nonEnclosableTagOpens) newTagCloseInsertPos = nonEnclosableTagOpens(1)-1; newTagOpenInsertPos = strfind(fstrm(nonEnclosableTagOpens(1):tagCloses(i)),'>') + nonEnclosableTagOpens(1) - 1; newTagOpenInsertPos = newTagOpenInsertPos(1) + length(type)+3; else newTagCloseInsertPos = nonEnclosableTagCloses(end)-1; newTagOpenInsertPos = nonEnclosableTagCloses(end)+length(nonEnclosableTagCloser) + length(type)+2; end if addKindToTag tagClose = strfind(fstrm(nonEnclosableTagOpens(1):tagCloses(i)),'>'); tagClose = tagClose(1) + nonEnclosableTagOpens(1) - 1; [insertStr, insertIdx] = add_to_html_tag(kind, get_val(vals,i), fstrm, nonEnclosableTag{:}(2:end-1),... nonEnclosableTagOpens(1), tagClose); fstrm = [fstrm(1:insertIdx-1) insertStr fstrm(insertIdx:end)]; update_idxs(insertIdx, length(insertStr)); newTagOpenInsertPos = newTagOpenInsertPos + length(insertStr); end % insert new [/type] tag fstrm = [fstrm(1:newTagCloseInsertPos) '[/' type ']' fstrm(newTagCloseInsertPos+1:end)]; update_idxs(newTagCloseInsertPos+1, length(type)+3); % insert new [type] tag tagLength = 1+tagEnds(i)-tagOpens(i); fstrm = [fstrm(1:newTagOpenInsertPos) fstrm(tagOpens(i):tagEnds(i)) fstrm(newTagOpenInsertPos+1:end)]; tagOpens = [tagOpens(1:i) newTagOpenInsertPos+1 tagOpens(i+1:end)]; tagEnds = [tagEnds(1:i) newTagOpenInsertPos+tagLength tagEnds(i+1:end) ]; tagCloses = [tagCloses(1:i-1) newTagCloseInsertPos+1 tagCloses(i:end) ]; update_idxs(newTagOpenInsertPos, tagLength); tagOpens(i+1) = newTagOpenInsertPos+1; tagEnds(i+1) = newTagOpenInsertPos+tagLength; vals = [vals(1:i) vals(i) vals(i+1:end)]; changesMade = true; break; end end i = i + 1; end end if DEBUG, write_fstrm_to_file(fstrm(sourceStart:htmlEnd), ['after processing [' type '] tags enclosing html tags.html'], 1); end % delete consecutive [type][/type] tags i = 1; while i <= length(tagOpens) if tagEnds(i)+1==tagCloses(i) fstrm(tagOpens(i):tagCloses(i)+length(type)+2) = []; update_idxs(tagCloses(i), -(tagCloses(i)+length(type)+3-tagOpens(i))); tagOpens(i) = []; tagCloses(i) = []; tagEnds(i) = []; vals(i) = []; else i = i + 1; end end if DEBUG, write_fstrm_to_file(fstrm(sourceStart:htmlEnd), ['after removing consecutive [' type '][' type 'close] tags.html'], 1); end for i = 1:length(tagOpens) if strcmp(type,'class') && ismember(get_val(vals,i),{'codeinput','codeoutput','error'}), htmlTag = 'pre'; else , htmlTag = 'span'; end spanOpen = strfind(fstrm(1:tagOpens(i)),['<' htmlTag ' ']); % could be "pre" rather than "span", but haven't changed variable names if ~isempty(spanOpen) spanOpen = spanOpen(end); spanClose = strfind(fstrm(spanOpen:tagOpens(i)),'>'); if ~isempty(spanClose), spanClose = spanClose(1) +spanOpen-1; end if ~strcmp(sscanf(fstrm(spanClose:tagOpens(i)),'%s'),'>[') spanOpen = []; end end % '</span>' if ( ~isempty(spanOpen) || strcmp(fstrm(tagEnds(i)+1:tagEnds(i)+length(htmlTag)+2),['<' htmlTag ' ']) ) ... && ( strcmp(fstrm(tagCloses(i)-(length(htmlTag)+3):tagCloses(i)-1),['</' htmlTag '>']) ... || strcmp(fstrm(tagCloses(i)+length(type)+3:tagCloses(i)+length(type)+3+length(htmlTag)+2),['</' htmlTag '>']) ) % [typex]<span kind="blah> -> <span kind="x blah (or "pre" rather than "span") % or <span kind="blah>[typex] -> <span kind="x blah if isempty(spanOpen) spanOpen = tagEnds(i)+1; spanClose = strfind(fstrm(tagEnds(i):tagCloses(i)),'>'); spanClose = spanClose(1) + tagEnds(i) - 1; end [insertStr, insertIdx] = add_to_html_tag(kind, get_val(vals,i), fstrm, htmlTag, spanOpen, spanClose); % remove </span> or </pre> (will be replaced later) if strcmp(fstrm(tagCloses(i)-(length(htmlTag)+3):tagCloses(i)-1),['</' htmlTag '>']) fstrm(tagCloses(i)-(length(htmlTag)+3):tagCloses(i)-1)=[]; tagCloses(tagCloses>tagCloses(i)-1) = tagCloses(tagCloses>tagCloses(i)-1) - (length(htmlTag)+3); else fstrm(tagCloses(i)+length(type)+3:tagCloses(i)+length(type)+3+length(htmlTag)+2)=[]; tagCloses(tagCloses>tagCloses(i)) = tagCloses(tagCloses>tagCloses(i)) - (length(htmlTag)+3); end tagEnds (tagEnds >tagCloses(i)) = tagEnds (tagEnds >tagCloses(i)) - (length(htmlTag)+3); tagOpens(tagOpens>tagCloses(i)) = tagOpens(tagOpens>tagCloses(i)) - (length(htmlTag)+3); else % [type] -> <span kind="x"> or <pre kind="x"> insertStr = ['<' htmlTag ' ' kind '="' get_val(vals,i) '">']; insertIdx = tagEnds(i)+1; end fstrm = [fstrm(1:insertIdx-1) insertStr fstrm(insertIdx:end)]; if insertIdx<tagOpens(i), fstrm(tagOpens(i)+length(insertStr):tagEnds(i)+length(insertStr)) = []; else , fstrm(tagOpens(i):tagEnds(i)) = []; end noOfCharsToRemove = 1+tagEnds(i)-tagOpens(i); charIncrease = length(insertStr) - noOfCharsToRemove; update_idxs(insertIdx-1, charIncrease); % [/"type"] -> </span> or </pre> closeTagLength = length(type)+3; insertPoint = tagCloses(i)-1; fstrm = [fstrm(1:insertPoint) ['</' htmlTag '>'] fstrm(insertPoint+1+closeTagLength:end)]; update_idxs(tagCloses(i), (length(htmlTag)+3) - closeTagLength) end function update_idxs(insertPoint, idxAdj) tagEnds (tagOpens >insertPoint) = tagEnds (tagOpens >insertPoint) + idxAdj; tagCloses(tagCloses>insertPoint) = tagCloses(tagCloses>insertPoint) + idxAdj; tagOpens (tagOpens >insertPoint) = tagOpens (tagOpens >insertPoint) + idxAdj; htmlEnd = htmlEnd + idxAdj; end end function [insertStr, insertIdx] = add_to_html_tag(kind, val, fstrm, htmlTag, tagOpen, tagClose) % kind: String defining the kind of attribute to be added to the htmlTag, e.g. 'style' or 'class' % val : The value of the attribute e.g. 'margin-bottom:10px' % fstrm: The html file in memory % htmlTag: String defining the html tag that the style or class will be applied to, and should not include the "<" or ">". e.g., if adding a style to a % table, htmlTag would be set to 'table' % tagOpen: Position of opening of tag in fstrm % tagClose: Position of closing of tag (i.e., the tag's ">" character for lookForStr = {[kind '="'],[kind ' ="'],[kind '= "'],[kind ' = "']} lookForIdx = strfind(fstrm(tagOpen:tagClose),lookForStr{:}); if ~isempty(lookForIdx) lookForIdx = lookForIdx(1) + tagOpen - 1; insertStr = [val ' ']; insertIdx = lookForIdx+length(lookForStr{:}); break; end end if isempty(lookForIdx) insertStr = [' ' kind '="' val '"']; insertIdx = tagOpen+length(htmlTag)+1; end end function [tagOpens, tagCloses] = get_tag_pairs(tag, fstrm, htmlEnd) closeTag = ['[/' tag(2:end)]; if ismember(tag,{'[jumpto]','[scale]','[colour]','[class]'}), openTag = tag(1:end-1); else , openTag = tag; end tagOpens = strfind(fstrm(1:htmlEnd),openTag); tagCloses = strfind(fstrm(1:htmlEnd),closeTag); try assert( length(tagOpens)==length(tagCloses), 'ERROR: Number of %s tags (%i) does not match number of %s tags (%i)', ... tag, length(tagOpens), closeTag, length(tagCloses)); if ~ismember(tag,{'[class]','[dtls]'}), check_tag_pairs(tagOpens, tagCloses, tag); end catch MEx throwAsCaller(MEx) end end function check_tag_pairs(tagOpens, tagCloses, type) if isempty(tagOpens), return, end try closeBeforeOpenError = 'ERROR: %s close tag %i appears before open tag %i'; for i = 1:length(tagOpens)-1 assert(tagOpens(i)<tagCloses(i),closeBeforeOpenError,type,i,i); assert(tagCloses(i)<tagOpens(i+1),'ERROR: %s open tag %i appears before close tag %i',type,i+1,i); end if isempty(i), i = 1; end assert(tagOpens(i)<tagCloses(i),closeBeforeOpenError,type,i,i); catch MEx throwAsCaller(MEx) end end function [countOfTags, tagCloses] = sort_tag_closes(tagOpens, tagCloses) countOfTags = length(tagOpens); tagClosesUnsorted = tagCloses; tagCloses(:) = 0; for i = 1:countOfTags tagCloseIdx = 1; while i+tagCloseIdx<=countOfTags && tagClosesUnsorted(tagCloseIdx)>tagOpens(i+tagCloseIdx), tagCloseIdx = tagCloseIdx+1; end tagCloses(i) = tagClosesUnsorted(tagCloseIdx); tagClosesUnsorted(tagCloseIdx) = []; end assert(~any(tagCloses==0),'prettifyMATLABhtml:internalError','Failed to sort nested tags'); end
Get path of this .m file
function myPath = my_path() % % NOTE: path returned includes trailing file separator % myName = mfilename(); myPath = mfilename('fullpath'); myPath = myPath(1:end-length(myName)); end
Save handle to built-in Publish function
function save_publish_handle publishName = which('publish'); if ~strcmp(publishName(end-1:end),'.p') error(['ERROR: You must run this before MATLAB''s "publish" function has been overloaded.'... ' In other words, the custom "publish" function must not be on the MATLAB path when you run save_publish_handle.']); else setappdata(0,'real_publish',@publish); end end
Warning functions
function warningsIssued = show_warning(msg) persistent warnCounter if islogical(msg), warnCounter = []; return; end if isempty(msg), warningsIssued = warnCounter; return, end if isempty(warnCounter) fprintf(1, wrap_text(sprintf('\n%s: prettify_MATLAB_html [\\bWARNING%cmessages:]\\b\n', datestr(now),char(160)),'')); warnCounter = 0; end warnCounter = warnCounter + 1; fprintf(1,'\n%i. [\b%s]\b\n', warnCounter, wrap_text(msg,' ')); end function wrappedText = wrap_text(str, lineStart) commandWindowSize = get(0, 'CommandWindowSize'); numCols = commandWindowSize(1)-2-length(lineStart); if length(str)<= numCols, wrappedText = str; return; end lines = strsplit(str,'\n'); lineCounter = 1; while lineCounter<=length(lines) if length(lines{lineCounter}) > numCols lastChar = 1; spaces = strfind(lines{lineCounter},' '); insertNewLine = spaces(find(spaces<=numCols,1,'last')); if isempty(insertNewLine) insertNewLine = numCols; lastChar = 0; end lines = [lines(1:lineCounter) {lines{lineCounter}(insertNewLine+1:end)} lines(lineCounter+1:end)]; lines{lineCounter} = lines{lineCounter}(1:insertNewLine-lastChar); end lineCounter = lineCounter + 1; end wrappedText = strjoin(lines,[NL lineStart]); end
Toolbar button functions
function insert_target(document) targetTagList = strfind(document.Text,'[target'); [~, ~, ~, ~, targetIDList, ~] = find_valid_prettify_tags('target', targetTagList, [], [], document.Text, length(document.Text)); if ~isempty(targetIDList), targetID = max(targetIDList)+1; else , targetID = 1; end document.insertTextAtPositionInLine(['[target' num2str(targetID) ']'], document.Selection(1), document.Selection(2)) end function insert_table(document) if document.Selection(2)>1, document.goToPositionInLine(document.Selection(1)+1,1); end HTML_TABLE_TEMPLATE = ['%' NL ... '% <html>' NL ... '% <table class="MATLAB-Help">' NL ... '% <thead><tr>' NL ... '% <th>COLUMN1 HEADING</th>' NL ... '% <th>COLUMN2 HEADING</th>' NL ... '% </tr></thead>' NL ... '% <tr><td>CELL1</td><td>CELL2</td></tr>' NL ... '% <tr><td>CELL3</td><td>CELL4</td></tr>' NL ... '% </table>' NL ... '% </html>' NL ... '%' NL]; document.insertTextAtPositionInLine(HTML_TABLE_TEMPLATE, document.Selection(1), document.Selection(2)) end function jumpton_wrap(document) targetTagList = strfind(document.Text,'[target'); [~, ~, ~, ~, targetIDList, ~] = find_valid_prettify_tags('target', targetTagList, [], [], document.Text, length(document.Text)); targetIDList(targetIDList==0) = []; targetID = 0; if ~isempty(targetIDList) if length(targetIDList) > 1 targetIDList = sort(targetIDList); targetList = arrayfun(@num2str,targetIDList,'UniformOutput',false); [indx, tf] = listdlg('PromptString','Select target ID','ListString',targetList,'SelectionMode','single'); if tf, targetID = targetIDList(indx); end else targetID = targetIDList; end end wrap_text_in_tag(document, '[jumpto', targetID); end function class_wrap(document) [~, classNames] = process_cssClasses_tags(document.Text, true); if contains(document.Text,'[themesEnabled]'), classNames = [classNames {'show-if-light', 'show-if-dark'}]; end className = 'CLASS-NAME'; if ~isempty(classNames) if length(classNames) > 1 [indx, tf] = listdlg('PromptString','Select class name','ListString',classNames,'SelectionMode','single'); if tf, className = classNames(indx); else , return, end else className = classNames{1}; end end if iscell(className), className = className{1}; end wrap_text_in_tag(document, '[class', ['.' className]); end function colour_wrap(document) c = uisetcolor; if c==0, return; else , c = round(255*c); c = [upper(dec2hex(c(1),2)) upper(dec2hex(c(2),2)) upper(dec2hex(c(3),2))]; end wrap_text_in_tag(document, '[colour', c); end function wrap_text_in_tag(document, openTag, n) WHITE_SPACE = char([9:13 32 133 160]); PUNCTUATION = '(),.;:'; if strcmp(openTag,'[cssClasses]') && contains(document.Text, openTag) uiwait(msgbox(['A cssClasses block already exists' NL 'Source files should have only one cssClasses block'],'','help','modal')); return, end selectedText = document.SelectedText; closeTag = [openTag(1) '/' openTag(2:end)]; if nargin==3 if ~ischar(n) if n == 0, n = 'n'; else , n = num2str(n); end end openTag = [openTag n ']']; closeTag(end+1) = ']'; end % convert the selection from Lines/Columns to index selectionPosition = document.Selection; startPos = matlab.desktop.editor.positionInLineToIndex(document, selectionPosition(1), selectionPosition(2)); endPos = matlab.desktop.editor.positionInLineToIndex(document, selectionPosition(3), selectionPosition(4)); if ~isempty(selectedText) while startPos>1 if ~ismember(document.Text(startPos-1),[WHITE_SPACE PUNCTUATION ']']), startPos = startPos-1; else , break, end end while endPos<=(length(document.Text)-1) if ~ismember(document.Text(endPos),[WHITE_SPACE PUNCTUATION '[']), endPos = endPos+1; else , break, end end if ismember(document.Text(startPos),{'*','_','<','|','$'}), openTag(end+1) = ' '; end if ismember(document.Text(endPos-1),{'*','_','>','|','$'}), closeTag = [' ' closeTag]; end end wrappedText = [openTag document.Text(startPos:endPos-1) closeTag]; document.Text = [document.Text(1:startPos-1) wrappedText document.Text(endPos:end)]; % Re-select [selectionPosition(1), selectionPosition(2)] = matlab.desktop.editor.indexToPositionInLine(document, startPos+length(openTag)); [selectionPosition(3), selectionPosition(4)] = matlab.desktop.editor.indexToPositionInLine(document, endPos +length(openTag)); document.Selection = selectionPosition; end function add_pretty_commands_to_toolbar PRETTY_CATEGORY = 'PRETTIFY'; COMMAND_LIST = {'dtls','smry','targetn','jumpton','cssClasses','class','scale','colour','html table'}; COMMAND_START = 'prettify_MATLAB_html([],[],[],'; COMMAND_FUNC = {[COMMAND_START '''[dtls]'')'],[COMMAND_START '''[smry]'')'],[COMMAND_START '''target'')'],[COMMAND_START '''jumpto'')'],... [COMMAND_START '''[cssClasses]'')'],[COMMAND_START '''class'')'],[COMMAND_START '''scale'')'],[COMMAND_START '''colour'')'],... [COMMAND_START '''table'')']}; fprintf(1,'\n'); foundFlag(length(COMMAND_LIST)) = false; if verLessThan('matlab','9.4') % add shortcuts scUtils = com.mathworks.mlwidgets.shortcuts.ShortcutUtils; scVector = scUtils.getShortcutsByCategory(PRETTY_CATEGORY); scArray = scVector.toArray; % Java array for scIdx = 1:length(scArray) scName = char(scArray(scIdx)); [alreadyExists, idx] = ismember(scName, COMMAND_LIST); if alreadyExists, foundFlag(idx) = true; end end i = 1:length(COMMAND_LIST); for i = i(~foundFlag) scUtils.addShortcutToBottom(COMMAND_LIST{i},COMMAND_FUNC{i},['Lower Case ' COMMAND_LIST{i}(1)], PRETTY_CATEGORY, 'true'); end if isempty(i), fprintf(1,'[\bAll shortcuts already exist]\b\n'); else , fprintf(1,'[\bShortcuts created]\b\n'); end MSG_END = ' - please add the shortcuts to the Quick-Access Toolbar manually]\b\n'; QA_XML = [prefdir filesep 'MATLABQuickAccess.xml']; if ~exist(QA_XML,'file') fprintf(1,['[\bCouldn''t find the Quick Access xml file' MSG_END]); elseif exist([QA_XML '.bak'],'file') ... || ( ~exist([QA_XML '.bak'],'file') ... && copyfile(QA_XML, [QA_XML '.bak']) ) fstrm = read_file_to_mem(QA_XML); configStart = strfind(fstrm,'<quick_access_configuration>'); if length(configStart)~=1, fprintf(1,['[\bUnexpected Quick Access xml file format' MSG_END]); return, end linesToAdd = ''; for command = COMMAND_LIST if ~contains(char(fstrm), command{:}) linesToAdd = [linesToAdd ' <tool display_condition="always" label_visible="true" section_id="PRETTIFY" tab_id="shortcuts" tool_id="' ... command{:} '" toolset_id="matlab_shortcut_toolset"/>' NL]; %#ok<AGROW> end end if isempty(linesToAdd) fprintf('[\bAll shortcuts already in Quick-Access Toolbar]\b\n'); else fstrm = [fstrm(1:configStart+28) linesToAdd fstrm(configStart+29:end)]; write_fstrm_to_file(fstrm, QA_XML); fprintf(1,'[\bPlease restart MATLAB to get the Prettify MATLAB html shortcuts in the Quick-Access Toolbar]\b\n'); end end else % add favourites FAV_XML = [prefdir filesep 'FavoriteCommands.xml']; if ~exist(FAV_XML,'file') fprintf(1,'[\bCouldn''t find Favourites xml file; may result in duplicate Favourites]\b\n'); else fstrm = read_file_to_mem(FAV_XML); for i = 1:length(COMMAND_LIST), if contains(char(fstrm), COMMAND_LIST{i}), foundFlag(i) = true; end, end end if all(foundFlag) fprintf(1,'[\bAll Toolbar buttons already exist]\b\n'); else fc = com.mathworks.mlwidgets.favoritecommands.FavoriteCommands.getInstance(); i = 1:length(COMMAND_LIST); for i = i(~foundFlag) % thanks to Martin Lechner at https://uk.mathworks.com/matlabcentral/answers/411846-how-to-create-favorites-by-code-command-window % for this code! command = com.mathworks.mlwidgets.favoritecommands.FavoriteCommandProperties(); command.setCategoryLabel(PRETTY_CATEGORY); command.setLabel(COMMAND_LIST{i}); command.setCode(COMMAND_FUNC{i}); command.setIsOnQuickToolBar(true); command.setIsShowingLabelOnToolBar(true); command.setIconName(['favorite_command_' COMMAND_LIST{i}(1)]); fc.addCommand(command); end fprintf(1,'[\bToolbar buttons created]\b\n'); end end fprintf(1,'\n'); end
File read & write
function fstrm = read_file_to_mem(fileName) try fileH = fopen(fileName,'r'); fstrm = fread(fileH, 'uchar')'; catch MEx fclose(fileH); throwAsCaller(MException('prettify:fileread', sprintf('ERROR: Unable to read file %s: %s', fileName, MEx.message) )); end fclose(fileH); end function write_fstrm_to_file(fstrm, fileName, isDebug) persistent debugCount if nargin<3, isDebug = false; elseif isempty(isDebug), debugCount = []; return, end if isDebug if isempty(debugCount), debugCount = 0; end debugCount = debugCount + 1; fileName = ['debug ' sprintf('%03i',debugCount) '. - ' fileName]; end if length(fileName)>=5 && strcmp(fileName(end-4:end),'.html') % Insert some line breaks to make source html a bit more readable if isDebug % have to be careful as this could change how the html renders, so be conservative when doing this for non-debug output fstrm = strrep(fstrm,'><',['>' NL '<']); else [sourceStart, htmlEnd] = find_source(fstrm); fstrm = strrep(fstrm,'><details',['>' NL '<details']); fstrm = strrep(fstrm,'</div></details>',[NL '</div>' NL '</details>']); fstrm = strrep(fstrm,'><div',['>' NL '<div']); fstrm = strrep(fstrm,'</li><li>',['</li>' NL '<li>']); fstrm = strrep(fstrm,'><p',['>' NL '<p']); preLocs = strfind(fstrm(sourceStart:htmlEnd),'<pre') + sourceStart - 1; if isempty(preLocs) fstrm = strrep(fstrm,'<br',[NL '<br']); else preCloseLocs = strfind(fstrm(sourceStart:htmlEnd),'</pre>') + sourceStart - 1; [countOfPreTags, preCloseLocs] = sort_tag_closes(preLocs, preCloseLocs); preLocs = [2 preLocs]; preCloseLocs = [1 preCloseLocs]; countOfPreTags = countOfPreTags + 1; for i = 2:countOfPreTags if preLocs(i)>max(preCloseLocs(1:i-1)) lengthBefore = length(fstrm); fstrm = [fstrm(1:max(preCloseLocs(1:i-1))) ... strrep(fstrm(max(preCloseLocs(1:i-1))+1:preLocs(i)),'<br',[NL '<br']) fstrm(preLocs(i)+1:end)]; charsAdded = length(fstrm) - lengthBefore; preLocs ( preLocs > max(preCloseLocs(1:i-1)) ) = preLocs ( preLocs > max(preCloseLocs(1:i-1))) + charsAdded; preCloseLocs( preCloseLocs > max(preCloseLocs(1:i-1)) ) = preCloseLocs( preCloseLocs > max(preCloseLocs(1:i-1))) + charsAdded; end end fstrm = [fstrm(1:max(preCloseLocs)) ... strrep(fstrm(max(preCloseLocs)+1:htmlEnd),'<br',[NL '<br']) fstrm(htmlEnd+1:end)]; end end end try fileH = fopen(fileName,'w'); fwrite(fileH, fstrm, 'char*1'); catch MEx fclose(fileH); throwAsCaller(MException('prettify:filewrite', sprintf('ERROR: Unable to write file %s: %s',fileName, MEx.message) )); end fclose(fileH); end
New line constant
function CONST = NL, CONST = char(10); end %#ok<CHARTEN> for backwards compatibility
Error using prettify_MATLAB_html Call syntax error. Call syntax is: prettify_MATLAB_html(inputhtmlFile, verbose, overloadPublish, addCommandsToToolbar) Where inputs verbose, overloadPublish, and addCommandsToToolbar are optional. Click <a href="matlab:ans=dir(which('prettify_MATLAB_html.m'));open([ans.folder filesep 'prettify documentation' filesep 'html' filesep 'prettify_MATLAB_html_helpdoc.html']);clear ans">here</a> to see the help document for more information.
==========================================================================================================================================================
Version history:
1.0 -- Original release 1.1 -- Improved method of closing <details> sections -- Better handling of consecutive [/dtls][dtls] tags 1.2 -- Minor change to warning shown if a targetn, jumpton, or brx tag seems a bit long -- Documentation tweaks -- Minor code tidying; mainly adding sections 2.0 -- Added support functions for adding shortcuts to Quick-Access Toolbar, that then allow easy adding of tags to source .m file -- Fixed bug that occurred when removing malformed [targetn] and [jumpton] tags 2.1 -- Improved method of adding Toolbar buttons 3.0 -- Added support for new tags: [cssClasses], [class.class-name], [scalex], [colour#], [themesEnabled], and [darkAlt] -- Added dark theme -- Improved handling of Toolbar buttons -- Other minor code cleanups and bug fixes 3.1 -- Added workaround for MATLAB central miscalculating page height 3.2 -- Improved method of assigning classes and styles 3.3 -- Fixed bug with "collapse/expand all on page" link when page has no sections 3.4 -- Added support for nesting of css classes e.g. [class.a][class.b]this item will be formatted according to css classes a and b[/class][/class] -- Increased robustness of enclosing inline html in [dtls] sections 3.5 -- Improved enclosing of inline html into [dtls] sections (again) -- Improved handling of nested [colour#], [scalex], [class.class-name] tags 3.6 -- Fixed bug when multiple images appear inside [dtls] sections -- Added Toolbar button to insert an html-table template 3.7 -- Added "built-in" MATLAB publish CSS classes codeinput, codeoutput, error, keyword, comment, string, untermstring, and syscmd, as valid class names when using the [class.class-name] tag, although these won't appear in the list presented when using the "class" Toolbar button -- Handles some incorrect syntax more gracefully 3.7.1 -- Minor code tidy 3.8 -- Fixed bug with "collapse/expand all" controls for pages with 10 or more sections 3.9 -- Added [frameBufferx] tag 3.10 -- Fixed bug associated with [frameBufferx] tag 3.11 -- Better handling of malformed syntax 3.12 -- Improved error handling 3.13 -- Added [imageFolder=x] tag (undocumented companion to undocumented [png2svg] tag) 3.14 -- Fixed bug that occured when any [dtls] sections were placed before MATLAB's auto-generated page contents list 4.0 -- Enables nesting of [dtls] boxes -- [dtls] boxes are now rounded on all four corners when closed -- Improved syntax checking for [/dtls] tags -- Uses new method to save theme preference for better compatibility with the MATLAB web browser -- [bottomMarginx] tags are now documented -- [bottomMarginx] now processed after [dtls] and [smry] tags -- Processes [class.codeinput], [class.codeoutput], and [class.error] correctly by using html <pre> elements instead of <span> -- Added undocumented [removepng] tags 4.1 -- Improved syntax checking for [dtls] tags -- Improved handling of html in [dtls] boxes (also fixes potential bug when nesting [dtls] boxes) 4.2 -- Fixed issue with collapse-all/expand-all links in help browser of recent versions of MATLAB -- Improved spacing after generated code output and lists that appear at the end of [dtls] boxes 4.3 -- No longer creates extra space after monospaced text (e.g. |text| ) that is wrapped in [class], [scale], or [colour] tags -- Added the [delsp] tag 4.4 -- Now includes version number of prettify_MATLAB_html in the footer link 4.5 -- In-page links now jump to correct location if page is viewed on Matlab Central (e.g. in the "Examples" tab) or the MATLAB help browser (the former, and some versions of the latter, put a floating banner across the top of the page that must be accounted for when jumping) -- If viewed on MATLAB Central, pages now have a scroll bar, so the [framebufferx] tag is no longer required -- Makes in-page links work for most browsers, if the page is viewed in the "Examples" tab on MATLAB Central 5.0 -- Bug fix - improved spacing after numbered lists that appear at the end of [dtls] boxes now works -- Improved spacing if a [dtls] box is the last element on the page -- Adds a "return" floating button/link when user follows an in-page link (but not if page is viewed in MATLAB help browser) -- Documented a method for adding headings with collapse/expand links without adding the heading to the page's contents list 5.1 -- Improved floating back button functionality when page is viewed on MATLAB Central -- More robust positioning of collapse/expand links 6.0 -- Fixed html 5 compliance issues, some due to the built-in publish, and some due to prettify -- Added [h2] and [h2.CElink] tags to create headings that are not added to the page's contents list -- Enhanced functionality of floating "return" link - arrow direction now updates as user scrolls up/down past the return target 6.1 -- Fixed bug when processing <style></style> tags in embedded html 6.2 -- Fixed bug when processing nested [class.<class name>] tags 6.3 -- Now allows negative values for the [brx] tag 6.4 -- Fixed bug that could occur when using [h2.CElink] tags in conjunction with nested [dtls] sections. 6.4.1 -- Fixed bug that could occur when <tt> tags are replaced by <code> tags (HTML5 validation fix) 6.5 -- 15 May 2021 -- Details boxes now open automatically if they are closed and the user clicks an internal page link that links to the box
%============================================================================================================================================================