Desktop: OneNote importer: Fix missing content in imported notebooks, improve math formula import (#13829)

This commit is contained in:
Henry Heino
2025-12-08 01:59:58 -08:00
committed by GitHub
parent a34010ef62
commit 9709721a73
19 changed files with 688 additions and 187 deletions

Binary file not shown.

View File

@@ -226,7 +226,7 @@ describe('InteropService_Importer_OneNote', () => {
const noteToTest = notes.find(n => n.title === 'Tips from a Pro Using Trees for Dramatic Landscape Photography');
expectWithInstructions(noteToTest).toBeTruthy();
expectWithInstructions(noteToTest.body.includes('<a href="onenote:https://d.docs.live.net/c8d3bbab7f1acf3a/Documents/Photography/风景.one#Tips%20from%20a%20Pro%20Using%20Trees%20for%20Dramatic%20Landscape%20Photography&section-id={262ADDFB-A4DC-4453-A239-0024D6769962}&page-id={88D803A5-4F43-48D4-9B16-4C024F5787DC}&end" style="">Tips from a Pro: Using Trees for Dramatic Landscape Photography</a>')).toBe(true);
expectWithInstructions(noteToTest.body).toContain('<a href="onenote:https://d.docs.live.net/c8d3bbab7f1acf3a/Documents/Photography/%E9%A3%8E%E6%99%AF.one#Tips%20from%20a%20Pro%20Using%20Trees%20for%20Dramatic%20Landscape%20Photography&section-id={262ADDFB-A4DC-4453-A239-0024D6769962}&page-id={88D803A5-4F43-48D4-9B16-4C024F5787DC}&end" style="">Tips from a Pro: Using Trees for Dramatic Landscape Photography</a>');
});
it('should render links properly by ignoring wrongly set indices when the first character is a hyperlink marker', async () => {
@@ -307,4 +307,14 @@ describe('InteropService_Importer_OneNote', () => {
expect(markdown).toMatchSnapshot('Test Todo: As Markdown');
});
it('should correctly import math formulas', async () => {
const notes = await importNote(`${supportDir}/onenote/Math.one`);
const importedNote = notes.find(n => n.title.startsWith('Math'));
const converter = new HtmlToMd();
const markdown = converter.parse(importedNote.body);
expect(markdown).toMatchSnapshot('Math');
});
});

View File

@@ -155,6 +155,44 @@ jeudi 23 octobre 2025
- [x] Documenter configuration synchro JBS saml pour un utilisateur (case cochée)"
`;
exports[`InteropService_Importer_OneNote should correctly import math formulas: Math 1`] = `
" Math
Math
Friday, November 28, 2025
2:47 PM
Cauchy's Integral Formula: $\\def\\∫#1#2#3{\\int_{#1}^{#2}{#3}}\\def\\parens#1{\\left( {#1} \\right)}𝑓\\parens{𝑥}=\\∫{𝛾}{}{\\frac{𝑓(𝑧)}{𝑧𝑥}𝑑𝑧}$
Pythagorean Theorem: $\\def\\pow#1#2{{#1}^{#2}}\\pow{𝑎}{2}+\\pow{𝑏}{2}=\\pow{𝑐}{2}$
Law of Cosines: $\\def\\pow#1#2{{#1}^{#2}}\\def\\fnCall#1#2{{ \\rm #1 }\\ {#2}}\\pow{𝑎}{2}+\\pow{𝑏}{2}=\\pow{𝑐}{2}+2𝑎𝑏\\fnCall{cos}{𝐶}$
Euler's Formula: $\\def\\pow#1#2{{#1}^{#2}}\\def\\fnCall#1#2{{ \\rm #1 }\\ {#2}}\\pow{𝑒}{𝑖𝜃}=\\fnCall{cos}{𝜃}+𝑖\\fnCall{sin}{𝜃}$
Determinant of a 2x2 matrix: $\\def\\parens#1{\\left( {#1} \\right)}\\def\\unknown#1{\\textsf{Unknown}(#1)}\\parens{\\unknown{20}{𝑎}{𝑏}{𝑐}{𝑑}}=𝑎𝑑𝑏𝑐$
Empty formula: $\\mathrm{Type equation here.}$
Fractions: $\\frac{𝑎}{𝑏}, \\frac{𝑎}{𝑏},\\mathrm{\\frac{𝑎}{𝑏}}, \\frac{𝑎}{𝑏}$.
Summation: $\\def\\∑#1#2#3{\\sum_{#1}^{#2}{#3}}\\def\\withSubscript#1#2{{#1}_{#2}}\\def\\fnCall#1#2{{ \\rm #1 }\\ {#2}}\\fnCall{\\withSubscript{lim}{𝑛→∞}}{\\∑{𝑘=0}{𝑛 }{\\frac{1}{𝑘}}}=∞$.
Definite integral: $\\def\\∫#1#2#3{\\int_{#1}^{#2}{#3}}\\∫{0}{1}{𝑥}𝑥=\\frac{1}{2}$
&nbsp;
Integral identities (see https://en.wikipedia.org/wiki/Lists_of_integrals), below, $𝐶∈ℝ$:
- $\\def\\parens#1{\\left( {#1} \\right)}\\def\\fnCall#1#2{{ \\rm #1 }\\ {#2}}∫\\mathrm{\\frac{1}{𝑥}𝑑𝑥}=\\fnCall{ln}{\\parens{𝑥}}+𝐶$.
- $\\def\\pow#1#2{{#1}^{#2}}∫\\pow{𝑒}{𝑎𝑥}𝑑𝑥=\\frac{1}{𝑎}\\pow{𝑒}{𝑎𝑥}+𝐶$
- $\\def\\parens#1{\\left( {#1} \\right)}\\def\\fnCall#1#2{{ \\rm #1 }\\ {#2}}∫\\parens{\\fnCall{sin}{𝑥}}𝑑𝑥=\\fnCall{cos}{𝑥}+𝐶$
&nbsp;"
`;
exports[`InteropService_Importer_OneNote should expect notes to be rendered the same: A page can have any width it wants 1`] = `
"<!DOCTYPE HTML>
<html lang="en"><head>
@@ -304,7 +342,7 @@ exports[`InteropService_Importer_OneNote should expect notes to be rendered the
</div></div><div class="container-outline" style="left: 48px; position: absolute; top: 115px; width: 624px;"><div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 10.5pt; padding-bottom: 7px; padding-top: 7px;">Suspendisse vitae odio nibh. Etiam fringilla mattis dapibus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Fusce vel ultricies ligula. Sed a nunc ante. Praesent suscipit fermentum magna. Aliquam convallis porttitor lacus ac posuere. Vestibulum maximus leo vel tortor condimentum, et tristique leo maximus. Nulla elementum, augue eu sollicitudin tempus, arcu ex lacinia enim, ut posuere lectus libero non eros. Vestibulum a libero leo. Donec id leo commodo, ornare ante ac, molestie tellus. Aenean a neque quis turpis euismod porta. Quisque vulputate augue vitae orci accumsan, a lobortis leo luctus. Nunc sodales sapien vitae lacus faucibus hendrerit. In ac lacinia diam.</p></div>
<div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt; padding-bottom: 7px; padding-top: 7px;"><span style="font-family: Calibri; font-size: 14pt;">Nam tempor urna eget posuere mollis. Aliquam erat volutpat. Sed ipsum massa, dictum eget sagittis id, fermentum a justo. Vivamus in iaculis libero. Pellentesque malesuada felis dictum turpis placerat, at ultrices justo viverra. Praesent nisi lectus, tincidunt ut tellus in, convallis euismod urna. Phasellus molestie porttitor odio vitae efficitur. Curabitur vulputate congue tincidunt. Fusce mattis orci at porttitor fermentum. Cras eu placerat odio. Fusce eu tortor sit amet massa pretium efficitur. Nam consequat, mauris at blandit placerat, est sapien feugiat felis, quis imperdiet sapien neque in justo. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Phasellus vestibulum rhoncus dolor, ut ullamcorper purus scelerisque eu. Integer sem felis, pellentesque in rutrum id, porta a ante.</span><span style="font-family: Calibri; font-size: 18pt;">Vivamus finibus imperdiet massa, at interdum turpis rhoncus et. Phasellus leo nibh, mattis vel tortor at, gravida finibus felis. Donec bibendum enim euismod, dignissim ipsum eu, laoreet nisl. Ut auctor sollicitudin eros dictum gravida.</span><span style="font-family: Calibri; font-size: 14pt;"> Vestibulum pellentesque, ex quis vulputate efficitur, dolor metus efficitur nisl, id elementum mi nulla sit amet orci. Nam odio sem, bibendum at hendrerit finibus, vestibulum vitae dolor. In hac habitasse platea dictumst. Curabitur et ligula elit. Donec vulputate, diam non gravida efficitur, mi odio imperdiet ipsum, nec rhoncus mi nibh non magna.</span></p></div>
<div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 10.5pt; padding-bottom: 7px; padding-top: 7px;">Suspendisse varius enim vel odio congue sodales. Integer sit amet nisi sagittis, dapibus mi ut, tincidunt magna. Duis posuere est felis, et rhoncus magna volutpat a. Nullam tempor dignissim suscipit. Vestibulum cursus felis vitae libero pulvinar molestie. Donec at metus eget arcu blandit tincidunt. Donec purus felis, malesuada ac egestas eu, interdum sed erat. Praesent nec accumsan orci. Nunc bibendum rutrum erat, vel luctus odio. Pellentesque iaculis gravida arcu, eu consequat turpis congue sit amet. Interdum et malesuada fames ac ante ipsum primis in faucibus. Duis eget urna vel erat aliquet fringilla. Praesent vel luctus ligula, nec viverra nisl. Sed ac sem consectetur, sodales ante sodales, feugiat arcu.</p></div>
<div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt;"><span style="color: rgb(66,66,67); font-family: Calibri; font-size: 10pt;">It was a bright cold day in April, and the clocks were striking thirteen. Winston Smith, his chin nuzzled into his breast in an effort to escape the vile wind, slipped quickly through the glass doors of Victory Mansions, though not quickly enough to prevent a swirl of gritty dust from entering along with him. </span><span style="color: rgb(66,66,67); font-family: Calibri; font-size: 10pt;"><br></span><span style="color: rgb(66,66,67); font-family: Calibri; font-size: 10pt;"><br></span><span style="color: rgb(66,66,67); font-family: Calibri; font-size: 10pt;">The hallway smelt of boiled cabbage and old rag mats. At one end of it a coloured poster, too large for indoor display, had been tacked to the wall. It depicted simply an enormous face, more than a metre wide: the face of a man of about forty-five, with a heavy black moustache and ruggedly handsome features. Winston made for the stairs. It was no use trying the lift. Even at the best of times it was seldom working, and at present the electric current was cut off during daylight hours. It was part of the economy drive in preparation for Hate Week. The flat was seven flights up, and Winston, who was thirty-nine and had a varicose ulcer above his right ankle, went slowly, resting several times on the way. On each landing, opposite the lift-shaft, the poster with the enormous face gazed from the wall. It was one of those pictures which are so contrived that the eyes follow you about when you move. BIG BROTHER IS WATCHING YOU, the caption beneath it ran. </span><span style="color: rgb(66,66,67); font-family: Calibri; font-size: 10pt;"><br></span><span style="color: rgb(66,66,67); font-family: Calibri; font-size: 10pt;"><br></span><span style="color: rgb(66,66,67); font-family: Calibri; font-size: 10pt;">Inside the flat a fruity voice was reading out a list of figures which had something to do with the production of pig-iron. The voice came from an oblong metal plaque like a dulled mirror which formed part of the surface of the right-hand wall. Winston turned a switch and the voice sank somewhat, though the words were still distinguishable. The instrument (the telescreen, it was called) could be dimmed, but there was no way of shutting it off completely. He moved over to the window: a smallish, frail figure, the meagreness of his body merely emphasized by the blue overalls which were the uniform of the party. His hair was very fair, his face naturally sanguine, his skin roughened by coarse soap and blunt razor blades and the cold of the winter that had just ended. </span><span style="color: rgb(66,66,67); font-family: Calibri; font-size: 10pt;"><br></span><span style="color: rgb(66,66,67); font-family: Calibri; font-size: 10pt;"><br></span></p></div>
<div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt;"><span style="color: rgb(66,66,67); font-family: Calibri; font-size: 10pt;">It was a bright cold day in April, and the clocks were striking thirteen. Winston Smith, his chin nuzzled into his breast in an effort to escape the vile wind, slipped quickly through the glass doors of Victory Mansions, though not quickly enough to prevent a swirl of gritty dust from entering along with him. </span><span style="color: rgb(66,66,67); font-family: Calibri; font-size: 10pt;"><br></span><span style="color: rgb(66,66,67); font-family: Calibri; font-size: 10pt;"><br></span><span style="color: rgb(66,66,67); font-family: Calibri; font-size: 10pt;">The hallway smelt of boiled cabbage and old rag mats. At one end of it a coloured poster, too large for indoor display, had been tacked to the wall. It depicted simply an enormous face, more than a metre wide: the face of a man of about forty-five, with a heavy black moustache and ruggedly handsome features. Winston made for the stairs. It was no use trying the lift. Even at the best of times it was seldom working, and at present the electric current was cut off during daylight hours. It was part of the economy drive in preparation for Hate Week. The flat was seven flights up, and Winston, who was thirty-nine and had a varicose ulcer above his right ankle, went slowly, resting several times on the way. On each landing, opposite the lift-shaft, the poster with the enormous face gazed from the wall. It was one of those pictures which are so contrived that the eyes follow you about when you move. BIG BROTHER IS WATCHING YOU, the caption beneath it ran. </span><span style="color: rgb(66,66,67); font-family: Calibri; font-size: 10pt;"><br></span><span style="color: rgb(66,66,67); font-family: Calibri; font-size: 10pt;"><br></span><span style="color: rgb(66,66,67); font-family: Calibri; font-size: 10pt;">Inside the flat a fruity voice was reading out a list of figures which had something to do with the production of pig-iron. The voice came from an oblong metal plaque like a dulled mirror which formed part of the surface of the right-hand wall. Winston turned a switch and the voice sank somewhat, though the words were still distinguishable. The instrument (the telescreen, it was called) could be dimmed, but there was no way of shutting it off completely. He moved over to the window: a smallish, frail figure, the meagreness of his body merely emphasized by the blue overalls which were the uniform of the party. His hair was very fair, his face naturally sanguine, his skin roughened by coarse soap and blunt razor blades and the cold of the winter that had just ended. </span><span style="color: rgb(66,66,67); font-family: Calibri; font-size: 10pt;"><br></span><span style="color: rgb(66,66,67); font-family: Calibri; font-size: 10pt;"><br></span>Outside, even through the shut window-pane, the world looked cold. Down in the street little eddies of wind were whirling dust and torn paper into spirals, and though the sun was shining and the sky a harsh blue, there seemed to be no colour in anything, except the posters that were plastered everywhere. The blackmoustachio'd face gazed down from every commanding corner. There was one on the house-front immediately opposite. BIG BROTHER IS WATCHING YOU, the caption said, while the dark eyes looked deep into Winston's own. Down at streetlevel another poster, torn at one corner, flapped fitfully in the wind, alternately covering and uncovering the single word INGSOC. In the far distance a helicopter skimmed down between the roofs, hovered for an instant like a bluebottle, and darted away again with a curving flight. It was the police patrol, snooping into people's windows. The patrols did not matter, however. Only the Thought Police mattered. <br><br>Behind Winston's back the voice from the telescreen was still babbling away about pig-iron and the overfulfilment of the Ninth Three-Year Plan. The telescreen received and transmitted simultaneously. Any sound that Winston made, above the level of a very low whisper, would be picked up by it, moreover, so long as he remained within the field of vision which the metal plaque commanded, he could be seen as well as heard. There was of course no way of knowing whether you were being watched at any given moment. How often, or on what system, the Thought Police plugged in on any individual wire was guesswork. It was even conceivable that they watched everybody all the time. But at any rate they could plug in your wire whenever they wanted to. You had to live -- did live, from habit that became instinct -- in the assumption that every sound you made was overheard, and, except in darkness, every movement scrutinized. </p></div>
</div><img style="height: 6px; left: 49px; overflow: visible; position: absolute; top: 127px; width: 72px;" src=":/204"><img style="height: 16px; left: 465px; overflow: visible; position: absolute; top: 227px; width: 17px;" src=":/205"><img style="height: 7px; left: 49px; overflow: visible; position: absolute; top: 262px; width: 37px;" src=":/206"><img style="height: 9px; left: 146px; overflow: visible; position: absolute; top: 521px; width: 82px;" src=":/207"><img style="height: 17px; left: 175px; overflow: visible; position: absolute; top: 713px; width: 28px;" src=":/208"><img style="height: 6px; left: 46px; overflow: visible; position: absolute; top: 883px; width: 12px;" src=":/209"><img style="height: 12px; left: 157px; overflow: visible; position: absolute; top: 1518px; width: 17px;" src=":/210">
<script>
@@ -788,17 +826,17 @@ exports[`InteropService_Importer_OneNote should ignore broken characters at the
<div class="title" style="left: 48px; position: absolute; top: 24px;"><div class="container-outline" style="width: 624px;"><div class="outline-element" style="margin-left: 0px;"><span style="font-family: Calibri Light; font-size: 20pt;">Action research - Wikipedia</span></div>
</div><div class="container-outline"><div class="outline-element" style="margin-left: 0px;"><span style="color: rgb(128,128,128); font-family: Calibri; font-size: 10pt;">Monday, May 27, 2019</span></div>
<div class="outline-element" style="margin-left: 0px;"><span style="color: rgb(128,128,128); font-family: Calibri; font-size: 10pt;">12:13 PM</span></div>
</div></div><div class="container-outline" style="left: 48px; position: absolute; top: 120px; width: 624px;"><div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt; padding-bottom: 7px; padding-top: 7px;"><span style="font-family: Verdana; font-size: 12pt;">Clipped from: </span><a href="https://en.wikipedia.org/wiki/Action_research#Action_research_in_organization_development" style="">https://en.wikipedia.org/wiki/Action_research#Action_research_in_organization_development</a></p></div>
</div></div><div class="container-outline" style="left: 48px; position: absolute; top: 120px; width: 624px;"><div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt; padding-bottom: 7px; padding-top: 7px;"><span style="font-family: Verdana; font-size: 12pt;">Clipped from: </span><a href="https://en.wikipedia.org/wiki/Action_research#Action_research_in_organization_development" style="font-family: Verdana; font-size: 12pt;">https://en.wikipedia.org/wiki/Action_research#Action_research_in_organization_development</a></p></div>
<div class="outline-element" style="margin-left: 0px;"><img src=":/5" style="max-height: -48px; max-width: -48px;" /></div>
<div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt; padding-bottom: 16px; padding-top: 7px;"><span style="font-family: Verdana; font-size: 12pt;">569 revisions since </span><a href="https://en.wikipedia.org/wiki/Special%3APermaLink%2F2264241" style="">2003-05-19</a><span style="font-family: Verdana; font-size: 12pt;"> (</span><a href="https://en.wikipedia.org/wiki/Special%3ADiff%2F898227450" style="">+5 days</a><span style="font-family: Verdana; font-size: 12pt;">), 328 editors, 90 watchers, </span><a href="https://tools.wmflabs.org/pageviews?project=en.wikipedia.org&pages=Action%20research&range=latest-30" style="">18,937 pageviews</a><span style="font-family: Verdana; font-size: 12pt;"> (30 days), created by: </span><a href="https://en.wikipedia.org/wiki/User:Thseamon" style="">Thseamon</a><span style="font-family: Verdana; font-size: 12pt;"> (</span><a href="http://xtools.wmflabs.org/ec/en.wikipedia.org/Thseamon" style="">762</a><span style="font-family: Verdana; font-size: 12pt;">) · </span><a href="http://xtools.wmflabs.org/articleinfo/en.wikipedia.org/Action%20research" style="">See full page statistics</a><span style="font-family: Verdana; font-size: 12pt;"> </span></p></div>
<div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt; padding-bottom: 16px; padding-top: 7px;"><a href="https://en.wikipedia.org/wiki/Action_research#mw-head" style="">Jump to navigation</a><span style="font-family: Verdana; font-size: 12pt;"> </span><a href="https://en.wikipedia.org/wiki/Action_research#p-search" style="">Jump to search</a><span style="font-family: Verdana; font-size: 12pt;"> </span></p></div>
<div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt; padding-bottom: 16px; padding-top: 7px;"><span style="font-family: Verdana; font-size: 12pt;">For the British charity formerly named Action Research, see </span><a href="https://en.wikipedia.org/wiki/Action_Medical_Research" style="">Action Medical Research</a><span style="font-family: Verdana; font-size: 12pt;">. For the academic journal titled Action Research, see </span><a href="https://en.wikipedia.org/wiki/Action_Research_(journal)" style="">Action Research (journal)</a><span style="font-family: Verdana; font-size: 12pt;">.</span></p></div>
<div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt; padding-bottom: 16px; padding-top: 7px;"><span style="font-family: Verdana; font-size: 12pt;">569 revisions since </span><a href="https://en.wikipedia.org/wiki/Special%3APermaLink%2F2264241" style="font-family: Verdana; font-size: 12pt;">2003-05-19</a><span style="font-family: Verdana; font-size: 12pt;"> (</span><a href="https://en.wikipedia.org/wiki/Special%3ADiff%2F898227450" style="font-family: Verdana; font-size: 12pt;">+5 days</a><span style="font-family: Verdana; font-size: 12pt;">), 328 editors, 90 watchers, </span><a href="https://tools.wmflabs.org/pageviews?project=en.wikipedia.org&pages=Action%20research&range=latest-30" style="font-family: Verdana; font-size: 12pt;">18,937 pageviews</a><span style="font-family: Verdana; font-size: 12pt;"> (30 days), created by: </span><a href="https://en.wikipedia.org/wiki/User:Thseamon" style="font-family: Verdana; font-size: 12pt;">Thseamon</a><span style="font-family: Verdana; font-size: 12pt;"> (</span><a href="http://xtools.wmflabs.org/ec/en.wikipedia.org/Thseamon" style="font-family: Verdana; font-size: 12pt;">762</a><span style="font-family: Verdana; font-size: 12pt;">) · </span><a href="http://xtools.wmflabs.org/articleinfo/en.wikipedia.org/Action%20research" style="font-family: Verdana; font-size: 12pt;">See full page statistics</a><span style="font-family: Verdana; font-size: 12pt;"> </span></p></div>
<div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt; padding-bottom: 16px; padding-top: 7px;"><a href="https://en.wikipedia.org/wiki/Action_research#mw-head" style="font-family: Verdana; font-size: 12pt;">Jump to navigation</a><span style="font-family: Verdana; font-size: 12pt;"> </span><a href="https://en.wikipedia.org/wiki/Action_research#p-search" style="font-family: Verdana; font-size: 12pt;">Jump to search</a><span style="font-family: Verdana; font-size: 12pt;"> </span></p></div>
<div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt; padding-bottom: 16px; padding-top: 7px;"><span style="font-family: Verdana; font-size: 12pt;">For the British charity formerly named Action Research, see </span><a href="https://en.wikipedia.org/wiki/Action_Medical_Research" style="font-family: Verdana; font-size: 12pt;">Action Medical Research</a><span style="font-family: Verdana; font-size: 12pt;">. For the academic journal titled Action Research, see </span><a href="https://en.wikipedia.org/wiki/Action_Research_(journal)" style="font-family: Verdana; font-size: 12pt;">Action Research (journal)</a><span style="font-family: Verdana; font-size: 12pt;">.</span></p></div>
<div class="outline-element" style="margin-left: 0px;"><table cellpadding="0" cellspacing="0" style="border-collapse: collapse;"><tr><td style="min-width: 48px; padding: 2pt; vertical-align: top;"><div class="outline-element" style="margin-left: 0px;"><img src=":/6" style="max-height: 39px; max-width: 50px;" /></div>
</td><td style="min-width: 48px; padding: 2pt; vertical-align: top;"><div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt;"><span style="font-family: Verdana; font-size: 12pt;">This article </span><span style="font-family: Verdana; font-size: 12pt; font-weight: bold;">needs additional citations for </span><a href="https://en.wikipedia.org/wiki/Wikipedia:Verifiability" style="">verification</a><span style="font-family: Verdana; font-size: 12pt; font-weight: bold;">. Please help </span><a href="https://en.wikipedia.org/w/index.php?title=Action_research&action=edit" style="">improve this article</a><span style="font-family: Verdana; font-size: 12pt; font-weight: bold;"> by </span><a href="https://en.wikipedia.org/wiki/Help:Introduction_to_referencing_with_Wiki_Markup/1" style="">adding citations to reliable sources</a><span style="font-family: Verdana; font-size: 12pt; font-weight: bold;">. Unsourced material may be challenged and removed.</span><span style="font-family: Verdana; font-size: 12pt; font-weight: bold;"><br></span><span style="font-family: Verdana; font-size: 12pt; font-weight: bold;">Find sources:</span><span style="font-family: Verdana; font-size: 12pt;"> </span><a href="https://www.google.com/search?as_eq=wikipedia&q=%22Action+research%22" style="">"Action research"</a><span style="font-family: Verdana; font-size: 12pt;">  </span><a href="https://www.google.com/search?tbm=nws&q=%22Action+research%22+-wikipedia" style="">news</a><span style="font-family: Verdana; font-size: 12pt;"> </span><span style="font-family: Verdana; font-size: 12pt; font-weight: bold;">·</span><span style="font-family: Verdana; font-size: 12pt;"> </span><a href="https://www.google.com/search?&q=%22Action+research%22+site:news.google.com/newspapers&source=newspapers" style="">newspapers</a><span style="font-family: Verdana; font-size: 12pt;"> </span><span style="font-family: Verdana; font-size: 12pt; font-weight: bold;">·</span><span style="font-family: Verdana; font-size: 12pt;"> </span><a href="https://www.google.com/search?tbs=bks:1&q=%22Action+research%22+-wikipedia" style="">books</a><span style="font-family: Verdana; font-size: 12pt;"> </span><span style="font-family: Verdana; font-size: 12pt; font-weight: bold;">·</span><span style="font-family: Verdana; font-size: 12pt;"> </span><a href="https://scholar.google.com/scholar?q=%22Action+research%22" style="">scholar</a><span style="font-family: Verdana; font-size: 12pt;"> </span><span style="font-family: Verdana; font-size: 12pt; font-weight: bold;">·</span><span style="font-family: Verdana; font-size: 12pt;"> </span><a href="https://www.jstor.org/action/doBasicSearch?Query=%22Action+research%22&acc=on&wc=on" style="">JSTOR</a><span style="font-family: Verdana; font-size: 12pt;"> </span><span style="font-family: Verdana; font-size: 12pt; font-style: italic;">(May 2019)</span><span style="font-family: Verdana; font-size: 12pt; font-style: italic;"> (</span><a href="https://en.wikipedia.org/wiki/Help:Maintenance_template_removal" style="">Learn how and when to remove this template message</a><span style="font-family: Verdana; font-size: 12pt; font-style: italic;">)</span></p></div>
</td><td style="min-width: 48px; padding: 2pt; vertical-align: top;"><div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt;"><span style="font-family: Verdana; font-size: 12pt;">This article </span><span style="font-family: Verdana; font-size: 12pt; font-weight: bold;">needs additional citations for </span><a href="https://en.wikipedia.org/wiki/Wikipedia:Verifiability" style="font-family: Verdana; font-size: 12pt; font-weight: bold;">verification</a><span style="font-family: Verdana; font-size: 12pt; font-weight: bold;">. Please help </span><a href="https://en.wikipedia.org/w/index.php?title=Action_research&action=edit" style="font-family: Verdana; font-size: 12pt; font-weight: bold;">improve this article</a><span style="font-family: Verdana; font-size: 12pt; font-weight: bold;"> by </span><a href="https://en.wikipedia.org/wiki/Help:Introduction_to_referencing_with_Wiki_Markup/1" style="font-family: Verdana; font-size: 12pt; font-weight: bold;">adding citations to reliable sources</a><span style="font-family: Verdana; font-size: 12pt; font-weight: bold;">. Unsourced material may be challenged and removed.</span><span style="font-family: Verdana; font-size: 12pt; font-weight: bold;"><br></span><span style="font-family: Verdana; font-size: 12pt; font-weight: bold;">Find sources:</span><span style="font-family: Verdana; font-size: 12pt;"> </span><a href="https://www.google.com/search?as_eq=wikipedia&q=%22Action+research%22" style="font-family: Verdana; font-size: 12pt;">&quot;Action research&quot;</a><span style="font-family: Verdana; font-size: 12pt;">  </span><a href="https://www.google.com/search?tbm=nws&q=%22Action+research%22+-wikipedia" style="font-family: Verdana; font-size: 12pt;">news</a><span style="font-family: Verdana; font-size: 12pt;"> </span><span style="font-family: Verdana; font-size: 12pt; font-weight: bold;">·</span><span style="font-family: Verdana; font-size: 12pt;"> </span><a href="https://www.google.com/search?&q=%22Action+research%22+site:news.google.com/newspapers&source=newspapers" style="font-family: Verdana; font-size: 12pt;">newspapers</a><span style="font-family: Verdana; font-size: 12pt;"> </span><span style="font-family: Verdana; font-size: 12pt; font-weight: bold;">·</span><span style="font-family: Verdana; font-size: 12pt;"> </span><a href="https://www.google.com/search?tbs=bks:1&q=%22Action+research%22+-wikipedia" style="font-family: Verdana; font-size: 12pt;">books</a><span style="font-family: Verdana; font-size: 12pt;"> </span><span style="font-family: Verdana; font-size: 12pt; font-weight: bold;">·</span><span style="font-family: Verdana; font-size: 12pt;"> </span><a href="https://scholar.google.com/scholar?q=%22Action+research%22" style="font-family: Verdana; font-size: 12pt;">scholar</a><span style="font-family: Verdana; font-size: 12pt;"> </span><span style="font-family: Verdana; font-size: 12pt; font-weight: bold;">·</span><span style="font-family: Verdana; font-size: 12pt;"> </span><a href="https://www.jstor.org/action/doBasicSearch?Query=%22Action+research%22&acc=on&wc=on" style="font-family: Verdana; font-size: 12pt;">JSTOR</a><span style="font-family: Verdana; font-size: 12pt;"> </span><span style="font-family: Verdana; font-size: 12pt; font-style: italic;">(May 2019)</span><span style="font-family: Verdana; font-size: 12pt; font-style: italic;"> (</span><a href="https://en.wikipedia.org/wiki/Help:Maintenance_template_removal" style="font-family: Verdana; font-size: 12pt; font-style: italic;">Learn how and when to remove this template message</a><span style="font-family: Verdana; font-size: 12pt; font-style: italic;">)</span></p></div>
</td></tr></table></div>
<div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt; padding-bottom: 16px; padding-top: 7px;"><span style="font-family: Verdana; font-size: 12pt; font-weight: bold;"><br>Action research</span><span style="font-family: Verdana; font-size: 12pt;"> seeks transformative change through the simultaneous process of taking action and doing research, which are linked together by critical reflection.</span><a href="https://en.wikipedia.org/wiki/Kurt_Lewin" style="">Kurt Lewin</a><span style="font-family: Verdana; font-size: 12pt;">, then a professor at </span><a href="https://en.wikipedia.org/wiki/MIT" style="">MIT</a><span style="font-family: Verdana; font-size: 12pt;">, first coined the term "action research" in 1944. In his 1946 paper "Action Research and Minority Problems" he described action research as "a comparative research on the conditions and effects of various forms of social action and research leading to social action" that uses "a spiral of steps, each of which is composed of a circle of planning, action and fact-finding about the result of the action". </span></p></div>
<div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt; padding-bottom: 16px; padding-top: 7px;"><span style="font-family: Verdana; font-size: 12pt;">Action research practitioners reflect upon the consequences of their own questions, beliefs, assumptions, and practices with the goal of understanding, developing, and improving social practices.</span><a href="https://en.wikipedia.org/wiki/Action_research#cite_note-1" style="">[1]</a><span style="font-family: Verdana; font-size: 12pt; vertical-align: super;"> This action is designed to create three levels of change</span><a href="https://en.wikipedia.org/wiki/Action_research#cite_note-2" style="">[2]</a><span style="font-family: Verdana; font-size: 12pt; vertical-align: super;"> (1) self-change as the only subject of action research is the person who conducting the research. This person is seeking to be better understand the effects of their action in social settings and to engage in a process of living his or her's values. The second level is a collective process of understanding change in a classroom, office, community, organization or institution. Action research enlists others and works to create a democratic sharing of voice to achieve deeper understanding of collective actions</span><a href="https://en.wikipedia.org/wiki/Action_research#cite_note-3" style="">[3]</a><span style="font-family: Verdana; font-size: 12pt; vertical-align: super;">. Finally action research is process of sharing finding with the community of researchers. This can be done is many ways, in journals</span><a href="https://en.wikipedia.org/wiki/Action_research#cite_note-4" style="">[4]</a><span style="font-family: Verdana; font-size: 12pt; vertical-align: super;">, on websites, in books, videos or at conferences. The Social Publishers Foundation</span><a href="https://en.wikipedia.org/wiki/Action_research#cite_note-5" style="">[5]</a><span style="font-family: Verdana; font-size: 12pt; vertical-align: super;"> provides support for this action research process. </span></p></div>
<div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt; padding-bottom: 16px; padding-top: 7px;"><span style="font-family: Verdana; font-size: 12pt;"><br>Action research involves actively participating in a change situation, often via an existing organization, whilst simultaneously conducting research. Action research can also be undertaken by larger organizations or institutions, assisted or guided by professional researchers, with the aim of improving their strategies, practices and knowledge of the environments within which they practice. As designers and stakeholders, researchers work with others to propose a new course of action to help their community improve its work practices. Depending upon the nature of the people involved in the action research as well as the person(s) organizing it, there are different ways of describing action research</span><a href="https://en.wikipedia.org/wiki/Action_research#cite_note-6" style="">[6]</a></p></div><ul class="list-0" style="left: -10px; position: relative;"><li class="outline-element" style="margin-left: 36px;"><span style="font-family: Verdana; font-size: 12pt;">Collaborative Action Research</span></li>
<div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt; padding-bottom: 16px; padding-top: 7px;"><span style="font-family: Verdana; font-size: 12pt; font-weight: bold;">Action research</span><span style="font-family: Verdana; font-size: 12pt;"> seeks transformative change through the simultaneous process of taking action and doing research, which are linked together by critical reflection.</span><a href="https://en.wikipedia.org/wiki/Kurt_Lewin" style="font-family: Verdana; font-size: 12pt;">Kurt Lewin</a><span style="font-family: Verdana; font-size: 12pt;">, then a professor at </span><a href="https://en.wikipedia.org/wiki/MIT" style="font-family: Verdana; font-size: 12pt;">MIT</a><span style="font-family: Verdana; font-size: 12pt;">, first coined the term &quot;action research&quot; in 1944. In his 1946 paper &quot;Action Research and Minority Problems&quot; he described action research as &quot;a comparative research on the conditions and effects of various forms of social action and research leading to social action&quot; that uses &quot;a spiral of steps, each of which is composed of a circle of planning, action and fact-finding about the result of the action&quot;. </span></p></div>
<div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt; padding-bottom: 16px; padding-top: 7px;"><span style="font-family: Verdana; font-size: 12pt;">Action research practitioners reflect upon the consequences of their own questions, beliefs, assumptions, and practices with the goal of understanding, developing, and improving social practices.</span><a href="https://en.wikipedia.org/wiki/Action_research#cite_note-1" style="font-family: Verdana; font-size: 12pt; vertical-align: super;">[1]</a><span style="font-family: Verdana; font-size: 12pt; vertical-align: super;"> This action is designed to create three levels of change</span><a href="https://en.wikipedia.org/wiki/Action_research#cite_note-2" style="font-family: Verdana; font-size: 12pt; vertical-align: super;">[2]</a><span style="font-family: Verdana; font-size: 12pt; vertical-align: super;"> (1) self-change as the only subject of action research is the person who conducting the research. This person is seeking to be better understand the effects of their action in social settings and to engage in a process of living his or her&apos;s values. The second level is a collective process of understanding change in a classroom, office, community, organization or institution. Action research enlists others and works to create a democratic sharing of voice to achieve deeper understanding of collective actions</span><a href="https://en.wikipedia.org/wiki/Action_research#cite_note-3" style="font-family: Verdana; font-size: 12pt; vertical-align: super;">[3]</a><span style="font-family: Verdana; font-size: 12pt; vertical-align: super;">. Finally action research is process of sharing finding with the community of researchers. This can be done is many ways, in journals</span><a href="https://en.wikipedia.org/wiki/Action_research#cite_note-4" style="font-family: Verdana; font-size: 12pt; vertical-align: super;">[4]</a><span style="font-family: Verdana; font-size: 12pt; vertical-align: super;">, on websites, in books, videos or at conferences. The Social Publishers Foundation</span><a href="https://en.wikipedia.org/wiki/Action_research#cite_note-5" style="font-family: Verdana; font-size: 12pt; vertical-align: super;">[5]</a><span style="font-family: Verdana; font-size: 12pt; vertical-align: super;"> provides support for this action research process. </span></p></div>
<div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt; padding-bottom: 16px; padding-top: 7px;"><span style="font-family: Verdana; font-size: 12pt;">Action research involves actively participating in a change situation, often via an existing organization, whilst simultaneously conducting research. Action research can also be undertaken by larger organizations or institutions, assisted or guided by professional researchers, with the aim of improving their strategies, practices and knowledge of the environments within which they practice. As designers and stakeholders, researchers work with others to propose a new course of action to help their community improve its work practices. Depending upon the nature of the people involved in the action research as well as the person(s) organizing it, there are different ways of describing action research</span><a href="https://en.wikipedia.org/wiki/Action_research#cite_note-6" style="font-family: Verdana; font-size: 12pt; vertical-align: super;">[6]</a><span style="font-family: Verdana; font-size: 12pt; vertical-align: super;">. </span></p></div><ul class="list-0" style="left: -10px; position: relative;"><li class="outline-element" style="margin-left: 36px;"><span style="font-family: Verdana; font-size: 12pt;">Collaborative Action Research</span></li>
<li class="outline-element" style="margin-left: 36px;"><span style="font-family: Verdana; font-size: 12pt;">Participatory Action Research</span></li>
<li class="outline-element" style="margin-left: 36px;"><span style="font-family: Verdana; font-size: 12pt;">Community-Based Action Research</span></li>
<li class="outline-element" style="margin-left: 36px;"><span style="font-family: Verdana; font-size: 12pt;">Youth Action Research</span></li>
@@ -808,13 +846,13 @@ exports[`InteropService_Importer_OneNote should ignore broken characters at the
<li class="outline-element" style="margin-left: 36px;"><span style="font-family: Verdana; font-size: 12pt;">Action Science</span></li>
<li class="outline-element" style="margin-left: 36px;"><span style="font-family: Verdana; font-size: 12pt;">Living Theory Action Research</span></li>
</ul>
<div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt; padding-bottom: 16px; padding-top: 7px;"><span style="font-family: Verdana; font-size: 12pt;"><br>There are also a set of approaches that share some properties with action research but have some different practices</span><a href="https://en.wikipedia.org/wiki/Action_research#cite_note-7" style="">[7]</a></p></div><ul class="list-1" style="left: -10px; position: relative;"><li class="outline-element" style="margin-left: 36px;"><span style="font-family: Verdana; font-size: 12pt;">Appreciative Inquiry is a way of starting with what is working well and then using action research to improve it.</span></li>
<div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt; padding-bottom: 16px; padding-top: 7px;"><span style="font-family: Verdana; font-size: 12pt;">There are also a set of approaches that share some properties with action research but have some different practices</span><a href="https://en.wikipedia.org/wiki/Action_research#cite_note-7" style="font-family: Verdana; font-size: 12pt; vertical-align: super;">[7]</a><span style="font-family: Verdana; font-size: 12pt; vertical-align: super;">. These include: </span></p></div><ul class="list-1" style="left: -10px; position: relative;"><li class="outline-element" style="margin-left: 36px;"><span style="font-family: Verdana; font-size: 12pt;">Appreciative Inquiry is a way of starting with what is working well and then using action research to improve it.</span></li>
<li class="outline-element" style="margin-left: 36px;"><span style="font-family: Calibri; font-size: 11pt;"><span style="font-family: Verdana; font-size: 12pt; font-style: italic;">Lesson Study</span><span style="font-family: Verdana; font-size: 12pt;"> places the teaching of a shared lesson as the action and has a set of protocols for understanding the outcomes.</span></span></li>
<li class="outline-element" style="margin-left: 36px;"><span style="font-family: Calibri; font-size: 11pt;"><span style="font-family: Verdana; font-size: 12pt; font-style: italic;">Practitioner Research</span><span style="font-family: Verdana; font-size: 12pt;"> does not have to be action research, as practitioners can engage in any form of the many forms of research.</span></span></li>
<li class="outline-element" style="margin-left: 36px;"><span style="font-family: Calibri; font-size: 11pt;"><span style="font-family: Verdana; font-size: 12pt; font-style: italic;">Reflective Practice/Self Study</span><span style="font-family: Verdana; font-size: 12pt;"> is the first part of action research but does not require the practitioner to make the results public, to share the results of the learning with others.  Many of these approaches will be described in these resources.</span></span></li>
<li class="outline-element" style="margin-left: 36px;"><span style="font-family: Calibri; font-size: 11pt;"><span style="font-family: Verdana; font-size: 12pt; font-style: italic;">Teacher Research</span><span style="font-family: Verdana; font-size: 12pt;"> can be any form of research that teachers do, including action research, but not limited to it. At George Mason University, teacher research is described in a way that is very similar to what most authors understand as action research. And at some point, they suggest that action research can be a synonym of teacher research.  The description of action research posted on this site is more closely aligned to what we have called reflective practice.   This shows the variation in the way that people working in the field have of conceptualizing these terms.</span></span></li>
<li class="outline-element" style="margin-left: 36px;"><span style="font-family: Verdana; font-size: 12pt;">Action Inquiry draws on action research and recasts evaluation research to help navigate complexity when enacting collective leadership. Find out more about by reading this document from Scotland.</span></li>
<li class="outline-element" style="margin-left: 36px;"><span style="font-family: Verdana; font-size: 12pt;">Improvement Science is explicitly designed to accelerate learning-by-doing. It's a more user-centered and problem-centered approached to improving teaching and learning that is highly similar to action research supported by the Carnegie Foundation for the Advancement of Teaching.</span></li>
<li class="outline-element" style="margin-left: 36px;"><span style="font-family: Verdana; font-size: 12pt;">Improvement Science is explicitly designed to accelerate learning-by-doing. It&apos;s a more user-centered and problem-centered approached to improving teaching and learning that is highly similar to action research supported by the Carnegie Foundation for the Advancement of Teaching.</span></li>
</ul>
<div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt; padding-bottom: 16px; padding-top: 7px;"><br></p></div>
</div>
@@ -982,7 +1020,7 @@ exports[`InteropService_Importer_OneNote should remove hyperlink from title: Tip
<div class="title" style="left: 48px; position: absolute; top: 24px;"><div class="container-outline" style="width: 624px;"><div class="outline-element" style="margin-left: 0px;"><span style="font-family: Calibri Light; font-size: 20pt; line-height: 32px;">&nbsp;</span></div>
</div><div class="container-outline" style="width: 624px;"><div class="outline-element" style="margin-left: 0px;"><span style="color: rgb(118,118,118); font-family: Calibri; font-size: 10pt; line-height: 16px;">Saturday, February 11, 2023</span></div>
<div class="outline-element" style="margin-left: 0px;"><span style="color: rgb(118,118,118); font-family: Calibri; font-size: 10pt; line-height: 16px;">12:56 AM</span></div>
</div></div><div class="container-outline" style="left: 48px; position: absolute; top: 115px; width: 624px;"><div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 14pt; line-height: 22px;"><a href="onenote:https://d.docs.live.net/c8d3bbab7f1acf3a/Documents/Photography/风景.one#Tips%20from%20a%20Pro%20Using%20Trees%20for%20Dramatic%20Landscape%20Photography&section-id={262ADDFB-A4DC-4453-A239-0024D6769962}&page-id={88D803A5-4F43-48D4-9B16-4C024F5787DC}&end" style="">Tips from a Pro: Using Trees for Dramatic Landscape Photography</a></p></div>
</div></div><div class="container-outline" style="left: 48px; position: absolute; top: 115px; width: 624px;"><div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 14pt; line-height: 22px;"><a href="onenote:https://d.docs.live.net/c8d3bbab7f1acf3a/Documents/Photography/%E9%A3%8E%E6%99%AF.one#Tips%20from%20a%20Pro%20Using%20Trees%20for%20Dramatic%20Landscape%20Photography&section-id={262ADDFB-A4DC-4453-A239-0024D6769962}&page-id={88D803A5-4F43-48D4-9B16-4C024F5787DC}&end" style="">Tips from a Pro: Using Trees for Dramatic Landscape Photography</a></p></div>
</div>
<script>
@@ -1044,7 +1082,7 @@ exports[`InteropService_Importer_OneNote should remove hyperlink from title: 风
<div class="title" style="left: 48px; position: absolute; top: 24px;"><div class="container-outline" style="width: 624px;"><div class="outline-element" style="margin-left: 0px;"><span style="font-family: Calibri Light; font-size: 20pt;">&nbsp;</span></div>
</div><div class="container-outline" style="width: 624px;"><div class="outline-element" style="margin-left: 0px;"><span style="color: rgb(128,128,128); font-family: Calibri; font-size: 10pt;">Sunday, January 5, 2025</span></div>
<div class="outline-element" style="margin-left: 0px;"><span style="color: rgb(128,128,128); font-family: Calibri; font-size: 10pt;">10:13 PM</span></div>
</div></div><div class="container-outline" style="left: 48px; position: absolute; top: 115px; width: 624px;"><div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt;"><a href="onenote:#风景&section-id={75256889-9e75-4ec2-82ed-fc799557e1b9}&page-id={d099b6f3-7f5a-4c08-aed7-e8d42c59523f}&end" style="">风景</a><span style="font-family: Calibri; font-size: 11pt;"> (</span><a href="https://onedrive.live.com/edit.aspx?resid=193EE54E3252492D!s9b62db4219f740709f444bc0129de4e9&migratedtospo=true&wd=target%28Quick%20Notes.one%7C75256889-9e75-4ec2-82ed-fc799557e1b9%2F%E9%A3%8E%E6%99%AF%7Cd099b6f3-7f5a-4c08-aed7-e8d42c59523f%2F%29&wdorigin=703&wdpreservelink=1" style="">Web view</a><span style="font-family: Calibri; font-size: 11pt;">)</span></p></div>
</div></div><div class="container-outline" style="left: 48px; position: absolute; top: 115px; width: 624px;"><div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt;"><a href="onenote:#%E9%A3%8E%E6%99%AF&section-id={75256889-9e75-4ec2-82ed-fc799557e1b9}&page-id={d099b6f3-7f5a-4c08-aed7-e8d42c59523f}&end" style="font-family: Calibri; font-size: 11pt;">风景</a><span style="font-family: Calibri; font-size: 11pt;"> (</span><a href="https://onedrive.live.com/edit.aspx?resid=193EE54E3252492D!s9b62db4219f740709f444bc0129de4e9&migratedtospo=true&wd=target%28Quick%20Notes.one%7C75256889-9e75-4ec2-82ed-fc799557e1b9%2F%E9%A3%8E%E6%99%AF%7Cd099b6f3-7f5a-4c08-aed7-e8d42c59523f%2F%29&wdorigin=703&wdpreservelink=1" style="font-family: Calibri; font-size: 11pt;">Web view</a><span style="font-family: Calibri; font-size: 11pt;">)</span></p></div>
</div>
<script>
@@ -1459,7 +1497,7 @@ exports[`InteropService_Importer_OneNote should use default value for EntityGuid
<div class="title" style="left: 48px; position: absolute; top: 24px;"><div class="container-outline"><div class="outline-element" style="margin-left: 0px;"><span style="font-family: Calibri Light; font-size: 20pt; line-height: 32px;">Decrease support costs</span></div>
</div><div class="container-outline"><div class="outline-element" style="margin-left: 0px;"><span style="color: rgb(128,128,128); font-family: Calibri; font-size: 10pt; line-height: 16px;">Saturday, October 10, 2015</span></div>
<div class="outline-element" style="margin-left: 0px;"><span style="color: rgb(128,128,128); font-family: Calibri; font-size: 10pt; line-height: 16px;">11:15 PM</span></div>
</div></div><div class="container-outline" style="left: 48px; position: absolute; top: 88px; width: 624px;"><div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt; line-height: 17px;">One of the strategic goals of training <span style="font-style: italic; font-weight: bold;">must</span> be to decrease the cost of customer support. To do this training must teach customers to "<span style="font-weight: bold;">do</span>" not to "know." How many customers call asking to know something?</p></div>
</div></div><div class="container-outline" style="left: 48px; position: absolute; top: 88px; width: 624px;"><div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt; line-height: 17px;">One of the strategic goals of training <span style="font-style: italic; font-weight: bold;">must</span> be to decrease the cost of customer support. To do this training must teach customers to &quot;<span style="font-weight: bold;">do</span>&quot; not to &quot;know.&quot; How many customers call asking to know something?</p></div>
<div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt; line-height: 17px;">&nbsp;</p></div>
<div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt; line-height: 17px;">&nbsp;</p></div>
<div class="outline-element" style="margin-left: 0px;"><p style="font-family: Calibri; font-size: 11pt; line-height: 17px;">&nbsp;</p></div>

View File

@@ -369,7 +369,6 @@ dependencies = [
"palette",
"parser-macros",
"parser-utils",
"percent-encoding",
"regex",
"sanitize-filename",
"uuid",
@@ -568,6 +567,7 @@ dependencies = [
"parser",
"parser-utils",
"paste",
"percent-encoding",
"regex",
"sanitize-filename",
"thiserror",

View File

@@ -13,7 +13,6 @@ color-eyre = "0.5"
log = "0.4.11"
mime_guess = "2.0.3"
palette = "0.5.0"
percent-encoding = "2.1.0"
regex = "1"
sanitize-filename = "0.3.0"
console_error_panic_hook = "0.1.7"

View File

@@ -67,5 +67,8 @@ pub mod property {
pub mod rich_text {
pub use crate::one::property::paragraph_alignment::ParagraphAlignment;
pub use crate::onenote::rich_text::ParagraphStyling;
pub use crate::onenote::text_region::Hyperlink;
pub use crate::onenote::text_region::MathExpression;
pub use crate::onenote::text_region::TextRegion;
}
}

View File

@@ -205,4 +205,7 @@ pub(crate) enum PropertyType {
EmbeddedInkSpaceHeight = 0x14001C28,
ImageEmbedType = 0x140035F2,
ImageEmbeddedUrl = 0x1C0035F3,
MathUnknown1 = 0x10003453, // Unknown 16-bit math-related property (operator variant?)
MathOperator = 0x1400344f,
}

View File

@@ -1,6 +1,7 @@
use crate::one::property::PropertyType;
use crate::onestore::object::Object;
use crate::shared::guid::Guid;
use crate::shared::prop_set::PropertySet;
use encoding_rs::mem::decode_latin1;
use parser_utils::Utf16ToString;
use parser_utils::errors::{ErrorKind, Result};
@@ -158,3 +159,24 @@ pub(crate) fn parse_guid(prop_type: PropertyType, object: &Object) -> Result<Opt
Ok(Some(Guid::parse(&mut Reader::new(data))?))
}
pub(crate) fn parse_property_values(
prop_type: PropertyType,
object: &Object,
) -> Result<Option<&[PropertySet]>> {
let value = match object.props().get(prop_type) {
Some(value) => Some(
value
.to_property_values()
.ok_or_else(|| {
parser_error!(
MalformedOneNoteFileData,
"PropertyValue value is not a PropertyValue"
)
})?
.1,
),
None => None,
};
Ok(value)
}

View File

@@ -7,8 +7,9 @@ use crate::one::property_set::PropertySetId;
use crate::one::property_set::note_tag_container::Data as NoteTagData;
use crate::onestore::object::Object;
use crate::shared::exguid::ExGuid;
use crate::shared::prop_set::PropertySet;
use parser_utils::errors::{ErrorKind, Result};
use parser_utils::log_warn;
use parser_utils::{Utf16ToString, log_warn};
/// A rich text paragraph.
///
@@ -23,12 +24,12 @@ pub(crate) struct Data {
pub(crate) text_run_formatting: Vec<ExGuid>,
pub(crate) text_run_indices: Vec<u32>,
pub(crate) text_run_data_object: Vec<ExGuid>,
pub(crate) text_run_data_values: Vec<PropertySet>,
pub(crate) paragraph_style: ExGuid,
pub(crate) paragraph_space_before: f32,
pub(crate) paragraph_space_after: f32,
pub(crate) paragraph_line_spacing_exact: Option<f32>,
pub(crate) paragraph_alignment: ParagraphAlignment,
pub(crate) text: Option<String>,
pub(crate) is_title_time: bool,
pub(crate) is_boiler_text: bool,
pub(crate) is_title_date: bool,
@@ -38,6 +39,8 @@ pub(crate) struct Data {
pub(crate) language_code: Option<u32>,
pub(crate) rtl: bool,
pub(crate) note_tags: Vec<NoteTagData>,
pub(crate) text: Option<String>,
pub(crate) text_utf_16: Option<Vec<u8>>,
}
pub(crate) fn parse(object: &Object) -> Result<Data> {
@@ -57,6 +60,8 @@ pub(crate) fn parse(object: &Object) -> Result<Data> {
simple::parse_vec_u32(PropertyType::TextRunIndex, object)?.unwrap_or_default();
let text_run_data_object =
ObjectReference::parse_vec(PropertyType::TextRunDataObject, object)?.unwrap_or_default();
let text_run_data_array =
simple::parse_property_values(PropertyType::TextRunData, object)?.unwrap_or(&[]);
let paragraph_style_result = ObjectReference::parse(PropertyType::ParagraphStyle, object);
let paragraph_style = match paragraph_style_result {
@@ -78,10 +83,23 @@ pub(crate) fn parse(object: &Object) -> Result<Data> {
simple::parse_f32(PropertyType::ParagraphLineSpacingExact, object)?;
let paragraph_alignment = ParagraphAlignment::parse(object)?.unwrap_or_default();
let text = match simple::parse_string(PropertyType::RichEditTextUnicode, object)? {
None => simple::parse_ascii(PropertyType::TextExtendedAscii, object)?,
text => text,
};
// Keep the text in its original UTF-16 byte array, if possible. This is needed later on for
// indexing.
let text_utf_16_bytes = simple::parse_vec(PropertyType::RichEditTextUnicode, object)?;
let text_ascii = simple::parse_ascii(PropertyType::TextExtendedAscii, object)?;
let text_string = text_utf_16_bytes
.as_ref()
.map(|data| data.as_slice().utf16_to_string())
.transpose()?
.or(text_ascii);
let text_utf_16_bytes = text_utf_16_bytes.or_else(|| {
// Fall back to re-encoding the ASCII representation as UTF-16, if it exists.
text_string.as_ref().map(|text| {
text.encode_utf16()
.flat_map(|two_bytes| two_bytes.to_le_bytes())
.collect()
})
});
let layout_alignment_in_parent =
LayoutAlignment::parse(PropertyType::LayoutAlignmentInParent, object)?;
@@ -103,13 +121,15 @@ pub(crate) fn parse(object: &Object) -> Result<Data> {
tight_layout,
text_run_formatting,
text_run_indices,
text_run_data_values: text_run_data_array.into(),
text_run_data_object,
paragraph_style,
paragraph_space_before,
paragraph_space_after,
paragraph_line_spacing_exact,
paragraph_alignment,
text,
text_utf_16: text_utf_16_bytes,
text: text_string,
is_title_time,
is_boiler_text,
is_title_date,

View File

@@ -19,6 +19,7 @@ pub(crate) mod page_series;
pub(crate) mod rich_text;
pub(crate) mod section;
pub(crate) mod table;
pub(crate) mod text_region;
/// The OneNote file parser.
pub struct Parser;

View File

@@ -7,6 +7,7 @@ use crate::one::property::paragraph_alignment::ParagraphAlignment;
use crate::one::property_set::{embedded_ink_container, paragraph_style_object, rich_text_node};
use crate::onenote::ink::{Ink, InkBoundingBox, parse_ink_data};
use crate::onenote::note_tag::{NoteTag, parse_note_tags};
use crate::onenote::text_region::TextRegion;
use crate::onestore::object::Object;
use crate::onestore::object_space::ObjectSpaceRef;
use crate::shared::exguid::ExGuid;
@@ -32,6 +33,7 @@ use parser_utils::log_warn;
#[derive(Clone, Debug)]
pub struct RichText {
pub(crate) text: String,
pub(crate) text_regions: Vec<TextRegion>,
pub(crate) text_run_formatting: Vec<ParagraphStyling>,
pub(crate) text_run_indices: Vec<u32>,
@@ -55,6 +57,11 @@ impl RichText {
&self.text
}
/// Computes which styles are associated with which text
pub fn text_segments(&self) -> &Vec<TextRegion> {
&self.text_regions
}
/// The formatting of each text run.
///
/// See [\[MS-ONE\] 2.3.77].
@@ -386,15 +393,16 @@ pub(crate) fn parse_rich_text(content_id: ExGuid, space: ObjectSpaceRef) -> Resu
.text_run_formatting
.iter()
.filter_map(|style_id| {
space
.get_object(*style_id)
.or_else(|| {
// Handle the case where styles are missing gracefully. It seems that style objects
// are sometimes missing, or can't be found:
// https://discourse.joplinapp.org/t/onenote-zip-file-import-not-working/47499/12
log_warn!("Paragraph styling not found: Unable to locate object with ID {:?}.", style_id);
None
})
space.get_object(*style_id).or_else(|| {
// Handle the case where styles are missing gracefully. It seems that style objects
// are sometimes missing, or can't be found:
// https://discourse.joplinapp.org/t/onenote-zip-file-import-not-working/47499/12
log_warn!(
"Paragraph styling not found: Unable to locate object with ID {:?}.",
style_id
);
None
})
})
.map(|style_object| paragraph_style_object::parse(&style_object))
.collect::<Result<Vec<_>>>()?;
@@ -460,6 +468,13 @@ pub(crate) fn parse_rich_text(content_id: ExGuid, space: ObjectSpaceRef) -> Resu
};
let text = RichText {
text_regions: TextRegion::parse(
&data.text_utf_16.unwrap_or_default(),
&data.text_run_indices,
&styles,
&data.text_run_data_values,
)?,
text,
embedded_objects,
text_run_formatting: styles,

View File

@@ -0,0 +1,369 @@
use crate::{
one::property::PropertyType, onenote::rich_text::ParagraphStyling,
shared::prop_set::PropertySet,
};
use parser_utils::{Utf16ToString, errors::Result};
/// Stores information about a part of a [RichText] region.
#[derive(Debug, Clone)]
pub struct TextRegion {
text: String,
style: Option<ParagraphStyling>,
hyperlink: Option<Hyperlink>,
math: Option<MathExpression>,
}
impl TextRegion {
/// The (visible) text content of this region
pub fn text(&self) -> &str {
&self.text
}
/// Styles associated with this region
pub fn style(&self) -> Option<&ParagraphStyling> {
self.style.as_ref()
}
/// If a hyperlink, the hyperlink data
pub fn hyperlink(&self) -> Option<&Hyperlink> {
self.hyperlink.as_ref()
}
/// If math, the math data
pub fn math(&self) -> Option<&MathExpression> {
self.math.as_ref()
}
fn from_text(text: &str) -> Self {
Self {
text: String::from(text),
style: None,
math: None,
hyperlink: None,
}
}
pub(crate) fn parse(
raw_text: &[u8],
text_run_indices: &[u32],
styles: &[ParagraphStyling],
text_run_data_values: &[PropertySet],
) -> Result<Vec<TextRegion>> {
if text_run_indices.is_empty() {
let text = raw_text.utf16_to_string()?;
return Ok(vec![TextRegion::from_text(&text)]);
}
let style_count = styles.len();
let index_count = text_run_indices.len();
if index_count + 1 < style_count {
return Err(parser_error!(
MalformedOneNoteData,
"Wrong number of styles in paragraph (styles: {style_count}, ranges: {index_count})"
)
.into());
}
// Split text into parts specified by indices
let texts = {
let mut text_iter = raw_text.iter().copied();
let mut texts: Vec<String> = Vec::new();
let mut last_index = 0;
for index in text_run_indices.iter().copied() {
let count = (index - last_index) as usize;
let count_utf_16 = count * 2;
let part: Vec<u8> = text_iter.by_ref().take(count_utf_16).collect();
let part_text = part.as_slice().utf16_to_string()?;
// TODO: When the bell character is at the start of the paragraph it shifts
// all styles and attributes by one. For now, ignore leading segments that contain
// only the bell character. In the future, look into why this issue is happening.
if !texts.is_empty() || part_text != "\u{000B}" {
texts.push(part_text);
}
last_index = index;
}
let end_text: Vec<u8> = text_iter.collect();
texts.push(end_text.as_slice().utf16_to_string()?);
texts
};
TextRegionParser::parse(texts, styles, text_run_data_values)
}
}
struct TextRegionParser {
parts: Vec<TextRegion>,
hyperlink_href: Option<String>,
// If true and hyperlink_href is Some, hyperlink_href contains a full HREF. Otherwise,
// hyperlink_href may be partial (in the process of being built).
hyperlink_href_finished: bool,
hyperlink_next_prefix: Option<String>,
}
impl TextRegionParser {
fn parse(
texts: Vec<String>,
styles: &[ParagraphStyling],
additional_data: &[PropertySet],
) -> Result<Vec<TextRegion>> {
let mut style_iterator = styles.iter();
let mut additional_data_iterator = additional_data.iter();
let mut text_region_parser = TextRegionParser::new();
for text_segment in texts.iter() {
let style = style_iterator.next();
let additional_data = additional_data_iterator.next();
text_region_parser.push(text_segment, style, additional_data)?;
}
text_region_parser.finish()
}
fn new() -> Self {
Self {
parts: Vec::new(),
hyperlink_href: None,
hyperlink_next_prefix: None,
hyperlink_href_finished: true,
}
}
fn push_hyperlink(&mut self, text: &str, styles: Option<&ParagraphStyling>) -> Result<()> {
let text = if let Some(prefix) = &self.hyperlink_next_prefix {
let prefixed = format!("{prefix}{text}");
self.hyperlink_next_prefix = None;
prefixed
} else {
text.into()
};
const HYPERLINK_MARKER: &str = "\u{fddf}HYPERLINK \"";
if text == "\u{fddf}" && self.parts.is_empty() {
self.hyperlink_next_prefix = Some(text);
} else if text.starts_with(HYPERLINK_MARKER) {
// Ensure that the previous link (if any) has ended
self.end_link();
let url = text.strip_prefix(HYPERLINK_MARKER).ok_or_else(|| {
parser_error!(MalformedOneNoteData, "Hyperlink has no start marker")
})?;
if let Some(url) = url.strip_suffix('"') {
self.hyperlink_href = Some(url.into());
self.hyperlink_href_finished = true;
} else {
// If we didn't find the double quotes, the HREF will be continued in
// the text regions that follow.
self.hyperlink_href = Some(url.into());
self.hyperlink_href_finished = false;
}
} else if let Some(href) = self.hyperlink_href.clone()
&& self.hyperlink_href_finished
{
self.hyperlink_href = None;
let is_link_start = if let Some(last) = self.parts.last() {
if let Some(link) = &last.hyperlink {
!link.is_link_end
} else {
true
}
} else {
true
};
self.parts.push(TextRegion {
text: text,
style: styles.cloned(),
hyperlink: Some(Hyperlink {
is_link_start,
is_link_end: false,
href,
}),
math: None,
});
} else if let Some(href_start) = &self.hyperlink_href
&& !self.hyperlink_href_finished
{
let url = text.strip_suffix('"');
if let Some(url) = url {
self.hyperlink_href = Some(format!("{href_start}{url}"));
self.hyperlink_href_finished = true;
} else {
self.hyperlink_href = Some(format!("{href_start}{text}"));
}
} else {
self.end_link();
self.parts.push(TextRegion {
text: text.clone(),
style: styles.cloned(),
hyperlink: Some(Hyperlink {
is_link_start: true,
is_link_end: true,
href: text,
}),
math: None,
})
}
Ok(())
}
fn push_math(
&mut self,
text: &str,
styles: Option<&ParagraphStyling>,
additional_data: Option<&PropertySet>,
) -> Result<()> {
let last_was_math = self
.parts
.last()
.map(|last| last.math.is_some())
.unwrap_or(false);
let additional_data = additional_data.cloned().unwrap_or_default();
self.parts.push(TextRegion {
text: text.into(),
style: styles.cloned(),
hyperlink: None,
math: Some(MathExpression {
latex: text_region_to_latex(text, &additional_data)?,
is_math_start: !last_was_math,
is_math_end: false,
}),
});
Ok(())
}
/// Updates the last item (if math) to mark it as a math-end region
fn end_math(&mut self) {
if let Some(last) = self.parts.last_mut()
&& let Some(math) = &mut last.math
{
math.is_math_end = true;
}
}
fn end_link(&mut self) {
if let Some(last) = self.parts.last_mut()
&& let Some(link) = &mut last.hyperlink
{
link.is_link_end = true;
// Reset link state
self.hyperlink_href_finished = true;
self.hyperlink_href = None;
}
}
fn push(
&mut self,
text: &str,
style: Option<&ParagraphStyling>,
additional_data: Option<&PropertySet>,
) -> Result<()> {
let (hyperlink, math) = match style {
Some(style) => (style.hyperlink(), style.math_formatting()),
None => (false, false),
};
if hyperlink {
self.end_math();
self.push_hyperlink(text, style)?;
} else if math {
self.end_link();
self.push_math(text, style, additional_data)?;
} else {
// Correct end information
self.end_math();
self.end_link();
self.parts.push(TextRegion {
text: text.into(),
style: style.cloned(),
hyperlink: None,
math: None,
});
}
Ok(())
}
fn finish(mut self) -> Result<Vec<TextRegion>> {
self.end_math();
self.end_link();
Ok(self.parts)
}
}
fn text_region_to_latex(text: &str, additional_data: &PropertySet) -> Result<String> {
let op_type = match additional_data
.get_from_type(PropertyType::MathOperator)
.and_then(|operator_value| operator_value.to_u32())
{
Some(21) => {
let variant = additional_data
.get_from_type(PropertyType::MathUnknown1)
.and_then(|variant| variant.to_u16())
.unwrap_or_default();
if variant == 8721 {
"".into()
} else {
"".into()
}
}
Some(13) => "parens".into(),
Some(17) => "fnCall".into(),
Some(19) => "withSubscript".into(),
Some(16 | 26) => "frac".into(),
Some(11) => "mathrm".into(),
Some(31) => "pow".into(),
Some(other) => {
format!("unknown{{{}}}", other)
}
None => "".into(),
};
let operator_name = if !op_type.is_empty() {
format!("\\{op_type}")
} else {
String::from("")
};
// See https://devblogs.microsoft.com/math-in-office/officemath/
let tex = text
.replace("\u{FDD0}", &format!("{operator_name}{{"))
.replace("\u{FDEF}", "}")
.replace("\u{FDEE}", "}{")
.replace("\u{FFFC}", "<obj>");
println!("Additional data: {:?}, for {}", additional_data, tex);
Ok(tex)
}
/// Information about a hyperlink region
#[allow(missing_docs)]
#[derive(Clone, Debug)]
pub struct Hyperlink {
pub is_link_start: bool,
pub is_link_end: bool,
pub href: String,
}
/// Information about a math expression
#[allow(missing_docs)]
#[derive(Clone, Debug)]
pub struct MathExpression {
pub is_math_start: bool,
pub is_math_end: bool,
pub latex: String,
}

View File

@@ -1,6 +1,5 @@
use super::{
object_stream_header::ObjectStreamHeader, prop_set::PropertySet, property::PropertyId,
property::PropertyValue,
object_stream_header::ObjectStreamHeader, prop_set::PropertySet, property::PropertyValue,
};
use crate::one::property::PropertyType;
use crate::shared::compact_id::CompactId;
@@ -77,6 +76,6 @@ impl Parse for ObjectPropSet {
impl ObjectPropSet {
pub(crate) fn get(&self, prop_type: PropertyType) -> Option<&PropertyValue> {
self.properties.get(PropertyId::new(prop_type as u32))
self.properties.get_from_type(prop_type)
}
}

View File

@@ -1,3 +1,4 @@
use crate::one::property::PropertyType;
use crate::shared::property::{PropertyId, PropertyValue};
use parser_utils::Reader;
use parser_utils::errors::Result;
@@ -62,6 +63,10 @@ impl PropertySet {
self.values.get(&id.id()).map(|(_, value)| value)
}
pub(crate) fn get_from_type(&self, prop_type: PropertyType) -> Option<&PropertyValue> {
self.get(PropertyId::new(prop_type as u32))
}
pub(crate) fn index(&self, id: PropertyId) -> Option<usize> {
self.values.get(&id.id()).map(|(index, _)| index).copied()
}

View File

@@ -12,6 +12,7 @@ keywords = ["onenote"]
askama = "0.14.0"
color-eyre = "0.5"
log = "0.4.11"
percent-encoding = "2.1.0"
mime_guess = "2.0.3"
once_cell = "1.4.1"
palette = "0.5.0"

View File

@@ -0,0 +1,56 @@
use crate::page::Renderer;
use crate::utils::{StyleSet, html_entities};
use color_eyre::Result;
use itertools::Itertools;
use parser::property::rich_text::MathExpression;
impl<'a> Renderer<'a> {
pub(crate) fn render_math(
&mut self,
math: &[MathExpression],
style: &StyleSet,
) -> Result<String> {
let tex = math.iter().map(|tex| &tex.latex).join("");
let source = format!("{}{}", self.render_tex_macros(&tex), tex,);
let opening_html = format!("<span class=\"joplin-editable\" {}>", style.to_html_attr(),);
let source_html = format!(
"<span class=\"joplin-source\" data-joplin-language=\"katex\" data-joplin-source-open=\"$\" data-joplin-source-close=\"$\" style=\"display: none;\">{}</span>",
html_entities(source.trim()),
);
// TODO: Render it! (For now, display the raw source).
let rendered_html = html_entities(tex.trim());
Ok(format!("{opening_html}{source_html}{rendered_html}</span>"))
}
/// Returns definitions for non-standard KaTeX macros used in `tex`.
fn render_tex_macros(&self, tex: &str) -> String {
let mut result = vec![];
if tex.contains("\\") {
result.push(r"\def\∫#1#2#3{\int_{#1}^{#2}{#3}}");
}
if tex.contains("\\") {
result.push(r"\def\∑#1#2#3{\sum_{#1}^{#2}{#3}}");
}
if tex.contains("\\parens") {
result.push(r"\def\parens#1{\left( {#1} \right)}");
}
if tex.contains("\\withSubscript") {
result.push(r"\def\withSubscript#1#2{{#1}_{#2}}");
}
if tex.contains("\\pow") {
result.push(r"\def\pow#1#2{{#1}^{#2}}");
}
if tex.contains("\\fnCall") {
result.push(r"\def\fnCall#1#2{{ \rm #1 }\ {#2}}");
}
if tex.contains("\\unknown") {
result.push(r"\def\unknown#1{\textsf{Unknown}(#1)}");
}
result.join("")
}
}

View File

@@ -9,6 +9,7 @@ pub(crate) mod embedded_file;
pub(crate) mod image;
pub(crate) mod ink;
pub(crate) mod list;
pub(crate) mod math;
pub(crate) mod note_tag;
pub(crate) mod outline;
pub(crate) mod rich_text;

View File

@@ -1,30 +1,29 @@
use crate::page::Renderer;
use crate::utils::{AttributeSet, StyleSet, px};
use crate::utils::{AttributeSet, StyleSet, html_entities, px, url_encode};
use color_eyre::Result;
use color_eyre::eyre::ContextCompat;
use itertools::Itertools;
use once_cell::sync::Lazy;
use parser::contents::{EmbeddedObject, RichText};
use parser::property::common::ColorRef;
use parser::property::rich_text::{ParagraphAlignment, ParagraphStyling};
use parser::property::rich_text::{MathExpression, ParagraphAlignment, ParagraphStyling};
use parser_utils::log_warn;
use regex::{Captures, Regex};
impl<'a> Renderer<'a> {
pub(crate) fn render_rich_text(&mut self, text: &RichText) -> Result<String> {
let mut content = String::new();
let mut content_html = String::new();
let mut attrs = AttributeSet::new();
let mut style = self.parse_paragraph_styles(text);
if let Some((note_tag_html, note_tag_styles)) = self.render_note_tags(text.note_tags()) {
content.push_str(&note_tag_html);
content_html.push_str(&note_tag_html);
style.extend(note_tag_styles);
}
content.push_str(&self.parse_content(text)?);
content_html.push_str(&self.parse_content(text)?);
if content.starts_with("http://") || content.starts_with("https://") {
content = format!("<a href=\"{}\">{}</a>", content, content);
if content_html.starts_with("http://") || content_html.starts_with("https://") {
content_html = format!("<a href=\"{}\">{}</a>", url_encode(&content_html), content_html);
}
if style.len() > 0 {
@@ -33,10 +32,10 @@ impl<'a> Renderer<'a> {
match text.paragraph_style().style_id() {
Some(t) if !self.in_list && is_tag(t) => {
Ok(format!("<{} {}>{}</{}>", t, attrs, content, t))
Ok(format!("<{} {}>{}</{}>", t, attrs, content_html, t))
}
_ if style.len() > 0 => Ok(format!("<span style=\"{}\">{}</span>", style, content)),
_ => Ok(content),
_ if style.len() > 0 => Ok(format!("<span {}>{}</span>", style.to_html_attr(), content_html)),
_ => Ok(content_html),
}
}
@@ -61,140 +60,61 @@ impl<'a> Renderer<'a> {
.join(""));
}
let mut indices = data.text_run_indices().to_vec();
let mut styles = data.text_run_formatting().to_vec();
let mut text = data.text().to_string();
if text.is_empty() {
text = "&nbsp;".to_string();
}
// TODO: Maybe this shouldn't be here
// When the this character is at the start of the paragraph it makes
// all the styles to be shifted by minus one.
// A better solution would be to look if there isn't anything wrong with the parser,
// but I haven't found what could be causing this yet.
if text.starts_with("\u{000B}") && !indices.is_empty() {
indices.remove(0);
styles.pop();
}
// Probably the best solution here would be to rewrite the render_hyperlink to take this
// case in account, backtracking if necessary, but this will do for now
// https://github.com/laurent22/joplin/issues/11617
if text.starts_with("\u{fddf}") {
let first_indice = match indices.get(0) {
Some(i) => *i,
None => 0,
};
if first_indice == 1 {
indices.remove(0);
styles.pop();
}
}
if indices.is_empty() {
return Ok(fix_newlines(&text));
}
assert!(indices.len() + 1 >= styles.len());
// Split text into parts specified by indices
let mut parts: Vec<String> = vec![];
for i in indices.iter().copied().rev() {
let part = text.chars().skip(i as usize).collect();
text = text.chars().take(i as usize).collect();
parts.push(part);
}
if !indices.is_empty() {
parts.push(text);
}
let mut in_hyperlink = false;
let mut is_href_finished = true;
let parts = data.text_segments();
// Stores LaTeX and original text data
let mut math_parts: Vec<MathExpression> = Vec::new();
let content = parts
.into_iter()
.rev()
.zip(styles.iter())
.map(|(text, style)| {
if style.hyperlink() {
let result =
self.render_hyperlink(text.clone(), style, in_hyperlink, is_href_finished);
if result.is_ok() {
in_hyperlink = true;
is_href_finished = result.as_ref().unwrap().1;
Ok(result.unwrap().0)
.iter()
.map(|part| -> Result<String> {
let style = part
.style()
.map(|style| self.parse_style(style))
.unwrap_or_default();
if let Some(hyperlink) = part.hyperlink() {
let hyperlink_start_html = if hyperlink.is_link_start {
format!(
"<a href=\"{}\" {}>",
url_encode(&hyperlink.href),
style.to_html_attr(),
)
} else {
Ok(text)
String::from("")
};
let hyperlink_end_html = if hyperlink.is_link_end { "</a>" } else { "" };
let content_html = html_entities(part.text());
Ok(format!(
"{hyperlink_start_html}{content_html}{hyperlink_end_html}"
))
} else if let Some(math) = part.math() {
if math.is_math_start {
math_parts.clear();
}
math_parts.push(math.clone());
if math.is_math_end {
Ok(self.render_math(&math_parts, &style)?)
} else {
Ok("".into())
}
} else {
in_hyperlink = false;
is_href_finished = true;
let style = self.parse_style(style);
let text_html = html_entities(part.text());
if style.len() > 0 {
Ok(format!("<span style=\"{}\">{}</span>", style, text))
let style_attr = style.to_html_attr();
Ok(format!("<span {style_attr}>{text_html}</span>"))
} else {
Ok(text)
Ok(text_html)
}
}
})
.collect::<Result<String>>()?;
Ok(fix_newlines(&content))
}
/// The hyperlink is delimited by the HYPERLINK_MARKER until the closing double quote
/// In some cases the hyperlink is broken in more than one style (e.g.: when there are
/// chinese characters on the url path), so we must keep track of the href status
/// https://github.com/laurent22/joplin/issues/11600
fn render_hyperlink(
&self,
text: String,
style: &ParagraphStyling,
in_hyperlink: bool,
is_href_finished: bool,
) -> Result<(String, bool)> {
const HYPERLINK_MARKER: &str = "\u{fddf}HYPERLINK \"";
let style = self.parse_style(style);
if text.starts_with(HYPERLINK_MARKER) {
let url = text
.strip_prefix(HYPERLINK_MARKER)
.wrap_err("Hyperlink has no start marker")?;
let url_2 = url.strip_suffix('"');
if url_2.is_some() {
return Ok((
format!("<a href=\"{}\" style=\"{}\">", url_2.unwrap(), style),
true,
));
} else {
// If we didn't find the double quotes means that href still has content in following styles
Ok((format!("<a href=\"{}", url), false))
}
} else if in_hyperlink && is_href_finished {
Ok((text + "</a>", true))
} else if in_hyperlink && !is_href_finished {
let url = text.strip_suffix('"');
if url.is_some() {
return Ok((format!("{}\" style=\"{}\">", url.unwrap(), style), true));
} else {
Ok((text, false))
}
let content = fix_newlines(&content);
if content.is_empty() {
Ok(String::from("&nbsp;"))
} else {
Ok((
format!("<a href=\"{}\" style=\"{}\">{}</a>", text, style, text),
true,
))
Ok(content)
}
}
@@ -288,21 +208,19 @@ impl<'a> Renderer<'a> {
}
if let Some(align) = &style.paragraph_alignment() {
styles.set("text-align", match align {
ParagraphAlignment::Center => {
"center"
},
ParagraphAlignment::Left => {
"left"
},
ParagraphAlignment::Right => {
"right"
},
other => {
log_warn!("Unknown/unsupported text-align value: {:?}", other);
""
styles.set(
"text-align",
match align {
ParagraphAlignment::Center => "center",
ParagraphAlignment::Left => "left",
ParagraphAlignment::Right => "right",
other => {
log_warn!("Unknown/unsupported text-align value: {:?}", other);
""
}
}
}.into());
.into(),
);
}
if let Some(space) = style.paragraph_space_before() {
@@ -320,10 +238,7 @@ impl<'a> Renderer<'a> {
if let Some(space) = style.paragraph_line_spacing_exact() {
if space != 0.0 {
styles.set(
"line-height",
format!("{}in", space / 2.),
)
styles.set("line-height", format!("{}in", space / 2.))
} else if let Some(size) = style.font_size() {
styles.set(
"line-height",

View File

@@ -1,5 +1,6 @@
use itertools::Itertools;
use parser_utils::errors::Result;
use percent_encoding::{AsciiSet, CONTROLS, utf8_percent_encode};
use std::collections::HashMap;
use std::fmt;
use std::fmt::Display;
@@ -42,7 +43,7 @@ impl Display for AttributeSet {
}
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Default)]
pub(crate) struct StyleSet(HashMap<&'static str, String>);
impl StyleSet {
@@ -61,6 +62,11 @@ impl StyleSet {
pub(crate) fn len(&self) -> usize {
self.0.len()
}
pub(crate) fn to_html_attr(&self) -> String {
let attr_content = format!("{}", self);
format!("style=\"{}\"", html_entities(&attr_content))
}
}
impl Display for StyleSet {
@@ -93,3 +99,41 @@ impl Utf16ToString for &[u8] {
Ok(value.to_string().unwrap())
}
}
pub(crate) fn html_entities(text: &str) -> String {
// Match the "special chars" mode of the html-entities library:
// https://github.com/mdevils/html-entities/blob/9ee63a120597292967f7d0d704d68d33950625ee/src/index.ts#L30
text.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;")
.replace("'", "&apos;")
}
pub(crate) fn url_encode(url: &str) -> String {
const ENCODED_CHARS: &AsciiSet = &CONTROLS.add(b'\'').add(b'\n').add(b'"').add(b'<').add(b'>');
utf8_percent_encode(url, ENCODED_CHARS).to_string()
}
#[cfg(test)]
mod test {
use crate::utils::url_encode;
use super::html_entities;
#[test]
fn should_encode_html_entities() {
assert_eq!(
html_entities("<a href=\"http://example.com/\">test</a>"),
"&lt;a href=&quot;http://example.com/&quot;&gt;test&lt;/a&gt;"
);
assert_eq!(html_entities("&gt;"), "&amp;gt;");
assert_eq!(html_entities("'&gt;'"), "&apos;&amp;gt;&apos;");
}
#[test]
fn should_encode_urls() {
assert_eq!(url_encode("http://example.com/"), "http://example.com/");
assert_eq!(url_encode("http://example.com/\""), "http://example.com/%22");
}
}