Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Provide support for pasting lists from google docs #72

Merged
merged 35 commits into from
Aug 5, 2019
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
173e44d
Add support for pasting lists from google docs.
Jul 29, 2019
7c4f7dc
Add unit test for google docslist item filters.
Jul 29, 2019
1111bea
Add data for autoamtic tests for pasted lists from google docs.
Jul 30, 2019
11cbe07
Provide option to obtain model's data.
Jul 30, 2019
348f3b6
Fix typo in folder name.
Jul 30, 2019
419ff3d
Correct test files. Add entry point for tests, add test to dataset.
Jul 30, 2019
3eda19c
Add unit test for mixed lists.
Jul 30, 2019
4a566b2
Fix small typos.
Jul 30, 2019
5bf77e7
Introduce generic nromalizer, to patch content which is wrong but not…
Jul 30, 2019
f6ee361
Add support and provide normalization for repeatedly nested lists.
Jul 31, 2019
2fb7ddb
Ad unit test for fixed filter.
Jul 31, 2019
e8e12c9
Add new automatic test for repeatedly nested lists.
Jul 31, 2019
6c9f2e9
Merge branch 'master' into t/69
Aug 1, 2019
975992e
Support mort cases with unwrapping lists.
Aug 1, 2019
cfb2a35
Fix sibling lists.
Aug 1, 2019
95c024f
Add unit test for sibling list, add autoamtic test for partially sele…
Aug 1, 2019
3806a55
Move generic nromalizer as last one which will be always active.
Aug 1, 2019
16ca74f
Move google docs list filter to list.js file.
Aug 1, 2019
4aa476a
Remove getModelData from manual test.
Aug 1, 2019
70495e8
Remove unecessary if. There always should be at least one active norm…
Aug 1, 2019
68d155e
Rename unwrapParagraph and moveNestedLists... function.
Aug 1, 2019
b8eaff4
Move description to jsdoc.
Aug 1, 2019
861ba50
Utilize walker insted of recurence to fix lists indentation.
Aug 1, 2019
a6fa897
Replace recurrence in unwrapParagraph with treewalker.
Aug 1, 2019
40c88a2
Simplify comment.
Aug 1, 2019
c9b208f
Correct names of generic normalizer in unit tests.
Aug 1, 2019
85c8764
Apply suggestions from code review
msamsel Aug 2, 2019
4a1dd43
Utilize new API of upcast writer.
Aug 2, 2019
383b387
Add comment to code and improve docs description.
Aug 2, 2019
1a95ece
Correct variables names and docs entries.
Aug 2, 2019
676730c
Remove generic normalizer.
Aug 2, 2019
d2da715
Fix examples alignment in the docs of fixListIndentation() function.
jodator Aug 5, 2019
5513c92
Use isList() helper function in isNewListNeeded.
jodator Aug 5, 2019
3d467da
Move unwrapParagraphInListItem & fixListIndentation to the top of a f…
jodator Aug 5, 2019
0a56b5e
Remove comment.
jodator Aug 5, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions src/filters/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,88 @@ function isNewListNeeded( previousItem, currentItem ) {
// Even with the same id the list does not have to be continuous (#43).
return !previousSibling.is( 'ul' ) && !previousSibling.is( 'ol' );
}

// Paste from Google Docs
/**
* Removes paragraph wrapping content inside a list item.
*
* @param {module:engine/view/documentfragment~DocumentFragment} documentFragment
* @param {module:engine/view/upcastwriter~UpcastWriter} writer
*/
export function unwrapParagraphInListItem( documentFragment, writer ) {
for ( const value of writer.createRangeIn( documentFragment ) ) {
const element = value.item;

if ( element.is( 'li' ) ) {
// Google Docs allows on single paragraph inside LI.
const firstChild = element.getChild( 0 );

if ( firstChild.is( 'p' ) ) {
jodator marked this conversation as resolved.
Show resolved Hide resolved
writer.unwrapElement( firstChild );
}
}
}
}

