', '');
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, "
');
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, "
");
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 | [](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 | 
29 | 2. Add wp_publisher MATLAB® App directory into your MATLAB® path permanently
30 |
31 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------