├── images ├── blogs.png ├── post.png ├── plugin.png ├── update.png ├── after_post.png ├── deployment.png ├── blog_setting.png └── no_encoding.png ├── publishing-tool ├── bloginfo.mat ├── convertScript.p ├── wp_publisher.mlapp ├── +wpfunc │ ├── clientRequest.m │ ├── styleSheetScript.js │ ├── checkPath.m │ ├── removeMlxTitle.m │ ├── getPostTitle.m │ ├── loadSavedSiteInfo.m │ ├── getAllLatex.m │ ├── postArticle.m │ ├── mlxDropdown.m │ ├── fixCode.m │ ├── delOldMlx.m │ ├── delOldImages.m │ ├── getAllEquation.m │ ├── copyBody.m │ ├── replaceEquation.m │ ├── getImgToFile.m │ ├── mlxTohtml.m │ ├── fixImageLayout.m │ ├── sendMlxToWp.m │ └── sendImgToWp.m ├── README.md ├── license.txt ├── wp_publish.m └── wp_generate.m ├── SECURITY.md ├── live-script-support ├── README.txt ├── static │ ├── mathjax.js │ └── rtc.css ├── LICENSE.txt └── live-script-support.php ├── license.txt └── README.md /images/blogs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathworks/WordPress_Publishing_Tool/HEAD/images/blogs.png -------------------------------------------------------------------------------- /images/post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathworks/WordPress_Publishing_Tool/HEAD/images/post.png -------------------------------------------------------------------------------- /images/plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathworks/WordPress_Publishing_Tool/HEAD/images/plugin.png -------------------------------------------------------------------------------- /images/update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathworks/WordPress_Publishing_Tool/HEAD/images/update.png -------------------------------------------------------------------------------- /images/after_post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathworks/WordPress_Publishing_Tool/HEAD/images/after_post.png -------------------------------------------------------------------------------- /images/deployment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathworks/WordPress_Publishing_Tool/HEAD/images/deployment.png -------------------------------------------------------------------------------- /images/blog_setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathworks/WordPress_Publishing_Tool/HEAD/images/blog_setting.png -------------------------------------------------------------------------------- /images/no_encoding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathworks/WordPress_Publishing_Tool/HEAD/images/no_encoding.png -------------------------------------------------------------------------------- /publishing-tool/bloginfo.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathworks/WordPress_Publishing_Tool/HEAD/publishing-tool/bloginfo.mat -------------------------------------------------------------------------------- /publishing-tool/convertScript.p: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathworks/WordPress_Publishing_Tool/HEAD/publishing-tool/convertScript.p -------------------------------------------------------------------------------- /publishing-tool/wp_publisher.mlapp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathworks/WordPress_Publishing_Tool/HEAD/publishing-tool/wp_publisher.mlapp -------------------------------------------------------------------------------- /publishing-tool/+wpfunc/clientRequest.m: -------------------------------------------------------------------------------- 1 | function [stats, output] = clientRequest(cmd, env) 2 | prodstr = "prod"; 3 | if strcmp(prodstr, env) 4 | [stats, output] = system(cmd); 5 | else 6 | [stats, output] = deal(200, 'success'); 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /publishing-tool/+wpfunc/styleSheetScript.js: -------------------------------------------------------------------------------- 1 | let head = document.head || document.getElementsByTagName('head')[0]; 2 | let style = document.createElement('style'); 3 | head.appendChild(style); 4 | style.type = 'text/css'; 5 | if (style.styleSheet) { 6 | style.styleSheet.cssText = css; 7 | } else { 8 | style.appendChild(document.createTextNode(css)); 9 | } 10 | 11 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting Security Vulnerabilities 2 | 3 | If you believe you have discovered a security vulnerability, please report it to 4 | [security@mathworks.com](mailto:security@mathworks.com). Please see 5 | [MathWorks Vulnerability Disclosure Policy for Security Researchers](https://www.mathworks.com/company/aboutus/policies_statements/vulnerability-disclosure-policy.html) 6 | for additional information. -------------------------------------------------------------------------------- /publishing-tool/+wpfunc/checkPath.m: -------------------------------------------------------------------------------- 1 | function customizedPath = checkPath(app) 2 | appDir = wp_publisher.getAppDirectory(); 3 | customizedPath = ""; 4 | if isfile(appDir + "bloginfo.mat") 5 | load(appDir + "bloginfo.mat", 'toolpath'); 6 | if ~isempty(toolpath) 7 | if (exist(toolpath, 'dir') ~= 0) 8 | if toolpath(end) == filesep 9 | customizedPath = string(toolpath); 10 | else 11 | customizedPath = string(toolpath) + filesep; 12 | end 13 | end 14 | end 15 | end 16 | end -------------------------------------------------------------------------------- /publishing-tool/+wpfunc/removeMlxTitle.m: -------------------------------------------------------------------------------- 1 | function removeMlxTitle(html) 2 | % Remove the title from the HTML 3 | 4 | tree = htmlTree(fileread(html)); 5 | postTitle = extractHTMLText(findElement(tree,"H1")); 6 | 7 | if ~isempty(postTitle) 8 | str = convertCharsToStrings(fileread(html)); 9 | treeStr = regexprep(str,'', ''); 10 | fileid = fopen(html, 'wb'); 11 | fwrite(fileid, treeStr, 'char'); 12 | fclose(fileid); 13 | 14 | fprintf('Found and Removed title.\n'); 15 | else 16 | fprintf('No title Found.\n'); 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /live-script-support/README.txt: -------------------------------------------------------------------------------- 1 | === Live Script Support for MATLAB® WordPress Publishing Tool === 2 | 3 | Contributors: Cheng Chen 4 | Requires at least: WordPress 4.7 5 | Tested up to: 5.8 6 | 7 | 8 | This plugin adds support to your WordPress blog when you are using MATLAB® WordPress Publishing Tool. 9 | 10 | 11 | == Description == 12 | 13 | The plugin will: 14 | 1. Leverage MathJax to render equations and formulas visually. 15 | 2. Provide extra style to display live script blog post appropriately 16 | 3. Allow your WordPress blog media library to accept .mlx file 17 | 18 | == Installation == 19 | 20 | Nothing unusual here! 21 | 22 | == Changelog == 23 | 24 | = 0.0 = 25 | initial release 26 | -------------------------------------------------------------------------------- /publishing-tool/+wpfunc/getPostTitle.m: -------------------------------------------------------------------------------- 1 | function title = getPostTitle(app, html) 2 | % Extract the title of the post from the HTML 3 | 4 | tree = htmlTree(fileread(html)); 5 | postTitle = extractHTMLText(findElement(tree,"H1")); 6 | if ~isempty(postTitle) 7 | title = postTitle; 8 | fprintf('Article - %s is ready to be published to your blog \n', postTitle); 9 | else 10 | title = "Post title placeholder"; 11 | if ~isempty(app) 12 | app.ErrorLabel.Text = "Article title is empty"; 13 | end 14 | fprintf('Article is ready to be published to your blog, please don\''t forget to change title of your article. \n'); 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /live-script-support/static/mathjax.js: -------------------------------------------------------------------------------- 1 | MathJax.Hub.Config({ 2 | tex2jax: { 3 | inlineMath: [ ['$','$'], ["\\(","\\)"] ], 4 | displayMath: [ ['$$','$$'], ["\\[","\\]"] ], 5 | processEscapes: true 6 | } 7 | }); 8 | jQuery(document).ready(function($) { 9 | $('.rtcContent span').each(function() { 10 | var texencoding = $(this).attr("texencoding"); 11 | var mathmlencoding = $(this).attr("mathmlencoding"); 12 | if(typeof texencoding !== typeof undefined){ 13 | $(this).css('font-size', 'small'); 14 | $(this).css('vertical-align', 'inherit'); 15 | }else if (typeof mathmlencoding !== typeof undefined && mathmlencoding.includes('display="inline"')){ 16 | $(this).css('vertical-align', 'inherit'); 17 | $(this).css('font-size', 'small'); 18 | } 19 | }); 20 | }) 21 | -------------------------------------------------------------------------------- /publishing-tool/+wpfunc/loadSavedSiteInfo.m: -------------------------------------------------------------------------------- 1 | function loadSavedSiteInfo(app, appDir) 2 | load(appDir + "bloginfo.mat", "site", "token", "username", "password", "auth", "toolpath"); 3 | if isempty(token) || isempty(site) || isempty(token) 4 | app.TabGroup.SelectedTab = app.SettingsTab; 5 | app.BlogInfoActionLabel.Text = "Please input your blog information"; 6 | else 7 | app.TabGroup.SelectedTab = app.PublishpostTab; 8 | app.BlogSiteEditField.Value = site; 9 | app.UsernameField.Value = username; 10 | app.PasswordField.Value = "******"; 11 | if (~isempty(toolpath)) 12 | app.PathField.Value = toolpath; 13 | end 14 | if (auth == 1) 15 | app.JwtButton.Value = true; 16 | elseif (auth == 2) 17 | app.BasicButton.Value = true; 18 | end 19 | end 20 | end -------------------------------------------------------------------------------- /publishing-tool/README.md: -------------------------------------------------------------------------------- 1 | # Publishing App and Commands for MATLAB® live scripts 2 | 3 | ## wp_publisher.mlapp 4 | 5 | This app provides a UI for configuring your Settings and publishing your Live Script. 6 | 7 | See top-level README for details on setting up and using this app. 8 | 9 | ## wp_generate.m 10 | 11 | This command will generate the text and media files from the live script. 12 | 13 | You can use this script to extract the Live Script content without 14 | sending it to WordPress afterward. 15 | 16 | - `wp_generate` - If there is only 1 MLX file in the current directory, 17 | it will be converted. 18 | - `wp_generate script.mlx` - Convert the live script that is the first 19 | input. 20 | - `wp_generate script.mlx show` - Convert the script, then open the 21 | generated html in a web browser without posting to WordPress. 22 | (Note: Do not publish after using the 'show' option.) 23 | 24 | ## wp_publish.m 25 | 26 | This command will publish the files created by `wp_generate`. 27 | 28 | - `wp_publish` - If there is only 1 MLX file in the current directory, 29 | it will publish content for that script. 30 | - `wp_publish script.mlx` - Publish script.mlx any generated content for 31 | that script. 32 | - `wp_publish('script.mlx', true)` - Also publish the .mlx file as a 33 | download option. 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /live-script-support/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021, The MathWorks, Inc.. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIEDi 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /publishing-tool/+wpfunc/getAllLatex.m: -------------------------------------------------------------------------------- 1 | function getAllLatex(html) 2 | % Replace LaTeX encoded images and replace with HTML supported encoding. 3 | 4 | str = convertCharsToStrings(fileread(html)); 5 | spanTrees = regexp(str,']*>(.*?)','match'); 6 | cnt = 0; 7 | if ~isempty(spanTrees) 8 | O = containers.Map({'span'}, {'newSpan'}); 9 | for index = 1:length(spanTrees) 10 | if contains(spanTrees(index), "texencoding") == 1 11 | cnt = cnt+1; 12 | latexSpan = htmlTree(spanTrees(index)); 13 | latex = "$ " + getAttribute(findElement(latexSpan, "span"), "texencoding") + " $"; 14 | newSpan = regexprep(string(spanTrees(index)), '', 'placeholder'); 15 | sp = strrep(newSpan, 'placeholder', latex); 16 | O(string(spanTrees(index))) = string(sp); 17 | end 18 | end 19 | k = keys(O); 20 | val = values(O); 21 | for i = 1:(length(O)-1) 22 | str = strrep(str, string(k{i}), string(val{i})); 23 | end 24 | end 25 | 26 | if cnt > 0 27 | fileid = fopen('article_body.html', 'wb'); 28 | fwrite(fileid, str, 'char'); 29 | fclose(fileid); 30 | 31 | fprintf('Converted %d LaTeX Equasions.\n', cnt); 32 | else 33 | fprintf('No LaTeX Equasions found.\n'); 34 | end 35 | 36 | end 37 | -------------------------------------------------------------------------------- /publishing-tool/+wpfunc/postArticle.m: -------------------------------------------------------------------------------- 1 | function postArticle(title, loc, auth, token) 2 | html = dir("article_body.html").name; 3 | postInfo = dir("post_info.mat"); 4 | if ~isempty(html) 5 | if ~isempty(postInfo) 6 | load('post_info.mat', 'postId'); % , 'postLink', 'postTitle'); 7 | endPoint = string(loc) + 'wp-json/wp/v2/posts/' + string(postId); 8 | else 9 | endPoint = string(loc) + 'wp-json/wp/v2/posts/'; 10 | end 11 | str = convertCharsToStrings(fileread(string(html))); 12 | content = regexprep(str, '\n
*\n',''); 13 | auth = sprintf('%s %s', auth, token); 14 | options = weboptions('HeaderFields',{'Authorization' auth}); 15 | data = struct('title',title,'status', 'draft', 'content', content); 16 | response = webwrite(endPoint,data,options); 17 | postId = response.id; 18 | postLink = response.link; 19 | editLink = string(loc) + 'wp-admin/post.php?post=' + postId + '&action=edit'; 20 | %postTitle = response.title.raw; 21 | save('post_info.mat', 'postId'); %, 'postLink', 'postTitle'); 22 | fprintf('SUCCESS! Please see your blog article here - %s \n', postLink, response.title.raw); 23 | web(postLink); 24 | web(editLink); 25 | else 26 | fprintf('Internal Error: No HTML for article found.'); 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /publishing-tool/+wpfunc/mlxDropdown.m: -------------------------------------------------------------------------------- 1 | function mlxDropdown(app, file) 2 | mlx = dir("*.mlx"); 3 | if ~isempty(mlx) 4 | if ~strcmp(file, "all") 5 | if isfile(file) 6 | mlx = mlx(~startsWith({mlx.name}, file)); 7 | mlx = [dir(file); mlx]; 8 | app.SelectLiveScriptDropDown.Items = {mlx.name}; 9 | else 10 | fprintf('Sorry, the file you input is not found in the folder \n'); 11 | app.ErrorLabel.Text = "File not found, please select from dropdown"; 12 | app.SelectLiveScriptDropDown.Items = {mlx.name}; 13 | end 14 | else 15 | app.SelectLiveScriptDropDown.Items = {mlx.name}; 16 | end 17 | mlxfile = app.SelectLiveScriptDropDown.Value; 18 | mlxname = erase(mlxfile, ".mlx"); 19 | customizedPath = wpfunc.checkPath(); 20 | newpath = customizedPath + mlxname; 21 | if exist(newpath, 'dir') && isfile(newpath + "/post_info.mat") 22 | load(fullfile(newpath,"/post_info.mat")); 23 | app.AppLabel.Text = "Edit existing blog article"; 24 | delete(newpath + "/*.html"); 25 | else 26 | app.AppLabel.Text = "Post new blog article"; 27 | end 28 | 29 | else 30 | fprintf('Sorry, there are no live scripts found in the folder \n'); 31 | app.ErrorLabel.Text = "Sorry, there are no live scripts found in the folder"; 32 | end 33 | end -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021-2024, The MathWorks, Inc. 2 | All rights reserved. 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 5 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 6 | 3. In all cases, the software is, and all modifications and derivatives of the software shall be, licensed to you solely for use in conjunction with MathWorks products and service offerings. 7 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /publishing-tool/license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021, The MathWorks, Inc. 2 | All rights reserved. 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 5 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 6 | 3. In all cases, the software is, and all modifications and derivatives of the software shall be, licensed to you solely for use in conjunction with MathWorks products and service offerings. 7 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /publishing-tool/+wpfunc/fixCode.m: -------------------------------------------------------------------------------- 1 | function fixCode(html) 2 | % Fix up the HTML that produces code blocks. 3 | % 4 | % Edits: 5 | % * Replace spaces used for indenting code with   so wordpress 6 | % doesn't remove them. 7 | 8 | str = convertCharsToStrings(fileread(html)); 9 | 10 | %% White space at beginning of code line 11 | [TE, tok] = regexp(str, '([ ]+)[^ ]', ... 12 | 'tokenExtents','tokens'); 13 | 14 | for i=numel(tok):-1:1 15 | newtok = regexprep(tok{i}, ' ', ' '); 16 | ext = TE{i}; 17 | str = replaceBetween(str,ext(1),ext(2), newtok); 18 | end 19 | 20 | %% White space in comments 21 | [TE, tok] = regexp(str, '%([^<]+)', ... 22 | 'tokenExtents','tokens'); 23 | 24 | for i=numel(tok):-1:1 25 | ext = TE{i}; 26 | substr = extractBetween(str, ext(1), ext(2)); 27 | [CTE, ctok] = regexp(substr, '( [ ]+)', 'tokenExtents','tokens'); 28 | if numel(ctok) > 0 29 | for si=numel(ctok):-1:1 30 | newtok = regexprep(ctok{si}, ' ', ' '); 31 | cext = CTE{si}; 32 | substr = replaceBetween(substr,cext(1),cext(2), newtok); 33 | end 34 | str = replaceBetween(str,ext(1), ext(2), substr); % Put new string back in 35 | end 36 | end 37 | 38 | prefix = ""; 39 | %prefix = "foo_"; 40 | fileid = fopen(prefix + html, 'wb'); 41 | fwrite(fileid, str, 'char'); 42 | fclose(fileid); 43 | fprintf('Fixing Code Snippets Complete.\n'); 44 | 45 | end 46 | -------------------------------------------------------------------------------- /publishing-tool/+wpfunc/delOldMlx.m: -------------------------------------------------------------------------------- 1 | function delOldMlx(app, path, loc, auth, token, env) 2 | % Delete the post draft from WordPress. 3 | % 4 | % The wordpress key is saved in mlxKey.mat when it is created. This 5 | % script will also delete the mat file once the draft is removed from 6 | % wordpress. 7 | 8 | mlxKey = dir("mlxKey.mat"); 9 | if ~isempty(mlxKey) 10 | load("mlxKey.mat", "allMlx"); 11 | mlxs = dir(string(path) + filesep + "*.mlx"); 12 | for index = 1:length(mlxs) 13 | mlxName = string(mlxs(index).name); 14 | if isKey(allMlx, mlxName) 15 | mlxId = allMlx(mlxName); 16 | delEndPoint = string(loc) + 'wp-json/wp/v2/media/' + string(mlxId) + "?force=true"; 17 | delcmd = sprintf('curl --location --request DELETE "%s" --header "Authorization: %s %s"', delEndPoint, auth, token); 18 | [~, output] = wpfunc.clientRequest(delcmd, env); 19 | if contains(output, "incorrect_password") 20 | fprintf('Sorry, your WordPress password is not correct in token \n'); 21 | if ~isempty(app) 22 | app.ErrorLabel.Text = "Sorry, your WordPress password is not correct in token"; 23 | end 24 | elseif contains(output, "error") 25 | fprintf('Sorry, there are errors in your WordPress \n'); 26 | if ~isempty(app) 27 | app.ErrorLabel.Text = "Sorry, there are errors in your WordPress"; 28 | end 29 | else 30 | fprintf('Previous Live Script is deleted in WordPress \n'); 31 | end 32 | end 33 | end 34 | clear allMlx; 35 | delete mlxKey.mat; 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /publishing-tool/+wpfunc/delOldImages.m: -------------------------------------------------------------------------------- 1 | function delOldImages(app, loc, auth, token, env) 2 | % This function removes images that have already been uploaded to 3 | % WordPress for this blog post. 4 | % 5 | % The map of wordpress keys and image files are kept in imgKey.mat 6 | % This script will also delete the MAT file after images are removed 7 | % from wordpress. 8 | 9 | imgKey = dir("imgKey.mat"); 10 | if ~isempty(imgKey) 11 | load("imgKey.mat", "M"); 12 | imgs = dir("*.png"); 13 | for index = 1:length(imgs) 14 | imgName = string(imgs(index).name); 15 | if isKey(M, imgName) 16 | imgId = M(imgName); 17 | delEndPoint = string(loc) + 'wp-json/wp/v2/media/' + string(imgId) + "?force=true"; 18 | delcmd = sprintf('curl --location --request DELETE "%s" --header "Authorization: %s %s"', delEndPoint, auth, token); 19 | [~, output] = wpfunc.clientRequest(delcmd, env); 20 | if contains(output, "incorrect_password") 21 | fprintf('Sorry, your WordPress password is not correct in token \n'); 22 | if ~isempty(app) 23 | app.ErrorLabel.Text = "Sorry, your WordPress password is not correct in token"; 24 | end 25 | elseif contains(output, "error") 26 | fprintf('Sorry, there are errors in your WordPress \n'); 27 | if ~isempty(app) 28 | app.ErrorLabel.Text = "Sorry, there are errors in your WordPress"; 29 | end 30 | else 31 | delete(imgName); 32 | fprintf('Old image %s is deleted in WordPress.\n', imgName); 33 | end 34 | end 35 | end 36 | clear M; 37 | delete imgKey.mat; 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /live-script-support/static/rtc.css: -------------------------------------------------------------------------------- 1 | .rtcContent .inlineElement.eoOutputWrapper.embeddedOutputsVariableTableElement { width: 100% !important; overflow-x: auto; } 2 | .rtcContent table { table-layout:fixed; width: 100%; overflow:scroll; } 3 | .rtcContent .scrollableOutput { width: 100% !important; } 4 | .rtcContent .inlineWrapper { overflow:auto; } 5 | .embeddedOutputsErrorElement {min-height: 18px; max-height: 250px; overflow: auto;} .embeddedOutputsErrorElement.inlineElement {} .embeddedOutputsErrorElement.rightPaneElement {} .embeddedOutputsWarningElement{min-height: 18px; max-height: 250px; overflow: auto;} .embeddedOutputsWarningElement.inlineElement {} .embeddedOutputsWarningElement.rightPaneElement {} .diagnosticMessage-wrapper {font-family: "Menlo, Monaco, Consolas, Courier New, monospace"; font-size: 12px;} 6 | .diagnosticMessage-wrapper.diagnosticMessage-warningType {color: rgb(255,100,0);} .diagnosticMessage-wrapper.diagnosticMessage-warningType a {color: rgb(255,100,0); text-decoration: underline;} .diagnosticMessage-wrapper.diagnosticMessage-errorType {color: rgb(230,0,0);} .diagnosticMessage-wrapper.diagnosticMessage-errorType a {color: rgb(230,0,0); text-decoration: underline;} 7 | .diagnosticMessage-wrapper .diagnosticMessage-messagePart,.diagnosticMessage-wrapper .diagnosticMessage-causePart {white-space: pre-wrap;} .diagnosticMessage-wrapper .diagnosticMessage-stackPart {white-space: pre;} .embeddedOutputsTextElement,.embeddedOutputsVariableStringElement {white-space: pre; word-wrap: initial; min-height: 18px; max-height: 250px; overflow: auto;} .textElement,.rtcDataTipElement .textElement {padding-top: 3px;} .embeddedOutputsTextElement.inlineElement,.embeddedOutputsVariableStringElement.inlineElement {} 8 | .inlineElement .textElement {} .embeddedOutputsTextElement.rightPaneElement,.embeddedOutputsVariableStringElement.rightPaneElement {min-height: 16px;} .rightPaneElement .textElement {padding-top: 2px; padding-left: 9px;} 9 | -------------------------------------------------------------------------------- /publishing-tool/+wpfunc/getAllEquation.m: -------------------------------------------------------------------------------- 1 | function getAllEquation(html) 2 | % Get all the regular equasions from the HTML and convert to mathml 3 | 4 | str = convertCharsToStrings(fileread(html)); 5 | spanTrees = regexp(str,']*>(.*?)','match'); 6 | cnt = 0; 7 | if ~isempty(spanTrees) 8 | O = containers.Map({'span'}, {'newSpan'}); 9 | for index = 1:length(spanTrees) 10 | if contains(spanTrees(index), "mathmlencoding") == 1 11 | cnt = cnt+1; 12 | newSpan = regexprep(string(spanTrees(index)), '', "equation_placeholder"); 13 | O(string(spanTrees(index))) = string(newSpan); 14 | end 15 | end 16 | k = keys(O); 17 | val = values(O); 18 | for i = 1:(length(O)-1) 19 | str = strrep(str, string(k{i}), string(val{i})); 20 | end 21 | end 22 | 23 | if cnt > 0 24 | % TODO: If I do this MathML works great, but LaTeX equasions break. :( 25 | 26 | % If we found any equasions, make sure MathJax is loaded in. 27 | % Copied this from snippet from stack overflow: 28 | % https://stackoverflow.com/questions/29682207/unable-to-render-mathml-content-in-google-chrome 29 | %mjScript = "" + newline + ""; 32 | 33 | % Stick it on the end. 34 | %str = str + mjScript; 35 | 36 | fileid = fopen('article_body.html', 'wb'); 37 | fwrite(fileid, str, 'char'); 38 | fclose(fileid); 39 | 40 | fprintf('Converted %d Regular Equasions.\n', cnt); 41 | else 42 | fprintf('No Regular Equasions found.\n'); 43 | end 44 | 45 | end 46 | -------------------------------------------------------------------------------- /publishing-tool/+wpfunc/copyBody.m: -------------------------------------------------------------------------------- 1 | function newHTML = copyBody(html) 2 | % Extract Images embedded in Live Editor generated HTML file 3 | % 4 | % Performs these operations: 5 | % * Remove some CSS type content from head. 6 | % * Extract core of the HTML from between body tags. 7 | % * Remove the giant comment that has the original post. 8 | % * Add js script that reconfigures the Style sheet. 9 | 10 | str = convertCharsToStrings(fileread(html)); 11 | 12 | % Extract core part of HTML, append script to change the stylesheet. 13 | headStr = extractBetween(str, ''); 14 | styleStr = regexprep(regexprep(headStr, '.rtcContent { padding: 30px; } ',''), '[\n\r]+',' '); 15 | 16 | % Keep our js in a separate file easy to edit and read. 17 | script=fileread(which('+wpfunc/styleSheetScript.js')); 18 | 19 | % Generate the dom elements for appended script. 20 | styleScript = sprintf("",... 21 | strrep(styleStr, '''', ''), ... % Name of style sheet 22 | script); 23 | 24 | % Extract the part of the original we want to keep, and append our stylesheet script. 25 | treeStr = regexp(str,'((?<=).*(?=<\/body>))','match'); 26 | 27 | % Remove the giant comment that reveals what the content is in plain text 28 | % WordPress already strips it out, so lets get rid of it early. 29 | expr = sprintf("\n?"); 30 | treeStr = regexprep(treeStr, expr,""); 31 | 32 | % Add our style sheet to the string. 33 | treeStr = treeStr + styleScript; 34 | 35 | % Save as new file, return the name of the new file. 36 | newHTML = "article_body.html"; 37 | fileid = fopen(newHTML, 'wb'); 38 | fwrite(fileid, treeStr, 'char'); 39 | fclose(fileid); 40 | fprintf('Copied body and updated StyleSheet.\n'); 41 | 42 | end 43 | -------------------------------------------------------------------------------- /publishing-tool/+wpfunc/replaceEquation.m: -------------------------------------------------------------------------------- 1 | function replaceEquation(origHtml, convertedHtml) 2 | % Replace Equasion encoded images and replace with HTML supporte the equasion. 3 | % 4 | % TODO: Why is this separate from getAllLatex andgetAllEquasion 5 | 6 | origStr = convertCharsToStrings(fileread(origHtml)); 7 | origSpanTrees = regexp(origStr,']*>(.*?)','match'); 8 | 9 | cnt = 0; 10 | if ~isempty(origSpanTrees) 11 | P = containers.Map({'span'}, {'newSpan'}); 12 | for i = 1:length(origSpanTrees) 13 | if contains(origSpanTrees(i), "mathmlencoding") == 1 14 | cnt = cnt+1; 15 | latexSpan = htmlTree(origSpanTrees(i)); 16 | latex = getAttribute(findElement(latexSpan, "span"), "mathmlencoding"); 17 | newSpan = regexprep(string(origSpanTrees(i)), '', latex); 18 | P(string(origSpanTrees(i))) = string(newSpan); 19 | end 20 | end 21 | val = values(P); 22 | end 23 | 24 | convertedStr = convertCharsToStrings(fileread(convertedHtml)); 25 | convertedSpanTrees = regexp(convertedStr,']*>(.*?)','match'); 26 | if ~isempty(convertedSpanTrees) 27 | Q = containers.Map({'span'}, {'newSpan'}); 28 | for i = 1:length(convertedSpanTrees) 29 | if contains(convertedSpanTrees(i), "mathmlencoding") == 1 30 | cnt = cnt+1; 31 | Q(string(convertedSpanTrees(i))) = ""; 32 | end 33 | end 34 | key = keys(Q); 35 | for i = 1:(length(Q)-1) 36 | convertedStr = strrep(convertedStr, string(key{i}), string(val{i})); 37 | end 38 | end 39 | 40 | if cnt > 0 41 | fileid = fopen('article_body.html', 'wb'); 42 | fwrite(fileid, convertedStr, 'char'); 43 | fclose(fileid); 44 | fprintf('Replaced %d Equations.\n',cnt); 45 | else 46 | fprintf('No Equations found to replace.\n'); 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /live-script-support/live-script-support.php: -------------------------------------------------------------------------------- 1 | ID, 'disable_auto_formatting', true) == 1) { 15 | remove_filter('the_content', 'wpautop'); 16 | } 17 | return $content; 18 | } 19 | add_filter( "the_content", "WP_auto_formatting", 1 ); 20 | 21 | function upload_mlx_files( $allowed ) { 22 | if ( !current_user_can( 'manage_options' ) ) 23 | return $allowed; 24 | $allowed['mlx'] = 'application/octet-stream'; 25 | return $allowed; 26 | } 27 | add_filter( 'upload_mimes', 'upload_mlx_files'); 28 | 29 | function upload_bmp_files( $allowed ) { 30 | if ( !current_user_can( 'manage_options' ) ) 31 | return $allowed; 32 | $allowed['bmp'] = 'image/bmp'; 33 | return $allowed; 34 | } 35 | add_filter( 'upload_mimes', 'upload_bmp_files'); 36 | 37 | add_action('init', 'register_script'); 38 | function register_script() { 39 | wp_register_script( 'mathjax_setting', plugins_url('/static/mathjax.js', __FILE__), array('jquery'), null, true ); 40 | 41 | wp_enqueue_script( 'mathjax', 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_HTMLorMML'); 42 | 43 | wp_register_style( 'live_scripts_style', plugins_url('/static/rtc.css', __FILE__), false, '1.0.0',); 44 | } 45 | 46 | // use the registered jquery and style above 47 | add_action('wp_enqueue_scripts', 'enqueue_static_files'); 48 | 49 | function enqueue_static_files(){ 50 | wp_enqueue_script('mathjax'); 51 | wp_enqueue_script('mathjax_setting'); 52 | wp_enqueue_style( 'live_scripts_style' ); 53 | } 54 | 55 | 56 | ?> 57 | -------------------------------------------------------------------------------- /publishing-tool/wp_publish.m: -------------------------------------------------------------------------------- 1 | function wp_publish(mlx, download, app) 2 | % Publish the html and images already extracted from an MLX file. 3 | 4 | arguments 5 | mlx = []; % by default, guess 6 | download = false; % don't provide download btn by default. 7 | app = [] % By default, don't update App fields. 8 | end 9 | 10 | if isempty(mlx) 11 | if nargin == 0 12 | mlxfiles = dir("*.mlx"); 13 | if isscalar(mlxfiles) 14 | mlx=mlxfiles.name; 15 | else 16 | error('Must pass in a valid mlx file'); 17 | end 18 | end 19 | end 20 | 21 | % Force our path to be maintained since this does a CD 22 | currentpath = pwd; 23 | cleanup = onCleanup(@()cd(currentpath)); 24 | 25 | [mlxhtml, htmlpath] = wp_generate(mlx, 'check'); % Don't generate, just check. 26 | cd(htmlpath); 27 | 28 | mlxname = erase(mlxhtml, ".html"); % Get name of this post 29 | 30 | % Get site info from data stored by the App 31 | appDir = wp_publisher.getAppDirectory(); 32 | if isfile(appDir + "bloginfo.mat") 33 | load(appDir + "bloginfo.mat", 'site', 'token', 'auth'); 34 | if auth == 1 35 | authway = "Bearer"; 36 | elseif auth == 2 37 | authway = "Basic"; 38 | end 39 | loc = site; 40 | else 41 | error('Use the wp_publisher app to setup your bloginfo'); 42 | end 43 | 44 | env = "prod"; 45 | 46 | title = wpfunc.getPostTitle(app, mlxhtml); 47 | 48 | % Setup a log file so the command line isn't so messy 49 | fname = fullfile(pwd, "publish_log.txt"); 50 | fid = fopen(fname, "w"); 51 | if fid < 0 52 | error('Error opening log file for publishing'); 53 | end 54 | cleanup2 = onCleanup(@()fclose(fid)); 55 | 56 | % Send content to WordPress site 57 | wpfunc.sendImgToWp(app, loc, authway, token, env, fid); 58 | wpfunc.sendMlxToWp(app, currentpath, loc, authway, token, env, mlx, download, fid); 59 | wpfunc.postArticle(title, loc, authway, token); 60 | 61 | fprintf('See publishing log at: publish_log.txt\n', fname); 62 | end 63 | 64 | -------------------------------------------------------------------------------- /publishing-tool/+wpfunc/getImgToFile.m: -------------------------------------------------------------------------------- 1 | function getImgToFile(mlxname, html, blog_url, local_only) 2 | % Extract Images embedded in Live Editor generated HTML file 3 | % 4 | % Performs these operations: 5 | % * Extract the images from base64 encoded text and save them to 6 | % image files so they can be uploaded to the target WordPress website. 7 | 8 | arguments 9 | mlxname 10 | html 11 | blog_url 12 | local_only = false; 13 | end 14 | 15 | treeStr = convertCharsToStrings(fileread(html)); 16 | 17 | tree = htmlTree(treeStr); 18 | imgs = findElement(tree,"div.rtcContent img"); 19 | imgsrcs = getAttribute(imgs, "src"); 20 | 21 | date = datetime('now', 'Format', 'yyyy/MM/'); 22 | 23 | cnt = 0; 24 | if ~isempty(imgsrcs) 25 | for index = 1:length(imgsrcs) 26 | cnt = cnt+1; 27 | imgType = extractBetween(imgsrcs(index), "data:image/", ";base64"); 28 | newStr = extractAfter(imgsrcs(index), ";base64,"); 29 | raw = matlab.net.base64decode(newStr); 30 | if strcmp(imgType, "jpeg") == 1 31 | imgFile = string(mlxname) + "_" + string(index) + ".jpg"; 32 | else 33 | imgFile = string(mlxname) + "_" + string(index) + ".png"; 34 | end 35 | imgName = string(mlxname) + "_" + string(index) + ".png"; 36 | if local_only 37 | imgUrl = string(imgName); 38 | else 39 | imgUrl = string(blog_url) + "wp-content/uploads/" + string(date) + string(imgName); 40 | end 41 | treeStr = strrep(treeStr, imgsrcs(index), imgUrl); 42 | % decode base64 to images 43 | fid = fopen(imgFile, 'wb'); 44 | fwrite(fid, raw); 45 | fclose(fid); 46 | end 47 | % Convert all .jpg to .png files. 48 | f=dir('*.jpg'); 49 | fil={f.name}; 50 | for k=1:numel(fil) 51 | file = fil{k}; 52 | new_file = strrep(file,'.jpg','.png'); 53 | im = imread(file); 54 | imwrite(im,new_file); 55 | delete(file); 56 | end 57 | end 58 | 59 | fileid = fopen("article_body.html", 'wb'); 60 | fwrite(fileid, treeStr, 'char'); 61 | fclose(fileid); 62 | fprintf('Converted %d embedded Images.\n',cnt); 63 | end 64 | -------------------------------------------------------------------------------- /publishing-tool/+wpfunc/mlxTohtml.m: -------------------------------------------------------------------------------- 1 | function tmpfile = mlxTohtml(mlxname, check) 2 | % Convert MLXNAME into HTML. Return the name of the generated file. 3 | % 4 | % Optional input CHECK means to just generate names and verify they exist and return 5 | % the file name of what would be created if it was converted. 6 | 7 | arguments 8 | mlxname 9 | check = false; 10 | end 11 | 12 | appDir = wp_publisher.getAppDirectory(); 13 | if isfile(appDir + "bloginfo.mat") 14 | load(appDir + "bloginfo.mat", 'toolpath'); 15 | end 16 | d = dir(mlxname); 17 | dname = d.name; 18 | dfolder = d.folder; 19 | 20 | if ~isempty(d) 21 | timestamp = datetime(d.datenum,"ConvertFrom","datenum",'Format','yyyy-MM-dd''T''HHmmss'); 22 | ts = convertCharsToStrings(datestr(timestamp, 'mm-dd-yy')); 23 | tmpfile = char(convertCharsToStrings(d.name) + '-' + ts + '.html'); 24 | mlxname = erase(d.name, ".mlx"); 25 | if ~isempty(toolpath) 26 | if (exist(toolpath, 'dir') ~= 0) 27 | cd(toolpath) 28 | if ~exist(fullfile(cd, mlxname), 'dir') 29 | mkdir(mlxname); 30 | end 31 | if check 32 | % TODO 33 | else 34 | customizedPath = wpfunc.checkPath(); 35 | mlxfile = string(dfolder) + filesep + string(dname); 36 | convertScript(convertStringsToChars(mlxfile), convertStringsToChars(customizedPath), mlxname, tmpfile); 37 | end 38 | 39 | else 40 | cd(dfolder) 41 | if ~exist(fullfile(cd, mlxname), 'dir') 42 | mkdir(mlxname); 43 | end 44 | 45 | if check 46 | % TODO 47 | else 48 | convertScript(which(dname), dfolder, mlxname, tmpfile); 49 | end 50 | end 51 | else 52 | cd(dfolder) 53 | if ~exist(fullfile(cd, mlxname), 'dir') 54 | mkdir(mlxname); 55 | end 56 | if check 57 | % TODO 58 | else 59 | convertScript(which(dname), dfolder, mlxname, tmpfile); 60 | end 61 | end 62 | if ~check 63 | fprintf('Live script is converted into HTML. \n'); 64 | end 65 | 66 | cd(mlxname) 67 | 68 | else 69 | fprintf('Sorry, there is no live script found in the folder \n'); 70 | % TODO - allow the below to work. 71 | %app.ErrorLabel.Text = "Sorry, there is no live script found in the folder"; 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /publishing-tool/wp_generate.m: -------------------------------------------------------------------------------- 1 | function [mlxhtml_out, html_path] = wp_generate(mlx, flag, app) 2 | % Convert the MLX file into the necessary files ready to post to word press 3 | % 4 | % Creates a subdirectory, and copies key parts out of the MLX file 5 | % into html and image files. 6 | % 7 | % Optional input flag can be one of: 8 | % 9 | % TRUE, 'show' - show the resultant HTML 10 | % 'check' - compute outputs and validate html exists, then exit. 11 | % 12 | % Last arg is the app to update labels 13 | 14 | arguments 15 | mlx = findMLX(); % If only 1 mlx file here, use it. 16 | flag = false; 17 | app = []; 18 | end 19 | 20 | if islogical(flag) 21 | show = flag; 22 | check = false; 23 | else 24 | switch flag 25 | case 'show' 26 | show = true; 27 | check = false; 28 | case 'check' 29 | show = false; 30 | check = true; 31 | end 32 | end 33 | 34 | % Force our path to be maintained since this does a CD 35 | currentpath = pwd; 36 | cleanup = onCleanup(@()cd(currentpath)); 37 | 38 | % Get site info from data stored by the App 39 | appDir = wp_publisher.getAppDirectory(); 40 | if isfile(appDir + "bloginfo.mat") 41 | load(appDir + "bloginfo.mat", 'site', 'token', 'auth'); 42 | if auth == 1 43 | authway = "Bearer"; 44 | elseif auth == 2 45 | authway = "Basic"; 46 | end 47 | loc = site; 48 | else 49 | error('Use the wp_publisher app to setup your bloginfo'); 50 | end 51 | env = "prod"; 52 | 53 | % Do the first conversion to HTML 54 | mlxhtml = wpfunc.mlxTohtml(mlx, check); % Extract from MLX file, get output name. 55 | 56 | if check 57 | % Do nothing, errors or whatever thrown from above. 58 | else 59 | mlxname = erase(mlxhtml, ".html"); % Get name of this post 60 | 61 | % Remove any old versions of images and mlx files of this same blog from this site 62 | wpfunc.delOldImages(app, loc, authway, token, env); 63 | wpfunc.delOldMlx(app, currentpath, loc, authway, token, env); 64 | 65 | % Tidy up the generated HTML 66 | bodyhtml = wpfunc.copyBody(mlxhtml); 67 | wpfunc.removeMlxTitle(bodyhtml); 68 | wpfunc.getAllLatex(bodyhtml); 69 | wpfunc.getAllEquation(bodyhtml); 70 | wpfunc.replaceEquation(mlxhtml, bodyhtml); 71 | wpfunc.getImgToFile(mlxname, bodyhtml, string(loc), show); 72 | wpfunc.fixImageLayout(bodyhtml, show); 73 | wpfunc.fixCode(bodyhtml); 74 | 75 | if show 76 | % web(mlxhtml) 77 | web(bodyhtml) % If you set 'show' to true, you can't publish the result. 78 | end 79 | end 80 | 81 | if nargout >= 1 82 | mlxhtml_out = mlxhtml; 83 | if nargout == 2 84 | html_path = pwd; 85 | end 86 | end 87 | 88 | end 89 | 90 | function mlx = findMLX() 91 | mlxfiles = dir("*.mlx"); 92 | if isscalar(mlxfiles) 93 | mlx=mlxfiles.name; 94 | else 95 | error('Must pass in a valid mlx file'); 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /publishing-tool/+wpfunc/fixImageLayout.m: -------------------------------------------------------------------------------- 1 | function fixImageLayout(html, show) 2 | % Fix how image outputs are positioning withing the output. 3 | % 4 | % Includes these features: 5 | % * Remove
tags before the image, and the trailing divs after 6 | % that tried to set background to white, but didn't do that, and 7 | % instead add a scrollbar to the image, and the prev code line. 8 | % * If the WP_DELETE_IMG key is found, delete the prev image from 9 | % the html. Useful if that img is the tail of an animation, and 10 | % the author sticks the animation in directly to the mlx file. 11 | % * Add max-width setting on images so that they are displayed actual-size, 12 | % and will shrink if they are too big to fit. 13 | 14 | str = convertCharsToStrings(fileread(html)); 15 | 16 | %% Remove all "doNotExport" class divs. They just get in the way. 17 | str = regexprep(str, '
[^<]*
', ''); 18 | 19 | %% Remove a width setting from the div around images. 20 | ext = regexp(str, ']+embeddedOutputsFigure[^>]+(style="width: [0-9]+px;")>', 'tokenExtents'); 21 | 22 | for idx=numel(ext):-1:1 23 | se = ext{idx}; 24 | str = eraseBetween(str, se(1), se(2)); 25 | end 26 | 27 | %% Move images out of code block. 28 | % Images end up in the SAME div as the LAST line of code in a code block. Get them 29 | % out so they aren't affected by the inlineWrapper stuff. 30 | [s,te] = regexp(str, "
]+> *" + ... 31 | "]+embeddedOutputsFigure[^>]+>" + ... 32 | "]+> *" + ... 33 | "]+>" + ... 34 | "(
)", ... 35 | 'start', 'tokenExtents'); 36 | 37 | for idx=numel(te):-1:1 38 | se = te{idx}; 39 | str = eraseBetween(str, se(1), se(2)); 40 | str = insertBefore(str, s(idx), '
'); 41 | end 42 | 43 | 44 | %% Remove images tagged with WP_DELETE_IMG 45 | % If you place the text WP_DELETE_IMG on a line by itself directly after a generated image, 46 | % the below matcher will delete that image. 47 | [s,e, imgToDelete] = regexp(str, "
]+> *" + ... 48 | "]+embeddedOutputsFigure[^>]+>" + ... 49 | "]+> *" + ... 50 | "]+src=""([^""]+)""[^>]+>" + ... 51 | "
" + ... 52 | "]+>WP_DELETE_IMG", ... 53 | 'start', 'end', 'tokens'); 54 | 55 | for idx=numel(s):-1:1 56 | str = replaceBetween(str, s(idx), e(idx), ""); 57 | end 58 | 59 | % Also remove the wp_delete image from the Filesystem so we don't 60 | % waste time sending it to wordpress. 61 | for idx=1:numel(imgToDelete) 62 | if show 63 | imgName = imgToDelete{idx}; % local show mode 64 | else 65 | % Extract fname from URL 66 | imgName=regexp(imgToDelete{idx}, "/([^/]+.png)$", 'tokens'); 67 | end 68 | disp("Removeing due to WP_DELETE_IMG tag: " + imgName{1}); 69 | delete(imgName{1}) 70 | end 71 | 72 | % For images that are very wide, lets add an HTML trick to make them look better in the 73 | % blog by using "max-width", so if not enough room, shrink image, if too much room, pixel 74 | % perfect as per live script. 75 | te = regexp(str, "]+style=""(width: [0-9]+px; )",'tokenExtents'); 76 | for idx =numel(te):-1:1 77 | ext = te{idx}; 78 | str = insertAfter(str, ext(2), "width:100%; "); 79 | str = insertBefore(str, ext(1), "max-"); 80 | end 81 | 82 | % Debug 83 | %tree = htmlTree(str) 84 | 85 | prefix = ""; 86 | %prefix = "foo_"; 87 | fileid = fopen(prefix + html, 'wb'); 88 | fwrite(fileid, str, 'char'); 89 | fclose(fileid); 90 | fprintf('Fixing %d Image Layouts. Removed %d tagged images.\n', numel(te), numel(s)); 91 | end 92 | -------------------------------------------------------------------------------- /publishing-tool/+wpfunc/sendMlxToWp.m: -------------------------------------------------------------------------------- 1 | function sendMlxToWp(app, path, loc, auth, token, env, mlxname, downLoadSetting, logfid) 2 | 3 | mlxKey = dir("mlxKey.mat"); 4 | if ~isempty(mlxKey) 5 | fprintf('MLX already sent once. Regenerate before sending again.\n'); 6 | return; 7 | end 8 | 9 | expectedLocation = string(path) + filesep + string(mlxname); 10 | mlx = dir(expectedLocation); 11 | if ~isempty(mlx) 12 | allMlx = containers.Map({'mlxName'}, {'id'}); 13 | mlxName = mlx.name; 14 | mlxDir = string(path) + filesep + mlxName; 15 | fprintf("MLX: %s\n", mlxDir); 16 | fprintf(logfid, "\n ** MLX: %s\n", mlxDir); 17 | 18 | endPoint = string(loc) + 'wp-json/wp/v2/media/'; 19 | cmd = sprintf('curl --location --silent --request POST "%s" --header "Content-Disposition: attachment;filename=%s" --header "Authorization: %s %s" --header "Content-Type: application/octet-stream" --data-binary "@%s"', endPoint, mlxName, auth, token, mlxDir); 20 | 21 | fprintf(logfid, "Upload MLX Cmd: %s\n", cmd); 22 | [~,cmdout] = wpfunc.clientRequest(cmd, env); 23 | 24 | fprintf(logfid, "--- output ---\n%s\n--- end ---\n", cmdout); 25 | 26 | 27 | if contains(cmdout, "incorrect_password") 28 | fprintf('Sorry, your WordPress password is not correct in token \n'); 29 | if ~isempty(app) 30 | app.ErrorLabel.Text = "Sorry, your WordPress password is not correct in token"; 31 | end 32 | elseif contains(cmdout, "error", "IgnoreCase", true) 33 | fprintf('Sorry, there are errors in your WordPress \n'); 34 | if ~isempty(app) 35 | app.ErrorLabel.Text = "Sorry, there are errors in your WordPress"; 36 | end 37 | else 38 | if contains(cmdout, '"id":') 39 | % cmdout is JSON 40 | json = jsondecode(cmdout); 41 | mlxUrl = json.source_url; 42 | mlxId = json.id; 43 | fprintf(logfid, "JSON Output detected, ID is: %d\n", mlxId); 44 | else 45 | mlxUrl = string(loc) + 'wp-json/wp/v2/media?search=' + string(mlxName); 46 | fprintf(logfid, "Need to find MLX via search\MLX Orig URL: %s\n", mlxUrl); 47 | uploadedMlx = webread(mlxUrl); 48 | if ~isequaln(uploadedMlx, []) 49 | fprintf(logfid, "Uploaded MLX: %s\n", uploadedMLX); 50 | mlxId = uploadedMlx.id; 51 | fprintf(logfid, "Uploaded MLX Id: %s\n", mlxId); 52 | disp(mlxId); 53 | mlxUrl = uploadedMlx.source_url; 54 | else 55 | date = datetime('now', 'Format', 'yyyy/MM/'); 56 | fprintf('Live Script "%s" upload failed, please upload Live Script in WordPress directly. \n', mlxName); 57 | mlxId = datetime('now', 'Format', 'yyyyMMdd'); 58 | mlxUrl = string(loc) + 'wp-content/uploads/' + string(date) + string(mlxName); 59 | end 60 | end 61 | 62 | bodyHtml = dir("article_body.html").name; 63 | %downLoadSetting = app.EnableDownLoadCheckBox.Value; 64 | if downLoadSetting 65 | fprintf('Adding Button to download MLX file at URL: %s\n', mlxUrl); 66 | mlxButton = sprintf('', mlxUrl); 67 | str = convertCharsToStrings(fileread(bodyHtml)) + mlxButton; 68 | else 69 | fprintf('NOT adding button to download MLX file.\n'); 70 | str = convertCharsToStrings(fileread(bodyHtml)); 71 | end 72 | allMlx(mlxName) = string(mlxId); 73 | save mlxKey.mat allMlx; 74 | fileid = fopen("article_body.html", 'wb'); 75 | fwrite(fileid, str, 'char'); 76 | fclose(fileid); 77 | end 78 | fprintf('Live script is uploaded to WordPress. \n'); 79 | else 80 | if downLoadSetting 81 | fprintf('Request to make MLX uploadable, but could not find %s\n',... 82 | expectedLocation); 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /publishing-tool/+wpfunc/sendImgToWp.m: -------------------------------------------------------------------------------- 1 | function sendImgToWp(app, loc, auth, token, env, logfid) 2 | 3 | imgKey = dir("imgKey.mat"); 4 | if ~isempty(imgKey) 5 | fprintf('Images already sent once. Regenerate before sending again.\n'); 6 | return; 7 | end 8 | 9 | imgs = dir("*.png"); 10 | if ~isempty(imgs) 11 | date = datetime('now', 'Format', 'yyyy/MM/'); 12 | M = containers.Map({'imgName'}, {'id'}); 13 | bodyHtml = dir("article_body.html").name; 14 | str = convertCharsToStrings(fileread(bodyHtml)); 15 | for index = 1:length(imgs) 16 | imgName = string(imgs(index).name); 17 | imgDir = string(pwd) + filesep + imgName; 18 | fprintf("Image: %s\n", imgDir); 19 | fprintf(logfid, "\n ** Image: %s\n", imgDir); 20 | endPoint = string(loc) + 'wp-json/wp/v2/media/'; 21 | cmd = sprintf('curl --location --request POST "%s" --header "Content-Disposition: attachment;filename=%s" --header "Authorization: %s %s" --header "Content-Type: image/png" --data-binary "@%s"', endPoint, imgName, auth, token, imgDir); 22 | fprintf(logfid, "Upload Image Cmd: %s\n", cmd); 23 | 24 | [~,cmdout] = wpfunc.clientRequest(cmd, env); 25 | fprintf(logfid, "--- output ---\n%s\n--- end ---\n", cmdout); 26 | 27 | if contains(cmdout, "incorrect_password") 28 | fprintf('Sorry, your WordPress password is not correct in token \n'); 29 | if ~isempty(app) 30 | app.ErrorLabel.Text = "Sorry, your WordPress password is not correct in token"; 31 | end 32 | elseif contains(cmdout, "error", "IgnoreCase", true) 33 | fprintf('Sorry, there are errors in your WordPress \n'); 34 | if ~isempty(app) 35 | app.ErrorLabel.Text = "Sorry, there are errors in your WordPress"; 36 | end 37 | else 38 | if contains(cmdout, '"id":') 39 | imgId = string(extractBetween(cmdout, '"id":', ',"date"')); 40 | fprintf(logfid, "Command out had ID: %s\n", imgId); 41 | imgUrl = string(loc) + "wp-content/uploads/" + string(date) + string(imgName); 42 | fprintf(logfid, "Image Orig URL: %s\n", imgUrl); 43 | imgNewUrl = string(extractBetween(cmdout, ',"raw":"', '"},"modified"')); 44 | fprintf(logfid, "Image New URL: %s\n", imgNewUrl); 45 | imgNewUrl = erase(imgNewUrl, "\"); 46 | str = strrep(str, imgUrl, imgNewUrl); 47 | M(imgName) = string(imgId); 48 | else 49 | imgUrl = string(loc) + 'wp-json/wp/v2/media?search=' + string(imgName); 50 | fprintf(logfid, "Need to find IMG via search\nImage Orig URL: %s\n", imgUrl); 51 | uploadedImg = webread(imgUrl); 52 | if ~isequaln(uploadedImg, []) 53 | fprintf(logfid, "Uploaded Image: %s\n", uploadedImg); 54 | imgId = uploadedImg.id; 55 | fprintf(logfid, "Uploaded Image Id: %d\n", imgId); 56 | imgNewUrl = uploadedImg.source_url; 57 | imgUrl = string(loc) + "wp-content/uploads/" + string(date) + string(imgName); 58 | str = strrep(str, imgUrl, imgNewUrl); 59 | M(imgName) = string(imgId); 60 | else 61 | fprintf('Image "%s" upload failed, please upload image in WordPress directly. \n', imgName); 62 | imgId = datetime('now', 'Format', 'yyyyMMdd'); 63 | imgNewUrl = string(loc) + 'files/' + string(date) + string(imgName); 64 | imgUrl = string(loc) + "wp-content/uploads/" + string(date) + string(imgName); 65 | str = strrep(str, imgUrl, imgNewUrl); 66 | M(imgName) = string(imgId); 67 | end 68 | end 69 | end 70 | end 71 | fileid = fopen("article_body.html", 'wb'); 72 | fwrite(fileid, str, 'char'); 73 | fclose(fileid); 74 | save imgKey.mat M; 75 | fprintf('Images are uploaded to WordPress. \n'); 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Publishing tool for MATLAB® live script to WordPress 3 | [![View on File Exchange](https://www.mathworks.com/matlabcentral/images/matlab-file-exchange.svg)](https://www.mathworks.com/matlabcentral/fileexchange/103730-publishing-tool-for-matlab-live-script-to-wordpress) 4 | 5 | This MATLAB® App provides a fast and easy way for users to publish their MATLAB® live scripts as blog posts to their WordPress sites. What the users type in live script is what the users will see in WordPress. 6 | The App will: 7 | 1. Keeps all the styles in the live script as well as outputs such as graphs and tables and convert entire live script into HTML markup and send to WordPress via WordPress JSON API. 8 | 2. All the images in the live script will be converted to media files and uploaded to WordPress automatically. 9 | 3. The animation output will be automatically converted into GIF files and uploaded to WordPress automatically. 10 | 4. All the equations and formulas will be rendered nicely via [MathJax](https://www.mathjax.org/) in WordPress. 11 | 5. Users also have the option to let the app upload their original live script as attachment for readers to download or not from their WordPress site. 12 | 13 | ## Setup 14 | ### Prerequisite 15 | - Be connected to internet 16 | ### MathWorks® Products ([https://www.mathworks.com](https://www.mathworks.com)) 17 | - Requires MATLAB® release R2020a or newer 18 | ### 3rd Party Products: 19 | - WordPress V4.7 and above with WP JSON API enabled 20 | - Update the setting of permalink to not be **plain** 21 | - Have [JWT Authentication for WP REST API](https://wordpress.org/plugins/jwt-authentication-for-wp-rest-api/) (preferred) or [Basic Authentication](https://github.com/WP-API/Basic-Auth) installed and configured in your WordPress site 22 | 23 | ## Deployment Steps 24 | ### MATLAB® 25 | 26 | 1. Get **wp_publisher.mlapp** and **convertScript.p** in your workspace 27 | 28 | ![enter image description here](https://github.com/mathworks/WordPress_Publishing_Tool/blob/master/images/deployment.png) 29 | 2. Add wp_publisher MATLAB® App directory into your MATLAB® path permanently 30 | 31 | ![enter image description here](https://www.mathworks.com/matlabcentral/answers/uploaded_files/227308/Untitled.png) 32 | 33 | ### WordPress 34 | 35 | 1. Upload **live-script-support** to your WordPress plugin (/wp-content/plugins) 36 | 2. Activate **Live Script Support** plugin: 37 | 38 | ![enter image description here](https://github.com/mathworks/WordPress_Publishing_Tool/blob/master/images/plugin.png) 39 | 40 | ## Getting Started 41 | According to your habits, you can create a folder for all your blog post live scripts, or a folder for all your blog post live scripts for a particular year (e.g. 2021_blog_posts). 42 | #### Note: If you are not in your blog post folder, please close the publishing tool, go to your blog post folder with live scripts and reopen the App. 43 | 44 | ### First time user 45 | - Go to your blog post folder with live scripts 46 | - In MATLAB® Command Window, simply type `wp_publisher`: 47 | 48 | ![enter image description here](https://github.com/mathworks/WordPress_Publishing_Tool/blob/master/images/blogs.png) 49 | 50 | - The publishing tool will be opened and lead you to the **settings** tab, where you can input your WordPress blog site information. 51 | - You also can choose the location to store the output files for your blog post live script 52 | - Choose your installed WordPress API authentication 53 | - Save your settings and your blog information will be saved in your workspace 54 | 55 | ![enter image description here](https://github.com/mathworks/WordPress_Publishing_Tool/blob/master/images/blog_setting.png) 56 | 57 | - Switch to **Publish post** tab, you will see a dropdown menu to choose the live script you want to post as article to your WordPress blog 58 | 59 | ![enter image description here](https://github.com/mathworks/WordPress_Publishing_Tool/blob/master/images/post.png) 60 | 61 | 62 | - You can check "**Allow your readers to download your source Live Script**" and your live script will be uploaded to your WordPress media library for users to download 63 | - Once you finish choosing your blog post, click **Publish draft** button, your live script will be posted to your WordPress as a draft. 64 | - The link to the draft of your post will be displayed in your MATLAB® Command Window (you need to log in your WordPress to see the draft) 65 | 66 | ![enter image description here](https://github.com/mathworks/WordPress_Publishing_Tool/blob/master/images/after_post.png) 67 | 68 | - You can preview the post, once you are happy about the post, you can then publish it. 69 | 70 | ### Return user 71 | - Go to your blog post folder with live scripts 72 | - In MATLAB® Command Window, simply type `wp_publisher`: 73 | - The publishing tool will be opened and lead you to the **Publish post** tab, you will see a dropdown menu to choose the live script you want to post as article to your WordPress blog 74 | - You can check "**Allow your readers to download your source Live Script**" and your live script will be uploaded to your WordPress media library for users to download 75 | - Once you finish choosing your blog post, click **Publish draft** button, your live script will be posted to your WordPress as a draft. 76 | - The link to the draft of your post will be displayed in your MATLAB® Command Window (you need to log in your WordPress to see the draft) 77 | - You can preview the post, once you are happy about the post, you can then publish it. 78 | 79 | ## Note 80 | 81 | - If you are not in your blog post folder, please close the publishing tool, go to your blog post folder with live scripts and reopen the App. 82 | - Once you click **Publish draft** button, the App will create a folder named as your live script to store the information of the article and images from your live script 83 | - To avoid additional formatting by WordPress Editor, please select 'No Character Encoding' value at the bottom of the editor 84 | ![enter image description here](https://github.com/mathworks/WordPress_Publishing_Tool/blob/master/images/no_encoding.png) 85 | - You can update your WordPress settings in the App whenever your are using the App 86 | - In your live script of blog post, we suggest you add your article title so the publishing tool will know what's your blog post title. If you did not add title in your live script, the publishing tool will add a placeholder title for your blog post, you can modify it later in your blog draft. 87 | - Once the live script is published to your WordPress site by the App and you want to make some editing on the article, instead updating directly in your WordPress, we'd suggest you edit your article in your MATLAB® live scripts and use the App again to keep content consistent. The App will know the post information from the output folder: 88 | 89 | ![enter image description here](https://github.com/mathworks/WordPress_Publishing_Tool/blob/master/images/update.png) 90 | 91 | 92 | ## License 93 | 94 | The license is available in the [License file](https://github.com/mathworks/WordPress_Publishing_Tool/blob/master/license.txt) within this repository. 95 | 96 | ## [](#community-support)Community Support 97 | 98 | [MATLAB Central](https://www.mathworks.com/matlabcentral) 99 | 100 | Copyright 2021-2024 The MathWorks, Inc. 101 | --------------------------------------------------------------------------------