/**
* Fix structure of nested lists to follow HTML guidelines and normalize content in predictable way.
*
* 1. Move nested lists to have sure that list items are the only children of lists.
*
* before: after:
* OL OL
* |-> LI |-> LI
* |-> OL |-> OL
* |-> LI |-> LI
*
* 2. Remove additional indentation which cannot be recreated in HTML structure.
*
* before: after:
* OL OL
* |-> LI |-> LI
* |-> OL |-> OL
* |-> OL |-> LI
* | |-> OL |-> LI
* | |-> OL
* | |-> LI
* |-> LI
*
* before: after:
* OL OL
* |-> OL |-> LI
* |-> OL
* |-> OL
* |-> LI
*
* @param {module:engine/view/documentfragment~DocumentFragment} documentFragment
* @param {module:engine/view/upcastwriter~UpcastWriter} writer
*/
export function fixListIndentation( documentFragment, writer ) {
for ( const value of writer.createRangeIn( documentFragment ) ) {
const element = value.item;

// case 1: The previous sibling of a list is a list item.
if ( element.is( 'li' ) ) {
const next = element.nextSibling;

if ( next && isList( next ) ) {
writer.remove( next );
writer.insertChild( element.childCount, next, element );
}
}

// case 2: The list is the first child of another list.
if ( isList( element ) ) {
let firstChild = element.getChild( 0 );

while ( isList( firstChild ) ) {
writer.unwrapElement( firstChild );
firstChild = element.getChild( 0 );
}
}
}
}

function isList( element ) {
return element.is( 'ol' ) || element.is( 'ul' );
}
35 changes: 35 additions & 0 deletions src/normalizers/genericnormalizer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/**
* @module paste-from-office/normalizers/genericnormalizer
*/

import { unwrapParagraphInListItem, fixListIndentation } from '../filters/list';
import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter';

/**
* Normalizer for the content pasted from different sources, where we can detect wrong syntax.
*
* @implements module:paste-from-office/normalizer~Normalizer
*/
export default class GenericNormalizer {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we need GenericNormalizer. It duplicates fixes for GD normalizer. It probably might also duplicate MSWord normalizer.

Right now I don't have a clear idea of how to deal with those fixes - they might be helpfull generally speaking one can copy such weird list indentation from various sources. But OTOH such argument is also valid for every other filter.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking about removing this ATM but I'll give it another thought.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here are the details and comments about why it's important:
#72 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it happen only on Safari? If so I'd still ad some env checking to be sure that this normalizer doesn't fire up too often. @mlewand WDYT? OTOH this normalizer will always fix pasted lists (even from other sources) so it may be beneficial.

I see two routes:

  1. Make it run only for Safari and name it more specific, like: SafariListNormalizer
  2. Make it general list fixer - the I'd name it NestedListNormalizer and maybe add a check if pasted content has broken list (AFAICS ul></ul, /p></li and /li><ul could be checked - with ul|ol variants check) - but the check idea is something to give another though if it makes sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not stick to "List" name here. As content is unrecognizable for any filter.
Google Docs for some specific contents (with tables) under Safari doesn't provide any string which could provide us info, that content came from Google Docs. It's just regular broken HTML.
So the same situation will repeat for heading filters or any other further filters. Where we still won't be knowing that content came from Google Docs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So in such cases better could be SafariNormalizer however this might be a little bit confusing, as there will be content from Safari that sometimes will activate SafariNormalizer and sometimes GoogleDocsNormalizer
We can prioritize it, but then it will be an exact copy of GoogleDocs normalizer and in such case, in my opinion, better will be making a change in isActive() method of google normalizer:

isActive( htmlString ) {
	return googleDocsMatch.test( htmlString ) || env.isSafari;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jodator @msamsel I took a peek at it and I see that:

  • Safari problem described in Provide support for pasting lists from google docs #72 (comment) happens when table is first, but does not happen if there's anything preceding it, which makes the bug that much less common (safari * edge case = very rare case).

    First of all it does look like upstream issue, I suspected Safari at first, but given that id appears if table is not the first item it sounds it's more of a Google Docs bug. Have we checked if this issue can be reported and tracked?

  • I don't feel good with exposing this filter as a generic feature if we don't have any need for it (no requests, no real use cases).

Since still pretty much all the cases will be handled on safari except this particular oddity, let's wait for some real life cases where it will be useful. Until then let's extract mentioned Safari problem as a separate issue and put it to backlog.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jodator I removed this normalizer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I extracted this case to separate issue: #75

/**
* @inheritDoc
*/
isActive() {
return true;
}

/**
* @inheritDoc
*/
execute( data ) {
const writer = new UpcastWriter();

fixListIndentation( data.content, writer );
unwrapParagraphInListItem( data.content, writer );
}
}
3 changes: 3 additions & 0 deletions src/normalizers/googledocsnormalizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

import removeBoldWrapper from '../filters/removeboldwrapper';
import { unwrapParagraphInListItem, fixListIndentation } from '../filters/list';
import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter';

const googleDocsMatch = /id=("|')docs-internal-guid-[-0-9a-f]+("|')/i;
Expand All @@ -32,5 +33,7 @@ export default class GoogleDocsNormalizer {
const writer = new UpcastWriter();

removeBoldWrapper( data.content, writer );
fixListIndentation( data.content, writer );
unwrapParagraphInListItem( data.content, writer );
}
}
10 changes: 6 additions & 4 deletions src/pastefromoffice.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import GoogleDocsNormalizer from './normalizers/googledocsnormalizer';
import MSWordNormalizer from './normalizers/mswordnormalizer';
import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';
import GenericNormalizer from './normalizers/genericnormalizer';

/**
* The Paste from Office plugin.
Expand Down Expand Up @@ -53,6 +54,9 @@ export default class PasteFromOffice extends Plugin {
normalizers.push( new MSWordNormalizer() );
normalizers.push( new GoogleDocsNormalizer() );

// GenericNormalizers has to be added as last one, as it always will be activated.
normalizers.push( new GenericNormalizer() );

editor.plugins.get( 'Clipboard' ).on(
'inputTransformation',
( evt, data ) => {
Expand All @@ -63,11 +67,9 @@ export default class PasteFromOffice extends Plugin {
const htmlString = data.dataTransfer.getData( 'text/html' );
const activeNormalizer = normalizers.find( normalizer => normalizer.isActive( htmlString ) );

if ( activeNormalizer ) {
activeNormalizer.execute( data );
activeNormalizer.execute( data );

data.isTransformedWithPasteFromOffice = true;
}
data.isTransformedWithPasteFromOffice = true;
},
{ priority: 'high' }
);
Expand Down
22 changes: 22 additions & 0 deletions tests/_data/generic/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

import listInTable from './list-in-table/input.html';
import listInTableNormalized from './list-in-table/normalized.html';
import listInTableModel from './list-in-table/model.html';

export const fixtures = {
input: {
listInTable
},
normalized: {
listInTable: listInTableNormalized
},
model: {
listInTable: listInTableModel
}
};

export const browserFixtures = {};
1 change: 1 addition & 0 deletions tests/_data/generic/list-in-table/input.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div dir="ltr" style="font-family: -webkit-standard; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; text-decoration: none; caret-color: rgb(0, 0, 0); color: rgb(0, 0, 0); margin-left: 0pt;"><table style="border: none; border-collapse: collapse; width: 468pt;"><tbody><tr style="height: 50pt;"><td style="border: 1pt solid rgb(0, 0, 0); vertical-align: top; padding: 5pt;"><ul style="margin-top: 0pt; margin-bottom: 0pt;"><li dir="ltr" style="list-style-type: disc; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">One</span></p></li><li dir="ltr" style="list-style-type: disc; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Two</span></p></li><li dir="ltr" style="list-style-type: disc; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Three</span></p></li></ul></td></tr></tbody></table></div>
1 change: 1 addition & 0 deletions tests/_data/generic/list-in-table/lists-in-table.html

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions tests/_data/generic/list-in-table/model.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<table><tableRow><tableCell><listItem listIndent="0" listType="bulleted">One</listItem><listItem listIndent="0" listType="bulleted">Two</listItem><listItem listIndent="0" listType="bulleted">Three</listItem></tableCell></tableRow></table>
1 change: 1 addition & 0 deletions tests/_data/generic/list-in-table/normalized.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div dir="ltr" style="-webkit-text-size-adjust:auto;-webkit-text-stroke-width:0px;caret-color:rgb(0, 0, 0);color:rgb(0, 0, 0);font-family:-webkit-standard;font-style:normal;font-variant-caps:normal;font-weight:normal;letter-spacing:normal;margin-left:0pt;orphans:auto;text-align:start;text-decoration:none;text-indent:0px;text-transform:none;white-space:normal;widows:auto;word-spacing:0px"><table style="border:none;border-collapse:collapse;width:468pt"><tbody><tr style="height:50pt"><td style="border:1pt solid rgb(0, 0, 0);padding:5pt;vertical-align:top"><ul style="margin-bottom:0pt;margin-top:0pt"><li dir="ltr" style="background-color:transparent;color:rgb(0, 0, 0);font-family:Arial;font-size:11pt;font-style:normal;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-position:normal;font-weight:400;list-style-type:disc;text-decoration:none;vertical-align:baseline;white-space:pre"><span style="background-color:transparent;color:rgb(0, 0, 0);font-family:Arial;font-size:11pt;font-style:normal;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-position:normal;font-weight:400;text-decoration:none;vertical-align:baseline;white-space:pre-wrap">One</span></li><li dir="ltr" style="background-color:transparent;color:rgb(0, 0, 0);font-family:Arial;font-size:11pt;font-style:normal;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-position:normal;font-weight:400;list-style-type:disc;text-decoration:none;vertical-align:baseline;white-space:pre"><span style="background-color:transparent;color:rgb(0, 0, 0);font-family:Arial;font-size:11pt;font-style:normal;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-position:normal;font-weight:400;text-decoration:none;vertical-align:baseline;white-space:pre-wrap">Two</span></li><li dir="ltr" style="background-color:transparent;color:rgb(0, 0, 0);font-family:Arial;font-size:11pt;font-style:normal;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-position:normal;font-weight:400;list-style-type:disc;text-decoration:none;vertical-align:baseline;white-space:pre"><span style="background-color:transparent;color:rgb(0, 0, 0);font-family:Arial;font-size:11pt;font-style:normal;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-position:normal;font-weight:400;text-decoration:none;vertical-align:baseline;white-space:pre-wrap">Three</span></li></ul></td></tr></tbody></table></div>
43 changes: 43 additions & 0 deletions tests/_data/paste-from-google-docs/lists/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

import nestedOrderedList from './nested-ordered-lists/input.html';
import nestedOrderedListNormalized from './nested-ordered-lists/normalized.html';
import nestedOrderedListModel from './nested-ordered-lists/model.html';

import mixedList from './mixed-list/input.html';
import mixedListNormalized from './mixed-list/normalized.html';
import mixedListModel from './mixed-list/model.html';

import repeatedlyNestedList from './repeatedly-nested-list/input.html';
import repeatedlyNestedListNormalized from './repeatedly-nested-list/normalized.html';
import repeatedlyNestedListModel from './repeatedly-nested-list/model.html';

import partiallySelected from './partially-selected/input.html';
import partiallySelectedNormalized from './partially-selected/normalized.html';
import partiallySelectedModel from './partially-selected/model.html';

export const fixtures = {
input: {
nestedOrderedList,
mixedList,
repeatedlyNestedList,
partiallySelected
},
normalized: {
nestedOrderedList: nestedOrderedListNormalized,
mixedList: mixedListNormalized,
repeatedlyNestedList: repeatedlyNestedListNormalized,
partiallySelected: partiallySelectedNormalized
},
model: {
nestedOrderedList: nestedOrderedListModel,
mixedList: mixedListModel,
repeatedlyNestedList: repeatedlyNestedListModel,
partiallySelected: partiallySelectedModel
}
};

export const browserFixtures = {};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<meta charset='utf-8'><meta charset="utf-8"><b style="font-weight:normal;" id="docs-internal-guid-6aba46e4-7fff-ffbf-43f7-6080e7ecadb8"><ol style="margin-top:0pt;margin-bottom:0pt;"><li dir="ltr" style="list-style-type:decimal;font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1</span></p></li><ul style="margin-top:0pt;margin-bottom:0pt;"><li dir="ltr" style="list-style-type:circle;font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">A</span></p></li><ol style="margin-top:0pt;margin-bottom:0pt;"><li dir="ltr" style="list-style-type:lower-roman;font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1</span></p></li></ol></ul><li dir="ltr" style="list-style-type:decimal;font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">2</span></p></li><li dir="ltr" style="list-style-type:decimal;font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">3</span></p></li><ul style="margin-top:0pt;margin-bottom:0pt;"><li dir="ltr" style="list-style-type:circle;font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">A</span></p></li><li dir="ltr" style="list-style-type:circle;font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">B</span></p></li></ul></ol><ul style="margin-top:0pt;margin-bottom:0pt;"><li dir="ltr" style="list-style-type:disc;font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">A</span></p></li><ol style="margin-top:0pt;margin-bottom:0pt;"><li dir="ltr" style="list-style-type:lower-alpha;font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1</span></p></li><li dir="ltr" style="list-style-type:lower-alpha;font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">2</span></p></li></ol></ul></b>

Large diffs are not rendered by default.

Loading