mediawiki-extensions-Visual.../modules/ve/test/dm/ve.dm.example.js

3331 lines
85 KiB
JavaScript
Raw Normal View History

JSDuck: Generated code documentation! See CODING.md for how to run it. Mistakes fixed: * Warning: Unknown type function -> Function * Warning: Unknown type DOMElement -> HTMLElement * Warning: Unknown type DOM Node -> HTMLElement * Warning: Unknown type Integer -> Mixed * Warning: Unknown type Command -> ve.Command * Warning: Unknown type any -> number * Warning: Unknown type ve.Transaction -> ve.dm.Transaction * Warning: Unknown type ve.dm.AnnotationSet -> ve.AnnotationSet * Warning: Unknown type false -> boolean * Warning: Unknown type ve.dm.AlienNode ve.dm doesn't have a generic AlienNode like ve.ce -> Unknown type ve.dm.AlienInlineNode|ve.dm.AlienBlockNode * Warning: Unknown type ve.ve.Surface -> ve.ce.Surface * ve.example.lookupNode: -> Last @param should be @return * ve.dm.Transaction.prototype.pushReplace: -> @param {Array] should be @param {Array} * Warning: ve.BranchNode.js:27: {@link ve.Node#hasChildren} links to non-existing member -> (removed) * Warning: ve.LeafNode.js:21: {@link ve.Node#hasChildren} links to non-existing member -> (removed) Differences fixed: * Variadic arguments are like @param {Type...} [name] instead of @param {Type} [name...] * Convert all file headers from /** to /*! because JSDuck tries to parse all /** blocks and fails to parse with all sorts of errors for "Global property", "Unnamed property", and "Duplicate property". Find: \/\*\*([^@]+)(@copyright) Replace: /*!$1$2 * Indented blocks are considered code examples. A few methods had documentation with numbered lists that were indented, which have now been updated to not be intended. * The free-form text descriptions are parsed with Markdown, which requires lists to be separated from paragraphs by an empty line. And we should use `backticks` instead of {braces} for inline code in text paragraphs. * Doc blocks for classes and their constructor have to be in the correct order (@constructor, @param, @return must be before @class, @abstract, @extends etc.) * `@extends Class` must not have Class {wrapped} * @throws must start with a {Type} * @example means something else. It is used for an inline demo iframe, not code block. For that simply indent with spaces. * @member means something else. Non-function properties are marked with @property, not @member. * To create a link to a class or member, in most cases the name is enough to create a link. E.g. Foo, Foo.bar, Foo.bar#quux, where a hash stands for "instance member", so Foo.bar#quux, links to Foo.bar.prototype.quux (the is not supported, as "prototype" is considered an implementation detail, it only indexes class name and method name). If the magic linker doesn't work for some case, the verbose syntax is {@link #target label}. * @property can't have sub-properties (nested @param and @return values are supported, only @static @property can't be nested). We only have one case of this, which can be worked around by moving those in a new virtual class. The code is unaltered (only moved down so that it isn't with the scope of the main @class block). ve.dm.TransactionProcessor.processors. New: * @mixins: Classes mixed into the current class. * @event: Events that can be emitted by a class. These are also inherited by subclasses. (+ @param, @return and @preventable). So ve.Node#event-attach is inherited to ve.dm.BreakNode, just like @method is. * @singleton: Plain objects such as ve, ve.dm, ve.ce were missing documentation causing a tree error. Documented those as a JSDuck singleton, which they but just weren't documented yet. NB: Members of @singleton don't need @static (if present, triggers a compiler warning). * @chainable: Shorthand for "@return this". We were using "@return {classname}" which is ambiguous (returns the same instance or another instance?), @chainable is specifically for "@return this". Creates proper labels in the generated HTML pages. Removed: * @mixin: (not to be confused with @mixins). Not supported by JSDuck. Every class is standalone anyway. Where needed marked them @class + @abstract instead. Change-Id: I6a7c9e8ee8f995731bc205d666167874eb2ebe23
2013-01-04 08:54:17 +00:00
/*!
* VisualEditor DataModel example data sets.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
JSDuck: Generated code documentation! See CODING.md for how to run it. Mistakes fixed: * Warning: Unknown type function -> Function * Warning: Unknown type DOMElement -> HTMLElement * Warning: Unknown type DOM Node -> HTMLElement * Warning: Unknown type Integer -> Mixed * Warning: Unknown type Command -> ve.Command * Warning: Unknown type any -> number * Warning: Unknown type ve.Transaction -> ve.dm.Transaction * Warning: Unknown type ve.dm.AnnotationSet -> ve.AnnotationSet * Warning: Unknown type false -> boolean * Warning: Unknown type ve.dm.AlienNode ve.dm doesn't have a generic AlienNode like ve.ce -> Unknown type ve.dm.AlienInlineNode|ve.dm.AlienBlockNode * Warning: Unknown type ve.ve.Surface -> ve.ce.Surface * ve.example.lookupNode: -> Last @param should be @return * ve.dm.Transaction.prototype.pushReplace: -> @param {Array] should be @param {Array} * Warning: ve.BranchNode.js:27: {@link ve.Node#hasChildren} links to non-existing member -> (removed) * Warning: ve.LeafNode.js:21: {@link ve.Node#hasChildren} links to non-existing member -> (removed) Differences fixed: * Variadic arguments are like @param {Type...} [name] instead of @param {Type} [name...] * Convert all file headers from /** to /*! because JSDuck tries to parse all /** blocks and fails to parse with all sorts of errors for "Global property", "Unnamed property", and "Duplicate property". Find: \/\*\*([^@]+)(@copyright) Replace: /*!$1$2 * Indented blocks are considered code examples. A few methods had documentation with numbered lists that were indented, which have now been updated to not be intended. * The free-form text descriptions are parsed with Markdown, which requires lists to be separated from paragraphs by an empty line. And we should use `backticks` instead of {braces} for inline code in text paragraphs. * Doc blocks for classes and their constructor have to be in the correct order (@constructor, @param, @return must be before @class, @abstract, @extends etc.) * `@extends Class` must not have Class {wrapped} * @throws must start with a {Type} * @example means something else. It is used for an inline demo iframe, not code block. For that simply indent with spaces. * @member means something else. Non-function properties are marked with @property, not @member. * To create a link to a class or member, in most cases the name is enough to create a link. E.g. Foo, Foo.bar, Foo.bar#quux, where a hash stands for "instance member", so Foo.bar#quux, links to Foo.bar.prototype.quux (the is not supported, as "prototype" is considered an implementation detail, it only indexes class name and method name). If the magic linker doesn't work for some case, the verbose syntax is {@link #target label}. * @property can't have sub-properties (nested @param and @return values are supported, only @static @property can't be nested). We only have one case of this, which can be worked around by moving those in a new virtual class. The code is unaltered (only moved down so that it isn't with the scope of the main @class block). ve.dm.TransactionProcessor.processors. New: * @mixins: Classes mixed into the current class. * @event: Events that can be emitted by a class. These are also inherited by subclasses. (+ @param, @return and @preventable). So ve.Node#event-attach is inherited to ve.dm.BreakNode, just like @method is. * @singleton: Plain objects such as ve, ve.dm, ve.ce were missing documentation causing a tree error. Documented those as a JSDuck singleton, which they but just weren't documented yet. NB: Members of @singleton don't need @static (if present, triggers a compiler warning). * @chainable: Shorthand for "@return this". We were using "@return {classname}" which is ambiguous (returns the same instance or another instance?), @chainable is specifically for "@return this". Creates proper labels in the generated HTML pages. Removed: * @mixin: (not to be confused with @mixins). Not supported by JSDuck. Every class is standalone anyway. Where needed marked them @class + @abstract instead. Change-Id: I6a7c9e8ee8f995731bc205d666167874eb2ebe23
2013-01-04 08:54:17 +00:00
/**
* @class
* @singleton
* @ignore
*/
ve.dm.example = {};
JSDuck: Generated code documentation! See CODING.md for how to run it. Mistakes fixed: * Warning: Unknown type function -> Function * Warning: Unknown type DOMElement -> HTMLElement * Warning: Unknown type DOM Node -> HTMLElement * Warning: Unknown type Integer -> Mixed * Warning: Unknown type Command -> ve.Command * Warning: Unknown type any -> number * Warning: Unknown type ve.Transaction -> ve.dm.Transaction * Warning: Unknown type ve.dm.AnnotationSet -> ve.AnnotationSet * Warning: Unknown type false -> boolean * Warning: Unknown type ve.dm.AlienNode ve.dm doesn't have a generic AlienNode like ve.ce -> Unknown type ve.dm.AlienInlineNode|ve.dm.AlienBlockNode * Warning: Unknown type ve.ve.Surface -> ve.ce.Surface * ve.example.lookupNode: -> Last @param should be @return * ve.dm.Transaction.prototype.pushReplace: -> @param {Array] should be @param {Array} * Warning: ve.BranchNode.js:27: {@link ve.Node#hasChildren} links to non-existing member -> (removed) * Warning: ve.LeafNode.js:21: {@link ve.Node#hasChildren} links to non-existing member -> (removed) Differences fixed: * Variadic arguments are like @param {Type...} [name] instead of @param {Type} [name...] * Convert all file headers from /** to /*! because JSDuck tries to parse all /** blocks and fails to parse with all sorts of errors for "Global property", "Unnamed property", and "Duplicate property". Find: \/\*\*([^@]+)(@copyright) Replace: /*!$1$2 * Indented blocks are considered code examples. A few methods had documentation with numbered lists that were indented, which have now been updated to not be intended. * The free-form text descriptions are parsed with Markdown, which requires lists to be separated from paragraphs by an empty line. And we should use `backticks` instead of {braces} for inline code in text paragraphs. * Doc blocks for classes and their constructor have to be in the correct order (@constructor, @param, @return must be before @class, @abstract, @extends etc.) * `@extends Class` must not have Class {wrapped} * @throws must start with a {Type} * @example means something else. It is used for an inline demo iframe, not code block. For that simply indent with spaces. * @member means something else. Non-function properties are marked with @property, not @member. * To create a link to a class or member, in most cases the name is enough to create a link. E.g. Foo, Foo.bar, Foo.bar#quux, where a hash stands for "instance member", so Foo.bar#quux, links to Foo.bar.prototype.quux (the is not supported, as "prototype" is considered an implementation detail, it only indexes class name and method name). If the magic linker doesn't work for some case, the verbose syntax is {@link #target label}. * @property can't have sub-properties (nested @param and @return values are supported, only @static @property can't be nested). We only have one case of this, which can be worked around by moving those in a new virtual class. The code is unaltered (only moved down so that it isn't with the scope of the main @class block). ve.dm.TransactionProcessor.processors. New: * @mixins: Classes mixed into the current class. * @event: Events that can be emitted by a class. These are also inherited by subclasses. (+ @param, @return and @preventable). So ve.Node#event-attach is inherited to ve.dm.BreakNode, just like @method is. * @singleton: Plain objects such as ve, ve.dm, ve.ce were missing documentation causing a tree error. Documented those as a JSDuck singleton, which they but just weren't documented yet. NB: Members of @singleton don't need @static (if present, triggers a compiler warning). * @chainable: Shorthand for "@return this". We were using "@return {classname}" which is ambiguous (returns the same instance or another instance?), @chainable is specifically for "@return this". Creates proper labels in the generated HTML pages. Removed: * @mixin: (not to be confused with @mixins). Not supported by JSDuck. Every class is standalone anyway. Where needed marked them @class + @abstract instead. Change-Id: I6a7c9e8ee8f995731bc205d666167874eb2ebe23
2013-01-04 08:54:17 +00:00
/* Methods */
/**
* Convert arrays of shorthand annotations in a data fragment to AnnotationSets with real
* annotation objects, and wraps the result in a ve.dm.ElementLinearData object.
*
* Shorthand notation for annotations is:
Great Annotation Refactor of 2013 This changes the annotation API to be the same as the node API, sans a few boolean flags that don't apply. The APIs were different, but there was really no good reason why, so this makes things simpler for API users. It also means we'll be able to factor a bunch of things out because they're now duplicated between nodes, meta items and annotations. Linear model annotations are now objects with 'type' and 'attributes' properties (rather than 'name' and 'data'), for consistency with elements. They now also contain html/0/* attributes for HTML attribute preservation, which obsoletes the htmlTagName and htmlAttributes properties. dm.Annotation subclasses take a reference to such an object and implement conversion using .static.toDataElement and .static.toDomElements just like nodes do. The custom .getHash() functions are no longer necessary because of the way HTML attribute preservation was reimplemented. CE rendering has been moved out of dm.Annotation (it never made sense to have CE rendering functions in DM classes, this was bothering me) and into separate ce.Annotation subclasses. These are very similar to CE nodes in that they have a this.$ generated based on something in the DM; the main difference is that nodes listen to events and update themselves, whereas annotations are static and are simply destroyed and rebuilt when they change. This change also adds whitelisted HTML attribute rendering for annotations, as well as class="ve-ce-FooAnnotation" attributes. Now that annotation classes produce real DOM nodes rather than weird objects describing HTML tags, we can't generate HTML as a string in ce.ContentBranchNode anymore. getRenderedContents() has been rewritten to be much more similar to the way the converter renders annotations; in fact, significant parts of it were copied from the converter, so that should be factored out in the future. This change actually fixes an annotation rendering discrepancy between ce.ContentBranchNode and dm.Converter; see the diff of ve.ce.ContentBranchNode.test.js. ve.ce.MWEntityNode.js: * Remove stray property ve.dm.MWExternalLinkAnnotation.js: * Store 'rel' attribute ve.dm.TextStyleAnnotation.js: * Put all the conversion logic in the abstract base class ve.dm.Converter.js: * Also feed annotations through getDomElementsFromDataElement() and createDataElement() ve.dm.Node.js: * Fix undocumented property ve.ce.ContentBranchNode.test.js: * Add descriptive messages for each test case * Compare DOM trees, not HTML strings * Compare without all the class="ve-ce-WhateverAnnotation" clutter ve.ui.LinkInspector.js: * Replace direct .getHash() calls (evil!) with ve.getHash() Bug: 46464 Bug: 44808 Change-Id: I31991488579b8cce6d98ed8b29b486ba5ec38cdc
2013-04-02 17:23:33 +00:00
* [ 'a', [ { 'type': 'link', 'attributes': { 'href': '...' } ] ]
*
* The actual storage format has an instance of ve.dm.LinkAnnotation instead of the plain object,
* and an instance of ve.dm.AnnotationSet instead of the array.
*
JSDuck: Generated code documentation! See CODING.md for how to run it. Mistakes fixed: * Warning: Unknown type function -> Function * Warning: Unknown type DOMElement -> HTMLElement * Warning: Unknown type DOM Node -> HTMLElement * Warning: Unknown type Integer -> Mixed * Warning: Unknown type Command -> ve.Command * Warning: Unknown type any -> number * Warning: Unknown type ve.Transaction -> ve.dm.Transaction * Warning: Unknown type ve.dm.AnnotationSet -> ve.AnnotationSet * Warning: Unknown type false -> boolean * Warning: Unknown type ve.dm.AlienNode ve.dm doesn't have a generic AlienNode like ve.ce -> Unknown type ve.dm.AlienInlineNode|ve.dm.AlienBlockNode * Warning: Unknown type ve.ve.Surface -> ve.ce.Surface * ve.example.lookupNode: -> Last @param should be @return * ve.dm.Transaction.prototype.pushReplace: -> @param {Array] should be @param {Array} * Warning: ve.BranchNode.js:27: {@link ve.Node#hasChildren} links to non-existing member -> (removed) * Warning: ve.LeafNode.js:21: {@link ve.Node#hasChildren} links to non-existing member -> (removed) Differences fixed: * Variadic arguments are like @param {Type...} [name] instead of @param {Type} [name...] * Convert all file headers from /** to /*! because JSDuck tries to parse all /** blocks and fails to parse with all sorts of errors for "Global property", "Unnamed property", and "Duplicate property". Find: \/\*\*([^@]+)(@copyright) Replace: /*!$1$2 * Indented blocks are considered code examples. A few methods had documentation with numbered lists that were indented, which have now been updated to not be intended. * The free-form text descriptions are parsed with Markdown, which requires lists to be separated from paragraphs by an empty line. And we should use `backticks` instead of {braces} for inline code in text paragraphs. * Doc blocks for classes and their constructor have to be in the correct order (@constructor, @param, @return must be before @class, @abstract, @extends etc.) * `@extends Class` must not have Class {wrapped} * @throws must start with a {Type} * @example means something else. It is used for an inline demo iframe, not code block. For that simply indent with spaces. * @member means something else. Non-function properties are marked with @property, not @member. * To create a link to a class or member, in most cases the name is enough to create a link. E.g. Foo, Foo.bar, Foo.bar#quux, where a hash stands for "instance member", so Foo.bar#quux, links to Foo.bar.prototype.quux (the is not supported, as "prototype" is considered an implementation detail, it only indexes class name and method name). If the magic linker doesn't work for some case, the verbose syntax is {@link #target label}. * @property can't have sub-properties (nested @param and @return values are supported, only @static @property can't be nested). We only have one case of this, which can be worked around by moving those in a new virtual class. The code is unaltered (only moved down so that it isn't with the scope of the main @class block). ve.dm.TransactionProcessor.processors. New: * @mixins: Classes mixed into the current class. * @event: Events that can be emitted by a class. These are also inherited by subclasses. (+ @param, @return and @preventable). So ve.Node#event-attach is inherited to ve.dm.BreakNode, just like @method is. * @singleton: Plain objects such as ve, ve.dm, ve.ce were missing documentation causing a tree error. Documented those as a JSDuck singleton, which they but just weren't documented yet. NB: Members of @singleton don't need @static (if present, triggers a compiler warning). * @chainable: Shorthand for "@return this". We were using "@return {classname}" which is ambiguous (returns the same instance or another instance?), @chainable is specifically for "@return this". Creates proper labels in the generated HTML pages. Removed: * @mixin: (not to be confused with @mixins). Not supported by JSDuck. Every class is standalone anyway. Where needed marked them @class + @abstract instead. Change-Id: I6a7c9e8ee8f995731bc205d666167874eb2ebe23
2013-01-04 08:54:17 +00:00
* @method
* @param {Array} data Linear model data
* @param {ve.dm.IndexValueStore} [store] Index-value store to use, creates one if undefined
* @returns {ve.dm.ElementLinearData} Element linear data store
* @throws {Error} Example data passed to preprocessAnnotations by reference
*/
ve.dm.example.preprocessAnnotations = function ( data, store ) {
var i, key;
// Sanity check to make sure ve.dm.example data has not been passed in
// by reference. Always use ve.copyArray.
for ( i in ve.dm.example ) {
if ( data === ve.dm.example[i] ) {
throw new Error( 'Example data passed to preprocessAnnotations by reference' );
}
}
store = store || new ve.dm.IndexValueStore();
for ( i = 0; i < data.length; i++ ) {
key = data[i].annotations ? 'annotations' : 1;
// check for shorthand annotation objects in array
if ( ve.isArray( data[i][key] ) && data[i][key][0].type ) {
data[i][key] = ve.dm.example.createAnnotationSet( store, data[i][key] ).getIndexes();
}
}
return new ve.dm.ElementLinearData( store, data );
};
/**
* Create an annotation object from shorthand notation.
JSDuck: Generated code documentation! See CODING.md for how to run it. Mistakes fixed: * Warning: Unknown type function -> Function * Warning: Unknown type DOMElement -> HTMLElement * Warning: Unknown type DOM Node -> HTMLElement * Warning: Unknown type Integer -> Mixed * Warning: Unknown type Command -> ve.Command * Warning: Unknown type any -> number * Warning: Unknown type ve.Transaction -> ve.dm.Transaction * Warning: Unknown type ve.dm.AnnotationSet -> ve.AnnotationSet * Warning: Unknown type false -> boolean * Warning: Unknown type ve.dm.AlienNode ve.dm doesn't have a generic AlienNode like ve.ce -> Unknown type ve.dm.AlienInlineNode|ve.dm.AlienBlockNode * Warning: Unknown type ve.ve.Surface -> ve.ce.Surface * ve.example.lookupNode: -> Last @param should be @return * ve.dm.Transaction.prototype.pushReplace: -> @param {Array] should be @param {Array} * Warning: ve.BranchNode.js:27: {@link ve.Node#hasChildren} links to non-existing member -> (removed) * Warning: ve.LeafNode.js:21: {@link ve.Node#hasChildren} links to non-existing member -> (removed) Differences fixed: * Variadic arguments are like @param {Type...} [name] instead of @param {Type} [name...] * Convert all file headers from /** to /*! because JSDuck tries to parse all /** blocks and fails to parse with all sorts of errors for "Global property", "Unnamed property", and "Duplicate property". Find: \/\*\*([^@]+)(@copyright) Replace: /*!$1$2 * Indented blocks are considered code examples. A few methods had documentation with numbered lists that were indented, which have now been updated to not be intended. * The free-form text descriptions are parsed with Markdown, which requires lists to be separated from paragraphs by an empty line. And we should use `backticks` instead of {braces} for inline code in text paragraphs. * Doc blocks for classes and their constructor have to be in the correct order (@constructor, @param, @return must be before @class, @abstract, @extends etc.) * `@extends Class` must not have Class {wrapped} * @throws must start with a {Type} * @example means something else. It is used for an inline demo iframe, not code block. For that simply indent with spaces. * @member means something else. Non-function properties are marked with @property, not @member. * To create a link to a class or member, in most cases the name is enough to create a link. E.g. Foo, Foo.bar, Foo.bar#quux, where a hash stands for "instance member", so Foo.bar#quux, links to Foo.bar.prototype.quux (the is not supported, as "prototype" is considered an implementation detail, it only indexes class name and method name). If the magic linker doesn't work for some case, the verbose syntax is {@link #target label}. * @property can't have sub-properties (nested @param and @return values are supported, only @static @property can't be nested). We only have one case of this, which can be worked around by moving those in a new virtual class. The code is unaltered (only moved down so that it isn't with the scope of the main @class block). ve.dm.TransactionProcessor.processors. New: * @mixins: Classes mixed into the current class. * @event: Events that can be emitted by a class. These are also inherited by subclasses. (+ @param, @return and @preventable). So ve.Node#event-attach is inherited to ve.dm.BreakNode, just like @method is. * @singleton: Plain objects such as ve, ve.dm, ve.ce were missing documentation causing a tree error. Documented those as a JSDuck singleton, which they but just weren't documented yet. NB: Members of @singleton don't need @static (if present, triggers a compiler warning). * @chainable: Shorthand for "@return this". We were using "@return {classname}" which is ambiguous (returns the same instance or another instance?), @chainable is specifically for "@return this". Creates proper labels in the generated HTML pages. Removed: * @mixin: (not to be confused with @mixins). Not supported by JSDuck. Every class is standalone anyway. Where needed marked them @class + @abstract instead. Change-Id: I6a7c9e8ee8f995731bc205d666167874eb2ebe23
2013-01-04 08:54:17 +00:00
* @method
Great Annotation Refactor of 2013 This changes the annotation API to be the same as the node API, sans a few boolean flags that don't apply. The APIs were different, but there was really no good reason why, so this makes things simpler for API users. It also means we'll be able to factor a bunch of things out because they're now duplicated between nodes, meta items and annotations. Linear model annotations are now objects with 'type' and 'attributes' properties (rather than 'name' and 'data'), for consistency with elements. They now also contain html/0/* attributes for HTML attribute preservation, which obsoletes the htmlTagName and htmlAttributes properties. dm.Annotation subclasses take a reference to such an object and implement conversion using .static.toDataElement and .static.toDomElements just like nodes do. The custom .getHash() functions are no longer necessary because of the way HTML attribute preservation was reimplemented. CE rendering has been moved out of dm.Annotation (it never made sense to have CE rendering functions in DM classes, this was bothering me) and into separate ce.Annotation subclasses. These are very similar to CE nodes in that they have a this.$ generated based on something in the DM; the main difference is that nodes listen to events and update themselves, whereas annotations are static and are simply destroyed and rebuilt when they change. This change also adds whitelisted HTML attribute rendering for annotations, as well as class="ve-ce-FooAnnotation" attributes. Now that annotation classes produce real DOM nodes rather than weird objects describing HTML tags, we can't generate HTML as a string in ce.ContentBranchNode anymore. getRenderedContents() has been rewritten to be much more similar to the way the converter renders annotations; in fact, significant parts of it were copied from the converter, so that should be factored out in the future. This change actually fixes an annotation rendering discrepancy between ce.ContentBranchNode and dm.Converter; see the diff of ve.ce.ContentBranchNode.test.js. ve.ce.MWEntityNode.js: * Remove stray property ve.dm.MWExternalLinkAnnotation.js: * Store 'rel' attribute ve.dm.TextStyleAnnotation.js: * Put all the conversion logic in the abstract base class ve.dm.Converter.js: * Also feed annotations through getDomElementsFromDataElement() and createDataElement() ve.dm.Node.js: * Fix undocumented property ve.ce.ContentBranchNode.test.js: * Add descriptive messages for each test case * Compare DOM trees, not HTML strings * Compare without all the class="ve-ce-WhateverAnnotation" clutter ve.ui.LinkInspector.js: * Replace direct .getHash() calls (evil!) with ve.getHash() Bug: 46464 Bug: 44808 Change-Id: I31991488579b8cce6d98ed8b29b486ba5ec38cdc
2013-04-02 17:23:33 +00:00
* @param {Object} annotation Plain object with type and attributes properties
* @returns {ve.dm.Annotation} Instance of the right ve.dm.Annotation subclass
*/
ve.dm.example.createAnnotation = function ( annotation ) {
Great Annotation Refactor of 2013 This changes the annotation API to be the same as the node API, sans a few boolean flags that don't apply. The APIs were different, but there was really no good reason why, so this makes things simpler for API users. It also means we'll be able to factor a bunch of things out because they're now duplicated between nodes, meta items and annotations. Linear model annotations are now objects with 'type' and 'attributes' properties (rather than 'name' and 'data'), for consistency with elements. They now also contain html/0/* attributes for HTML attribute preservation, which obsoletes the htmlTagName and htmlAttributes properties. dm.Annotation subclasses take a reference to such an object and implement conversion using .static.toDataElement and .static.toDomElements just like nodes do. The custom .getHash() functions are no longer necessary because of the way HTML attribute preservation was reimplemented. CE rendering has been moved out of dm.Annotation (it never made sense to have CE rendering functions in DM classes, this was bothering me) and into separate ce.Annotation subclasses. These are very similar to CE nodes in that they have a this.$ generated based on something in the DM; the main difference is that nodes listen to events and update themselves, whereas annotations are static and are simply destroyed and rebuilt when they change. This change also adds whitelisted HTML attribute rendering for annotations, as well as class="ve-ce-FooAnnotation" attributes. Now that annotation classes produce real DOM nodes rather than weird objects describing HTML tags, we can't generate HTML as a string in ce.ContentBranchNode anymore. getRenderedContents() has been rewritten to be much more similar to the way the converter renders annotations; in fact, significant parts of it were copied from the converter, so that should be factored out in the future. This change actually fixes an annotation rendering discrepancy between ce.ContentBranchNode and dm.Converter; see the diff of ve.ce.ContentBranchNode.test.js. ve.ce.MWEntityNode.js: * Remove stray property ve.dm.MWExternalLinkAnnotation.js: * Store 'rel' attribute ve.dm.TextStyleAnnotation.js: * Put all the conversion logic in the abstract base class ve.dm.Converter.js: * Also feed annotations through getDomElementsFromDataElement() and createDataElement() ve.dm.Node.js: * Fix undocumented property ve.ce.ContentBranchNode.test.js: * Add descriptive messages for each test case * Compare DOM trees, not HTML strings * Compare without all the class="ve-ce-WhateverAnnotation" clutter ve.ui.LinkInspector.js: * Replace direct .getHash() calls (evil!) with ve.getHash() Bug: 46464 Bug: 44808 Change-Id: I31991488579b8cce6d98ed8b29b486ba5ec38cdc
2013-04-02 17:23:33 +00:00
return ve.dm.annotationFactory.create( annotation.type, annotation );
};
/**
* Create an AnnotationSet from an array of shorthand annotations.
*
* This calls ve.dm.example.createAnnotation() for each element and puts the result in an
* AnnotationSet.
*
JSDuck: Generated code documentation! See CODING.md for how to run it. Mistakes fixed: * Warning: Unknown type function -> Function * Warning: Unknown type DOMElement -> HTMLElement * Warning: Unknown type DOM Node -> HTMLElement * Warning: Unknown type Integer -> Mixed * Warning: Unknown type Command -> ve.Command * Warning: Unknown type any -> number * Warning: Unknown type ve.Transaction -> ve.dm.Transaction * Warning: Unknown type ve.dm.AnnotationSet -> ve.AnnotationSet * Warning: Unknown type false -> boolean * Warning: Unknown type ve.dm.AlienNode ve.dm doesn't have a generic AlienNode like ve.ce -> Unknown type ve.dm.AlienInlineNode|ve.dm.AlienBlockNode * Warning: Unknown type ve.ve.Surface -> ve.ce.Surface * ve.example.lookupNode: -> Last @param should be @return * ve.dm.Transaction.prototype.pushReplace: -> @param {Array] should be @param {Array} * Warning: ve.BranchNode.js:27: {@link ve.Node#hasChildren} links to non-existing member -> (removed) * Warning: ve.LeafNode.js:21: {@link ve.Node#hasChildren} links to non-existing member -> (removed) Differences fixed: * Variadic arguments are like @param {Type...} [name] instead of @param {Type} [name...] * Convert all file headers from /** to /*! because JSDuck tries to parse all /** blocks and fails to parse with all sorts of errors for "Global property", "Unnamed property", and "Duplicate property". Find: \/\*\*([^@]+)(@copyright) Replace: /*!$1$2 * Indented blocks are considered code examples. A few methods had documentation with numbered lists that were indented, which have now been updated to not be intended. * The free-form text descriptions are parsed with Markdown, which requires lists to be separated from paragraphs by an empty line. And we should use `backticks` instead of {braces} for inline code in text paragraphs. * Doc blocks for classes and their constructor have to be in the correct order (@constructor, @param, @return must be before @class, @abstract, @extends etc.) * `@extends Class` must not have Class {wrapped} * @throws must start with a {Type} * @example means something else. It is used for an inline demo iframe, not code block. For that simply indent with spaces. * @member means something else. Non-function properties are marked with @property, not @member. * To create a link to a class or member, in most cases the name is enough to create a link. E.g. Foo, Foo.bar, Foo.bar#quux, where a hash stands for "instance member", so Foo.bar#quux, links to Foo.bar.prototype.quux (the is not supported, as "prototype" is considered an implementation detail, it only indexes class name and method name). If the magic linker doesn't work for some case, the verbose syntax is {@link #target label}. * @property can't have sub-properties (nested @param and @return values are supported, only @static @property can't be nested). We only have one case of this, which can be worked around by moving those in a new virtual class. The code is unaltered (only moved down so that it isn't with the scope of the main @class block). ve.dm.TransactionProcessor.processors. New: * @mixins: Classes mixed into the current class. * @event: Events that can be emitted by a class. These are also inherited by subclasses. (+ @param, @return and @preventable). So ve.Node#event-attach is inherited to ve.dm.BreakNode, just like @method is. * @singleton: Plain objects such as ve, ve.dm, ve.ce were missing documentation causing a tree error. Documented those as a JSDuck singleton, which they but just weren't documented yet. NB: Members of @singleton don't need @static (if present, triggers a compiler warning). * @chainable: Shorthand for "@return this". We were using "@return {classname}" which is ambiguous (returns the same instance or another instance?), @chainable is specifically for "@return this". Creates proper labels in the generated HTML pages. Removed: * @mixin: (not to be confused with @mixins). Not supported by JSDuck. Every class is standalone anyway. Where needed marked them @class + @abstract instead. Change-Id: I6a7c9e8ee8f995731bc205d666167874eb2ebe23
2013-01-04 08:54:17 +00:00
* @method
* @param {Array} annotations Array of annotations in shorthand format
* @returns {ve.dm.AnnotationSet}
*/
ve.dm.example.createAnnotationSet = function ( store, annotations ) {
var i;
for ( i = 0; i < annotations.length; i++ ) {
annotations[i] = ve.dm.example.createAnnotation( annotations[i] );
}
return new ve.dm.AnnotationSet( store, store.indexes( annotations ) );
};
/* Some common annotations in shorthand format */
Great Annotation Refactor of 2013 This changes the annotation API to be the same as the node API, sans a few boolean flags that don't apply. The APIs were different, but there was really no good reason why, so this makes things simpler for API users. It also means we'll be able to factor a bunch of things out because they're now duplicated between nodes, meta items and annotations. Linear model annotations are now objects with 'type' and 'attributes' properties (rather than 'name' and 'data'), for consistency with elements. They now also contain html/0/* attributes for HTML attribute preservation, which obsoletes the htmlTagName and htmlAttributes properties. dm.Annotation subclasses take a reference to such an object and implement conversion using .static.toDataElement and .static.toDomElements just like nodes do. The custom .getHash() functions are no longer necessary because of the way HTML attribute preservation was reimplemented. CE rendering has been moved out of dm.Annotation (it never made sense to have CE rendering functions in DM classes, this was bothering me) and into separate ce.Annotation subclasses. These are very similar to CE nodes in that they have a this.$ generated based on something in the DM; the main difference is that nodes listen to events and update themselves, whereas annotations are static and are simply destroyed and rebuilt when they change. This change also adds whitelisted HTML attribute rendering for annotations, as well as class="ve-ce-FooAnnotation" attributes. Now that annotation classes produce real DOM nodes rather than weird objects describing HTML tags, we can't generate HTML as a string in ce.ContentBranchNode anymore. getRenderedContents() has been rewritten to be much more similar to the way the converter renders annotations; in fact, significant parts of it were copied from the converter, so that should be factored out in the future. This change actually fixes an annotation rendering discrepancy between ce.ContentBranchNode and dm.Converter; see the diff of ve.ce.ContentBranchNode.test.js. ve.ce.MWEntityNode.js: * Remove stray property ve.dm.MWExternalLinkAnnotation.js: * Store 'rel' attribute ve.dm.TextStyleAnnotation.js: * Put all the conversion logic in the abstract base class ve.dm.Converter.js: * Also feed annotations through getDomElementsFromDataElement() and createDataElement() ve.dm.Node.js: * Fix undocumented property ve.ce.ContentBranchNode.test.js: * Add descriptive messages for each test case * Compare DOM trees, not HTML strings * Compare without all the class="ve-ce-WhateverAnnotation" clutter ve.ui.LinkInspector.js: * Replace direct .getHash() calls (evil!) with ve.getHash() Bug: 46464 Bug: 44808 Change-Id: I31991488579b8cce6d98ed8b29b486ba5ec38cdc
2013-04-02 17:23:33 +00:00
ve.dm.example.bold = { 'type': 'textStyle/bold' };
ve.dm.example.italic = { 'type': 'textStyle/italic' };
ve.dm.example.underline = { 'type': 'textStyle/underline' };
ve.dm.example.span = { 'type': 'textStyle/span' };
/**
* Creates a document from example data.
*
* Defaults to ve.dm.example.data if no name is supplied.
*
* @param {string} [name='data'] Named element of ve.dm.example
* @param {ve.dm.IndexValueStore} [store] A specific index-value store to use, optionally.
* @returns {ve.dm.Document} Document
* @throws {Error} Example data not found
*/
ve.dm.example.createExampleDocument = function ( name, store ) {
var doc, i;
name = name || 'data';
store = store || new ve.dm.IndexValueStore();
if ( ve.dm.example[name] === undefined ) {
throw new Error( 'Example data \'' + name + '\' not found' );
}
doc = new ve.dm.Document(
ve.dm.example.preprocessAnnotations( ve.copyArray( ve.dm.example[name] ), store )
);
// HACK internalList isn't populated when creating a document from data
if ( ve.dm.example[name].internalItems ) {
for ( i = 0; i < ve.dm.example[name].internalItems.length; i++ ) {
doc.internalList.queueItemHtml(
ve.dm.example[name].internalItems[i].key,
ve.dm.example[name].internalItems[i].body
);
}
}
return doc;
};
/**
* Looks up a value in a node tree.
*
* @method
* @param {ve.Node} root Root node to lookup from
* @param {number...} [paths] Index path
* @returns {ve.Node} Node at given path
*/
ve.dm.example.lookupNode = function ( root ) {
var i,
node = root;
for ( i = 1; i < arguments.length; i++ ) {
node = node.children[arguments[i]];
}
return node;
};
ve.dm.example.createDomElement = function ( type, attributes ) {
var key,
element = document.createElement( type );
for ( key in attributes ) {
element.setAttribute( key, attributes[key] );
}
return element;
};
ve.dm.example.testDir = window.mw ?
( window.mw.config.get( 'wgExtensionAssetsPath' ) + '/VisualEditor/modules/ve/test' ) :
'.';
ve.dm.example.imgSrc = ve.dm.example.testDir + '/example.png';
/**
* Serialized HTML.
*
* This is what the parser will emit.
* TODO remove some of the <p>s here to test automatic wrapping
*/
ve.dm.example.html =
'<h1>a<b>b</b><i>c</i></h1>' +
'<table>' +
'<tr>' +
'<td>' +
'<p>d</p>' +
'<ul>' +
'<li>' +
'<p>e</p>' +
'<ul>' +
'<li>' +
'<p>f</p>' +
'</li>' +
'</ul>' +
'</li>' +
'</ul>' +
'<ol>' +
'<li>' +
'<p>g</p>' +
'</li>' +
'</ol>' +
'</td>' +
'</tr>' +
'</table>' +
'<pre>h<img src="' + ve.dm.example.imgSrc + '">i</pre>'+
'<dl>' +
'<dt>' +
'<p>j</p>' +
'</dt>' +
'<dd>' +
'<p>k</p>' +
'</dd>' +
'</dl>' +
'<p>l</p>' +
'<p>m</p>';
/*
* Linear data.
*
* This is what we convert serialized HTML from the parser into so we can work with it more easily.
*
* There are three types of components in content data:
*
JSDuck: Generated code documentation! See CODING.md for how to run it. Mistakes fixed: * Warning: Unknown type function -> Function * Warning: Unknown type DOMElement -> HTMLElement * Warning: Unknown type DOM Node -> HTMLElement * Warning: Unknown type Integer -> Mixed * Warning: Unknown type Command -> ve.Command * Warning: Unknown type any -> number * Warning: Unknown type ve.Transaction -> ve.dm.Transaction * Warning: Unknown type ve.dm.AnnotationSet -> ve.AnnotationSet * Warning: Unknown type false -> boolean * Warning: Unknown type ve.dm.AlienNode ve.dm doesn't have a generic AlienNode like ve.ce -> Unknown type ve.dm.AlienInlineNode|ve.dm.AlienBlockNode * Warning: Unknown type ve.ve.Surface -> ve.ce.Surface * ve.example.lookupNode: -> Last @param should be @return * ve.dm.Transaction.prototype.pushReplace: -> @param {Array] should be @param {Array} * Warning: ve.BranchNode.js:27: {@link ve.Node#hasChildren} links to non-existing member -> (removed) * Warning: ve.LeafNode.js:21: {@link ve.Node#hasChildren} links to non-existing member -> (removed) Differences fixed: * Variadic arguments are like @param {Type...} [name] instead of @param {Type} [name...] * Convert all file headers from /** to /*! because JSDuck tries to parse all /** blocks and fails to parse with all sorts of errors for "Global property", "Unnamed property", and "Duplicate property". Find: \/\*\*([^@]+)(@copyright) Replace: /*!$1$2 * Indented blocks are considered code examples. A few methods had documentation with numbered lists that were indented, which have now been updated to not be intended. * The free-form text descriptions are parsed with Markdown, which requires lists to be separated from paragraphs by an empty line. And we should use `backticks` instead of {braces} for inline code in text paragraphs. * Doc blocks for classes and their constructor have to be in the correct order (@constructor, @param, @return must be before @class, @abstract, @extends etc.) * `@extends Class` must not have Class {wrapped} * @throws must start with a {Type} * @example means something else. It is used for an inline demo iframe, not code block. For that simply indent with spaces. * @member means something else. Non-function properties are marked with @property, not @member. * To create a link to a class or member, in most cases the name is enough to create a link. E.g. Foo, Foo.bar, Foo.bar#quux, where a hash stands for "instance member", so Foo.bar#quux, links to Foo.bar.prototype.quux (the is not supported, as "prototype" is considered an implementation detail, it only indexes class name and method name). If the magic linker doesn't work for some case, the verbose syntax is {@link #target label}. * @property can't have sub-properties (nested @param and @return values are supported, only @static @property can't be nested). We only have one case of this, which can be worked around by moving those in a new virtual class. The code is unaltered (only moved down so that it isn't with the scope of the main @class block). ve.dm.TransactionProcessor.processors. New: * @mixins: Classes mixed into the current class. * @event: Events that can be emitted by a class. These are also inherited by subclasses. (+ @param, @return and @preventable). So ve.Node#event-attach is inherited to ve.dm.BreakNode, just like @method is. * @singleton: Plain objects such as ve, ve.dm, ve.ce were missing documentation causing a tree error. Documented those as a JSDuck singleton, which they but just weren't documented yet. NB: Members of @singleton don't need @static (if present, triggers a compiler warning). * @chainable: Shorthand for "@return this". We were using "@return {classname}" which is ambiguous (returns the same instance or another instance?), @chainable is specifically for "@return this". Creates proper labels in the generated HTML pages. Removed: * @mixin: (not to be confused with @mixins). Not supported by JSDuck. Every class is standalone anyway. Where needed marked them @class + @abstract instead. Change-Id: I6a7c9e8ee8f995731bc205d666167874eb2ebe23
2013-01-04 08:54:17 +00:00
* {string} Plain text character
*
* {Array} Annotated character
JSDuck: Generated code documentation! See CODING.md for how to run it. Mistakes fixed: * Warning: Unknown type function -> Function * Warning: Unknown type DOMElement -> HTMLElement * Warning: Unknown type DOM Node -> HTMLElement * Warning: Unknown type Integer -> Mixed * Warning: Unknown type Command -> ve.Command * Warning: Unknown type any -> number * Warning: Unknown type ve.Transaction -> ve.dm.Transaction * Warning: Unknown type ve.dm.AnnotationSet -> ve.AnnotationSet * Warning: Unknown type false -> boolean * Warning: Unknown type ve.dm.AlienNode ve.dm doesn't have a generic AlienNode like ve.ce -> Unknown type ve.dm.AlienInlineNode|ve.dm.AlienBlockNode * Warning: Unknown type ve.ve.Surface -> ve.ce.Surface * ve.example.lookupNode: -> Last @param should be @return * ve.dm.Transaction.prototype.pushReplace: -> @param {Array] should be @param {Array} * Warning: ve.BranchNode.js:27: {@link ve.Node#hasChildren} links to non-existing member -> (removed) * Warning: ve.LeafNode.js:21: {@link ve.Node#hasChildren} links to non-existing member -> (removed) Differences fixed: * Variadic arguments are like @param {Type...} [name] instead of @param {Type} [name...] * Convert all file headers from /** to /*! because JSDuck tries to parse all /** blocks and fails to parse with all sorts of errors for "Global property", "Unnamed property", and "Duplicate property". Find: \/\*\*([^@]+)(@copyright) Replace: /*!$1$2 * Indented blocks are considered code examples. A few methods had documentation with numbered lists that were indented, which have now been updated to not be intended. * The free-form text descriptions are parsed with Markdown, which requires lists to be separated from paragraphs by an empty line. And we should use `backticks` instead of {braces} for inline code in text paragraphs. * Doc blocks for classes and their constructor have to be in the correct order (@constructor, @param, @return must be before @class, @abstract, @extends etc.) * `@extends Class` must not have Class {wrapped} * @throws must start with a {Type} * @example means something else. It is used for an inline demo iframe, not code block. For that simply indent with spaces. * @member means something else. Non-function properties are marked with @property, not @member. * To create a link to a class or member, in most cases the name is enough to create a link. E.g. Foo, Foo.bar, Foo.bar#quux, where a hash stands for "instance member", so Foo.bar#quux, links to Foo.bar.prototype.quux (the is not supported, as "prototype" is considered an implementation detail, it only indexes class name and method name). If the magic linker doesn't work for some case, the verbose syntax is {@link #target label}. * @property can't have sub-properties (nested @param and @return values are supported, only @static @property can't be nested). We only have one case of this, which can be worked around by moving those in a new virtual class. The code is unaltered (only moved down so that it isn't with the scope of the main @class block). ve.dm.TransactionProcessor.processors. New: * @mixins: Classes mixed into the current class. * @event: Events that can be emitted by a class. These are also inherited by subclasses. (+ @param, @return and @preventable). So ve.Node#event-attach is inherited to ve.dm.BreakNode, just like @method is. * @singleton: Plain objects such as ve, ve.dm, ve.ce were missing documentation causing a tree error. Documented those as a JSDuck singleton, which they but just weren't documented yet. NB: Members of @singleton don't need @static (if present, triggers a compiler warning). * @chainable: Shorthand for "@return this". We were using "@return {classname}" which is ambiguous (returns the same instance or another instance?), @chainable is specifically for "@return this". Creates proper labels in the generated HTML pages. Removed: * @mixin: (not to be confused with @mixins). Not supported by JSDuck. Every class is standalone anyway. Where needed marked them @class + @abstract instead. Change-Id: I6a7c9e8ee8f995731bc205d666167874eb2ebe23
2013-01-04 08:54:17 +00:00
* 0: {string} Character
* 1: {Object} List of references to immutable annotation objects, keyed by JSON
* serializations of their values (hashes)
*
* {Object} Opening or closing structural element
JSDuck: Generated code documentation! See CODING.md for how to run it. Mistakes fixed: * Warning: Unknown type function -> Function * Warning: Unknown type DOMElement -> HTMLElement * Warning: Unknown type DOM Node -> HTMLElement * Warning: Unknown type Integer -> Mixed * Warning: Unknown type Command -> ve.Command * Warning: Unknown type any -> number * Warning: Unknown type ve.Transaction -> ve.dm.Transaction * Warning: Unknown type ve.dm.AnnotationSet -> ve.AnnotationSet * Warning: Unknown type false -> boolean * Warning: Unknown type ve.dm.AlienNode ve.dm doesn't have a generic AlienNode like ve.ce -> Unknown type ve.dm.AlienInlineNode|ve.dm.AlienBlockNode * Warning: Unknown type ve.ve.Surface -> ve.ce.Surface * ve.example.lookupNode: -> Last @param should be @return * ve.dm.Transaction.prototype.pushReplace: -> @param {Array] should be @param {Array} * Warning: ve.BranchNode.js:27: {@link ve.Node#hasChildren} links to non-existing member -> (removed) * Warning: ve.LeafNode.js:21: {@link ve.Node#hasChildren} links to non-existing member -> (removed) Differences fixed: * Variadic arguments are like @param {Type...} [name] instead of @param {Type} [name...] * Convert all file headers from /** to /*! because JSDuck tries to parse all /** blocks and fails to parse with all sorts of errors for "Global property", "Unnamed property", and "Duplicate property". Find: \/\*\*([^@]+)(@copyright) Replace: /*!$1$2 * Indented blocks are considered code examples. A few methods had documentation with numbered lists that were indented, which have now been updated to not be intended. * The free-form text descriptions are parsed with Markdown, which requires lists to be separated from paragraphs by an empty line. And we should use `backticks` instead of {braces} for inline code in text paragraphs. * Doc blocks for classes and their constructor have to be in the correct order (@constructor, @param, @return must be before @class, @abstract, @extends etc.) * `@extends Class` must not have Class {wrapped} * @throws must start with a {Type} * @example means something else. It is used for an inline demo iframe, not code block. For that simply indent with spaces. * @member means something else. Non-function properties are marked with @property, not @member. * To create a link to a class or member, in most cases the name is enough to create a link. E.g. Foo, Foo.bar, Foo.bar#quux, where a hash stands for "instance member", so Foo.bar#quux, links to Foo.bar.prototype.quux (the is not supported, as "prototype" is considered an implementation detail, it only indexes class name and method name). If the magic linker doesn't work for some case, the verbose syntax is {@link #target label}. * @property can't have sub-properties (nested @param and @return values are supported, only @static @property can't be nested). We only have one case of this, which can be worked around by moving those in a new virtual class. The code is unaltered (only moved down so that it isn't with the scope of the main @class block). ve.dm.TransactionProcessor.processors. New: * @mixins: Classes mixed into the current class. * @event: Events that can be emitted by a class. These are also inherited by subclasses. (+ @param, @return and @preventable). So ve.Node#event-attach is inherited to ve.dm.BreakNode, just like @method is. * @singleton: Plain objects such as ve, ve.dm, ve.ce were missing documentation causing a tree error. Documented those as a JSDuck singleton, which they but just weren't documented yet. NB: Members of @singleton don't need @static (if present, triggers a compiler warning). * @chainable: Shorthand for "@return this". We were using "@return {classname}" which is ambiguous (returns the same instance or another instance?), @chainable is specifically for "@return this". Creates proper labels in the generated HTML pages. Removed: * @mixin: (not to be confused with @mixins). Not supported by JSDuck. Every class is standalone anyway. Where needed marked them @class + @abstract instead. Change-Id: I6a7c9e8ee8f995731bc205d666167874eb2ebe23
2013-01-04 08:54:17 +00:00
* type: {string} Symbolic node type name, if closing element first character will be "/"
* [attributes]: {Object} List of symbolic attribute name and literal value pairs
*/
ve.dm.example.data = [
// 0 - Beginning of heading
{ 'type': 'heading', 'attributes': { 'level': 1 } },
// 1 - Plain "a"
'a',
// 2 - Bold "b"
['b', [ ve.dm.example.bold ]],
// 3 - Italic "c"
['c', [ ve.dm.example.italic ]],
// 4 - End of heading
{ 'type': '/heading' },
// 5 - Beginning of table
{ 'type': 'table' },
// 6 - Beginning of body
{ 'type': 'tableSection', 'attributes': { 'style': 'body' } },
// 7 - Beginning of row
{ 'type': 'tableRow' },
// 8 - Beginning of cell
{ 'type': 'tableCell', 'attributes': { 'style': 'data' } },
// 9 - Beginning of paragraph
{ 'type': 'paragraph' },
// 10 - Plain "d"
'd',
// 11 - End of paragraph
{ 'type': '/paragraph' },
// 12 - Beginning of bullet list
{ 'type': 'list', 'attributes': { 'style': 'bullet' } },
// 13 - Beginning of list item
{ 'type': 'listItem' },
// 14 - Beginning of paragraph
{ 'type': 'paragraph' },
// 15 - Plain "e"
'e',
// 16 - End of paragraph
{ 'type': '/paragraph' },
// 17 - Beginning of nested bullet list
{ 'type': 'list', 'attributes': { 'style': 'bullet' } },
// 18 - Beginning of nested bullet list item
{ 'type': 'listItem' },
// 19 - Beginning of paragraph
{ 'type': 'paragraph' },
// 20 - Plain "f"
'f',
// 21 - End of paragraph
{ 'type': '/paragraph' },
// 22 - End of nested bullet list item
{ 'type': '/listItem' },
// 23 - End of nested bullet list
{ 'type': '/list' },
// 24 - End of bullet list item
{ 'type': '/listItem' },
// 25 - End of bullet list
{ 'type': '/list' },
// 26 - Beginning of numbered list
{ 'type': 'list', 'attributes': { 'style': 'number' } },
// 27 - Beginning of numbered list item
{ 'type': 'listItem' },
// 28 - Beginning of paragraph
{ 'type': 'paragraph' },
// 29 - Plain "g"
'g',
// 30 - End of paragraph
{ 'type': '/paragraph' },
// 31 - End of item
{ 'type': '/listItem' },
// 32 - End of lis t
{ 'type': '/list' },
// 33 - End of cell
{ 'type': '/tableCell' },
// 34 - End of row
{ 'type': '/tableRow' },
// 35 - End of body
{ 'type': '/tableSection' },
// 36 - End of table
{ 'type': '/table' },
// 37 - Beginning of preformatted
{ 'type': 'preformatted' },
// 38 - Plain "h"
'h',
// 39 - Beginning of inline image
{
'type': 'image',
'attributes': {
'src': ve.dm.example.imgSrc,
'width': null,
'height': null
},
'htmlAttributes': [ { 'values': { 'src': ve.dm.example.imgSrc } } ]
},
// 40 - End of inline image
{ 'type': '/image' },
// 41 - Plain "i"
'i',
// 42 - End of preformatted
{ 'type': '/preformatted' },
// 43 - Beginning of definition list
{ 'type': 'definitionList' },
// 44 - Beginning of definition list term item
{ 'type': 'definitionListItem', 'attributes': { 'style': 'term' } },
// 45 - Beginning of paragraph
{ 'type': 'paragraph' },
// 46 - Plain "j"
'j',
// 47 - End of paragraph
{ 'type': '/paragraph' },
// 48 - End of definition list term item
{ 'type': '/definitionListItem' },
// 49 - Beginning of definition list definition item
{ 'type': 'definitionListItem', 'attributes': { 'style': 'definition' } },
// 50 - Beginning of paragraph
{ 'type': 'paragraph' },
// 51 - Plain "k"
'k',
// 52 - End of paragraph
{ 'type': '/paragraph' },
// 53 - End of definition list definition item
{ 'type': '/definitionListItem' },
// 54 - End of definition list
{ 'type': '/definitionList' },
// 55 - Beginning of paragraph
{ 'type': 'paragraph' },
// 56 - Plain "l"
'l',
// 57 - End of paragraph
{ 'type': '/paragraph' },
// 58 - Beginning of paragraph
{ 'type': 'paragraph' },
// 59 - Plain "m"
'm',
// 60 - End of paragraph
{ 'type': '/paragraph' },
// 61 - Beginning of internalList
{ 'type': 'internalList' },
// 62 - End of internalList
{ 'type': '/internalList' }
// 63 - End of document
];
ve.dm.example.alienData = [
// 0 - Open alienBlock
{ 'type': 'alienBlock' },
// 1 - Close alienBlock
{ 'type': '/alienBlock' },
// 2 - Open paragraph
{ 'type': 'paragraph' },
// 3 - Plain character 'a'
'a',
// 4 - Open alienInline
{ 'type': 'alienBlock' },
// 5 - Close alienInline
{ 'type': '/alienBlock' },
// 6 - Plain character 'b'
'b',
// 7 - Close paragraph
{ 'type': '/paragraph' },
// 8 - Open alienBlock
{ 'type': 'alienBlock' },
// 9 - Close alienBlock
{ 'type': '/alienBlock' },
// 10 - Beginning of internalList
{ 'type': 'internalList' },
// 11 - End of internalList
{ 'type': '/internalList' }
// 12 - End of document
];
ve.dm.example.internalData = [
{ 'type': 'paragraph' },
'F', 'o', 'o',
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': 'internalItem' },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'B', 'a', 'r',
{ 'type': '/paragraph' },
{ 'type': '/internalItem' },
{ 'type': 'internalItem' },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'B', 'a', 'z',
{ 'type': '/paragraph' },
{ 'type': '/internalItem' },
{ 'type': '/internalList' },
{ 'type': 'paragraph' },
'Q', 'u', 'u', 'x',
{ 'type': '/paragraph' }
];
ve.dm.example.internalData.internalItems = [
{ 'key': 'bar', 'body': 'Bar' },
{ 'key': 'baz', 'body': 'Baz' }
];
ve.dm.example.withMeta = [
{
'type': 'alienMeta',
'attributes': {
'domElements': $( '<!-- No content conversion -->' ).toArray()
}
},
{ 'type': '/alienMeta' },
{
'type': 'mwAlienMeta',
'attributes': {
'domElements': $( '<meta property="mw:PageProp/nocc" />' ).toArray()
}
},
{ 'type': '/mwAlienMeta' },
{ 'type': 'paragraph' },
'F',
'o',
'o',
{
'type': 'mwCategory',
'attributes': {
'hrefPrefix': './',
'category': 'Category:Bar',
'origCategory': 'Category:Bar',
'sortkey': '',
'origSortkey': ''
},
'htmlAttributes': [ { 'values': { 'rel': 'mw:WikiLink/Category', 'href': './Category:Bar' } } ]
},
{ 'type': '/mwCategory' },
'B',
'a',
'r',
{
'type': 'mwAlienMeta',
'attributes': {
'domElements': $( '<meta property="mw:foo" content="bar" />' ).toArray()
}
},
{ 'type': '/mwAlienMeta' },
'B',
'a',
{
'type': 'alienMeta',
'attributes': {
'domElements': $( '<!-- inline -->' ).toArray()
}
},
{ 'type': '/alienMeta' },
'z',
{ 'type': '/paragraph' },
{
'type': 'mwAlienMeta',
'attributes': {
'domElements': $( '<meta property="mw:bar" content="baz" />' ).toArray()
}
},
{ 'type': '/mwAlienMeta' },
{
'type': 'alienMeta',
'attributes': {
'domElements': $( '<!--barbaz-->' ).toArray()
}
},
{ 'type': '/alienMeta' },
{
'type': 'mwCategory',
'attributes': {
'hrefPrefix': './',
'category': 'Category:Foo foo',
'origCategory': 'Category:Foo_foo',
'sortkey': 'Bar baz#quux',
'origSortkey': 'Bar baz%23quux'
},
'htmlAttributes': [ { 'values': {
'rel': 'mw:WikiLink/Category',
'href': './Category:Foo_foo#Bar baz%23quux'
} } ]
},
{ 'type': '/mwCategory' },
{
'type': 'mwAlienMeta',
'attributes': {
'domElements': $( '<meta typeof="mw:Placeholder" data-parsoid="foobar" />' ).toArray()
}
},
{ 'type': '/mwAlienMeta' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
];
ve.dm.example.withMetaPlainData = [
{ 'type': 'paragraph' },
'F',
'o',
'o',
'B',
'a',
'r',
'B',
'a',
'z',
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
];
ve.dm.example.withMetaMetaData = [
[
{
'type': 'alienMeta',
'attributes': {
'domElements': $( '<!-- No content conversion -->' ).toArray()
}
},
{
'type': 'mwAlienMeta',
'attributes': {
'domElements': $( '<meta property="mw:PageProp/nocc" />' ).toArray()
}
}
],
undefined,
undefined,
undefined,
[
{
'type': 'mwCategory',
'attributes': {
'hrefPrefix': './',
'category': 'Category:Bar',
'origCategory': 'Category:Bar',
'sortkey': '',
'origSortkey': ''
},
'htmlAttributes': [ { 'values': { 'rel': 'mw:WikiLink/Category', 'href': './Category:Bar' } } ]
}
],
undefined,
undefined,
[
{
'type': 'mwAlienMeta',
'attributes': {
'domElements': $( '<meta property="mw:foo" content="bar" />' ).toArray()
}
}
],
undefined,
[
{
'type': 'alienMeta',
'attributes': {
'domElements': $( '<!-- inline -->' ).toArray()
}
}
],
undefined,
[
{
'type': 'mwAlienMeta',
'attributes': {
'domElements': $( '<meta property="mw:bar" content="baz" />' ).toArray()
}
},
{
'type': 'alienMeta',
'attributes': {
'domElements': $( '<!--barbaz-->' ).toArray()
}
},
{
'type': 'mwCategory',
'attributes': {
'hrefPrefix': './',
'category': 'Category:Foo foo',
'origCategory': 'Category:Foo_foo',
'sortkey': 'Bar baz#quux',
'origSortkey': 'Bar baz%23quux'
},
'htmlAttributes': [ { 'values': {
'rel': 'mw:WikiLink/Category',
'href': './Category:Foo_foo#Bar baz%23quux'
} } ]
},
{
'type': 'mwAlienMeta',
'attributes': {
'domElements': $( '<meta typeof="mw:Placeholder" data-parsoid="foobar" />' ).toArray()
}
}
],
undefined,
undefined
];
ve.dm.example.complexTableHtml = '<table><caption>Foo</caption><thead><tr><th>Bar</th></tr></thead>' +
'<tfoot><tr><td>Baz</td></tr></tfoot><tbody><tr><td>Quux</td><td>Whee</td></tr></tbody></table>';
ve.dm.example.complexTable = [
{ 'type': 'table' },
{ 'type': 'tableCaption' },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'F',
'o',
'o',
{ 'type': '/paragraph' },
{ 'type': '/tableCaption' },
{ 'type': 'tableSection', 'attributes': { 'style': 'header' } },
{ 'type': 'tableRow' },
{ 'type': 'tableCell', 'attributes': { 'style': 'header' } },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'B',
'a',
'r',
{ 'type': '/paragraph' },
{ 'type': '/tableCell' },
{ 'type': '/tableRow' },
{ 'type': '/tableSection' },
{ 'type': 'tableSection', 'attributes': { 'style': 'footer' } },
{ 'type': 'tableRow' },
{ 'type': 'tableCell', 'attributes': { 'style': 'data' } },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'B',
'a',
'z',
{ 'type': '/paragraph' },
{ 'type': '/tableCell' },
{ 'type': '/tableRow' },
{ 'type': '/tableSection' },
{ 'type': 'tableSection', 'attributes': { 'style': 'body' } },
{ 'type': 'tableRow' },
{ 'type': 'tableCell', 'attributes': { 'style': 'data' } },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'Q',
'u',
'u',
'x',
{ 'type': '/paragraph' },
{ 'type': '/tableCell' },
{ 'type': 'tableCell', 'attributes': { 'style': 'data' } },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'W',
'h',
'e',
'e',
{ 'type': '/paragraph' },
{ 'type': '/tableCell' },
{ 'type': '/tableRow' },
{ 'type': '/tableSection' },
{ 'type': '/table' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
];
ve.dm.example.inlineAtEdges = [
{ 'type': 'paragraph' },
{
'type': 'image',
'attributes': {
'src': ve.dm.example.imgSrc,
'width': null,
'height': null
},
'htmlAttributes': [ { 'values': { 'src': ve.dm.example.imgSrc } } ]
},
{ 'type': '/image' },
'F',
'o',
'o',
{ 'type': 'alienInline', 'attributes': { 'domElements': $( '<foobar />' ).toArray() } },
{ 'type': '/alienInline' },
{ 'type': '/paragraph' }
];
ve.dm.example.emptyBranch = [
{ 'type': 'table' },
{ 'type': '/table' }
];
/**
* Sample content data index.
*
* This is part of what a ve.dm.DocumentFragment generates when given linear data.
*
* (21) branch nodes
* (01) document node
* (01) heading node
* (01) table node
* (01) tableRow node
* (01) tableCell node
* (06) paragraph nodes
* (03) list nodes
* (03) listItem nodes
* (01) preformatted node
* (01) definitionList node
* (02) definitionListItem nodes
* (10) leaf nodes
* (09) text nodes
* (01) image node
*/
ve.dm.example.tree = new ve.dm.DocumentNode( [
// Heading with "abc"
new ve.dm.HeadingNode( [new ve.dm.TextNode( 3 )], ve.dm.example.data[0] ),
new ve.dm.TableNode( [
new ve.dm.TableSectionNode( [
new ve.dm.TableRowNode( [
new ve.dm.TableCellNode( [
// Paragraph with "d"
new ve.dm.ParagraphNode( [new ve.dm.TextNode( 1 )],
ve.dm.example.data[9] ),
new ve.dm.ListNode( [
// 1st level bullet list item with "e"
new ve.dm.ListItemNode( [
new ve.dm.ParagraphNode(
[new ve.dm.TextNode( 1 )],
ve.dm.example.data[14]
),
new ve.dm.ListNode( [
// 2nd level bullet list item with "f"
new ve.dm.ListItemNode( [
new ve.dm.ParagraphNode(
[new ve.dm.TextNode( 1 )],
ve.dm.example.data[19]
)
], ve.dm.example.data[18] )
], ve.dm.example.data[17] )
], ve.dm.example.data[13] )
], ve.dm.example.data[12] ),
new ve.dm.ListNode( [
// Numbered list item with "g"
new ve.dm.ListItemNode( [
new ve.dm.ParagraphNode(
[new ve.dm.TextNode( 1 )],
ve.dm.example.data[28]
)
], ve.dm.example.data[27] )
], ve.dm.example.data[26] )
], ve.dm.example.data[8] )
], ve.dm.example.data[7] )
], ve.dm.example.data[6] )
], ve.dm.example.data[5] ),
// Preformatted with "h[example.png]i"
new ve.dm.PreformattedNode( [
new ve.dm.TextNode( 1 ),
new ve.dm.ImageNode( [], ve.dm.example.data[39] ),
new ve.dm.TextNode( 1 )
], ve.dm.example.data[37] ),
new ve.dm.DefinitionListNode( [
// Definition list term item with "j"
new ve.dm.DefinitionListItemNode( [
new ve.dm.ParagraphNode( [new ve.dm.TextNode( 1 )], ve.dm.example.data[45] )
], ve.dm.example.data[44] ),
// Definition list definition item with "k"
new ve.dm.DefinitionListItemNode( [
new ve.dm.ParagraphNode( [new ve.dm.TextNode( 1 )], ve.dm.example.data[50] )
], ve.dm.example.data[49] )
], ve.dm.example.data[43] ),
new ve.dm.ParagraphNode( [new ve.dm.TextNode( 1 )], ve.dm.example.data[55] ),
new ve.dm.ParagraphNode( [new ve.dm.TextNode( 1 )], ve.dm.example.data[58] ),
new ve.dm.InternalListNode( [], ve.dm.example.data[61] )
] );
ve.dm.example.conversions = {
'definitionListItem term': {
'domElement': ve.dm.example.createDomElement( 'dt' ),
'dataElement': { 'type': 'definitionListItem', 'attributes': { 'style': 'term' } }
},
'definitionListItem definition': {
'domElement': ve.dm.example.createDomElement( 'dd' ),
'dataElement': { 'type': 'definitionListItem', 'attributes': { 'style': 'definition' } }
},
'definitionList definition': {
'domElement': ve.dm.example.createDomElement( 'dl' ),
'dataElement': { 'type': 'definitionList' }
},
'heading level 1': {
'domElement': ve.dm.example.createDomElement( 'h1' ),
'dataElement': { 'type': 'heading', 'attributes': { 'level': 1 } }
},
'heading level 2': {
'domElement': ve.dm.example.createDomElement( 'h2' ),
'dataElement': { 'type': 'heading', 'attributes': { 'level': 2 } }
},
'heading level 3': {
'domElement': ve.dm.example.createDomElement( 'h3' ),
'dataElement': { 'type': 'heading', 'attributes': { 'level': 3 } }
},
'heading level 4': {
'domElement': ve.dm.example.createDomElement( 'h4' ),
'dataElement': { 'type': 'heading', 'attributes': { 'level': 4 } }
},
'heading level 5': {
'domElement': ve.dm.example.createDomElement( 'h5' ),
'dataElement': { 'type': 'heading', 'attributes': { 'level': 5 } }
},
'heading level 6': {
'domElement': ve.dm.example.createDomElement( 'h6' ),
'dataElement': { 'type': 'heading', 'attributes': { 'level': 6 } }
},
'image': {
'domElement': ve.dm.example.createDomElement( 'img' ),
'dataElement': { 'type': 'image' }
},
'listItem': {
'domElement': ve.dm.example.createDomElement( 'li' ),
'dataElement': { 'type': 'listItem' }
},
'list bullet': {
'domElement': ve.dm.example.createDomElement( 'ul' ),
'dataElement': { 'type': 'list', 'attributes': { 'style': 'bullet' } }
},
'list number': {
'domElement': ve.dm.example.createDomElement( 'ol' ),
'dataElement': { 'type': 'list', 'attributes': { 'style': 'number' } }
},
'paragraph': {
'domElement': ve.dm.example.createDomElement( 'p' ),
'dataElement': { 'type': 'paragraph' }
},
'preformatted': {
'domElement': ve.dm.example.createDomElement( 'pre' ),
'dataElement': { 'type': 'preformatted' }
},
'tableCell': {
'domElement': ve.dm.example.createDomElement( 'td' ),
'dataElement': { 'type': 'tableCell', 'attributes': { 'style': 'data' } }
},
'table': {
'domElement': ve.dm.example.createDomElement( 'table' ),
'dataElement': { 'type': 'table' }
},
'tableRow': {
'domElement': ve.dm.example.createDomElement( 'tr' ),
'dataElement': { 'type': 'tableRow' }
},
'paragraph with data-mw attribute': {
'domElement': ve.dm.example.createDomElement( 'p', { 'data-mw': '{"test":1234}' } ),
'dataElement': {
'type': 'paragraph',
'htmlAttributes': [ { 'values': { 'data-mw': '{"test":1234}' } } ]
}
},
'paragraph with style attribute': {
'domElement': ve.dm.example.createDomElement( 'p', { 'style': 'color:blue' } ),
'dataElement': {
'type': 'paragraph',
'htmlAttributes': [ { 'values': { 'style': 'color:blue' } } ]
}
}
};
ve.dm.example.domToDataCases = {
'paragraph with plain text': {
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'html': '<body><p>abc</p></body>',
'data': [
{ 'type': 'paragraph' },
'a',
'b',
'c',
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'annotated text with bold, italic, underline formatting': {
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'html': '<body><p><b>a</b><i>b</i><u>c</u></p></body>',
'data': [
{ 'type': 'paragraph' },
['a', [ ve.dm.example.bold ]],
['b', [ ve.dm.example.italic ]],
['c', [ ve.dm.example.underline ]],
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'image': {
'html': '<body><img src="' + ve.dm.example.imgSrc + '"></body>',
'data': [
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
{
'type': 'image',
'attributes' : {
'width': null,
'height': null,
'src': ve.dm.example.imgSrc
},
'htmlAttributes': [ { 'values': { 'src': ve.dm.example.imgSrc } } ]
},
{ 'type' : '/image' },
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'paragraph with alienInline inside': {
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'html': '<body><p>a<tt class="foo">b</tt>c</p></body>',
'data': [
{ 'type': 'paragraph' },
'a',
{
'type': 'alienInline',
'attributes': { 'domElements': $( '<tt class="foo">b</tt>' ).toArray() }
},
{ 'type': '/alienInline' },
'c',
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'paragraphs with an alienBlock between them': {
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'html': '<body><p>abc</p><figure>abc</figure><p>def</p></body>',
'data': [
{ 'type': 'paragraph' },
'a',
'b',
'c',
{ 'type': '/paragraph' },
{ 'type': 'alienBlock', 'attributes': { 'domElements': $( '<figure>abc</figure>' ).toArray() } },
{ 'type': '/alienBlock' },
{ 'type': 'paragraph' },
'd',
'e',
'f',
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'annotated inline nodes': {
'html': '<body><p>a<b><tt class="foo">b</tt><i><tt class="bar">c</tt></i></b>' +
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'<i><br/>d</i>e</p></body>',
'data': [
{ 'type': 'paragraph' },
'a',
{
'type': 'alienInline',
'attributes': { 'domElements': $( '<tt class="foo">b</tt>' ).toArray() },
'annotations': [ ve.dm.example.bold ]
},
{ 'type': '/alienInline' },
{
'type': 'alienInline',
'attributes': { 'domElements': $( '<tt class="bar">c</tt>' ).toArray() },
'annotations': [ ve.dm.example.bold, ve.dm.example.italic ]
},
{ 'type': '/alienInline' },
{
'type': 'break',
'annotations': [ ve.dm.example.italic ]
},
{ 'type': '/break' },
['d', [ ve.dm.example.italic ]],
'e',
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'annotated metadata': {
'html': '<body><p><b><!--foo-->bar<!--baz--></b></p></body>',
'data': [
{ 'type': 'paragraph' },
{
'type': 'alienMeta',
'annotations': [ ve.dm.example.bold ],
'attributes': {
'domElements': $( '<!--foo-->' ).toArray()
}
},
{ 'type': '/alienMeta' },
[ 'b', [ ve.dm.example.bold ] ],
[ 'a', [ ve.dm.example.bold ] ],
[ 'r', [ ve.dm.example.bold ] ],
{
'type': 'alienMeta',
'annotations': [ ve.dm.example.bold ],
'attributes': {
'domElements': $( '<!--baz-->' ).toArray()
}
},
{ 'type': '/alienMeta' },
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'annotated metadata in a wrapper': {
'html': '<body><b><!--foo-->bar<!--baz-->quux<!--whee--></b></body>',
'data': [
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
{
'type': 'alienMeta',
'annotations': [ ve.dm.example.bold ],
'attributes': {
'domElements': $( '<!--foo-->' ).toArray()
}
},
{ 'type': '/alienMeta' },
[ 'b', [ ve.dm.example.bold ] ],
[ 'a', [ ve.dm.example.bold ] ],
[ 'r', [ ve.dm.example.bold ] ],
{
'type': 'alienMeta',
'annotations': [ ve.dm.example.bold ],
'attributes': {
'domElements': $( '<!--baz-->' ).toArray()
}
},
{ 'type': '/alienMeta' },
[ 'q', [ ve.dm.example.bold ] ],
[ 'u', [ ve.dm.example.bold ] ],
[ 'u', [ ve.dm.example.bold ] ],
[ 'x', [ ve.dm.example.bold ] ],
{
'type': 'alienMeta',
'annotations': [ ve.dm.example.bold ],
'attributes': {
'domElements': $( '<!--whee-->' ).toArray()
}
},
{ 'type': '/alienMeta' },
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'metadata in a wrapper followed by annotated text': {
'html': '<body>Foo<!--bar--><b>Baz</b></body>',
'data': [
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'F',
'o',
'o',
{
'type': 'alienMeta',
'attributes': {
'domElements': $( '<!--bar-->' ).toArray()
}
},
{ 'type': '/alienMeta' },
[ 'B', [ ve.dm.example.bold ] ],
[ 'a', [ ve.dm.example.bold ] ],
[ 'z', [ ve.dm.example.bold ] ],
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'wrapping of bare content': {
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'html': '<body>abc</body>',
'data': [
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'a',
'b',
'c',
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'wrapping of bare content with inline node': {
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'html': '<body>1<br/>2</body>',
'data': [
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'1',
{ 'type': 'break' },
{ 'type': '/break' },
'2',
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'wrapping of bare content starting with inline node': {
'html': '<body><img src="' + ve.dm.example.imgSrc + '">12</body>',
'data': [
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
{
'type': 'image',
'attributes': {
'src': ve.dm.example.imgSrc,
'width': null,
'height': null
},
'htmlAttributes': [ { 'values': { 'src': ve.dm.example.imgSrc } } ]
},
{ 'type': '/image' },
'1',
'2',
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'wrapping of bare content with inline alien': {
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'html': '<body>1<tt class="bar">baz</tt>2</body>',
'data': [
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'1',
{
'type': 'alienInline',
'attributes': { 'domElements': $( '<tt class="bar">baz</tt>' ).toArray() }
},
{ 'type': '/alienInline' },
'2',
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'wrapping of bare content with block alien': {
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'html': '<body>1<figure class="bar">baz</figure>2</body>',
'data': [
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'1',
{ 'type': '/paragraph' },
{
'type': 'alienBlock',
'attributes': { 'domElements': $( '<figure class="bar">baz</figure>' ).toArray() }
},
{ 'type': '/alienBlock' },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'2',
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'wrapping of bare content starting with inline alien': {
'html': '<body><tt class="bar">Foo</tt>Bar</body>',
'data': [
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
{
'type': 'alienInline',
'attributes': { 'domElements': $( '<tt class="bar">Foo</tt>' ).toArray() }
(bug 43056) Inline tags like <span> are block-alienated sometimes This happens when the <span> is the start of unwrapped content. The converter logic to look at the tag name in wrapping mode doesn't kick in because we're not yet in wrapping mode at that point. The core issue was that previously, we relied on the document structure/state to choose between alienBlock and alienInline, and only used the tag name where the document structure was ambiguous (wrapping). Changed this to be the other way around: we now rely primarily on the tag name, and if that doesn't match what we expect based on the document structure, we work around that if possible. Specifically: * inline tag in our wrapper --> inline alien * block tag in our wrapper --> close wrapper, block alien * inline tag in wrapper that's not ours --> inline alien * block tag in wrapper that's not ours --> *inline* alien * inline tag in structural location --> open wrapper, inline alien * block tag in structural location --> block alien * inline tag in content location --> inline alien * block tag in content location --> *inline* alien only in the fourth and the last case do we need to use the "wrong" alien type to preserve document validity, and it will always be inline where block was expected, which should reduce UI issues. The condensed version of the above, which is used in the code, is: * If in a non-wrapper content location, use inline * If in a wrapper that's not ours, use inline * Otherwise, decide based on tag name * Open or close wrapper if needed ve.dm.Converter: * Replace isInline logic in createAlien() with the above * Factor out code to start wrapping (was duplicated) into startWrapping() * Call startWrapping() if createAlien() returns an alienInline and we're in a structural location Tests: * Add test cases with aliens at the start and end of unwrapped content ** The first one failed prior to these changes and now passes, the second one was already passing * Fix about group test case, was exhibiting the bug that this commit fixes Change-Id: I657aa0ff5bc2b57cd48ef8a99c8ca930936c03b8
2012-12-20 00:59:58 +00:00
},
{ 'type': '/alienInline' },
'B',
'a',
'r',
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
(bug 43056) Inline tags like <span> are block-alienated sometimes This happens when the <span> is the start of unwrapped content. The converter logic to look at the tag name in wrapping mode doesn't kick in because we're not yet in wrapping mode at that point. The core issue was that previously, we relied on the document structure/state to choose between alienBlock and alienInline, and only used the tag name where the document structure was ambiguous (wrapping). Changed this to be the other way around: we now rely primarily on the tag name, and if that doesn't match what we expect based on the document structure, we work around that if possible. Specifically: * inline tag in our wrapper --> inline alien * block tag in our wrapper --> close wrapper, block alien * inline tag in wrapper that's not ours --> inline alien * block tag in wrapper that's not ours --> *inline* alien * inline tag in structural location --> open wrapper, inline alien * block tag in structural location --> block alien * inline tag in content location --> inline alien * block tag in content location --> *inline* alien only in the fourth and the last case do we need to use the "wrong" alien type to preserve document validity, and it will always be inline where block was expected, which should reduce UI issues. The condensed version of the above, which is used in the code, is: * If in a non-wrapper content location, use inline * If in a wrapper that's not ours, use inline * Otherwise, decide based on tag name * Open or close wrapper if needed ve.dm.Converter: * Replace isInline logic in createAlien() with the above * Factor out code to start wrapping (was duplicated) into startWrapping() * Call startWrapping() if createAlien() returns an alienInline and we're in a structural location Tests: * Add test cases with aliens at the start and end of unwrapped content ** The first one failed prior to these changes and now passes, the second one was already passing * Fix about group test case, was exhibiting the bug that this commit fixes Change-Id: I657aa0ff5bc2b57cd48ef8a99c8ca930936c03b8
2012-12-20 00:59:58 +00:00
]
},
'wrapping of bare content ending with inline alien': {
'html': '<body>Foo<tt class="bar">Bar</tt></body>',
(bug 43056) Inline tags like <span> are block-alienated sometimes This happens when the <span> is the start of unwrapped content. The converter logic to look at the tag name in wrapping mode doesn't kick in because we're not yet in wrapping mode at that point. The core issue was that previously, we relied on the document structure/state to choose between alienBlock and alienInline, and only used the tag name where the document structure was ambiguous (wrapping). Changed this to be the other way around: we now rely primarily on the tag name, and if that doesn't match what we expect based on the document structure, we work around that if possible. Specifically: * inline tag in our wrapper --> inline alien * block tag in our wrapper --> close wrapper, block alien * inline tag in wrapper that's not ours --> inline alien * block tag in wrapper that's not ours --> *inline* alien * inline tag in structural location --> open wrapper, inline alien * block tag in structural location --> block alien * inline tag in content location --> inline alien * block tag in content location --> *inline* alien only in the fourth and the last case do we need to use the "wrong" alien type to preserve document validity, and it will always be inline where block was expected, which should reduce UI issues. The condensed version of the above, which is used in the code, is: * If in a non-wrapper content location, use inline * If in a wrapper that's not ours, use inline * Otherwise, decide based on tag name * Open or close wrapper if needed ve.dm.Converter: * Replace isInline logic in createAlien() with the above * Factor out code to start wrapping (was duplicated) into startWrapping() * Call startWrapping() if createAlien() returns an alienInline and we're in a structural location Tests: * Add test cases with aliens at the start and end of unwrapped content ** The first one failed prior to these changes and now passes, the second one was already passing * Fix about group test case, was exhibiting the bug that this commit fixes Change-Id: I657aa0ff5bc2b57cd48ef8a99c8ca930936c03b8
2012-12-20 00:59:58 +00:00
'data': [
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'F',
'o',
'o',
{
'type': 'alienInline',
'attributes': { 'domElements': $( '<tt class="bar">Bar</tt>' ).toArray() }
(bug 43056) Inline tags like <span> are block-alienated sometimes This happens when the <span> is the start of unwrapped content. The converter logic to look at the tag name in wrapping mode doesn't kick in because we're not yet in wrapping mode at that point. The core issue was that previously, we relied on the document structure/state to choose between alienBlock and alienInline, and only used the tag name where the document structure was ambiguous (wrapping). Changed this to be the other way around: we now rely primarily on the tag name, and if that doesn't match what we expect based on the document structure, we work around that if possible. Specifically: * inline tag in our wrapper --> inline alien * block tag in our wrapper --> close wrapper, block alien * inline tag in wrapper that's not ours --> inline alien * block tag in wrapper that's not ours --> *inline* alien * inline tag in structural location --> open wrapper, inline alien * block tag in structural location --> block alien * inline tag in content location --> inline alien * block tag in content location --> *inline* alien only in the fourth and the last case do we need to use the "wrong" alien type to preserve document validity, and it will always be inline where block was expected, which should reduce UI issues. The condensed version of the above, which is used in the code, is: * If in a non-wrapper content location, use inline * If in a wrapper that's not ours, use inline * Otherwise, decide based on tag name * Open or close wrapper if needed ve.dm.Converter: * Replace isInline logic in createAlien() with the above * Factor out code to start wrapping (was duplicated) into startWrapping() * Call startWrapping() if createAlien() returns an alienInline and we're in a structural location Tests: * Add test cases with aliens at the start and end of unwrapped content ** The first one failed prior to these changes and now passes, the second one was already passing * Fix about group test case, was exhibiting the bug that this commit fixes Change-Id: I657aa0ff5bc2b57cd48ef8a99c8ca930936c03b8
2012-12-20 00:59:58 +00:00
},
{ 'type': '/alienInline' },
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
(bug 43056) Inline tags like <span> are block-alienated sometimes This happens when the <span> is the start of unwrapped content. The converter logic to look at the tag name in wrapping mode doesn't kick in because we're not yet in wrapping mode at that point. The core issue was that previously, we relied on the document structure/state to choose between alienBlock and alienInline, and only used the tag name where the document structure was ambiguous (wrapping). Changed this to be the other way around: we now rely primarily on the tag name, and if that doesn't match what we expect based on the document structure, we work around that if possible. Specifically: * inline tag in our wrapper --> inline alien * block tag in our wrapper --> close wrapper, block alien * inline tag in wrapper that's not ours --> inline alien * block tag in wrapper that's not ours --> *inline* alien * inline tag in structural location --> open wrapper, inline alien * block tag in structural location --> block alien * inline tag in content location --> inline alien * block tag in content location --> *inline* alien only in the fourth and the last case do we need to use the "wrong" alien type to preserve document validity, and it will always be inline where block was expected, which should reduce UI issues. The condensed version of the above, which is used in the code, is: * If in a non-wrapper content location, use inline * If in a wrapper that's not ours, use inline * Otherwise, decide based on tag name * Open or close wrapper if needed ve.dm.Converter: * Replace isInline logic in createAlien() with the above * Factor out code to start wrapping (was duplicated) into startWrapping() * Call startWrapping() if createAlien() returns an alienInline and we're in a structural location Tests: * Add test cases with aliens at the start and end of unwrapped content ** The first one failed prior to these changes and now passes, the second one was already passing * Fix about group test case, was exhibiting the bug that this commit fixes Change-Id: I657aa0ff5bc2b57cd48ef8a99c8ca930936c03b8
2012-12-20 00:59:58 +00:00
]
},
'wrapping of bare content with about group': {
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'html': '<body>1<tt about="#mwt1">foo</tt><tt about="#mwt1">bar</tt>2</body>',
'data': [
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'1',
{
'type': 'alienInline',
'attributes': { 'domElements': $( '<tt about="#mwt1">foo</tt><tt about="#mwt1">bar</tt>' ).toArray() }
},
{ 'type': '/alienInline' },
'2',
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'wrapping of bare content between structural nodes': {
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'html': '<body><table></table>abc<table></table></body>',
'data': [
{ 'type': 'table' },
{ 'type': '/table' },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'a',
'b',
'c',
{ 'type': '/paragraph' },
{ 'type': 'table' },
{ 'type': '/table' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'wrapping of bare content between paragraphs': {
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'html': '<body><p>abc</p>def<p></p></body>',
'data': [
{ 'type': 'paragraph' },
'a',
'b',
'c',
{ 'type': '/paragraph' },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'd',
'e',
'f',
{ 'type': '/paragraph' },
{ 'type': 'paragraph' },
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'wrapping prevents empty list items': {
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'html': '<body><ul><li></li></ul></body>',
'data': [
{ 'type': 'list', 'attributes': { 'style': 'bullet' } },
{ 'type': 'listItem' },
{ 'type': 'paragraph', 'internal': { 'generated': 'empty' } },
{ 'type': '/paragraph' },
{ 'type': '/listItem' },
{ 'type': '/list' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'empty document': {
'html': '',
'data': [
{ 'type': 'paragraph', 'internal': { 'generated': 'empty' } },
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'empty document with content added by the editor': {
'html': null,
'data': [
{ 'type': 'paragraph', 'internal': { 'generated': 'empty' } },
'F',
'o',
'o',
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
],
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'normalizedHtml': '<body><p>Foo</p></body>'
},
'empty list item with content added by the editor': {
'html': null,
'data': [
{ 'type': 'list', 'attributes': { 'style': 'bullet' } },
{ 'type': 'listItem' },
{ 'type': 'paragraph', 'internal': { 'generated': 'empty' } },
'F',
'o',
'o',
{ 'type': '/paragraph' },
{ 'type': '/listItem' },
{ 'type': '/list' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
],
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'normalizedHtml': '<body><ul><li><p>Foo</p></li></ul></body>'
},
'example document': {
'html': ve.dm.example.html,
'data': ve.dm.example.data
},
'empty annotation': {
'html': '<body><p>Foo<span id="anchorTarget"></span>Bar</p></body>',
'data': [
{ 'type': 'paragraph' },
'F', 'o', 'o',
{
'type': 'alienMeta',
'attributes': {
'domElements': $( '<span id="anchorTarget"></span>' ).toArray()
}
},
{ 'type': '/alienMeta' },
'B', 'a', 'r',
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'empty annotation in wrapper paragraph': {
'html': '<body>Foo<span id="anchorTarget"></span>Bar</body>',
'data': [
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'F', 'o', 'o',
{
'type': 'alienMeta',
'attributes': {
'domElements': $( '<span id="anchorTarget"></span>' ).toArray()
}
},
{ 'type': '/alienMeta' },
'B', 'a', 'r',
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'nested empty annotation': {
'html': '<body><p>Foo<i><b><u></u></b></i>Bar</p></body>',
'data': [
{ 'type': 'paragraph' },
'F', 'o', 'o',
{
'type': 'alienMeta',
'attributes': {
'domElements': $( '<i><b><u></u></b></i>' ).toArray()
}
},
{ 'type': '/alienMeta' },
'B', 'a', 'r',
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'empty annotation inside nonempty annotation': {
'html': '<body><p><i>Foo<b></b></i></p></body>',
'data': [
{ 'type': 'paragraph' },
[ 'F', [ ve.dm.example.italic ] ],
[ 'o', [ ve.dm.example.italic ] ],
[ 'o', [ ve.dm.example.italic ] ],
{
'type': 'alienMeta',
'attributes': {
'domElements': $( '<b></b>' ).toArray()
},
'annotations': [ ve.dm.example.italic ]
},
{ 'type': '/alienMeta' },
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'empty annotation with comment': {
'html': '<body><p>Foo<b><!-- Bar --></b>Baz</p></body>',
'data': [
{ 'type': 'paragraph' },
'F', 'o', 'o',
{
'type': 'alienMeta',
'attributes': {
'domElements': $( '<b><!-- Bar --></b>' ).toArray()
}
},
{ 'type': '/alienMeta' },
'B', 'a', 'z',
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'list item with space followed by link': {
'html': '<body><ul><li><p> <a href="Foobar">bar</a></p></li></ul></body>',
'data': [
{ 'type': 'list', 'attributes': { 'style': 'bullet' } },
{ 'type': 'listItem' },
{ 'type': 'paragraph', 'internal': { 'whitespace': [ undefined, ' ' ] } },
[
'b',
[ {
'type': 'link',
Great Annotation Refactor of 2013 This changes the annotation API to be the same as the node API, sans a few boolean flags that don't apply. The APIs were different, but there was really no good reason why, so this makes things simpler for API users. It also means we'll be able to factor a bunch of things out because they're now duplicated between nodes, meta items and annotations. Linear model annotations are now objects with 'type' and 'attributes' properties (rather than 'name' and 'data'), for consistency with elements. They now also contain html/0/* attributes for HTML attribute preservation, which obsoletes the htmlTagName and htmlAttributes properties. dm.Annotation subclasses take a reference to such an object and implement conversion using .static.toDataElement and .static.toDomElements just like nodes do. The custom .getHash() functions are no longer necessary because of the way HTML attribute preservation was reimplemented. CE rendering has been moved out of dm.Annotation (it never made sense to have CE rendering functions in DM classes, this was bothering me) and into separate ce.Annotation subclasses. These are very similar to CE nodes in that they have a this.$ generated based on something in the DM; the main difference is that nodes listen to events and update themselves, whereas annotations are static and are simply destroyed and rebuilt when they change. This change also adds whitelisted HTML attribute rendering for annotations, as well as class="ve-ce-FooAnnotation" attributes. Now that annotation classes produce real DOM nodes rather than weird objects describing HTML tags, we can't generate HTML as a string in ce.ContentBranchNode anymore. getRenderedContents() has been rewritten to be much more similar to the way the converter renders annotations; in fact, significant parts of it were copied from the converter, so that should be factored out in the future. This change actually fixes an annotation rendering discrepancy between ce.ContentBranchNode and dm.Converter; see the diff of ve.ce.ContentBranchNode.test.js. ve.ce.MWEntityNode.js: * Remove stray property ve.dm.MWExternalLinkAnnotation.js: * Store 'rel' attribute ve.dm.TextStyleAnnotation.js: * Put all the conversion logic in the abstract base class ve.dm.Converter.js: * Also feed annotations through getDomElementsFromDataElement() and createDataElement() ve.dm.Node.js: * Fix undocumented property ve.ce.ContentBranchNode.test.js: * Add descriptive messages for each test case * Compare DOM trees, not HTML strings * Compare without all the class="ve-ce-WhateverAnnotation" clutter ve.ui.LinkInspector.js: * Replace direct .getHash() calls (evil!) with ve.getHash() Bug: 46464 Bug: 44808 Change-Id: I31991488579b8cce6d98ed8b29b486ba5ec38cdc
2013-04-02 17:23:33 +00:00
'attributes': {
'href': 'Foobar'
},
'htmlAttributes': [ { 'values': {
'href': 'Foobar'
} } ]
} ]
],
[
'a',
[ {
'type': 'link',
Great Annotation Refactor of 2013 This changes the annotation API to be the same as the node API, sans a few boolean flags that don't apply. The APIs were different, but there was really no good reason why, so this makes things simpler for API users. It also means we'll be able to factor a bunch of things out because they're now duplicated between nodes, meta items and annotations. Linear model annotations are now objects with 'type' and 'attributes' properties (rather than 'name' and 'data'), for consistency with elements. They now also contain html/0/* attributes for HTML attribute preservation, which obsoletes the htmlTagName and htmlAttributes properties. dm.Annotation subclasses take a reference to such an object and implement conversion using .static.toDataElement and .static.toDomElements just like nodes do. The custom .getHash() functions are no longer necessary because of the way HTML attribute preservation was reimplemented. CE rendering has been moved out of dm.Annotation (it never made sense to have CE rendering functions in DM classes, this was bothering me) and into separate ce.Annotation subclasses. These are very similar to CE nodes in that they have a this.$ generated based on something in the DM; the main difference is that nodes listen to events and update themselves, whereas annotations are static and are simply destroyed and rebuilt when they change. This change also adds whitelisted HTML attribute rendering for annotations, as well as class="ve-ce-FooAnnotation" attributes. Now that annotation classes produce real DOM nodes rather than weird objects describing HTML tags, we can't generate HTML as a string in ce.ContentBranchNode anymore. getRenderedContents() has been rewritten to be much more similar to the way the converter renders annotations; in fact, significant parts of it were copied from the converter, so that should be factored out in the future. This change actually fixes an annotation rendering discrepancy between ce.ContentBranchNode and dm.Converter; see the diff of ve.ce.ContentBranchNode.test.js. ve.ce.MWEntityNode.js: * Remove stray property ve.dm.MWExternalLinkAnnotation.js: * Store 'rel' attribute ve.dm.TextStyleAnnotation.js: * Put all the conversion logic in the abstract base class ve.dm.Converter.js: * Also feed annotations through getDomElementsFromDataElement() and createDataElement() ve.dm.Node.js: * Fix undocumented property ve.ce.ContentBranchNode.test.js: * Add descriptive messages for each test case * Compare DOM trees, not HTML strings * Compare without all the class="ve-ce-WhateverAnnotation" clutter ve.ui.LinkInspector.js: * Replace direct .getHash() calls (evil!) with ve.getHash() Bug: 46464 Bug: 44808 Change-Id: I31991488579b8cce6d98ed8b29b486ba5ec38cdc
2013-04-02 17:23:33 +00:00
'attributes': {
'href': 'Foobar'
},
'htmlAttributes': [ { 'values': {
'href': 'Foobar'
} } ]
} ]
],
[
'r',
[ {
'type': 'link',
Great Annotation Refactor of 2013 This changes the annotation API to be the same as the node API, sans a few boolean flags that don't apply. The APIs were different, but there was really no good reason why, so this makes things simpler for API users. It also means we'll be able to factor a bunch of things out because they're now duplicated between nodes, meta items and annotations. Linear model annotations are now objects with 'type' and 'attributes' properties (rather than 'name' and 'data'), for consistency with elements. They now also contain html/0/* attributes for HTML attribute preservation, which obsoletes the htmlTagName and htmlAttributes properties. dm.Annotation subclasses take a reference to such an object and implement conversion using .static.toDataElement and .static.toDomElements just like nodes do. The custom .getHash() functions are no longer necessary because of the way HTML attribute preservation was reimplemented. CE rendering has been moved out of dm.Annotation (it never made sense to have CE rendering functions in DM classes, this was bothering me) and into separate ce.Annotation subclasses. These are very similar to CE nodes in that they have a this.$ generated based on something in the DM; the main difference is that nodes listen to events and update themselves, whereas annotations are static and are simply destroyed and rebuilt when they change. This change also adds whitelisted HTML attribute rendering for annotations, as well as class="ve-ce-FooAnnotation" attributes. Now that annotation classes produce real DOM nodes rather than weird objects describing HTML tags, we can't generate HTML as a string in ce.ContentBranchNode anymore. getRenderedContents() has been rewritten to be much more similar to the way the converter renders annotations; in fact, significant parts of it were copied from the converter, so that should be factored out in the future. This change actually fixes an annotation rendering discrepancy between ce.ContentBranchNode and dm.Converter; see the diff of ve.ce.ContentBranchNode.test.js. ve.ce.MWEntityNode.js: * Remove stray property ve.dm.MWExternalLinkAnnotation.js: * Store 'rel' attribute ve.dm.TextStyleAnnotation.js: * Put all the conversion logic in the abstract base class ve.dm.Converter.js: * Also feed annotations through getDomElementsFromDataElement() and createDataElement() ve.dm.Node.js: * Fix undocumented property ve.ce.ContentBranchNode.test.js: * Add descriptive messages for each test case * Compare DOM trees, not HTML strings * Compare without all the class="ve-ce-WhateverAnnotation" clutter ve.ui.LinkInspector.js: * Replace direct .getHash() calls (evil!) with ve.getHash() Bug: 46464 Bug: 44808 Change-Id: I31991488579b8cce6d98ed8b29b486ba5ec38cdc
2013-04-02 17:23:33 +00:00
'attributes': {
'href': 'Foobar'
},
'htmlAttributes': [ { 'values': {
'href': 'Foobar'
} } ]
} ]
],
{ 'type': '/paragraph' },
{ 'type': '/listItem' },
{ 'type': '/list' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
2012-08-10 21:09:04 +00:00
},
'whitespace between unwrapped inline nodes': {
'html':
'<body>' +
'<tt>c</tt> <tt>d</tt>\n<tt>e</tt>' +
'</body>',
'data': [
{
'type': 'paragraph',
'internal': {
'generated': 'wrapper'
}
},
{
'type': 'alienInline',
'attributes': {
'domElements': $( '<tt>c</tt>' ).toArray()
}
},
{ 'type': '/alienInline' },
' ',
{
'type': 'alienInline',
'attributes': {
'domElements': $( '<tt>d</tt>' ).toArray()
}
},
{ 'type': '/alienInline' },
'\n',
{
'type': 'alienInline',
'attributes': {
'domElements': $( '<tt>e</tt>' ).toArray()
}
},
{ 'type': '/alienInline' },
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
2012-08-10 21:09:04 +00:00
'whitespace preservation in headings': {
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'html': '<body><h2>Foo</h2><h2> Bar</h2><h2>Baz </h2><h2> Quux </h2></body>',
2012-08-10 21:09:04 +00:00
'data': [
{ 'type': 'heading', 'attributes': { 'level': 2 } },
'F',
'o',
'o',
{ 'type': '/heading' },
{
'type': 'heading',
'attributes': { 'level': 2 },
'internal': { 'whitespace': [ undefined, ' ' ] }
2012-08-10 21:09:04 +00:00
},
'B',
'a',
'r',
{ 'type': '/heading' },
{
'type': 'heading',
'attributes': { 'level': 2 },
'internal': { 'whitespace': [ undefined, undefined, ' ' ] }
2012-08-10 21:09:04 +00:00
},
'B',
'a',
'z',
{ 'type': '/heading' },
{
'type': 'heading',
'attributes': { 'level': 2 },
'internal': { 'whitespace': [ undefined, ' ', ' ' ] }
2012-08-10 21:09:04 +00:00
},
'Q',
'u',
'u',
'x',
{ 'type': '/heading' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
2012-08-10 21:09:04 +00:00
]
},
'whitespace preservation in list items': {
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'html': '<body><ul><li>Foo</li><li> Bar</li><li>Baz </li><li> Quux </li></ul></body>',
2012-08-10 21:09:04 +00:00
'data': [
{ 'type': 'list', 'attributes': { 'style': 'bullet' } },
{ 'type': 'listItem' },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
2012-08-10 21:09:04 +00:00
'F',
'o',
'o',
{ 'type': '/paragraph' },
{ 'type': '/listItem' },
Preserve whitespace between elements This commit fully utilizes all four positions in the internal.whitespace array. Outer whitespace is now preserved as well, and is duplicated either in the adjacent sibling (one node's outerPost is the next sibling's outerPre) or in the parent (a branch node's innerPre is its first child's outerPre, and its innerPost is its last child's outerPost). Before restoring saved whitespace, we check that these two agree with each other, and if they disagree we assume the user has been moving stuff around and don't restore any whitespace in that spot. The whitespace at the very beginning and the very end of the document (i.e. the first node's outerPre and the last node's outerPost) isn't duplicated anywhere, nor is inner whitespace in content nodes. The basic outline of the implementation is: * When we encounter whitespace, strip it and store it in the previous node's outerPost. Also store it in nextWhitespace so we can put it in the next node's outerPre once we encounter that node. * When we encounter whitespace in wrapped bare text, we don't know in advance if it's gonna be succeeded by more non-whitespace (in which case it needs to be output verbatim), or not (in which case it's leading whitespace and needs to be stripped and stored). The fact that annotations are nodes in HTML makes this trickier. So we write the whitespace to the temporary linmod and store it in wrappedWhitespace, then if it turns out to be trailing whitespace we take it back out of the data array and record it the usual way. * Because text nodes can contain any combination of leading whitespace actual text and trailing whitespace, and because we may or may not already have opened a wrapping paragraph, there are a lot of different combinations to handle. We handle all of them but the resulting code is pretty dense and verbose. More low-level list of changes: In getDataFromDom(): * Added helper function addWhitespace() for storing whitespace for an element * Added helper function processNextWhitespace() for processing any whitespace passed on from the previous node via the nextWhitespace var * Rename paragraph to wrappingParagraph. Make wrapping default to alreadyWrapped so we can simplify wrapping||alreadyWrapped and !wrapping&&!alreadyWrapped. Add wrappingIsOurs to track whether the wrapping originated in this recursion level (needed for deciding when to close the wrapper). * Add prevElement to track the previous element so we can propagate whitespace to it, and nextWhitespace so we can propagate whitespace to the next element. * Remove previous newline stripping hacks * Integrate the logic for wrapping bare content with the outer whitespace preservation code * Remove wrapperElement, no longer needed because we have a dedicated variable for the wrapping paragraph now and what was previously inner whitespace preservation for wrapper paragraphs is now covered by the outer whitespace preservation code. In getDomFromData(): * Reinsert whitespace where appropriate ** outerPre is inserted when opening the element ** This covers outerPost as well except for the last child's outerPost, which is handled as the parent's innerPost when closing the parent. ** innerPre and innerPost are inserted when closing the element. Care is taken not to insert these if they're duplicates of something else. * Propagate each node's outerPost to the next node (either the next sibling or the parent) using parentDomElement.lastOuterPost. We can't get this using .lastChild because we will have destroyed that child's .veInternal by then, and we can't tell whether a node will be its parent's last child when we process it (all other processing, including first child handling is done when processing the node itself, but this cannot be). * Special handling is needed for the last node's outerPost, which ends up in the container's .lastOuterPost property. Tests: * Allow .html to be null in data<->DOM converter tests. This indicates that the test is a one-way data->DOM test, not a DOM->data->DOM round-trip test. The data will be converted to HTML and checked against .normalizedHtml * Update existing tests as needed * Add tests for outer whitespace preservation and storage * Add test for squashing of whitespace in case of disagreement (this requires .html=null) Change-Id: I4db4fe372a421182e80a2535657af7784ff15f95
2012-08-21 00:37:42 +00:00
{ 'type': 'listItem', 'internal': { 'whitespace': [ undefined, ' ' ]} },
{ 'type': 'paragraph', 'internal': { 'whitespace': [ ' ' ], 'generated': 'wrapper' } },
2012-08-10 21:09:04 +00:00
'B',
'a',
'r',
{ 'type': '/paragraph' },
{ 'type': '/listItem' },
Preserve whitespace between elements This commit fully utilizes all four positions in the internal.whitespace array. Outer whitespace is now preserved as well, and is duplicated either in the adjacent sibling (one node's outerPost is the next sibling's outerPre) or in the parent (a branch node's innerPre is its first child's outerPre, and its innerPost is its last child's outerPost). Before restoring saved whitespace, we check that these two agree with each other, and if they disagree we assume the user has been moving stuff around and don't restore any whitespace in that spot. The whitespace at the very beginning and the very end of the document (i.e. the first node's outerPre and the last node's outerPost) isn't duplicated anywhere, nor is inner whitespace in content nodes. The basic outline of the implementation is: * When we encounter whitespace, strip it and store it in the previous node's outerPost. Also store it in nextWhitespace so we can put it in the next node's outerPre once we encounter that node. * When we encounter whitespace in wrapped bare text, we don't know in advance if it's gonna be succeeded by more non-whitespace (in which case it needs to be output verbatim), or not (in which case it's leading whitespace and needs to be stripped and stored). The fact that annotations are nodes in HTML makes this trickier. So we write the whitespace to the temporary linmod and store it in wrappedWhitespace, then if it turns out to be trailing whitespace we take it back out of the data array and record it the usual way. * Because text nodes can contain any combination of leading whitespace actual text and trailing whitespace, and because we may or may not already have opened a wrapping paragraph, there are a lot of different combinations to handle. We handle all of them but the resulting code is pretty dense and verbose. More low-level list of changes: In getDataFromDom(): * Added helper function addWhitespace() for storing whitespace for an element * Added helper function processNextWhitespace() for processing any whitespace passed on from the previous node via the nextWhitespace var * Rename paragraph to wrappingParagraph. Make wrapping default to alreadyWrapped so we can simplify wrapping||alreadyWrapped and !wrapping&&!alreadyWrapped. Add wrappingIsOurs to track whether the wrapping originated in this recursion level (needed for deciding when to close the wrapper). * Add prevElement to track the previous element so we can propagate whitespace to it, and nextWhitespace so we can propagate whitespace to the next element. * Remove previous newline stripping hacks * Integrate the logic for wrapping bare content with the outer whitespace preservation code * Remove wrapperElement, no longer needed because we have a dedicated variable for the wrapping paragraph now and what was previously inner whitespace preservation for wrapper paragraphs is now covered by the outer whitespace preservation code. In getDomFromData(): * Reinsert whitespace where appropriate ** outerPre is inserted when opening the element ** This covers outerPost as well except for the last child's outerPost, which is handled as the parent's innerPost when closing the parent. ** innerPre and innerPost are inserted when closing the element. Care is taken not to insert these if they're duplicates of something else. * Propagate each node's outerPost to the next node (either the next sibling or the parent) using parentDomElement.lastOuterPost. We can't get this using .lastChild because we will have destroyed that child's .veInternal by then, and we can't tell whether a node will be its parent's last child when we process it (all other processing, including first child handling is done when processing the node itself, but this cannot be). * Special handling is needed for the last node's outerPost, which ends up in the container's .lastOuterPost property. Tests: * Allow .html to be null in data<->DOM converter tests. This indicates that the test is a one-way data->DOM test, not a DOM->data->DOM round-trip test. The data will be converted to HTML and checked against .normalizedHtml * Update existing tests as needed * Add tests for outer whitespace preservation and storage * Add test for squashing of whitespace in case of disagreement (this requires .html=null) Change-Id: I4db4fe372a421182e80a2535657af7784ff15f95
2012-08-21 00:37:42 +00:00
{ 'type': 'listItem', 'internal': { 'whitespace': [ undefined, undefined, ' ' ] } },
{ 'type': 'paragraph', 'internal': { 'whitespace': [ undefined, undefined, undefined, ' ' ], 'generated': 'wrapper' } },
2012-08-10 21:09:04 +00:00
'B',
'a',
'z',
{ 'type': '/paragraph' },
{ 'type': '/listItem' },
Preserve whitespace between elements This commit fully utilizes all four positions in the internal.whitespace array. Outer whitespace is now preserved as well, and is duplicated either in the adjacent sibling (one node's outerPost is the next sibling's outerPre) or in the parent (a branch node's innerPre is its first child's outerPre, and its innerPost is its last child's outerPost). Before restoring saved whitespace, we check that these two agree with each other, and if they disagree we assume the user has been moving stuff around and don't restore any whitespace in that spot. The whitespace at the very beginning and the very end of the document (i.e. the first node's outerPre and the last node's outerPost) isn't duplicated anywhere, nor is inner whitespace in content nodes. The basic outline of the implementation is: * When we encounter whitespace, strip it and store it in the previous node's outerPost. Also store it in nextWhitespace so we can put it in the next node's outerPre once we encounter that node. * When we encounter whitespace in wrapped bare text, we don't know in advance if it's gonna be succeeded by more non-whitespace (in which case it needs to be output verbatim), or not (in which case it's leading whitespace and needs to be stripped and stored). The fact that annotations are nodes in HTML makes this trickier. So we write the whitespace to the temporary linmod and store it in wrappedWhitespace, then if it turns out to be trailing whitespace we take it back out of the data array and record it the usual way. * Because text nodes can contain any combination of leading whitespace actual text and trailing whitespace, and because we may or may not already have opened a wrapping paragraph, there are a lot of different combinations to handle. We handle all of them but the resulting code is pretty dense and verbose. More low-level list of changes: In getDataFromDom(): * Added helper function addWhitespace() for storing whitespace for an element * Added helper function processNextWhitespace() for processing any whitespace passed on from the previous node via the nextWhitespace var * Rename paragraph to wrappingParagraph. Make wrapping default to alreadyWrapped so we can simplify wrapping||alreadyWrapped and !wrapping&&!alreadyWrapped. Add wrappingIsOurs to track whether the wrapping originated in this recursion level (needed for deciding when to close the wrapper). * Add prevElement to track the previous element so we can propagate whitespace to it, and nextWhitespace so we can propagate whitespace to the next element. * Remove previous newline stripping hacks * Integrate the logic for wrapping bare content with the outer whitespace preservation code * Remove wrapperElement, no longer needed because we have a dedicated variable for the wrapping paragraph now and what was previously inner whitespace preservation for wrapper paragraphs is now covered by the outer whitespace preservation code. In getDomFromData(): * Reinsert whitespace where appropriate ** outerPre is inserted when opening the element ** This covers outerPost as well except for the last child's outerPost, which is handled as the parent's innerPost when closing the parent. ** innerPre and innerPost are inserted when closing the element. Care is taken not to insert these if they're duplicates of something else. * Propagate each node's outerPost to the next node (either the next sibling or the parent) using parentDomElement.lastOuterPost. We can't get this using .lastChild because we will have destroyed that child's .veInternal by then, and we can't tell whether a node will be its parent's last child when we process it (all other processing, including first child handling is done when processing the node itself, but this cannot be). * Special handling is needed for the last node's outerPost, which ends up in the container's .lastOuterPost property. Tests: * Allow .html to be null in data<->DOM converter tests. This indicates that the test is a one-way data->DOM test, not a DOM->data->DOM round-trip test. The data will be converted to HTML and checked against .normalizedHtml * Update existing tests as needed * Add tests for outer whitespace preservation and storage * Add test for squashing of whitespace in case of disagreement (this requires .html=null) Change-Id: I4db4fe372a421182e80a2535657af7784ff15f95
2012-08-21 00:37:42 +00:00
{ 'type': 'listItem', 'internal': { 'whitespace': [ undefined, ' ', ' '] } },
2012-08-10 21:09:04 +00:00
{
'type': 'paragraph',
Preserve whitespace between elements This commit fully utilizes all four positions in the internal.whitespace array. Outer whitespace is now preserved as well, and is duplicated either in the adjacent sibling (one node's outerPost is the next sibling's outerPre) or in the parent (a branch node's innerPre is its first child's outerPre, and its innerPost is its last child's outerPost). Before restoring saved whitespace, we check that these two agree with each other, and if they disagree we assume the user has been moving stuff around and don't restore any whitespace in that spot. The whitespace at the very beginning and the very end of the document (i.e. the first node's outerPre and the last node's outerPost) isn't duplicated anywhere, nor is inner whitespace in content nodes. The basic outline of the implementation is: * When we encounter whitespace, strip it and store it in the previous node's outerPost. Also store it in nextWhitespace so we can put it in the next node's outerPre once we encounter that node. * When we encounter whitespace in wrapped bare text, we don't know in advance if it's gonna be succeeded by more non-whitespace (in which case it needs to be output verbatim), or not (in which case it's leading whitespace and needs to be stripped and stored). The fact that annotations are nodes in HTML makes this trickier. So we write the whitespace to the temporary linmod and store it in wrappedWhitespace, then if it turns out to be trailing whitespace we take it back out of the data array and record it the usual way. * Because text nodes can contain any combination of leading whitespace actual text and trailing whitespace, and because we may or may not already have opened a wrapping paragraph, there are a lot of different combinations to handle. We handle all of them but the resulting code is pretty dense and verbose. More low-level list of changes: In getDataFromDom(): * Added helper function addWhitespace() for storing whitespace for an element * Added helper function processNextWhitespace() for processing any whitespace passed on from the previous node via the nextWhitespace var * Rename paragraph to wrappingParagraph. Make wrapping default to alreadyWrapped so we can simplify wrapping||alreadyWrapped and !wrapping&&!alreadyWrapped. Add wrappingIsOurs to track whether the wrapping originated in this recursion level (needed for deciding when to close the wrapper). * Add prevElement to track the previous element so we can propagate whitespace to it, and nextWhitespace so we can propagate whitespace to the next element. * Remove previous newline stripping hacks * Integrate the logic for wrapping bare content with the outer whitespace preservation code * Remove wrapperElement, no longer needed because we have a dedicated variable for the wrapping paragraph now and what was previously inner whitespace preservation for wrapper paragraphs is now covered by the outer whitespace preservation code. In getDomFromData(): * Reinsert whitespace where appropriate ** outerPre is inserted when opening the element ** This covers outerPost as well except for the last child's outerPost, which is handled as the parent's innerPost when closing the parent. ** innerPre and innerPost are inserted when closing the element. Care is taken not to insert these if they're duplicates of something else. * Propagate each node's outerPost to the next node (either the next sibling or the parent) using parentDomElement.lastOuterPost. We can't get this using .lastChild because we will have destroyed that child's .veInternal by then, and we can't tell whether a node will be its parent's last child when we process it (all other processing, including first child handling is done when processing the node itself, but this cannot be). * Special handling is needed for the last node's outerPost, which ends up in the container's .lastOuterPost property. Tests: * Allow .html to be null in data<->DOM converter tests. This indicates that the test is a one-way data->DOM test, not a DOM->data->DOM round-trip test. The data will be converted to HTML and checked against .normalizedHtml * Update existing tests as needed * Add tests for outer whitespace preservation and storage * Add test for squashing of whitespace in case of disagreement (this requires .html=null) Change-Id: I4db4fe372a421182e80a2535657af7784ff15f95
2012-08-21 00:37:42 +00:00
'internal': { 'whitespace': [ ' ', undefined, undefined, ' ' ], 'generated': 'wrapper' }
2012-08-10 21:09:04 +00:00
},
'Q',
'u',
'u',
'x',
{ 'type': '/paragraph' },
{ 'type': '/listItem' },
{ 'type': '/list' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
2012-08-10 21:09:04 +00:00
]
},
'whitespace preservation with annotations': {
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'html': '<body><p> <i> Foo </i> </p></body>',
2012-08-10 21:09:04 +00:00
'data': [
{
'type': 'paragraph',
'internal': { 'whitespace': [ undefined, ' ', ' ' ] }
2012-08-10 21:09:04 +00:00
},
[ ' ', [ ve.dm.example.italic ] ],
[ ' ', [ ve.dm.example.italic ] ],
[ 'F', [ ve.dm.example.italic ] ],
[ 'o', [ ve.dm.example.italic ] ],
[ 'o', [ ve.dm.example.italic ] ],
[ ' ', [ ve.dm.example.italic ] ],
[ ' ', [ ve.dm.example.italic ] ],
[ ' ', [ ve.dm.example.italic ] ],
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
2012-08-10 21:09:04 +00:00
]
Preserve whitespace between elements This commit fully utilizes all four positions in the internal.whitespace array. Outer whitespace is now preserved as well, and is duplicated either in the adjacent sibling (one node's outerPost is the next sibling's outerPre) or in the parent (a branch node's innerPre is its first child's outerPre, and its innerPost is its last child's outerPost). Before restoring saved whitespace, we check that these two agree with each other, and if they disagree we assume the user has been moving stuff around and don't restore any whitespace in that spot. The whitespace at the very beginning and the very end of the document (i.e. the first node's outerPre and the last node's outerPost) isn't duplicated anywhere, nor is inner whitespace in content nodes. The basic outline of the implementation is: * When we encounter whitespace, strip it and store it in the previous node's outerPost. Also store it in nextWhitespace so we can put it in the next node's outerPre once we encounter that node. * When we encounter whitespace in wrapped bare text, we don't know in advance if it's gonna be succeeded by more non-whitespace (in which case it needs to be output verbatim), or not (in which case it's leading whitespace and needs to be stripped and stored). The fact that annotations are nodes in HTML makes this trickier. So we write the whitespace to the temporary linmod and store it in wrappedWhitespace, then if it turns out to be trailing whitespace we take it back out of the data array and record it the usual way. * Because text nodes can contain any combination of leading whitespace actual text and trailing whitespace, and because we may or may not already have opened a wrapping paragraph, there are a lot of different combinations to handle. We handle all of them but the resulting code is pretty dense and verbose. More low-level list of changes: In getDataFromDom(): * Added helper function addWhitespace() for storing whitespace for an element * Added helper function processNextWhitespace() for processing any whitespace passed on from the previous node via the nextWhitespace var * Rename paragraph to wrappingParagraph. Make wrapping default to alreadyWrapped so we can simplify wrapping||alreadyWrapped and !wrapping&&!alreadyWrapped. Add wrappingIsOurs to track whether the wrapping originated in this recursion level (needed for deciding when to close the wrapper). * Add prevElement to track the previous element so we can propagate whitespace to it, and nextWhitespace so we can propagate whitespace to the next element. * Remove previous newline stripping hacks * Integrate the logic for wrapping bare content with the outer whitespace preservation code * Remove wrapperElement, no longer needed because we have a dedicated variable for the wrapping paragraph now and what was previously inner whitespace preservation for wrapper paragraphs is now covered by the outer whitespace preservation code. In getDomFromData(): * Reinsert whitespace where appropriate ** outerPre is inserted when opening the element ** This covers outerPost as well except for the last child's outerPost, which is handled as the parent's innerPost when closing the parent. ** innerPre and innerPost are inserted when closing the element. Care is taken not to insert these if they're duplicates of something else. * Propagate each node's outerPost to the next node (either the next sibling or the parent) using parentDomElement.lastOuterPost. We can't get this using .lastChild because we will have destroyed that child's .veInternal by then, and we can't tell whether a node will be its parent's last child when we process it (all other processing, including first child handling is done when processing the node itself, but this cannot be). * Special handling is needed for the last node's outerPost, which ends up in the container's .lastOuterPost property. Tests: * Allow .html to be null in data<->DOM converter tests. This indicates that the test is a one-way data->DOM test, not a DOM->data->DOM round-trip test. The data will be converted to HTML and checked against .normalizedHtml * Update existing tests as needed * Add tests for outer whitespace preservation and storage * Add test for squashing of whitespace in case of disagreement (this requires .html=null) Change-Id: I4db4fe372a421182e80a2535657af7784ff15f95
2012-08-21 00:37:42 +00:00
},
'outer whitespace preservation in a list with bare text and a wrapper paragraph': {
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'html': '<body>\n<ul>\n\n<li>\n\n\nBa re\n\n\n\n</li>\n\n\n\n\n<li>\t<p>\t\tP\t\t\t</p>\t\t\t\t</li>\t\n</ul>\t\n\t\n</body>',
Preserve whitespace between elements This commit fully utilizes all four positions in the internal.whitespace array. Outer whitespace is now preserved as well, and is duplicated either in the adjacent sibling (one node's outerPost is the next sibling's outerPre) or in the parent (a branch node's innerPre is its first child's outerPre, and its innerPost is its last child's outerPost). Before restoring saved whitespace, we check that these two agree with each other, and if they disagree we assume the user has been moving stuff around and don't restore any whitespace in that spot. The whitespace at the very beginning and the very end of the document (i.e. the first node's outerPre and the last node's outerPost) isn't duplicated anywhere, nor is inner whitespace in content nodes. The basic outline of the implementation is: * When we encounter whitespace, strip it and store it in the previous node's outerPost. Also store it in nextWhitespace so we can put it in the next node's outerPre once we encounter that node. * When we encounter whitespace in wrapped bare text, we don't know in advance if it's gonna be succeeded by more non-whitespace (in which case it needs to be output verbatim), or not (in which case it's leading whitespace and needs to be stripped and stored). The fact that annotations are nodes in HTML makes this trickier. So we write the whitespace to the temporary linmod and store it in wrappedWhitespace, then if it turns out to be trailing whitespace we take it back out of the data array and record it the usual way. * Because text nodes can contain any combination of leading whitespace actual text and trailing whitespace, and because we may or may not already have opened a wrapping paragraph, there are a lot of different combinations to handle. We handle all of them but the resulting code is pretty dense and verbose. More low-level list of changes: In getDataFromDom(): * Added helper function addWhitespace() for storing whitespace for an element * Added helper function processNextWhitespace() for processing any whitespace passed on from the previous node via the nextWhitespace var * Rename paragraph to wrappingParagraph. Make wrapping default to alreadyWrapped so we can simplify wrapping||alreadyWrapped and !wrapping&&!alreadyWrapped. Add wrappingIsOurs to track whether the wrapping originated in this recursion level (needed for deciding when to close the wrapper). * Add prevElement to track the previous element so we can propagate whitespace to it, and nextWhitespace so we can propagate whitespace to the next element. * Remove previous newline stripping hacks * Integrate the logic for wrapping bare content with the outer whitespace preservation code * Remove wrapperElement, no longer needed because we have a dedicated variable for the wrapping paragraph now and what was previously inner whitespace preservation for wrapper paragraphs is now covered by the outer whitespace preservation code. In getDomFromData(): * Reinsert whitespace where appropriate ** outerPre is inserted when opening the element ** This covers outerPost as well except for the last child's outerPost, which is handled as the parent's innerPost when closing the parent. ** innerPre and innerPost are inserted when closing the element. Care is taken not to insert these if they're duplicates of something else. * Propagate each node's outerPost to the next node (either the next sibling or the parent) using parentDomElement.lastOuterPost. We can't get this using .lastChild because we will have destroyed that child's .veInternal by then, and we can't tell whether a node will be its parent's last child when we process it (all other processing, including first child handling is done when processing the node itself, but this cannot be). * Special handling is needed for the last node's outerPost, which ends up in the container's .lastOuterPost property. Tests: * Allow .html to be null in data<->DOM converter tests. This indicates that the test is a one-way data->DOM test, not a DOM->data->DOM round-trip test. The data will be converted to HTML and checked against .normalizedHtml * Update existing tests as needed * Add tests for outer whitespace preservation and storage * Add test for squashing of whitespace in case of disagreement (this requires .html=null) Change-Id: I4db4fe372a421182e80a2535657af7784ff15f95
2012-08-21 00:37:42 +00:00
'data': [
{ 'type': 'list', 'attributes': { 'style': 'bullet' }, 'internal': { 'whitespace': [ '\n', '\n\n', '\t\n', '\t\n\t\n' ] } },
{ 'type': 'listItem', 'internal': { 'whitespace': [ '\n\n', '\n\n\n', '\n\n\n\n', '\n\n\n\n\n' ] } },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper', 'whitespace': [ '\n\n\n', undefined, undefined, '\n\n\n\n' ] } },
'B',
'a',
' ',
'r',
'e',
{ 'type': '/paragraph' },
{ 'type': '/listItem' },
{ 'type': 'listItem', 'internal': { 'whitespace': [ '\n\n\n\n\n', '\t', '\t\t\t\t', '\t\n' ] } },
{ 'type': 'paragraph', 'internal': { 'whitespace': [ '\t', '\t\t', '\t\t\t', '\t\t\t\t' ] } },
'P',
{ 'type': '/paragraph' },
{ 'type': '/listItem' },
{ 'type': '/list' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
Preserve whitespace between elements This commit fully utilizes all four positions in the internal.whitespace array. Outer whitespace is now preserved as well, and is duplicated either in the adjacent sibling (one node's outerPost is the next sibling's outerPre) or in the parent (a branch node's innerPre is its first child's outerPre, and its innerPost is its last child's outerPost). Before restoring saved whitespace, we check that these two agree with each other, and if they disagree we assume the user has been moving stuff around and don't restore any whitespace in that spot. The whitespace at the very beginning and the very end of the document (i.e. the first node's outerPre and the last node's outerPost) isn't duplicated anywhere, nor is inner whitespace in content nodes. The basic outline of the implementation is: * When we encounter whitespace, strip it and store it in the previous node's outerPost. Also store it in nextWhitespace so we can put it in the next node's outerPre once we encounter that node. * When we encounter whitespace in wrapped bare text, we don't know in advance if it's gonna be succeeded by more non-whitespace (in which case it needs to be output verbatim), or not (in which case it's leading whitespace and needs to be stripped and stored). The fact that annotations are nodes in HTML makes this trickier. So we write the whitespace to the temporary linmod and store it in wrappedWhitespace, then if it turns out to be trailing whitespace we take it back out of the data array and record it the usual way. * Because text nodes can contain any combination of leading whitespace actual text and trailing whitespace, and because we may or may not already have opened a wrapping paragraph, there are a lot of different combinations to handle. We handle all of them but the resulting code is pretty dense and verbose. More low-level list of changes: In getDataFromDom(): * Added helper function addWhitespace() for storing whitespace for an element * Added helper function processNextWhitespace() for processing any whitespace passed on from the previous node via the nextWhitespace var * Rename paragraph to wrappingParagraph. Make wrapping default to alreadyWrapped so we can simplify wrapping||alreadyWrapped and !wrapping&&!alreadyWrapped. Add wrappingIsOurs to track whether the wrapping originated in this recursion level (needed for deciding when to close the wrapper). * Add prevElement to track the previous element so we can propagate whitespace to it, and nextWhitespace so we can propagate whitespace to the next element. * Remove previous newline stripping hacks * Integrate the logic for wrapping bare content with the outer whitespace preservation code * Remove wrapperElement, no longer needed because we have a dedicated variable for the wrapping paragraph now and what was previously inner whitespace preservation for wrapper paragraphs is now covered by the outer whitespace preservation code. In getDomFromData(): * Reinsert whitespace where appropriate ** outerPre is inserted when opening the element ** This covers outerPost as well except for the last child's outerPost, which is handled as the parent's innerPost when closing the parent. ** innerPre and innerPost are inserted when closing the element. Care is taken not to insert these if they're duplicates of something else. * Propagate each node's outerPost to the next node (either the next sibling or the parent) using parentDomElement.lastOuterPost. We can't get this using .lastChild because we will have destroyed that child's .veInternal by then, and we can't tell whether a node will be its parent's last child when we process it (all other processing, including first child handling is done when processing the node itself, but this cannot be). * Special handling is needed for the last node's outerPost, which ends up in the container's .lastOuterPost property. Tests: * Allow .html to be null in data<->DOM converter tests. This indicates that the test is a one-way data->DOM test, not a DOM->data->DOM round-trip test. The data will be converted to HTML and checked against .normalizedHtml * Update existing tests as needed * Add tests for outer whitespace preservation and storage * Add test for squashing of whitespace in case of disagreement (this requires .html=null) Change-Id: I4db4fe372a421182e80a2535657af7784ff15f95
2012-08-21 00:37:42 +00:00
]
},
'outer whitespace preservation in a list with bare text and a sublist': {
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'html': '<body><ul>\n<li>\n\nBa re\n\n\n<ul>\n\n\n\n<li> <p> P </p> </li>\t</ul>\t\t</li>\t\t\t</ul></body>',
Preserve whitespace between elements This commit fully utilizes all four positions in the internal.whitespace array. Outer whitespace is now preserved as well, and is duplicated either in the adjacent sibling (one node's outerPost is the next sibling's outerPre) or in the parent (a branch node's innerPre is its first child's outerPre, and its innerPost is its last child's outerPost). Before restoring saved whitespace, we check that these two agree with each other, and if they disagree we assume the user has been moving stuff around and don't restore any whitespace in that spot. The whitespace at the very beginning and the very end of the document (i.e. the first node's outerPre and the last node's outerPost) isn't duplicated anywhere, nor is inner whitespace in content nodes. The basic outline of the implementation is: * When we encounter whitespace, strip it and store it in the previous node's outerPost. Also store it in nextWhitespace so we can put it in the next node's outerPre once we encounter that node. * When we encounter whitespace in wrapped bare text, we don't know in advance if it's gonna be succeeded by more non-whitespace (in which case it needs to be output verbatim), or not (in which case it's leading whitespace and needs to be stripped and stored). The fact that annotations are nodes in HTML makes this trickier. So we write the whitespace to the temporary linmod and store it in wrappedWhitespace, then if it turns out to be trailing whitespace we take it back out of the data array and record it the usual way. * Because text nodes can contain any combination of leading whitespace actual text and trailing whitespace, and because we may or may not already have opened a wrapping paragraph, there are a lot of different combinations to handle. We handle all of them but the resulting code is pretty dense and verbose. More low-level list of changes: In getDataFromDom(): * Added helper function addWhitespace() for storing whitespace for an element * Added helper function processNextWhitespace() for processing any whitespace passed on from the previous node via the nextWhitespace var * Rename paragraph to wrappingParagraph. Make wrapping default to alreadyWrapped so we can simplify wrapping||alreadyWrapped and !wrapping&&!alreadyWrapped. Add wrappingIsOurs to track whether the wrapping originated in this recursion level (needed for deciding when to close the wrapper). * Add prevElement to track the previous element so we can propagate whitespace to it, and nextWhitespace so we can propagate whitespace to the next element. * Remove previous newline stripping hacks * Integrate the logic for wrapping bare content with the outer whitespace preservation code * Remove wrapperElement, no longer needed because we have a dedicated variable for the wrapping paragraph now and what was previously inner whitespace preservation for wrapper paragraphs is now covered by the outer whitespace preservation code. In getDomFromData(): * Reinsert whitespace where appropriate ** outerPre is inserted when opening the element ** This covers outerPost as well except for the last child's outerPost, which is handled as the parent's innerPost when closing the parent. ** innerPre and innerPost are inserted when closing the element. Care is taken not to insert these if they're duplicates of something else. * Propagate each node's outerPost to the next node (either the next sibling or the parent) using parentDomElement.lastOuterPost. We can't get this using .lastChild because we will have destroyed that child's .veInternal by then, and we can't tell whether a node will be its parent's last child when we process it (all other processing, including first child handling is done when processing the node itself, but this cannot be). * Special handling is needed for the last node's outerPost, which ends up in the container's .lastOuterPost property. Tests: * Allow .html to be null in data<->DOM converter tests. This indicates that the test is a one-way data->DOM test, not a DOM->data->DOM round-trip test. The data will be converted to HTML and checked against .normalizedHtml * Update existing tests as needed * Add tests for outer whitespace preservation and storage * Add test for squashing of whitespace in case of disagreement (this requires .html=null) Change-Id: I4db4fe372a421182e80a2535657af7784ff15f95
2012-08-21 00:37:42 +00:00
'data': [
{ 'type': 'list', 'attributes': { 'style': 'bullet' }, 'internal': { 'whitespace': [ undefined, '\n', '\t\t\t' ] } },
{ 'type': 'listItem', 'internal': { 'whitespace': [ '\n', '\n\n', '\t\t', '\t\t\t' ] } },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper', 'whitespace': [ '\n\n', undefined, undefined, '\n\n\n' ] } },
'B',
'a',
' ',
'r',
'e',
{ 'type': '/paragraph' },
{ 'type': 'list', 'attributes': { 'style': 'bullet' }, 'internal': { 'whitespace': [ '\n\n\n', '\n\n\n\n', '\t', '\t\t' ] } },
{ 'type': 'listItem', 'internal': { 'whitespace': [ '\n\n\n\n', ' ', ' ', '\t' ] } },
{ 'type': 'paragraph', 'internal': { 'whitespace': [ ' ', ' ', ' ', ' '] } },
'P',
{ 'type': '/paragraph' },
{ 'type': '/listItem' },
{ 'type': '/list' },
{ 'type': '/listItem' },
{ 'type': '/list' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
Preserve whitespace between elements This commit fully utilizes all four positions in the internal.whitespace array. Outer whitespace is now preserved as well, and is duplicated either in the adjacent sibling (one node's outerPost is the next sibling's outerPre) or in the parent (a branch node's innerPre is its first child's outerPre, and its innerPost is its last child's outerPost). Before restoring saved whitespace, we check that these two agree with each other, and if they disagree we assume the user has been moving stuff around and don't restore any whitespace in that spot. The whitespace at the very beginning and the very end of the document (i.e. the first node's outerPre and the last node's outerPost) isn't duplicated anywhere, nor is inner whitespace in content nodes. The basic outline of the implementation is: * When we encounter whitespace, strip it and store it in the previous node's outerPost. Also store it in nextWhitespace so we can put it in the next node's outerPre once we encounter that node. * When we encounter whitespace in wrapped bare text, we don't know in advance if it's gonna be succeeded by more non-whitespace (in which case it needs to be output verbatim), or not (in which case it's leading whitespace and needs to be stripped and stored). The fact that annotations are nodes in HTML makes this trickier. So we write the whitespace to the temporary linmod and store it in wrappedWhitespace, then if it turns out to be trailing whitespace we take it back out of the data array and record it the usual way. * Because text nodes can contain any combination of leading whitespace actual text and trailing whitespace, and because we may or may not already have opened a wrapping paragraph, there are a lot of different combinations to handle. We handle all of them but the resulting code is pretty dense and verbose. More low-level list of changes: In getDataFromDom(): * Added helper function addWhitespace() for storing whitespace for an element * Added helper function processNextWhitespace() for processing any whitespace passed on from the previous node via the nextWhitespace var * Rename paragraph to wrappingParagraph. Make wrapping default to alreadyWrapped so we can simplify wrapping||alreadyWrapped and !wrapping&&!alreadyWrapped. Add wrappingIsOurs to track whether the wrapping originated in this recursion level (needed for deciding when to close the wrapper). * Add prevElement to track the previous element so we can propagate whitespace to it, and nextWhitespace so we can propagate whitespace to the next element. * Remove previous newline stripping hacks * Integrate the logic for wrapping bare content with the outer whitespace preservation code * Remove wrapperElement, no longer needed because we have a dedicated variable for the wrapping paragraph now and what was previously inner whitespace preservation for wrapper paragraphs is now covered by the outer whitespace preservation code. In getDomFromData(): * Reinsert whitespace where appropriate ** outerPre is inserted when opening the element ** This covers outerPost as well except for the last child's outerPost, which is handled as the parent's innerPost when closing the parent. ** innerPre and innerPost are inserted when closing the element. Care is taken not to insert these if they're duplicates of something else. * Propagate each node's outerPost to the next node (either the next sibling or the parent) using parentDomElement.lastOuterPost. We can't get this using .lastChild because we will have destroyed that child's .veInternal by then, and we can't tell whether a node will be its parent's last child when we process it (all other processing, including first child handling is done when processing the node itself, but this cannot be). * Special handling is needed for the last node's outerPost, which ends up in the container's .lastOuterPost property. Tests: * Allow .html to be null in data<->DOM converter tests. This indicates that the test is a one-way data->DOM test, not a DOM->data->DOM round-trip test. The data will be converted to HTML and checked against .normalizedHtml * Update existing tests as needed * Add tests for outer whitespace preservation and storage * Add test for squashing of whitespace in case of disagreement (this requires .html=null) Change-Id: I4db4fe372a421182e80a2535657af7784ff15f95
2012-08-21 00:37:42 +00:00
]
},
'whitespace preservation leaves non-edge content whitespace alone': {
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'html': '<body><p> A B <b> C\t</b>\t\tD\t\t\t</p>\nE\n\nF\n\n\n<b>\n\n\n\nG </b> H </body>',
Preserve whitespace between elements This commit fully utilizes all four positions in the internal.whitespace array. Outer whitespace is now preserved as well, and is duplicated either in the adjacent sibling (one node's outerPost is the next sibling's outerPre) or in the parent (a branch node's innerPre is its first child's outerPre, and its innerPost is its last child's outerPost). Before restoring saved whitespace, we check that these two agree with each other, and if they disagree we assume the user has been moving stuff around and don't restore any whitespace in that spot. The whitespace at the very beginning and the very end of the document (i.e. the first node's outerPre and the last node's outerPost) isn't duplicated anywhere, nor is inner whitespace in content nodes. The basic outline of the implementation is: * When we encounter whitespace, strip it and store it in the previous node's outerPost. Also store it in nextWhitespace so we can put it in the next node's outerPre once we encounter that node. * When we encounter whitespace in wrapped bare text, we don't know in advance if it's gonna be succeeded by more non-whitespace (in which case it needs to be output verbatim), or not (in which case it's leading whitespace and needs to be stripped and stored). The fact that annotations are nodes in HTML makes this trickier. So we write the whitespace to the temporary linmod and store it in wrappedWhitespace, then if it turns out to be trailing whitespace we take it back out of the data array and record it the usual way. * Because text nodes can contain any combination of leading whitespace actual text and trailing whitespace, and because we may or may not already have opened a wrapping paragraph, there are a lot of different combinations to handle. We handle all of them but the resulting code is pretty dense and verbose. More low-level list of changes: In getDataFromDom(): * Added helper function addWhitespace() for storing whitespace for an element * Added helper function processNextWhitespace() for processing any whitespace passed on from the previous node via the nextWhitespace var * Rename paragraph to wrappingParagraph. Make wrapping default to alreadyWrapped so we can simplify wrapping||alreadyWrapped and !wrapping&&!alreadyWrapped. Add wrappingIsOurs to track whether the wrapping originated in this recursion level (needed for deciding when to close the wrapper). * Add prevElement to track the previous element so we can propagate whitespace to it, and nextWhitespace so we can propagate whitespace to the next element. * Remove previous newline stripping hacks * Integrate the logic for wrapping bare content with the outer whitespace preservation code * Remove wrapperElement, no longer needed because we have a dedicated variable for the wrapping paragraph now and what was previously inner whitespace preservation for wrapper paragraphs is now covered by the outer whitespace preservation code. In getDomFromData(): * Reinsert whitespace where appropriate ** outerPre is inserted when opening the element ** This covers outerPost as well except for the last child's outerPost, which is handled as the parent's innerPost when closing the parent. ** innerPre and innerPost are inserted when closing the element. Care is taken not to insert these if they're duplicates of something else. * Propagate each node's outerPost to the next node (either the next sibling or the parent) using parentDomElement.lastOuterPost. We can't get this using .lastChild because we will have destroyed that child's .veInternal by then, and we can't tell whether a node will be its parent's last child when we process it (all other processing, including first child handling is done when processing the node itself, but this cannot be). * Special handling is needed for the last node's outerPost, which ends up in the container's .lastOuterPost property. Tests: * Allow .html to be null in data<->DOM converter tests. This indicates that the test is a one-way data->DOM test, not a DOM->data->DOM round-trip test. The data will be converted to HTML and checked against .normalizedHtml * Update existing tests as needed * Add tests for outer whitespace preservation and storage * Add test for squashing of whitespace in case of disagreement (this requires .html=null) Change-Id: I4db4fe372a421182e80a2535657af7784ff15f95
2012-08-21 00:37:42 +00:00
'data': [
{ 'type': 'paragraph', 'internal': { 'whitespace': [ undefined, ' ', '\t\t\t', '\n' ] } },
'A',
' ',
' ',
'B',
' ',
' ',
' ',
[ ' ', [ ve.dm.example.bold ] ],
[ ' ', [ ve.dm.example.bold ] ],
[ ' ', [ ve.dm.example.bold ] ],
[ ' ', [ ve.dm.example.bold ] ],
[ 'C', [ ve.dm.example.bold ] ],
[ '\t', [ ve.dm.example.bold ] ],
Preserve whitespace between elements This commit fully utilizes all four positions in the internal.whitespace array. Outer whitespace is now preserved as well, and is duplicated either in the adjacent sibling (one node's outerPost is the next sibling's outerPre) or in the parent (a branch node's innerPre is its first child's outerPre, and its innerPost is its last child's outerPost). Before restoring saved whitespace, we check that these two agree with each other, and if they disagree we assume the user has been moving stuff around and don't restore any whitespace in that spot. The whitespace at the very beginning and the very end of the document (i.e. the first node's outerPre and the last node's outerPost) isn't duplicated anywhere, nor is inner whitespace in content nodes. The basic outline of the implementation is: * When we encounter whitespace, strip it and store it in the previous node's outerPost. Also store it in nextWhitespace so we can put it in the next node's outerPre once we encounter that node. * When we encounter whitespace in wrapped bare text, we don't know in advance if it's gonna be succeeded by more non-whitespace (in which case it needs to be output verbatim), or not (in which case it's leading whitespace and needs to be stripped and stored). The fact that annotations are nodes in HTML makes this trickier. So we write the whitespace to the temporary linmod and store it in wrappedWhitespace, then if it turns out to be trailing whitespace we take it back out of the data array and record it the usual way. * Because text nodes can contain any combination of leading whitespace actual text and trailing whitespace, and because we may or may not already have opened a wrapping paragraph, there are a lot of different combinations to handle. We handle all of them but the resulting code is pretty dense and verbose. More low-level list of changes: In getDataFromDom(): * Added helper function addWhitespace() for storing whitespace for an element * Added helper function processNextWhitespace() for processing any whitespace passed on from the previous node via the nextWhitespace var * Rename paragraph to wrappingParagraph. Make wrapping default to alreadyWrapped so we can simplify wrapping||alreadyWrapped and !wrapping&&!alreadyWrapped. Add wrappingIsOurs to track whether the wrapping originated in this recursion level (needed for deciding when to close the wrapper). * Add prevElement to track the previous element so we can propagate whitespace to it, and nextWhitespace so we can propagate whitespace to the next element. * Remove previous newline stripping hacks * Integrate the logic for wrapping bare content with the outer whitespace preservation code * Remove wrapperElement, no longer needed because we have a dedicated variable for the wrapping paragraph now and what was previously inner whitespace preservation for wrapper paragraphs is now covered by the outer whitespace preservation code. In getDomFromData(): * Reinsert whitespace where appropriate ** outerPre is inserted when opening the element ** This covers outerPost as well except for the last child's outerPost, which is handled as the parent's innerPost when closing the parent. ** innerPre and innerPost are inserted when closing the element. Care is taken not to insert these if they're duplicates of something else. * Propagate each node's outerPost to the next node (either the next sibling or the parent) using parentDomElement.lastOuterPost. We can't get this using .lastChild because we will have destroyed that child's .veInternal by then, and we can't tell whether a node will be its parent's last child when we process it (all other processing, including first child handling is done when processing the node itself, but this cannot be). * Special handling is needed for the last node's outerPost, which ends up in the container's .lastOuterPost property. Tests: * Allow .html to be null in data<->DOM converter tests. This indicates that the test is a one-way data->DOM test, not a DOM->data->DOM round-trip test. The data will be converted to HTML and checked against .normalizedHtml * Update existing tests as needed * Add tests for outer whitespace preservation and storage * Add test for squashing of whitespace in case of disagreement (this requires .html=null) Change-Id: I4db4fe372a421182e80a2535657af7784ff15f95
2012-08-21 00:37:42 +00:00
'\t',
'\t',
'D',
{ 'type': '/paragraph' },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper', 'whitespace': [ '\n', undefined, undefined, ' ' ] } },
'E',
'\n',
'\n',
'F',
'\n',
'\n',
'\n',
[ '\n', [ ve.dm.example.bold ] ],
[ '\n', [ ve.dm.example.bold ] ],
[ '\n', [ ve.dm.example.bold ] ],
[ '\n', [ ve.dm.example.bold ] ],
[ 'G', [ ve.dm.example.bold ] ],
[ ' ', [ ve.dm.example.bold ] ],
Preserve whitespace between elements This commit fully utilizes all four positions in the internal.whitespace array. Outer whitespace is now preserved as well, and is duplicated either in the adjacent sibling (one node's outerPost is the next sibling's outerPre) or in the parent (a branch node's innerPre is its first child's outerPre, and its innerPost is its last child's outerPost). Before restoring saved whitespace, we check that these two agree with each other, and if they disagree we assume the user has been moving stuff around and don't restore any whitespace in that spot. The whitespace at the very beginning and the very end of the document (i.e. the first node's outerPre and the last node's outerPost) isn't duplicated anywhere, nor is inner whitespace in content nodes. The basic outline of the implementation is: * When we encounter whitespace, strip it and store it in the previous node's outerPost. Also store it in nextWhitespace so we can put it in the next node's outerPre once we encounter that node. * When we encounter whitespace in wrapped bare text, we don't know in advance if it's gonna be succeeded by more non-whitespace (in which case it needs to be output verbatim), or not (in which case it's leading whitespace and needs to be stripped and stored). The fact that annotations are nodes in HTML makes this trickier. So we write the whitespace to the temporary linmod and store it in wrappedWhitespace, then if it turns out to be trailing whitespace we take it back out of the data array and record it the usual way. * Because text nodes can contain any combination of leading whitespace actual text and trailing whitespace, and because we may or may not already have opened a wrapping paragraph, there are a lot of different combinations to handle. We handle all of them but the resulting code is pretty dense and verbose. More low-level list of changes: In getDataFromDom(): * Added helper function addWhitespace() for storing whitespace for an element * Added helper function processNextWhitespace() for processing any whitespace passed on from the previous node via the nextWhitespace var * Rename paragraph to wrappingParagraph. Make wrapping default to alreadyWrapped so we can simplify wrapping||alreadyWrapped and !wrapping&&!alreadyWrapped. Add wrappingIsOurs to track whether the wrapping originated in this recursion level (needed for deciding when to close the wrapper). * Add prevElement to track the previous element so we can propagate whitespace to it, and nextWhitespace so we can propagate whitespace to the next element. * Remove previous newline stripping hacks * Integrate the logic for wrapping bare content with the outer whitespace preservation code * Remove wrapperElement, no longer needed because we have a dedicated variable for the wrapping paragraph now and what was previously inner whitespace preservation for wrapper paragraphs is now covered by the outer whitespace preservation code. In getDomFromData(): * Reinsert whitespace where appropriate ** outerPre is inserted when opening the element ** This covers outerPost as well except for the last child's outerPost, which is handled as the parent's innerPost when closing the parent. ** innerPre and innerPost are inserted when closing the element. Care is taken not to insert these if they're duplicates of something else. * Propagate each node's outerPost to the next node (either the next sibling or the parent) using parentDomElement.lastOuterPost. We can't get this using .lastChild because we will have destroyed that child's .veInternal by then, and we can't tell whether a node will be its parent's last child when we process it (all other processing, including first child handling is done when processing the node itself, but this cannot be). * Special handling is needed for the last node's outerPost, which ends up in the container's .lastOuterPost property. Tests: * Allow .html to be null in data<->DOM converter tests. This indicates that the test is a one-way data->DOM test, not a DOM->data->DOM round-trip test. The data will be converted to HTML and checked against .normalizedHtml * Update existing tests as needed * Add tests for outer whitespace preservation and storage * Add test for squashing of whitespace in case of disagreement (this requires .html=null) Change-Id: I4db4fe372a421182e80a2535657af7784ff15f95
2012-08-21 00:37:42 +00:00
' ',
' ',
'H',
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
Preserve whitespace between elements This commit fully utilizes all four positions in the internal.whitespace array. Outer whitespace is now preserved as well, and is duplicated either in the adjacent sibling (one node's outerPost is the next sibling's outerPre) or in the parent (a branch node's innerPre is its first child's outerPre, and its innerPost is its last child's outerPost). Before restoring saved whitespace, we check that these two agree with each other, and if they disagree we assume the user has been moving stuff around and don't restore any whitespace in that spot. The whitespace at the very beginning and the very end of the document (i.e. the first node's outerPre and the last node's outerPost) isn't duplicated anywhere, nor is inner whitespace in content nodes. The basic outline of the implementation is: * When we encounter whitespace, strip it and store it in the previous node's outerPost. Also store it in nextWhitespace so we can put it in the next node's outerPre once we encounter that node. * When we encounter whitespace in wrapped bare text, we don't know in advance if it's gonna be succeeded by more non-whitespace (in which case it needs to be output verbatim), or not (in which case it's leading whitespace and needs to be stripped and stored). The fact that annotations are nodes in HTML makes this trickier. So we write the whitespace to the temporary linmod and store it in wrappedWhitespace, then if it turns out to be trailing whitespace we take it back out of the data array and record it the usual way. * Because text nodes can contain any combination of leading whitespace actual text and trailing whitespace, and because we may or may not already have opened a wrapping paragraph, there are a lot of different combinations to handle. We handle all of them but the resulting code is pretty dense and verbose. More low-level list of changes: In getDataFromDom(): * Added helper function addWhitespace() for storing whitespace for an element * Added helper function processNextWhitespace() for processing any whitespace passed on from the previous node via the nextWhitespace var * Rename paragraph to wrappingParagraph. Make wrapping default to alreadyWrapped so we can simplify wrapping||alreadyWrapped and !wrapping&&!alreadyWrapped. Add wrappingIsOurs to track whether the wrapping originated in this recursion level (needed for deciding when to close the wrapper). * Add prevElement to track the previous element so we can propagate whitespace to it, and nextWhitespace so we can propagate whitespace to the next element. * Remove previous newline stripping hacks * Integrate the logic for wrapping bare content with the outer whitespace preservation code * Remove wrapperElement, no longer needed because we have a dedicated variable for the wrapping paragraph now and what was previously inner whitespace preservation for wrapper paragraphs is now covered by the outer whitespace preservation code. In getDomFromData(): * Reinsert whitespace where appropriate ** outerPre is inserted when opening the element ** This covers outerPost as well except for the last child's outerPost, which is handled as the parent's innerPost when closing the parent. ** innerPre and innerPost are inserted when closing the element. Care is taken not to insert these if they're duplicates of something else. * Propagate each node's outerPost to the next node (either the next sibling or the parent) using parentDomElement.lastOuterPost. We can't get this using .lastChild because we will have destroyed that child's .veInternal by then, and we can't tell whether a node will be its parent's last child when we process it (all other processing, including first child handling is done when processing the node itself, but this cannot be). * Special handling is needed for the last node's outerPost, which ends up in the container's .lastOuterPost property. Tests: * Allow .html to be null in data<->DOM converter tests. This indicates that the test is a one-way data->DOM test, not a DOM->data->DOM round-trip test. The data will be converted to HTML and checked against .normalizedHtml * Update existing tests as needed * Add tests for outer whitespace preservation and storage * Add test for squashing of whitespace in case of disagreement (this requires .html=null) Change-Id: I4db4fe372a421182e80a2535657af7784ff15f95
2012-08-21 00:37:42 +00:00
]
},
'whitespace preservation with non-edge content whitespace with nested annotations': {
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'html': '<body><p> A B <b> C\t<i>\t\tD\t\t\t</i>\t\t\t\tE\n</b>\n\nF\n\n\n</p></body>',
Preserve whitespace between elements This commit fully utilizes all four positions in the internal.whitespace array. Outer whitespace is now preserved as well, and is duplicated either in the adjacent sibling (one node's outerPost is the next sibling's outerPre) or in the parent (a branch node's innerPre is its first child's outerPre, and its innerPost is its last child's outerPost). Before restoring saved whitespace, we check that these two agree with each other, and if they disagree we assume the user has been moving stuff around and don't restore any whitespace in that spot. The whitespace at the very beginning and the very end of the document (i.e. the first node's outerPre and the last node's outerPost) isn't duplicated anywhere, nor is inner whitespace in content nodes. The basic outline of the implementation is: * When we encounter whitespace, strip it and store it in the previous node's outerPost. Also store it in nextWhitespace so we can put it in the next node's outerPre once we encounter that node. * When we encounter whitespace in wrapped bare text, we don't know in advance if it's gonna be succeeded by more non-whitespace (in which case it needs to be output verbatim), or not (in which case it's leading whitespace and needs to be stripped and stored). The fact that annotations are nodes in HTML makes this trickier. So we write the whitespace to the temporary linmod and store it in wrappedWhitespace, then if it turns out to be trailing whitespace we take it back out of the data array and record it the usual way. * Because text nodes can contain any combination of leading whitespace actual text and trailing whitespace, and because we may or may not already have opened a wrapping paragraph, there are a lot of different combinations to handle. We handle all of them but the resulting code is pretty dense and verbose. More low-level list of changes: In getDataFromDom(): * Added helper function addWhitespace() for storing whitespace for an element * Added helper function processNextWhitespace() for processing any whitespace passed on from the previous node via the nextWhitespace var * Rename paragraph to wrappingParagraph. Make wrapping default to alreadyWrapped so we can simplify wrapping||alreadyWrapped and !wrapping&&!alreadyWrapped. Add wrappingIsOurs to track whether the wrapping originated in this recursion level (needed for deciding when to close the wrapper). * Add prevElement to track the previous element so we can propagate whitespace to it, and nextWhitespace so we can propagate whitespace to the next element. * Remove previous newline stripping hacks * Integrate the logic for wrapping bare content with the outer whitespace preservation code * Remove wrapperElement, no longer needed because we have a dedicated variable for the wrapping paragraph now and what was previously inner whitespace preservation for wrapper paragraphs is now covered by the outer whitespace preservation code. In getDomFromData(): * Reinsert whitespace where appropriate ** outerPre is inserted when opening the element ** This covers outerPost as well except for the last child's outerPost, which is handled as the parent's innerPost when closing the parent. ** innerPre and innerPost are inserted when closing the element. Care is taken not to insert these if they're duplicates of something else. * Propagate each node's outerPost to the next node (either the next sibling or the parent) using parentDomElement.lastOuterPost. We can't get this using .lastChild because we will have destroyed that child's .veInternal by then, and we can't tell whether a node will be its parent's last child when we process it (all other processing, including first child handling is done when processing the node itself, but this cannot be). * Special handling is needed for the last node's outerPost, which ends up in the container's .lastOuterPost property. Tests: * Allow .html to be null in data<->DOM converter tests. This indicates that the test is a one-way data->DOM test, not a DOM->data->DOM round-trip test. The data will be converted to HTML and checked against .normalizedHtml * Update existing tests as needed * Add tests for outer whitespace preservation and storage * Add test for squashing of whitespace in case of disagreement (this requires .html=null) Change-Id: I4db4fe372a421182e80a2535657af7784ff15f95
2012-08-21 00:37:42 +00:00
'data': [
{ 'type': 'paragraph', 'internal': { 'whitespace': [ undefined, ' ', '\n\n\n' ] } },
'A',
' ',
' ',
'B',
' ',
' ',
' ',
[ ' ', [ ve.dm.example.bold ] ],
[ ' ', [ ve.dm.example.bold ] ],
[ ' ', [ ve.dm.example.bold ] ],
[ ' ', [ ve.dm.example.bold ] ],
[ 'C', [ ve.dm.example.bold ] ],
[ '\t', [ ve.dm.example.bold ] ],
[ '\t', [ ve.dm.example.bold, ve.dm.example.italic ] ],
[ '\t', [ ve.dm.example.bold, ve.dm.example.italic ] ],
[ 'D', [ ve.dm.example.bold, ve.dm.example.italic ] ],
[ '\t', [ ve.dm.example.bold, ve.dm.example.italic ] ],
[ '\t', [ ve.dm.example.bold, ve.dm.example.italic ] ],
[ '\t', [ ve.dm.example.bold, ve.dm.example.italic ] ],
[ '\t', [ ve.dm.example.bold ] ],
[ '\t', [ ve.dm.example.bold ] ],
[ '\t', [ ve.dm.example.bold ] ],
[ '\t', [ ve.dm.example.bold ] ],
[ 'E', [ ve.dm.example.bold ] ],
[ '\n', [ ve.dm.example.bold ] ],
Preserve whitespace between elements This commit fully utilizes all four positions in the internal.whitespace array. Outer whitespace is now preserved as well, and is duplicated either in the adjacent sibling (one node's outerPost is the next sibling's outerPre) or in the parent (a branch node's innerPre is its first child's outerPre, and its innerPost is its last child's outerPost). Before restoring saved whitespace, we check that these two agree with each other, and if they disagree we assume the user has been moving stuff around and don't restore any whitespace in that spot. The whitespace at the very beginning and the very end of the document (i.e. the first node's outerPre and the last node's outerPost) isn't duplicated anywhere, nor is inner whitespace in content nodes. The basic outline of the implementation is: * When we encounter whitespace, strip it and store it in the previous node's outerPost. Also store it in nextWhitespace so we can put it in the next node's outerPre once we encounter that node. * When we encounter whitespace in wrapped bare text, we don't know in advance if it's gonna be succeeded by more non-whitespace (in which case it needs to be output verbatim), or not (in which case it's leading whitespace and needs to be stripped and stored). The fact that annotations are nodes in HTML makes this trickier. So we write the whitespace to the temporary linmod and store it in wrappedWhitespace, then if it turns out to be trailing whitespace we take it back out of the data array and record it the usual way. * Because text nodes can contain any combination of leading whitespace actual text and trailing whitespace, and because we may or may not already have opened a wrapping paragraph, there are a lot of different combinations to handle. We handle all of them but the resulting code is pretty dense and verbose. More low-level list of changes: In getDataFromDom(): * Added helper function addWhitespace() for storing whitespace for an element * Added helper function processNextWhitespace() for processing any whitespace passed on from the previous node via the nextWhitespace var * Rename paragraph to wrappingParagraph. Make wrapping default to alreadyWrapped so we can simplify wrapping||alreadyWrapped and !wrapping&&!alreadyWrapped. Add wrappingIsOurs to track whether the wrapping originated in this recursion level (needed for deciding when to close the wrapper). * Add prevElement to track the previous element so we can propagate whitespace to it, and nextWhitespace so we can propagate whitespace to the next element. * Remove previous newline stripping hacks * Integrate the logic for wrapping bare content with the outer whitespace preservation code * Remove wrapperElement, no longer needed because we have a dedicated variable for the wrapping paragraph now and what was previously inner whitespace preservation for wrapper paragraphs is now covered by the outer whitespace preservation code. In getDomFromData(): * Reinsert whitespace where appropriate ** outerPre is inserted when opening the element ** This covers outerPost as well except for the last child's outerPost, which is handled as the parent's innerPost when closing the parent. ** innerPre and innerPost are inserted when closing the element. Care is taken not to insert these if they're duplicates of something else. * Propagate each node's outerPost to the next node (either the next sibling or the parent) using parentDomElement.lastOuterPost. We can't get this using .lastChild because we will have destroyed that child's .veInternal by then, and we can't tell whether a node will be its parent's last child when we process it (all other processing, including first child handling is done when processing the node itself, but this cannot be). * Special handling is needed for the last node's outerPost, which ends up in the container's .lastOuterPost property. Tests: * Allow .html to be null in data<->DOM converter tests. This indicates that the test is a one-way data->DOM test, not a DOM->data->DOM round-trip test. The data will be converted to HTML and checked against .normalizedHtml * Update existing tests as needed * Add tests for outer whitespace preservation and storage * Add test for squashing of whitespace in case of disagreement (this requires .html=null) Change-Id: I4db4fe372a421182e80a2535657af7784ff15f95
2012-08-21 00:37:42 +00:00
'\n',
'\n',
'F',
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
Preserve whitespace between elements This commit fully utilizes all four positions in the internal.whitespace array. Outer whitespace is now preserved as well, and is duplicated either in the adjacent sibling (one node's outerPost is the next sibling's outerPre) or in the parent (a branch node's innerPre is its first child's outerPre, and its innerPost is its last child's outerPost). Before restoring saved whitespace, we check that these two agree with each other, and if they disagree we assume the user has been moving stuff around and don't restore any whitespace in that spot. The whitespace at the very beginning and the very end of the document (i.e. the first node's outerPre and the last node's outerPost) isn't duplicated anywhere, nor is inner whitespace in content nodes. The basic outline of the implementation is: * When we encounter whitespace, strip it and store it in the previous node's outerPost. Also store it in nextWhitespace so we can put it in the next node's outerPre once we encounter that node. * When we encounter whitespace in wrapped bare text, we don't know in advance if it's gonna be succeeded by more non-whitespace (in which case it needs to be output verbatim), or not (in which case it's leading whitespace and needs to be stripped and stored). The fact that annotations are nodes in HTML makes this trickier. So we write the whitespace to the temporary linmod and store it in wrappedWhitespace, then if it turns out to be trailing whitespace we take it back out of the data array and record it the usual way. * Because text nodes can contain any combination of leading whitespace actual text and trailing whitespace, and because we may or may not already have opened a wrapping paragraph, there are a lot of different combinations to handle. We handle all of them but the resulting code is pretty dense and verbose. More low-level list of changes: In getDataFromDom(): * Added helper function addWhitespace() for storing whitespace for an element * Added helper function processNextWhitespace() for processing any whitespace passed on from the previous node via the nextWhitespace var * Rename paragraph to wrappingParagraph. Make wrapping default to alreadyWrapped so we can simplify wrapping||alreadyWrapped and !wrapping&&!alreadyWrapped. Add wrappingIsOurs to track whether the wrapping originated in this recursion level (needed for deciding when to close the wrapper). * Add prevElement to track the previous element so we can propagate whitespace to it, and nextWhitespace so we can propagate whitespace to the next element. * Remove previous newline stripping hacks * Integrate the logic for wrapping bare content with the outer whitespace preservation code * Remove wrapperElement, no longer needed because we have a dedicated variable for the wrapping paragraph now and what was previously inner whitespace preservation for wrapper paragraphs is now covered by the outer whitespace preservation code. In getDomFromData(): * Reinsert whitespace where appropriate ** outerPre is inserted when opening the element ** This covers outerPost as well except for the last child's outerPost, which is handled as the parent's innerPost when closing the parent. ** innerPre and innerPost are inserted when closing the element. Care is taken not to insert these if they're duplicates of something else. * Propagate each node's outerPost to the next node (either the next sibling or the parent) using parentDomElement.lastOuterPost. We can't get this using .lastChild because we will have destroyed that child's .veInternal by then, and we can't tell whether a node will be its parent's last child when we process it (all other processing, including first child handling is done when processing the node itself, but this cannot be). * Special handling is needed for the last node's outerPost, which ends up in the container's .lastOuterPost property. Tests: * Allow .html to be null in data<->DOM converter tests. This indicates that the test is a one-way data->DOM test, not a DOM->data->DOM round-trip test. The data will be converted to HTML and checked against .normalizedHtml * Update existing tests as needed * Add tests for outer whitespace preservation and storage * Add test for squashing of whitespace in case of disagreement (this requires .html=null) Change-Id: I4db4fe372a421182e80a2535657af7784ff15f95
2012-08-21 00:37:42 +00:00
]
},
'whitespace preservation with tightly nested annotations': {
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'html': '<body><p> A B <b><i>\t\tC\t\t\t</i></b>\n\nD\n\n\n</p></body>',
Preserve whitespace between elements This commit fully utilizes all four positions in the internal.whitespace array. Outer whitespace is now preserved as well, and is duplicated either in the adjacent sibling (one node's outerPost is the next sibling's outerPre) or in the parent (a branch node's innerPre is its first child's outerPre, and its innerPost is its last child's outerPost). Before restoring saved whitespace, we check that these two agree with each other, and if they disagree we assume the user has been moving stuff around and don't restore any whitespace in that spot. The whitespace at the very beginning and the very end of the document (i.e. the first node's outerPre and the last node's outerPost) isn't duplicated anywhere, nor is inner whitespace in content nodes. The basic outline of the implementation is: * When we encounter whitespace, strip it and store it in the previous node's outerPost. Also store it in nextWhitespace so we can put it in the next node's outerPre once we encounter that node. * When we encounter whitespace in wrapped bare text, we don't know in advance if it's gonna be succeeded by more non-whitespace (in which case it needs to be output verbatim), or not (in which case it's leading whitespace and needs to be stripped and stored). The fact that annotations are nodes in HTML makes this trickier. So we write the whitespace to the temporary linmod and store it in wrappedWhitespace, then if it turns out to be trailing whitespace we take it back out of the data array and record it the usual way. * Because text nodes can contain any combination of leading whitespace actual text and trailing whitespace, and because we may or may not already have opened a wrapping paragraph, there are a lot of different combinations to handle. We handle all of them but the resulting code is pretty dense and verbose. More low-level list of changes: In getDataFromDom(): * Added helper function addWhitespace() for storing whitespace for an element * Added helper function processNextWhitespace() for processing any whitespace passed on from the previous node via the nextWhitespace var * Rename paragraph to wrappingParagraph. Make wrapping default to alreadyWrapped so we can simplify wrapping||alreadyWrapped and !wrapping&&!alreadyWrapped. Add wrappingIsOurs to track whether the wrapping originated in this recursion level (needed for deciding when to close the wrapper). * Add prevElement to track the previous element so we can propagate whitespace to it, and nextWhitespace so we can propagate whitespace to the next element. * Remove previous newline stripping hacks * Integrate the logic for wrapping bare content with the outer whitespace preservation code * Remove wrapperElement, no longer needed because we have a dedicated variable for the wrapping paragraph now and what was previously inner whitespace preservation for wrapper paragraphs is now covered by the outer whitespace preservation code. In getDomFromData(): * Reinsert whitespace where appropriate ** outerPre is inserted when opening the element ** This covers outerPost as well except for the last child's outerPost, which is handled as the parent's innerPost when closing the parent. ** innerPre and innerPost are inserted when closing the element. Care is taken not to insert these if they're duplicates of something else. * Propagate each node's outerPost to the next node (either the next sibling or the parent) using parentDomElement.lastOuterPost. We can't get this using .lastChild because we will have destroyed that child's .veInternal by then, and we can't tell whether a node will be its parent's last child when we process it (all other processing, including first child handling is done when processing the node itself, but this cannot be). * Special handling is needed for the last node's outerPost, which ends up in the container's .lastOuterPost property. Tests: * Allow .html to be null in data<->DOM converter tests. This indicates that the test is a one-way data->DOM test, not a DOM->data->DOM round-trip test. The data will be converted to HTML and checked against .normalizedHtml * Update existing tests as needed * Add tests for outer whitespace preservation and storage * Add test for squashing of whitespace in case of disagreement (this requires .html=null) Change-Id: I4db4fe372a421182e80a2535657af7784ff15f95
2012-08-21 00:37:42 +00:00
'data': [
{ 'type': 'paragraph', 'internal': { 'whitespace': [ undefined, ' ', '\n\n\n' ] } },
'A',
' ',
' ',
'B',
' ',
' ',
' ',
[ '\t', [ ve.dm.example.bold, ve.dm.example.italic ] ],
[ '\t', [ ve.dm.example.bold, ve.dm.example.italic ] ],
[ 'C', [ ve.dm.example.bold, ve.dm.example.italic ] ],
[ '\t', [ ve.dm.example.bold, ve.dm.example.italic ] ],
[ '\t', [ ve.dm.example.bold, ve.dm.example.italic ] ],
[ '\t', [ ve.dm.example.bold, ve.dm.example.italic ] ],
Preserve whitespace between elements This commit fully utilizes all four positions in the internal.whitespace array. Outer whitespace is now preserved as well, and is duplicated either in the adjacent sibling (one node's outerPost is the next sibling's outerPre) or in the parent (a branch node's innerPre is its first child's outerPre, and its innerPost is its last child's outerPost). Before restoring saved whitespace, we check that these two agree with each other, and if they disagree we assume the user has been moving stuff around and don't restore any whitespace in that spot. The whitespace at the very beginning and the very end of the document (i.e. the first node's outerPre and the last node's outerPost) isn't duplicated anywhere, nor is inner whitespace in content nodes. The basic outline of the implementation is: * When we encounter whitespace, strip it and store it in the previous node's outerPost. Also store it in nextWhitespace so we can put it in the next node's outerPre once we encounter that node. * When we encounter whitespace in wrapped bare text, we don't know in advance if it's gonna be succeeded by more non-whitespace (in which case it needs to be output verbatim), or not (in which case it's leading whitespace and needs to be stripped and stored). The fact that annotations are nodes in HTML makes this trickier. So we write the whitespace to the temporary linmod and store it in wrappedWhitespace, then if it turns out to be trailing whitespace we take it back out of the data array and record it the usual way. * Because text nodes can contain any combination of leading whitespace actual text and trailing whitespace, and because we may or may not already have opened a wrapping paragraph, there are a lot of different combinations to handle. We handle all of them but the resulting code is pretty dense and verbose. More low-level list of changes: In getDataFromDom(): * Added helper function addWhitespace() for storing whitespace for an element * Added helper function processNextWhitespace() for processing any whitespace passed on from the previous node via the nextWhitespace var * Rename paragraph to wrappingParagraph. Make wrapping default to alreadyWrapped so we can simplify wrapping||alreadyWrapped and !wrapping&&!alreadyWrapped. Add wrappingIsOurs to track whether the wrapping originated in this recursion level (needed for deciding when to close the wrapper). * Add prevElement to track the previous element so we can propagate whitespace to it, and nextWhitespace so we can propagate whitespace to the next element. * Remove previous newline stripping hacks * Integrate the logic for wrapping bare content with the outer whitespace preservation code * Remove wrapperElement, no longer needed because we have a dedicated variable for the wrapping paragraph now and what was previously inner whitespace preservation for wrapper paragraphs is now covered by the outer whitespace preservation code. In getDomFromData(): * Reinsert whitespace where appropriate ** outerPre is inserted when opening the element ** This covers outerPost as well except for the last child's outerPost, which is handled as the parent's innerPost when closing the parent. ** innerPre and innerPost are inserted when closing the element. Care is taken not to insert these if they're duplicates of something else. * Propagate each node's outerPost to the next node (either the next sibling or the parent) using parentDomElement.lastOuterPost. We can't get this using .lastChild because we will have destroyed that child's .veInternal by then, and we can't tell whether a node will be its parent's last child when we process it (all other processing, including first child handling is done when processing the node itself, but this cannot be). * Special handling is needed for the last node's outerPost, which ends up in the container's .lastOuterPost property. Tests: * Allow .html to be null in data<->DOM converter tests. This indicates that the test is a one-way data->DOM test, not a DOM->data->DOM round-trip test. The data will be converted to HTML and checked against .normalizedHtml * Update existing tests as needed * Add tests for outer whitespace preservation and storage * Add test for squashing of whitespace in case of disagreement (this requires .html=null) Change-Id: I4db4fe372a421182e80a2535657af7784ff15f95
2012-08-21 00:37:42 +00:00
'\n',
'\n',
'D',
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
Preserve whitespace between elements This commit fully utilizes all four positions in the internal.whitespace array. Outer whitespace is now preserved as well, and is duplicated either in the adjacent sibling (one node's outerPost is the next sibling's outerPre) or in the parent (a branch node's innerPre is its first child's outerPre, and its innerPost is its last child's outerPost). Before restoring saved whitespace, we check that these two agree with each other, and if they disagree we assume the user has been moving stuff around and don't restore any whitespace in that spot. The whitespace at the very beginning and the very end of the document (i.e. the first node's outerPre and the last node's outerPost) isn't duplicated anywhere, nor is inner whitespace in content nodes. The basic outline of the implementation is: * When we encounter whitespace, strip it and store it in the previous node's outerPost. Also store it in nextWhitespace so we can put it in the next node's outerPre once we encounter that node. * When we encounter whitespace in wrapped bare text, we don't know in advance if it's gonna be succeeded by more non-whitespace (in which case it needs to be output verbatim), or not (in which case it's leading whitespace and needs to be stripped and stored). The fact that annotations are nodes in HTML makes this trickier. So we write the whitespace to the temporary linmod and store it in wrappedWhitespace, then if it turns out to be trailing whitespace we take it back out of the data array and record it the usual way. * Because text nodes can contain any combination of leading whitespace actual text and trailing whitespace, and because we may or may not already have opened a wrapping paragraph, there are a lot of different combinations to handle. We handle all of them but the resulting code is pretty dense and verbose. More low-level list of changes: In getDataFromDom(): * Added helper function addWhitespace() for storing whitespace for an element * Added helper function processNextWhitespace() for processing any whitespace passed on from the previous node via the nextWhitespace var * Rename paragraph to wrappingParagraph. Make wrapping default to alreadyWrapped so we can simplify wrapping||alreadyWrapped and !wrapping&&!alreadyWrapped. Add wrappingIsOurs to track whether the wrapping originated in this recursion level (needed for deciding when to close the wrapper). * Add prevElement to track the previous element so we can propagate whitespace to it, and nextWhitespace so we can propagate whitespace to the next element. * Remove previous newline stripping hacks * Integrate the logic for wrapping bare content with the outer whitespace preservation code * Remove wrapperElement, no longer needed because we have a dedicated variable for the wrapping paragraph now and what was previously inner whitespace preservation for wrapper paragraphs is now covered by the outer whitespace preservation code. In getDomFromData(): * Reinsert whitespace where appropriate ** outerPre is inserted when opening the element ** This covers outerPost as well except for the last child's outerPost, which is handled as the parent's innerPost when closing the parent. ** innerPre and innerPost are inserted when closing the element. Care is taken not to insert these if they're duplicates of something else. * Propagate each node's outerPost to the next node (either the next sibling or the parent) using parentDomElement.lastOuterPost. We can't get this using .lastChild because we will have destroyed that child's .veInternal by then, and we can't tell whether a node will be its parent's last child when we process it (all other processing, including first child handling is done when processing the node itself, but this cannot be). * Special handling is needed for the last node's outerPost, which ends up in the container's .lastOuterPost property. Tests: * Allow .html to be null in data<->DOM converter tests. This indicates that the test is a one-way data->DOM test, not a DOM->data->DOM round-trip test. The data will be converted to HTML and checked against .normalizedHtml * Update existing tests as needed * Add tests for outer whitespace preservation and storage * Add test for squashing of whitespace in case of disagreement (this requires .html=null) Change-Id: I4db4fe372a421182e80a2535657af7784ff15f95
2012-08-21 00:37:42 +00:00
]
},
'whitespace preservation with nested annotations with whitespace on the left side': {
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'html': '<body><p> A B <b>\n\t<i>\t\tC\t\t\t</i></b>\n\nD\n\n\n</p></body>',
Preserve whitespace between elements This commit fully utilizes all four positions in the internal.whitespace array. Outer whitespace is now preserved as well, and is duplicated either in the adjacent sibling (one node's outerPost is the next sibling's outerPre) or in the parent (a branch node's innerPre is its first child's outerPre, and its innerPost is its last child's outerPost). Before restoring saved whitespace, we check that these two agree with each other, and if they disagree we assume the user has been moving stuff around and don't restore any whitespace in that spot. The whitespace at the very beginning and the very end of the document (i.e. the first node's outerPre and the last node's outerPost) isn't duplicated anywhere, nor is inner whitespace in content nodes. The basic outline of the implementation is: * When we encounter whitespace, strip it and store it in the previous node's outerPost. Also store it in nextWhitespace so we can put it in the next node's outerPre once we encounter that node. * When we encounter whitespace in wrapped bare text, we don't know in advance if it's gonna be succeeded by more non-whitespace (in which case it needs to be output verbatim), or not (in which case it's leading whitespace and needs to be stripped and stored). The fact that annotations are nodes in HTML makes this trickier. So we write the whitespace to the temporary linmod and store it in wrappedWhitespace, then if it turns out to be trailing whitespace we take it back out of the data array and record it the usual way. * Because text nodes can contain any combination of leading whitespace actual text and trailing whitespace, and because we may or may not already have opened a wrapping paragraph, there are a lot of different combinations to handle. We handle all of them but the resulting code is pretty dense and verbose. More low-level list of changes: In getDataFromDom(): * Added helper function addWhitespace() for storing whitespace for an element * Added helper function processNextWhitespace() for processing any whitespace passed on from the previous node via the nextWhitespace var * Rename paragraph to wrappingParagraph. Make wrapping default to alreadyWrapped so we can simplify wrapping||alreadyWrapped and !wrapping&&!alreadyWrapped. Add wrappingIsOurs to track whether the wrapping originated in this recursion level (needed for deciding when to close the wrapper). * Add prevElement to track the previous element so we can propagate whitespace to it, and nextWhitespace so we can propagate whitespace to the next element. * Remove previous newline stripping hacks * Integrate the logic for wrapping bare content with the outer whitespace preservation code * Remove wrapperElement, no longer needed because we have a dedicated variable for the wrapping paragraph now and what was previously inner whitespace preservation for wrapper paragraphs is now covered by the outer whitespace preservation code. In getDomFromData(): * Reinsert whitespace where appropriate ** outerPre is inserted when opening the element ** This covers outerPost as well except for the last child's outerPost, which is handled as the parent's innerPost when closing the parent. ** innerPre and innerPost are inserted when closing the element. Care is taken not to insert these if they're duplicates of something else. * Propagate each node's outerPost to the next node (either the next sibling or the parent) using parentDomElement.lastOuterPost. We can't get this using .lastChild because we will have destroyed that child's .veInternal by then, and we can't tell whether a node will be its parent's last child when we process it (all other processing, including first child handling is done when processing the node itself, but this cannot be). * Special handling is needed for the last node's outerPost, which ends up in the container's .lastOuterPost property. Tests: * Allow .html to be null in data<->DOM converter tests. This indicates that the test is a one-way data->DOM test, not a DOM->data->DOM round-trip test. The data will be converted to HTML and checked against .normalizedHtml * Update existing tests as needed * Add tests for outer whitespace preservation and storage * Add test for squashing of whitespace in case of disagreement (this requires .html=null) Change-Id: I4db4fe372a421182e80a2535657af7784ff15f95
2012-08-21 00:37:42 +00:00
'data': [
{ 'type': 'paragraph', 'internal': { 'whitespace': [ undefined, ' ', '\n\n\n' ] } },
'A',
' ',
' ',
'B',
' ',
' ',
' ',
[ '\n', [ ve.dm.example.bold ] ],
[ '\t', [ ve.dm.example.bold ] ],
[ '\t', [ ve.dm.example.bold, ve.dm.example.italic ] ],
[ '\t', [ ve.dm.example.bold, ve.dm.example.italic ] ],
[ 'C', [ ve.dm.example.bold, ve.dm.example.italic ] ],
[ '\t', [ ve.dm.example.bold, ve.dm.example.italic ] ],
[ '\t', [ ve.dm.example.bold, ve.dm.example.italic ] ],
[ '\t', [ ve.dm.example.bold, ve.dm.example.italic ] ],
Preserve whitespace between elements This commit fully utilizes all four positions in the internal.whitespace array. Outer whitespace is now preserved as well, and is duplicated either in the adjacent sibling (one node's outerPost is the next sibling's outerPre) or in the parent (a branch node's innerPre is its first child's outerPre, and its innerPost is its last child's outerPost). Before restoring saved whitespace, we check that these two agree with each other, and if they disagree we assume the user has been moving stuff around and don't restore any whitespace in that spot. The whitespace at the very beginning and the very end of the document (i.e. the first node's outerPre and the last node's outerPost) isn't duplicated anywhere, nor is inner whitespace in content nodes. The basic outline of the implementation is: * When we encounter whitespace, strip it and store it in the previous node's outerPost. Also store it in nextWhitespace so we can put it in the next node's outerPre once we encounter that node. * When we encounter whitespace in wrapped bare text, we don't know in advance if it's gonna be succeeded by more non-whitespace (in which case it needs to be output verbatim), or not (in which case it's leading whitespace and needs to be stripped and stored). The fact that annotations are nodes in HTML makes this trickier. So we write the whitespace to the temporary linmod and store it in wrappedWhitespace, then if it turns out to be trailing whitespace we take it back out of the data array and record it the usual way. * Because text nodes can contain any combination of leading whitespace actual text and trailing whitespace, and because we may or may not already have opened a wrapping paragraph, there are a lot of different combinations to handle. We handle all of them but the resulting code is pretty dense and verbose. More low-level list of changes: In getDataFromDom(): * Added helper function addWhitespace() for storing whitespace for an element * Added helper function processNextWhitespace() for processing any whitespace passed on from the previous node via the nextWhitespace var * Rename paragraph to wrappingParagraph. Make wrapping default to alreadyWrapped so we can simplify wrapping||alreadyWrapped and !wrapping&&!alreadyWrapped. Add wrappingIsOurs to track whether the wrapping originated in this recursion level (needed for deciding when to close the wrapper). * Add prevElement to track the previous element so we can propagate whitespace to it, and nextWhitespace so we can propagate whitespace to the next element. * Remove previous newline stripping hacks * Integrate the logic for wrapping bare content with the outer whitespace preservation code * Remove wrapperElement, no longer needed because we have a dedicated variable for the wrapping paragraph now and what was previously inner whitespace preservation for wrapper paragraphs is now covered by the outer whitespace preservation code. In getDomFromData(): * Reinsert whitespace where appropriate ** outerPre is inserted when opening the element ** This covers outerPost as well except for the last child's outerPost, which is handled as the parent's innerPost when closing the parent. ** innerPre and innerPost are inserted when closing the element. Care is taken not to insert these if they're duplicates of something else. * Propagate each node's outerPost to the next node (either the next sibling or the parent) using parentDomElement.lastOuterPost. We can't get this using .lastChild because we will have destroyed that child's .veInternal by then, and we can't tell whether a node will be its parent's last child when we process it (all other processing, including first child handling is done when processing the node itself, but this cannot be). * Special handling is needed for the last node's outerPost, which ends up in the container's .lastOuterPost property. Tests: * Allow .html to be null in data<->DOM converter tests. This indicates that the test is a one-way data->DOM test, not a DOM->data->DOM round-trip test. The data will be converted to HTML and checked against .normalizedHtml * Update existing tests as needed * Add tests for outer whitespace preservation and storage * Add test for squashing of whitespace in case of disagreement (this requires .html=null) Change-Id: I4db4fe372a421182e80a2535657af7784ff15f95
2012-08-21 00:37:42 +00:00
'\n',
'\n',
'D',
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
Preserve whitespace between elements This commit fully utilizes all four positions in the internal.whitespace array. Outer whitespace is now preserved as well, and is duplicated either in the adjacent sibling (one node's outerPost is the next sibling's outerPre) or in the parent (a branch node's innerPre is its first child's outerPre, and its innerPost is its last child's outerPost). Before restoring saved whitespace, we check that these two agree with each other, and if they disagree we assume the user has been moving stuff around and don't restore any whitespace in that spot. The whitespace at the very beginning and the very end of the document (i.e. the first node's outerPre and the last node's outerPost) isn't duplicated anywhere, nor is inner whitespace in content nodes. The basic outline of the implementation is: * When we encounter whitespace, strip it and store it in the previous node's outerPost. Also store it in nextWhitespace so we can put it in the next node's outerPre once we encounter that node. * When we encounter whitespace in wrapped bare text, we don't know in advance if it's gonna be succeeded by more non-whitespace (in which case it needs to be output verbatim), or not (in which case it's leading whitespace and needs to be stripped and stored). The fact that annotations are nodes in HTML makes this trickier. So we write the whitespace to the temporary linmod and store it in wrappedWhitespace, then if it turns out to be trailing whitespace we take it back out of the data array and record it the usual way. * Because text nodes can contain any combination of leading whitespace actual text and trailing whitespace, and because we may or may not already have opened a wrapping paragraph, there are a lot of different combinations to handle. We handle all of them but the resulting code is pretty dense and verbose. More low-level list of changes: In getDataFromDom(): * Added helper function addWhitespace() for storing whitespace for an element * Added helper function processNextWhitespace() for processing any whitespace passed on from the previous node via the nextWhitespace var * Rename paragraph to wrappingParagraph. Make wrapping default to alreadyWrapped so we can simplify wrapping||alreadyWrapped and !wrapping&&!alreadyWrapped. Add wrappingIsOurs to track whether the wrapping originated in this recursion level (needed for deciding when to close the wrapper). * Add prevElement to track the previous element so we can propagate whitespace to it, and nextWhitespace so we can propagate whitespace to the next element. * Remove previous newline stripping hacks * Integrate the logic for wrapping bare content with the outer whitespace preservation code * Remove wrapperElement, no longer needed because we have a dedicated variable for the wrapping paragraph now and what was previously inner whitespace preservation for wrapper paragraphs is now covered by the outer whitespace preservation code. In getDomFromData(): * Reinsert whitespace where appropriate ** outerPre is inserted when opening the element ** This covers outerPost as well except for the last child's outerPost, which is handled as the parent's innerPost when closing the parent. ** innerPre and innerPost are inserted when closing the element. Care is taken not to insert these if they're duplicates of something else. * Propagate each node's outerPost to the next node (either the next sibling or the parent) using parentDomElement.lastOuterPost. We can't get this using .lastChild because we will have destroyed that child's .veInternal by then, and we can't tell whether a node will be its parent's last child when we process it (all other processing, including first child handling is done when processing the node itself, but this cannot be). * Special handling is needed for the last node's outerPost, which ends up in the container's .lastOuterPost property. Tests: * Allow .html to be null in data<->DOM converter tests. This indicates that the test is a one-way data->DOM test, not a DOM->data->DOM round-trip test. The data will be converted to HTML and checked against .normalizedHtml * Update existing tests as needed * Add tests for outer whitespace preservation and storage * Add test for squashing of whitespace in case of disagreement (this requires .html=null) Change-Id: I4db4fe372a421182e80a2535657af7784ff15f95
2012-08-21 00:37:42 +00:00
]
},
'whitespace preservation with nested annotations with whitespace on the right side': {
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'html': '<body><p> A B <b><i>\t\tC\t\t\t</i>\n\t</b>\n\nD\n\n\n</p></body>',
Preserve whitespace between elements This commit fully utilizes all four positions in the internal.whitespace array. Outer whitespace is now preserved as well, and is duplicated either in the adjacent sibling (one node's outerPost is the next sibling's outerPre) or in the parent (a branch node's innerPre is its first child's outerPre, and its innerPost is its last child's outerPost). Before restoring saved whitespace, we check that these two agree with each other, and if they disagree we assume the user has been moving stuff around and don't restore any whitespace in that spot. The whitespace at the very beginning and the very end of the document (i.e. the first node's outerPre and the last node's outerPost) isn't duplicated anywhere, nor is inner whitespace in content nodes. The basic outline of the implementation is: * When we encounter whitespace, strip it and store it in the previous node's outerPost. Also store it in nextWhitespace so we can put it in the next node's outerPre once we encounter that node. * When we encounter whitespace in wrapped bare text, we don't know in advance if it's gonna be succeeded by more non-whitespace (in which case it needs to be output verbatim), or not (in which case it's leading whitespace and needs to be stripped and stored). The fact that annotations are nodes in HTML makes this trickier. So we write the whitespace to the temporary linmod and store it in wrappedWhitespace, then if it turns out to be trailing whitespace we take it back out of the data array and record it the usual way. * Because text nodes can contain any combination of leading whitespace actual text and trailing whitespace, and because we may or may not already have opened a wrapping paragraph, there are a lot of different combinations to handle. We handle all of them but the resulting code is pretty dense and verbose. More low-level list of changes: In getDataFromDom(): * Added helper function addWhitespace() for storing whitespace for an element * Added helper function processNextWhitespace() for processing any whitespace passed on from the previous node via the nextWhitespace var * Rename paragraph to wrappingParagraph. Make wrapping default to alreadyWrapped so we can simplify wrapping||alreadyWrapped and !wrapping&&!alreadyWrapped. Add wrappingIsOurs to track whether the wrapping originated in this recursion level (needed for deciding when to close the wrapper). * Add prevElement to track the previous element so we can propagate whitespace to it, and nextWhitespace so we can propagate whitespace to the next element. * Remove previous newline stripping hacks * Integrate the logic for wrapping bare content with the outer whitespace preservation code * Remove wrapperElement, no longer needed because we have a dedicated variable for the wrapping paragraph now and what was previously inner whitespace preservation for wrapper paragraphs is now covered by the outer whitespace preservation code. In getDomFromData(): * Reinsert whitespace where appropriate ** outerPre is inserted when opening the element ** This covers outerPost as well except for the last child's outerPost, which is handled as the parent's innerPost when closing the parent. ** innerPre and innerPost are inserted when closing the element. Care is taken not to insert these if they're duplicates of something else. * Propagate each node's outerPost to the next node (either the next sibling or the parent) using parentDomElement.lastOuterPost. We can't get this using .lastChild because we will have destroyed that child's .veInternal by then, and we can't tell whether a node will be its parent's last child when we process it (all other processing, including first child handling is done when processing the node itself, but this cannot be). * Special handling is needed for the last node's outerPost, which ends up in the container's .lastOuterPost property. Tests: * Allow .html to be null in data<->DOM converter tests. This indicates that the test is a one-way data->DOM test, not a DOM->data->DOM round-trip test. The data will be converted to HTML and checked against .normalizedHtml * Update existing tests as needed * Add tests for outer whitespace preservation and storage * Add test for squashing of whitespace in case of disagreement (this requires .html=null) Change-Id: I4db4fe372a421182e80a2535657af7784ff15f95
2012-08-21 00:37:42 +00:00
'data': [
{ 'type': 'paragraph', 'internal': { 'whitespace': [ undefined, ' ', '\n\n\n' ] } },
'A',
' ',
' ',
'B',
' ',
' ',
' ',
[ '\t', [ ve.dm.example.bold, ve.dm.example.italic ] ],
[ '\t', [ ve.dm.example.bold, ve.dm.example.italic ] ],
[ 'C', [ ve.dm.example.bold, ve.dm.example.italic ] ],
[ '\t', [ ve.dm.example.bold, ve.dm.example.italic ] ],
[ '\t', [ ve.dm.example.bold, ve.dm.example.italic ] ],
[ '\t', [ ve.dm.example.bold, ve.dm.example.italic ] ],
[ '\n', [ ve.dm.example.bold ] ],
[ '\t', [ ve.dm.example.bold ] ],
Preserve whitespace between elements This commit fully utilizes all four positions in the internal.whitespace array. Outer whitespace is now preserved as well, and is duplicated either in the adjacent sibling (one node's outerPost is the next sibling's outerPre) or in the parent (a branch node's innerPre is its first child's outerPre, and its innerPost is its last child's outerPost). Before restoring saved whitespace, we check that these two agree with each other, and if they disagree we assume the user has been moving stuff around and don't restore any whitespace in that spot. The whitespace at the very beginning and the very end of the document (i.e. the first node's outerPre and the last node's outerPost) isn't duplicated anywhere, nor is inner whitespace in content nodes. The basic outline of the implementation is: * When we encounter whitespace, strip it and store it in the previous node's outerPost. Also store it in nextWhitespace so we can put it in the next node's outerPre once we encounter that node. * When we encounter whitespace in wrapped bare text, we don't know in advance if it's gonna be succeeded by more non-whitespace (in which case it needs to be output verbatim), or not (in which case it's leading whitespace and needs to be stripped and stored). The fact that annotations are nodes in HTML makes this trickier. So we write the whitespace to the temporary linmod and store it in wrappedWhitespace, then if it turns out to be trailing whitespace we take it back out of the data array and record it the usual way. * Because text nodes can contain any combination of leading whitespace actual text and trailing whitespace, and because we may or may not already have opened a wrapping paragraph, there are a lot of different combinations to handle. We handle all of them but the resulting code is pretty dense and verbose. More low-level list of changes: In getDataFromDom(): * Added helper function addWhitespace() for storing whitespace for an element * Added helper function processNextWhitespace() for processing any whitespace passed on from the previous node via the nextWhitespace var * Rename paragraph to wrappingParagraph. Make wrapping default to alreadyWrapped so we can simplify wrapping||alreadyWrapped and !wrapping&&!alreadyWrapped. Add wrappingIsOurs to track whether the wrapping originated in this recursion level (needed for deciding when to close the wrapper). * Add prevElement to track the previous element so we can propagate whitespace to it, and nextWhitespace so we can propagate whitespace to the next element. * Remove previous newline stripping hacks * Integrate the logic for wrapping bare content with the outer whitespace preservation code * Remove wrapperElement, no longer needed because we have a dedicated variable for the wrapping paragraph now and what was previously inner whitespace preservation for wrapper paragraphs is now covered by the outer whitespace preservation code. In getDomFromData(): * Reinsert whitespace where appropriate ** outerPre is inserted when opening the element ** This covers outerPost as well except for the last child's outerPost, which is handled as the parent's innerPost when closing the parent. ** innerPre and innerPost are inserted when closing the element. Care is taken not to insert these if they're duplicates of something else. * Propagate each node's outerPost to the next node (either the next sibling or the parent) using parentDomElement.lastOuterPost. We can't get this using .lastChild because we will have destroyed that child's .veInternal by then, and we can't tell whether a node will be its parent's last child when we process it (all other processing, including first child handling is done when processing the node itself, but this cannot be). * Special handling is needed for the last node's outerPost, which ends up in the container's .lastOuterPost property. Tests: * Allow .html to be null in data<->DOM converter tests. This indicates that the test is a one-way data->DOM test, not a DOM->data->DOM round-trip test. The data will be converted to HTML and checked against .normalizedHtml * Update existing tests as needed * Add tests for outer whitespace preservation and storage * Add test for squashing of whitespace in case of disagreement (this requires .html=null) Change-Id: I4db4fe372a421182e80a2535657af7784ff15f95
2012-08-21 00:37:42 +00:00
'\n',
'\n',
'D',
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
Preserve whitespace between elements This commit fully utilizes all four positions in the internal.whitespace array. Outer whitespace is now preserved as well, and is duplicated either in the adjacent sibling (one node's outerPost is the next sibling's outerPre) or in the parent (a branch node's innerPre is its first child's outerPre, and its innerPost is its last child's outerPost). Before restoring saved whitespace, we check that these two agree with each other, and if they disagree we assume the user has been moving stuff around and don't restore any whitespace in that spot. The whitespace at the very beginning and the very end of the document (i.e. the first node's outerPre and the last node's outerPost) isn't duplicated anywhere, nor is inner whitespace in content nodes. The basic outline of the implementation is: * When we encounter whitespace, strip it and store it in the previous node's outerPost. Also store it in nextWhitespace so we can put it in the next node's outerPre once we encounter that node. * When we encounter whitespace in wrapped bare text, we don't know in advance if it's gonna be succeeded by more non-whitespace (in which case it needs to be output verbatim), or not (in which case it's leading whitespace and needs to be stripped and stored). The fact that annotations are nodes in HTML makes this trickier. So we write the whitespace to the temporary linmod and store it in wrappedWhitespace, then if it turns out to be trailing whitespace we take it back out of the data array and record it the usual way. * Because text nodes can contain any combination of leading whitespace actual text and trailing whitespace, and because we may or may not already have opened a wrapping paragraph, there are a lot of different combinations to handle. We handle all of them but the resulting code is pretty dense and verbose. More low-level list of changes: In getDataFromDom(): * Added helper function addWhitespace() for storing whitespace for an element * Added helper function processNextWhitespace() for processing any whitespace passed on from the previous node via the nextWhitespace var * Rename paragraph to wrappingParagraph. Make wrapping default to alreadyWrapped so we can simplify wrapping||alreadyWrapped and !wrapping&&!alreadyWrapped. Add wrappingIsOurs to track whether the wrapping originated in this recursion level (needed for deciding when to close the wrapper). * Add prevElement to track the previous element so we can propagate whitespace to it, and nextWhitespace so we can propagate whitespace to the next element. * Remove previous newline stripping hacks * Integrate the logic for wrapping bare content with the outer whitespace preservation code * Remove wrapperElement, no longer needed because we have a dedicated variable for the wrapping paragraph now and what was previously inner whitespace preservation for wrapper paragraphs is now covered by the outer whitespace preservation code. In getDomFromData(): * Reinsert whitespace where appropriate ** outerPre is inserted when opening the element ** This covers outerPost as well except for the last child's outerPost, which is handled as the parent's innerPost when closing the parent. ** innerPre and innerPost are inserted when closing the element. Care is taken not to insert these if they're duplicates of something else. * Propagate each node's outerPost to the next node (either the next sibling or the parent) using parentDomElement.lastOuterPost. We can't get this using .lastChild because we will have destroyed that child's .veInternal by then, and we can't tell whether a node will be its parent's last child when we process it (all other processing, including first child handling is done when processing the node itself, but this cannot be). * Special handling is needed for the last node's outerPost, which ends up in the container's .lastOuterPost property. Tests: * Allow .html to be null in data<->DOM converter tests. This indicates that the test is a one-way data->DOM test, not a DOM->data->DOM round-trip test. The data will be converted to HTML and checked against .normalizedHtml * Update existing tests as needed * Add tests for outer whitespace preservation and storage * Add test for squashing of whitespace in case of disagreement (this requires .html=null) Change-Id: I4db4fe372a421182e80a2535657af7784ff15f95
2012-08-21 00:37:42 +00:00
]
},
'whitespace preservation with aliens': {
'html': '<body> <figure> <br> </figure> <p>\tFoo\t\t<tt>\t\t\tBar\t\t\t\t</tt>\nBaz\n\n<tt>\n\n\nQuux\n\n\n\n</tt> \tWhee \n</p>\t\n<figure>\n\tYay \t </figure> \n </body>',
'data': [
{
'type': 'alienBlock',
'attributes': {
'domElements': $( '<figure> <br> </figure>' ).toArray()
},
'internal': {
'whitespace': [ ' ', undefined, undefined, ' ' ]
}
},
{ 'type': '/alienBlock' },
{ 'type': 'paragraph', 'internal': { 'whitespace': [ ' ', '\t', ' \n', '\t\n' ] } },
'F',
'o',
'o',
'\t',
'\t',
{ 'type': 'alienInline', 'attributes': { 'domElements': $( '<tt>\t\t\tBar\t\t\t\t</tt>' ).toArray() } },
{ 'type': '/alienInline' },
'\n',
'B',
'a',
'z',
'\n',
'\n',
{
'type': 'alienInline',
'attributes': {
'domElements': $( '<tt>\n\n\nQuux\n\n\n\n</tt>' ).toArray()
}
},
{ 'type': '/alienInline' },
' ',
'\t',
'W',
'h',
'e',
'e',
{ 'type': '/paragraph' },
{
'type': 'alienBlock',
'attributes': {
'domElements': $( '<figure>\n\tYay \t </figure>' ).toArray()
},
'internal': {
'whitespace': [ '\t\n', undefined, undefined, ' \n ' ]
}
},
{ 'type': '/alienBlock' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'whitespace preservation not triggered inside <pre>': {
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'html': '<body>\n<pre>\n\n\nFoo\n\n\nBar\n\n\n\n</pre>\n\n\n\n\n</body>',
'data': [
{ 'type': 'preformatted', 'internal': { 'whitespace': ['\n', undefined, undefined, '\n\n\n\n\n' ] } },
'\n',
'\n',
'F',
'o',
'o',
'\n',
'\n',
'\n',
'B',
'a',
'r',
'\n',
'\n',
'\n',
'\n',
{ 'type': '/preformatted' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'whitespace preservation in table cell starting with text and ending with annotation': {
'html': '<body><table><tbody><tr><td>Foo <b>Bar</b></td></tr></tbody></table></body>',
'data': [
{ 'type': 'table' },
{ 'type': 'tableSection', 'attributes': { 'style': 'body' } },
{ 'type': 'tableRow' },
{ 'type': 'tableCell', 'attributes': { 'style': 'data' } },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'F',
'o',
'o',
' ',
[ 'B', [ ve.dm.example.bold ] ],
[ 'a', [ ve.dm.example.bold ] ],
[ 'r', [ ve.dm.example.bold ] ],
{ 'type': '/paragraph' },
{ 'type': '/tableCell' },
{ 'type': '/tableRow' },
{ 'type': '/tableSection' },
{ 'type': '/table' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'whitespace preservation with wrapped text and comments': {
'html': '<body><!-- Foo --> <!-- Bar -->\nFoo</body>',
'data': [
{
'type': 'alienMeta',
'internal': { 'whitespace': [ undefined, undefined, undefined, ' ' ] },
'attributes': {
'domElements': $( '<!-- Foo -->' ).toArray()
}
},
{ 'type': '/alienMeta' },
{
'type': 'alienMeta',
'internal': { 'whitespace': [ ' ', undefined, undefined, '\n' ] },
'attributes': {
'domElements': $( '<!-- Bar -->' ).toArray()
}
},
{ 'type': '/alienMeta' },
{
'type': 'paragraph',
'internal': {
'generated': 'wrapper',
'whitespace': [ '\n' ]
}
},
'F',
'o',
'o',
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'whitespace preservation with comments at end of wrapper paragraph': {
'html': '<body><ul><li> bar<!-- baz -->quux </li></ul></body>',
'data': [
{ 'type': 'list', 'attributes': { 'style': 'bullet' } },
{
'type': 'listItem',
'internal': {
'whitespace': [
undefined,
' ',
' '
]
}
},
{
'type': 'paragraph',
'internal': {
'generated': 'wrapper',
'whitespace': [
' ',
undefined,
undefined,
' '
]
}
},
'b', 'a', 'r',
{
'type': 'alienMeta',
'attributes': {
'domElements': $( '<!-- baz -->' ).toArray()
}
},
{ 'type': '/alienMeta' },
'q', 'u', 'u', 'x',
{ 'type': '/paragraph' },
{ 'type': '/listItem' },
{ 'type': '/list' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'whitespace preservation with comment at end of wrapper paragraph': {
'html': '<body><ul><li> bar<!-- baz --> </li></ul></body>',
'data': [
{ 'type': 'list', 'attributes': { 'style': 'bullet' } },
{
'type': 'listItem',
'internal': {
'whitespace': [
undefined,
' ',
' '
]
}
},
{
'type': 'paragraph',
'internal': {
'generated': 'wrapper',
'whitespace': [
' '
]
}
},
'b', 'a', 'r',
{ 'type': '/paragraph' },
{
'type': 'alienMeta',
'attributes': {
'domElements': $( '<!-- baz -->' ).toArray()
},
'internal': {
'whitespace': [
undefined,
undefined,
undefined,
' '
]
}
},
{ 'type': '/alienMeta' },
{ 'type': '/listItem' },
{ 'type': '/list' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'whitespace preservation with multiple comments at end of wrapper paragraph': {
'html': '<body><ul><li> foo <!-- bar --> <!-- baz --> </li></ul></body>',
'data': [
{ 'type': 'list', 'attributes': { 'style': 'bullet' } },
{
'type': 'listItem',
'internal': {
'whitespace': [
undefined,
' ',
' '
]
}
},
{
'type': 'paragraph',
'internal': {
'generated': 'wrapper',
'whitespace': [
' ',
undefined,
undefined,
' '
]
}
},
'f', 'o', 'o',
{ 'type': '/paragraph' },
{
'type': 'alienMeta',
'attributes': {
'domElements': $( '<!-- bar -->' ).toArray()
},
'internal': {
'whitespace': [
' ',
undefined,
undefined,
' '
]
}
},
{ 'type': '/alienMeta' },
{
'type': 'alienMeta',
'attributes': {
'domElements': $( '<!-- baz -->' ).toArray()
},
'internal': {
'whitespace': [
' ',
undefined,
undefined,
' '
]
}
},
{ 'type': '/alienMeta' },
{ 'type': '/listItem' },
{ 'type': '/list' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'whitespace preservation with comment at start or end of element': {
'html': '<body><p> <!-- foo -->bar<!-- baz --> </p></body>',
'data': [
{
'type': 'paragraph',
'internal': {
'whitespace': [
undefined,
' ',
' '
]
}
},
{
'type': 'alienMeta',
'attributes': {
'domElements': $( '<!-- foo -->' ).toArray()
}
},
{ 'type': '/alienMeta' },
'b', 'a', 'r',
{
'type': 'alienMeta',
'attributes': {
'domElements': $( '<!-- baz -->' ).toArray()
}
},
{ 'type': '/alienMeta' },
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'whitespace preservation in empty branch node': {
'html': '<body><table>\n\n</table></body>',
'data': [
{ 'type': 'table', 'internal': { 'whitespace': [ undefined, '\n\n' ] } },
{ 'type': '/table' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
Preserve whitespace between elements This commit fully utilizes all four positions in the internal.whitespace array. Outer whitespace is now preserved as well, and is duplicated either in the adjacent sibling (one node's outerPost is the next sibling's outerPre) or in the parent (a branch node's innerPre is its first child's outerPre, and its innerPost is its last child's outerPost). Before restoring saved whitespace, we check that these two agree with each other, and if they disagree we assume the user has been moving stuff around and don't restore any whitespace in that spot. The whitespace at the very beginning and the very end of the document (i.e. the first node's outerPre and the last node's outerPost) isn't duplicated anywhere, nor is inner whitespace in content nodes. The basic outline of the implementation is: * When we encounter whitespace, strip it and store it in the previous node's outerPost. Also store it in nextWhitespace so we can put it in the next node's outerPre once we encounter that node. * When we encounter whitespace in wrapped bare text, we don't know in advance if it's gonna be succeeded by more non-whitespace (in which case it needs to be output verbatim), or not (in which case it's leading whitespace and needs to be stripped and stored). The fact that annotations are nodes in HTML makes this trickier. So we write the whitespace to the temporary linmod and store it in wrappedWhitespace, then if it turns out to be trailing whitespace we take it back out of the data array and record it the usual way. * Because text nodes can contain any combination of leading whitespace actual text and trailing whitespace, and because we may or may not already have opened a wrapping paragraph, there are a lot of different combinations to handle. We handle all of them but the resulting code is pretty dense and verbose. More low-level list of changes: In getDataFromDom(): * Added helper function addWhitespace() for storing whitespace for an element * Added helper function processNextWhitespace() for processing any whitespace passed on from the previous node via the nextWhitespace var * Rename paragraph to wrappingParagraph. Make wrapping default to alreadyWrapped so we can simplify wrapping||alreadyWrapped and !wrapping&&!alreadyWrapped. Add wrappingIsOurs to track whether the wrapping originated in this recursion level (needed for deciding when to close the wrapper). * Add prevElement to track the previous element so we can propagate whitespace to it, and nextWhitespace so we can propagate whitespace to the next element. * Remove previous newline stripping hacks * Integrate the logic for wrapping bare content with the outer whitespace preservation code * Remove wrapperElement, no longer needed because we have a dedicated variable for the wrapping paragraph now and what was previously inner whitespace preservation for wrapper paragraphs is now covered by the outer whitespace preservation code. In getDomFromData(): * Reinsert whitespace where appropriate ** outerPre is inserted when opening the element ** This covers outerPost as well except for the last child's outerPost, which is handled as the parent's innerPost when closing the parent. ** innerPre and innerPost are inserted when closing the element. Care is taken not to insert these if they're duplicates of something else. * Propagate each node's outerPost to the next node (either the next sibling or the parent) using parentDomElement.lastOuterPost. We can't get this using .lastChild because we will have destroyed that child's .veInternal by then, and we can't tell whether a node will be its parent's last child when we process it (all other processing, including first child handling is done when processing the node itself, but this cannot be). * Special handling is needed for the last node's outerPost, which ends up in the container's .lastOuterPost property. Tests: * Allow .html to be null in data<->DOM converter tests. This indicates that the test is a one-way data->DOM test, not a DOM->data->DOM round-trip test. The data will be converted to HTML and checked against .normalizedHtml * Update existing tests as needed * Add tests for outer whitespace preservation and storage * Add test for squashing of whitespace in case of disagreement (this requires .html=null) Change-Id: I4db4fe372a421182e80a2535657af7784ff15f95
2012-08-21 00:37:42 +00:00
'mismatching whitespace data is ignored': {
'html': null,
'data': [
{ 'type': 'list', 'attributes': { 'style': 'bullet' }, 'internal': { 'whitespace': [ ' ', ' ', ' ', ' ' ] } },
{ 'type': 'listItem', 'internal': { 'whitespace': [ ' ', ' ', ' ', ' ' ] } },
{ 'type': 'paragraph', 'internal': { 'whitespace': [ ' ', '\t', '\n', ' ' ] } },
'A',
{ 'type': '/paragraph' },
{ 'type': 'paragraph', 'internal': { 'whitespace': [ ' ' ] } },
'B',
{ 'type': '/paragraph' },
{ 'type': '/listItem' },
{ 'type': '/list' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
Preserve whitespace between elements This commit fully utilizes all four positions in the internal.whitespace array. Outer whitespace is now preserved as well, and is duplicated either in the adjacent sibling (one node's outerPost is the next sibling's outerPre) or in the parent (a branch node's innerPre is its first child's outerPre, and its innerPost is its last child's outerPost). Before restoring saved whitespace, we check that these two agree with each other, and if they disagree we assume the user has been moving stuff around and don't restore any whitespace in that spot. The whitespace at the very beginning and the very end of the document (i.e. the first node's outerPre and the last node's outerPost) isn't duplicated anywhere, nor is inner whitespace in content nodes. The basic outline of the implementation is: * When we encounter whitespace, strip it and store it in the previous node's outerPost. Also store it in nextWhitespace so we can put it in the next node's outerPre once we encounter that node. * When we encounter whitespace in wrapped bare text, we don't know in advance if it's gonna be succeeded by more non-whitespace (in which case it needs to be output verbatim), or not (in which case it's leading whitespace and needs to be stripped and stored). The fact that annotations are nodes in HTML makes this trickier. So we write the whitespace to the temporary linmod and store it in wrappedWhitespace, then if it turns out to be trailing whitespace we take it back out of the data array and record it the usual way. * Because text nodes can contain any combination of leading whitespace actual text and trailing whitespace, and because we may or may not already have opened a wrapping paragraph, there are a lot of different combinations to handle. We handle all of them but the resulting code is pretty dense and verbose. More low-level list of changes: In getDataFromDom(): * Added helper function addWhitespace() for storing whitespace for an element * Added helper function processNextWhitespace() for processing any whitespace passed on from the previous node via the nextWhitespace var * Rename paragraph to wrappingParagraph. Make wrapping default to alreadyWrapped so we can simplify wrapping||alreadyWrapped and !wrapping&&!alreadyWrapped. Add wrappingIsOurs to track whether the wrapping originated in this recursion level (needed for deciding when to close the wrapper). * Add prevElement to track the previous element so we can propagate whitespace to it, and nextWhitespace so we can propagate whitespace to the next element. * Remove previous newline stripping hacks * Integrate the logic for wrapping bare content with the outer whitespace preservation code * Remove wrapperElement, no longer needed because we have a dedicated variable for the wrapping paragraph now and what was previously inner whitespace preservation for wrapper paragraphs is now covered by the outer whitespace preservation code. In getDomFromData(): * Reinsert whitespace where appropriate ** outerPre is inserted when opening the element ** This covers outerPost as well except for the last child's outerPost, which is handled as the parent's innerPost when closing the parent. ** innerPre and innerPost are inserted when closing the element. Care is taken not to insert these if they're duplicates of something else. * Propagate each node's outerPost to the next node (either the next sibling or the parent) using parentDomElement.lastOuterPost. We can't get this using .lastChild because we will have destroyed that child's .veInternal by then, and we can't tell whether a node will be its parent's last child when we process it (all other processing, including first child handling is done when processing the node itself, but this cannot be). * Special handling is needed for the last node's outerPost, which ends up in the container's .lastOuterPost property. Tests: * Allow .html to be null in data<->DOM converter tests. This indicates that the test is a one-way data->DOM test, not a DOM->data->DOM round-trip test. The data will be converted to HTML and checked against .normalizedHtml * Update existing tests as needed * Add tests for outer whitespace preservation and storage * Add test for squashing of whitespace in case of disagreement (this requires .html=null) Change-Id: I4db4fe372a421182e80a2535657af7784ff15f95
2012-08-21 00:37:42 +00:00
],
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'normalizedHtml': '<body> <ul><li><p>\tA\n</p> <p>B</p></li></ul> </body>'
},
'order of nested annotations is preserved': {
'html': '<body><p><b><u><i>Foo</i></u></b></p></body>',
'data': [
{ 'type': 'paragraph' },
[
'F',
[
ve.dm.example.bold,
ve.dm.example.underline,
ve.dm.example.italic
]
],
[
'o',
[
ve.dm.example.bold,
ve.dm.example.underline,
ve.dm.example.italic
]
],
[
'o',
[
ve.dm.example.bold,
ve.dm.example.underline,
ve.dm.example.italic
]
],
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'nested annotations are closed and reopened in the correct order': {
'html': '<body><p><a href="Foo">F<b>o<i>o</i></b><i>b</i></a><i>a<b>r</b>b<u>a</u>z</i></p></body>',
'data': [
{ 'type': 'paragraph' },
[
'F',
[
{
'type': 'link',
Great Annotation Refactor of 2013 This changes the annotation API to be the same as the node API, sans a few boolean flags that don't apply. The APIs were different, but there was really no good reason why, so this makes things simpler for API users. It also means we'll be able to factor a bunch of things out because they're now duplicated between nodes, meta items and annotations. Linear model annotations are now objects with 'type' and 'attributes' properties (rather than 'name' and 'data'), for consistency with elements. They now also contain html/0/* attributes for HTML attribute preservation, which obsoletes the htmlTagName and htmlAttributes properties. dm.Annotation subclasses take a reference to such an object and implement conversion using .static.toDataElement and .static.toDomElements just like nodes do. The custom .getHash() functions are no longer necessary because of the way HTML attribute preservation was reimplemented. CE rendering has been moved out of dm.Annotation (it never made sense to have CE rendering functions in DM classes, this was bothering me) and into separate ce.Annotation subclasses. These are very similar to CE nodes in that they have a this.$ generated based on something in the DM; the main difference is that nodes listen to events and update themselves, whereas annotations are static and are simply destroyed and rebuilt when they change. This change also adds whitelisted HTML attribute rendering for annotations, as well as class="ve-ce-FooAnnotation" attributes. Now that annotation classes produce real DOM nodes rather than weird objects describing HTML tags, we can't generate HTML as a string in ce.ContentBranchNode anymore. getRenderedContents() has been rewritten to be much more similar to the way the converter renders annotations; in fact, significant parts of it were copied from the converter, so that should be factored out in the future. This change actually fixes an annotation rendering discrepancy between ce.ContentBranchNode and dm.Converter; see the diff of ve.ce.ContentBranchNode.test.js. ve.ce.MWEntityNode.js: * Remove stray property ve.dm.MWExternalLinkAnnotation.js: * Store 'rel' attribute ve.dm.TextStyleAnnotation.js: * Put all the conversion logic in the abstract base class ve.dm.Converter.js: * Also feed annotations through getDomElementsFromDataElement() and createDataElement() ve.dm.Node.js: * Fix undocumented property ve.ce.ContentBranchNode.test.js: * Add descriptive messages for each test case * Compare DOM trees, not HTML strings * Compare without all the class="ve-ce-WhateverAnnotation" clutter ve.ui.LinkInspector.js: * Replace direct .getHash() calls (evil!) with ve.getHash() Bug: 46464 Bug: 44808 Change-Id: I31991488579b8cce6d98ed8b29b486ba5ec38cdc
2013-04-02 17:23:33 +00:00
'attributes': {
'href': 'Foo'
},
'htmlAttributes': [ { 'values': {
'href': 'Foo'
} } ]
}
]
],
[
'o',
[
{
'type': 'link',
Great Annotation Refactor of 2013 This changes the annotation API to be the same as the node API, sans a few boolean flags that don't apply. The APIs were different, but there was really no good reason why, so this makes things simpler for API users. It also means we'll be able to factor a bunch of things out because they're now duplicated between nodes, meta items and annotations. Linear model annotations are now objects with 'type' and 'attributes' properties (rather than 'name' and 'data'), for consistency with elements. They now also contain html/0/* attributes for HTML attribute preservation, which obsoletes the htmlTagName and htmlAttributes properties. dm.Annotation subclasses take a reference to such an object and implement conversion using .static.toDataElement and .static.toDomElements just like nodes do. The custom .getHash() functions are no longer necessary because of the way HTML attribute preservation was reimplemented. CE rendering has been moved out of dm.Annotation (it never made sense to have CE rendering functions in DM classes, this was bothering me) and into separate ce.Annotation subclasses. These are very similar to CE nodes in that they have a this.$ generated based on something in the DM; the main difference is that nodes listen to events and update themselves, whereas annotations are static and are simply destroyed and rebuilt when they change. This change also adds whitelisted HTML attribute rendering for annotations, as well as class="ve-ce-FooAnnotation" attributes. Now that annotation classes produce real DOM nodes rather than weird objects describing HTML tags, we can't generate HTML as a string in ce.ContentBranchNode anymore. getRenderedContents() has been rewritten to be much more similar to the way the converter renders annotations; in fact, significant parts of it were copied from the converter, so that should be factored out in the future. This change actually fixes an annotation rendering discrepancy between ce.ContentBranchNode and dm.Converter; see the diff of ve.ce.ContentBranchNode.test.js. ve.ce.MWEntityNode.js: * Remove stray property ve.dm.MWExternalLinkAnnotation.js: * Store 'rel' attribute ve.dm.TextStyleAnnotation.js: * Put all the conversion logic in the abstract base class ve.dm.Converter.js: * Also feed annotations through getDomElementsFromDataElement() and createDataElement() ve.dm.Node.js: * Fix undocumented property ve.ce.ContentBranchNode.test.js: * Add descriptive messages for each test case * Compare DOM trees, not HTML strings * Compare without all the class="ve-ce-WhateverAnnotation" clutter ve.ui.LinkInspector.js: * Replace direct .getHash() calls (evil!) with ve.getHash() Bug: 46464 Bug: 44808 Change-Id: I31991488579b8cce6d98ed8b29b486ba5ec38cdc
2013-04-02 17:23:33 +00:00
'attributes': {
'href': 'Foo'
},
'htmlAttributes': [ { 'values': {
'href': 'Foo'
} } ]
},
ve.dm.example.bold
]
],
[
'o',
[
{
'type': 'link',
Great Annotation Refactor of 2013 This changes the annotation API to be the same as the node API, sans a few boolean flags that don't apply. The APIs were different, but there was really no good reason why, so this makes things simpler for API users. It also means we'll be able to factor a bunch of things out because they're now duplicated between nodes, meta items and annotations. Linear model annotations are now objects with 'type' and 'attributes' properties (rather than 'name' and 'data'), for consistency with elements. They now also contain html/0/* attributes for HTML attribute preservation, which obsoletes the htmlTagName and htmlAttributes properties. dm.Annotation subclasses take a reference to such an object and implement conversion using .static.toDataElement and .static.toDomElements just like nodes do. The custom .getHash() functions are no longer necessary because of the way HTML attribute preservation was reimplemented. CE rendering has been moved out of dm.Annotation (it never made sense to have CE rendering functions in DM classes, this was bothering me) and into separate ce.Annotation subclasses. These are very similar to CE nodes in that they have a this.$ generated based on something in the DM; the main difference is that nodes listen to events and update themselves, whereas annotations are static and are simply destroyed and rebuilt when they change. This change also adds whitelisted HTML attribute rendering for annotations, as well as class="ve-ce-FooAnnotation" attributes. Now that annotation classes produce real DOM nodes rather than weird objects describing HTML tags, we can't generate HTML as a string in ce.ContentBranchNode anymore. getRenderedContents() has been rewritten to be much more similar to the way the converter renders annotations; in fact, significant parts of it were copied from the converter, so that should be factored out in the future. This change actually fixes an annotation rendering discrepancy between ce.ContentBranchNode and dm.Converter; see the diff of ve.ce.ContentBranchNode.test.js. ve.ce.MWEntityNode.js: * Remove stray property ve.dm.MWExternalLinkAnnotation.js: * Store 'rel' attribute ve.dm.TextStyleAnnotation.js: * Put all the conversion logic in the abstract base class ve.dm.Converter.js: * Also feed annotations through getDomElementsFromDataElement() and createDataElement() ve.dm.Node.js: * Fix undocumented property ve.ce.ContentBranchNode.test.js: * Add descriptive messages for each test case * Compare DOM trees, not HTML strings * Compare without all the class="ve-ce-WhateverAnnotation" clutter ve.ui.LinkInspector.js: * Replace direct .getHash() calls (evil!) with ve.getHash() Bug: 46464 Bug: 44808 Change-Id: I31991488579b8cce6d98ed8b29b486ba5ec38cdc
2013-04-02 17:23:33 +00:00
'attributes': {
'href': 'Foo'
},
'htmlAttributes': [ { 'values': {
'href': 'Foo'
} } ]
},
ve.dm.example.bold,
ve.dm.example.italic
]
],
[
'b',
[
{
'type': 'link',
Great Annotation Refactor of 2013 This changes the annotation API to be the same as the node API, sans a few boolean flags that don't apply. The APIs were different, but there was really no good reason why, so this makes things simpler for API users. It also means we'll be able to factor a bunch of things out because they're now duplicated between nodes, meta items and annotations. Linear model annotations are now objects with 'type' and 'attributes' properties (rather than 'name' and 'data'), for consistency with elements. They now also contain html/0/* attributes for HTML attribute preservation, which obsoletes the htmlTagName and htmlAttributes properties. dm.Annotation subclasses take a reference to such an object and implement conversion using .static.toDataElement and .static.toDomElements just like nodes do. The custom .getHash() functions are no longer necessary because of the way HTML attribute preservation was reimplemented. CE rendering has been moved out of dm.Annotation (it never made sense to have CE rendering functions in DM classes, this was bothering me) and into separate ce.Annotation subclasses. These are very similar to CE nodes in that they have a this.$ generated based on something in the DM; the main difference is that nodes listen to events and update themselves, whereas annotations are static and are simply destroyed and rebuilt when they change. This change also adds whitelisted HTML attribute rendering for annotations, as well as class="ve-ce-FooAnnotation" attributes. Now that annotation classes produce real DOM nodes rather than weird objects describing HTML tags, we can't generate HTML as a string in ce.ContentBranchNode anymore. getRenderedContents() has been rewritten to be much more similar to the way the converter renders annotations; in fact, significant parts of it were copied from the converter, so that should be factored out in the future. This change actually fixes an annotation rendering discrepancy between ce.ContentBranchNode and dm.Converter; see the diff of ve.ce.ContentBranchNode.test.js. ve.ce.MWEntityNode.js: * Remove stray property ve.dm.MWExternalLinkAnnotation.js: * Store 'rel' attribute ve.dm.TextStyleAnnotation.js: * Put all the conversion logic in the abstract base class ve.dm.Converter.js: * Also feed annotations through getDomElementsFromDataElement() and createDataElement() ve.dm.Node.js: * Fix undocumented property ve.ce.ContentBranchNode.test.js: * Add descriptive messages for each test case * Compare DOM trees, not HTML strings * Compare without all the class="ve-ce-WhateverAnnotation" clutter ve.ui.LinkInspector.js: * Replace direct .getHash() calls (evil!) with ve.getHash() Bug: 46464 Bug: 44808 Change-Id: I31991488579b8cce6d98ed8b29b486ba5ec38cdc
2013-04-02 17:23:33 +00:00
'attributes': {
'href': 'Foo'
},
'htmlAttributes': [ { 'values': {
'href': 'Foo'
} } ]
},
ve.dm.example.italic
]
],
[
'a',
[
ve.dm.example.italic
]
],
[
'r',
[
ve.dm.example.italic,
ve.dm.example.bold
]
],
[
'b',
[
ve.dm.example.italic
]
],
[
'a',
[
ve.dm.example.italic,
ve.dm.example.underline
]
],
[
'z',
[
ve.dm.example.italic
]
],
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'about grouping': {
'html': '<body><figure about="#mwt1">Foo</figure>' +
'<figure about="#mwt1">Bar</figure>' +
'<figure about="#mwt2">Baz</figure>' +
'<tt about="#mwt2">Quux</tt>' +
'<p>Whee</p>' +
'<tt about="#mwt2">Yay</tt>' +
'<figure about="#mwt2">Blah</figure>' +
'<tt about="#mwt3">Meh</tt></body>',
'data': [
{
'type': 'alienBlock',
'attributes': {
'domElements': $( '<figure about="#mwt1">Foo</figure>' +
'<figure about="#mwt1">Bar</figure>' ).toArray()
}
},
{ 'type': '/alienBlock' },
{
'type': 'alienBlock',
'attributes': {
'domElements': $( '<figure about="#mwt2">Baz</figure>' +
'<tt about="#mwt2">Quux</tt>' ).toArray()
}
},
{ 'type': '/alienBlock' },
{ 'type': 'paragraph' },
'W',
'h',
'e',
'e',
{ 'type': '/paragraph' },
{
'type': 'alienBlock',
'attributes': {
'domElements': $( '<tt about="#mwt2">Yay</tt>' +
'<figure about="#mwt2">Blah</figure>' ).toArray()
}
},
{ 'type': '/alienBlock' },
(bug 43056) Inline tags like <span> are block-alienated sometimes This happens when the <span> is the start of unwrapped content. The converter logic to look at the tag name in wrapping mode doesn't kick in because we're not yet in wrapping mode at that point. The core issue was that previously, we relied on the document structure/state to choose between alienBlock and alienInline, and only used the tag name where the document structure was ambiguous (wrapping). Changed this to be the other way around: we now rely primarily on the tag name, and if that doesn't match what we expect based on the document structure, we work around that if possible. Specifically: * inline tag in our wrapper --> inline alien * block tag in our wrapper --> close wrapper, block alien * inline tag in wrapper that's not ours --> inline alien * block tag in wrapper that's not ours --> *inline* alien * inline tag in structural location --> open wrapper, inline alien * block tag in structural location --> block alien * inline tag in content location --> inline alien * block tag in content location --> *inline* alien only in the fourth and the last case do we need to use the "wrong" alien type to preserve document validity, and it will always be inline where block was expected, which should reduce UI issues. The condensed version of the above, which is used in the code, is: * If in a non-wrapper content location, use inline * If in a wrapper that's not ours, use inline * Otherwise, decide based on tag name * Open or close wrapper if needed ve.dm.Converter: * Replace isInline logic in createAlien() with the above * Factor out code to start wrapping (was duplicated) into startWrapping() * Call startWrapping() if createAlien() returns an alienInline and we're in a structural location Tests: * Add test cases with aliens at the start and end of unwrapped content ** The first one failed prior to these changes and now passes, the second one was already passing * Fix about group test case, was exhibiting the bug that this commit fixes Change-Id: I657aa0ff5bc2b57cd48ef8a99c8ca930936c03b8
2012-12-20 00:59:58 +00:00
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
{
(bug 43056) Inline tags like <span> are block-alienated sometimes This happens when the <span> is the start of unwrapped content. The converter logic to look at the tag name in wrapping mode doesn't kick in because we're not yet in wrapping mode at that point. The core issue was that previously, we relied on the document structure/state to choose between alienBlock and alienInline, and only used the tag name where the document structure was ambiguous (wrapping). Changed this to be the other way around: we now rely primarily on the tag name, and if that doesn't match what we expect based on the document structure, we work around that if possible. Specifically: * inline tag in our wrapper --> inline alien * block tag in our wrapper --> close wrapper, block alien * inline tag in wrapper that's not ours --> inline alien * block tag in wrapper that's not ours --> *inline* alien * inline tag in structural location --> open wrapper, inline alien * block tag in structural location --> block alien * inline tag in content location --> inline alien * block tag in content location --> *inline* alien only in the fourth and the last case do we need to use the "wrong" alien type to preserve document validity, and it will always be inline where block was expected, which should reduce UI issues. The condensed version of the above, which is used in the code, is: * If in a non-wrapper content location, use inline * If in a wrapper that's not ours, use inline * Otherwise, decide based on tag name * Open or close wrapper if needed ve.dm.Converter: * Replace isInline logic in createAlien() with the above * Factor out code to start wrapping (was duplicated) into startWrapping() * Call startWrapping() if createAlien() returns an alienInline and we're in a structural location Tests: * Add test cases with aliens at the start and end of unwrapped content ** The first one failed prior to these changes and now passes, the second one was already passing * Fix about group test case, was exhibiting the bug that this commit fixes Change-Id: I657aa0ff5bc2b57cd48ef8a99c8ca930936c03b8
2012-12-20 00:59:58 +00:00
'type': 'alienInline',
'attributes': {
'domElements': $( '<tt about="#mwt3">Meh</tt>' ).toArray()
}
},
(bug 43056) Inline tags like <span> are block-alienated sometimes This happens when the <span> is the start of unwrapped content. The converter logic to look at the tag name in wrapping mode doesn't kick in because we're not yet in wrapping mode at that point. The core issue was that previously, we relied on the document structure/state to choose between alienBlock and alienInline, and only used the tag name where the document structure was ambiguous (wrapping). Changed this to be the other way around: we now rely primarily on the tag name, and if that doesn't match what we expect based on the document structure, we work around that if possible. Specifically: * inline tag in our wrapper --> inline alien * block tag in our wrapper --> close wrapper, block alien * inline tag in wrapper that's not ours --> inline alien * block tag in wrapper that's not ours --> *inline* alien * inline tag in structural location --> open wrapper, inline alien * block tag in structural location --> block alien * inline tag in content location --> inline alien * block tag in content location --> *inline* alien only in the fourth and the last case do we need to use the "wrong" alien type to preserve document validity, and it will always be inline where block was expected, which should reduce UI issues. The condensed version of the above, which is used in the code, is: * If in a non-wrapper content location, use inline * If in a wrapper that's not ours, use inline * Otherwise, decide based on tag name * Open or close wrapper if needed ve.dm.Converter: * Replace isInline logic in createAlien() with the above * Factor out code to start wrapping (was duplicated) into startWrapping() * Call startWrapping() if createAlien() returns an alienInline and we're in a structural location Tests: * Add test cases with aliens at the start and end of unwrapped content ** The first one failed prior to these changes and now passes, the second one was already passing * Fix about group test case, was exhibiting the bug that this commit fixes Change-Id: I657aa0ff5bc2b57cd48ef8a99c8ca930936c03b8
2012-12-20 00:59:58 +00:00
{ 'type': '/alienInline' },
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'whitespace preservation with an about group': {
'html': '<body> <figure about="#mwt1">\tFoo\t\t</figure>\t\t\t' +
'<figure about="#mwt1"> Bar </figure> </body>',
'data': [
{
'type': 'alienBlock',
'attributes': {
'domElements': $( '<figure about="#mwt1">\tFoo\t\t</figure>\t\t\t' +
'<figure about="#mwt1"> Bar </figure>' ).toArray()
},
'internal': {
'whitespace': [ ' ', undefined, undefined, ' ' ]
}
},
{ 'type': '/alienBlock' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
(bug 42487) Don't crash the converter for "<span>\n<p>Foo</p></span>" The converter was misbehaving when handling <p>s inside <span>s. This can't be expressed in the linmod, but it would try to anyway. <span><p> would result in too many paragraph closing elements, leading to an exception in ve.dm.Document complaining about unbalanced input. <span>\n<p> would result in an exception in the converter itself while trying to perform whitespace preservation on the newline. This change makes the converter detect these scenarios and alienate the offending node. So <span><p>Foo</p></span> converts to a wrapper paragraph containing an alienInline whose HTML is "<p>Foo</p>" and which is annotated with a TextStyleSpanAnnotation. ve.dm.Converter.getDomFromData(): * Change the criteria for alienBlock vs alienInline ** Only infer from the node type if we're in wrapping mode AND we're at the same level where the wrapping started (wrappingIsOurs). If the latter isn't the case, we can't split the wrapper in the block case because we're at the wrong level. ** Use alienInline not only if the branch is a content branch, but also if there are active annotations. This catches e.g. <li><b><p> (and generally <span><p> on the top level). * Before converting a child element, check that the child isn't "bad". Bad children are non-content children in content branches, and non-content children encountered within a wrapper that we can't split. Only good children are converted, and bad children are alienated (cue Santa/Sinterklaas jokes). * Add childIsContent and rename branchIsContent to branchHasContent Change-Id: If420ae80ab0777424a9a5517335ef9d0170e87ae
2012-12-05 22:35:10 +00:00
'block node inside annotation node is alienated': {
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'html': '<body><span>\n<p>Bar</p></span></body>',
(bug 42487) Don't crash the converter for "<span>\n<p>Foo</p></span>" The converter was misbehaving when handling <p>s inside <span>s. This can't be expressed in the linmod, but it would try to anyway. <span><p> would result in too many paragraph closing elements, leading to an exception in ve.dm.Document complaining about unbalanced input. <span>\n<p> would result in an exception in the converter itself while trying to perform whitespace preservation on the newline. This change makes the converter detect these scenarios and alienate the offending node. So <span><p>Foo</p></span> converts to a wrapper paragraph containing an alienInline whose HTML is "<p>Foo</p>" and which is annotated with a TextStyleSpanAnnotation. ve.dm.Converter.getDomFromData(): * Change the criteria for alienBlock vs alienInline ** Only infer from the node type if we're in wrapping mode AND we're at the same level where the wrapping started (wrappingIsOurs). If the latter isn't the case, we can't split the wrapper in the block case because we're at the wrong level. ** Use alienInline not only if the branch is a content branch, but also if there are active annotations. This catches e.g. <li><b><p> (and generally <span><p> on the top level). * Before converting a child element, check that the child isn't "bad". Bad children are non-content children in content branches, and non-content children encountered within a wrapper that we can't split. Only good children are converted, and bad children are alienated (cue Santa/Sinterklaas jokes). * Add childIsContent and rename branchIsContent to branchHasContent Change-Id: If420ae80ab0777424a9a5517335ef9d0170e87ae
2012-12-05 22:35:10 +00:00
'data': [
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
[ '\n', [ ve.dm.example.span ] ],
{
'type': 'alienInline',
'attributes': {
'domElements': $( '<p>Bar</p>' ).toArray()
(bug 42487) Don't crash the converter for "<span>\n<p>Foo</p></span>" The converter was misbehaving when handling <p>s inside <span>s. This can't be expressed in the linmod, but it would try to anyway. <span><p> would result in too many paragraph closing elements, leading to an exception in ve.dm.Document complaining about unbalanced input. <span>\n<p> would result in an exception in the converter itself while trying to perform whitespace preservation on the newline. This change makes the converter detect these scenarios and alienate the offending node. So <span><p>Foo</p></span> converts to a wrapper paragraph containing an alienInline whose HTML is "<p>Foo</p>" and which is annotated with a TextStyleSpanAnnotation. ve.dm.Converter.getDomFromData(): * Change the criteria for alienBlock vs alienInline ** Only infer from the node type if we're in wrapping mode AND we're at the same level where the wrapping started (wrappingIsOurs). If the latter isn't the case, we can't split the wrapper in the block case because we're at the wrong level. ** Use alienInline not only if the branch is a content branch, but also if there are active annotations. This catches e.g. <li><b><p> (and generally <span><p> on the top level). * Before converting a child element, check that the child isn't "bad". Bad children are non-content children in content branches, and non-content children encountered within a wrapper that we can't split. Only good children are converted, and bad children are alienated (cue Santa/Sinterklaas jokes). * Add childIsContent and rename branchIsContent to branchHasContent Change-Id: If420ae80ab0777424a9a5517335ef9d0170e87ae
2012-12-05 22:35:10 +00:00
},
'annotations': [ ve.dm.example.span ]
},
{ 'type': '/alienInline' },
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
(bug 42487) Don't crash the converter for "<span>\n<p>Foo</p></span>" The converter was misbehaving when handling <p>s inside <span>s. This can't be expressed in the linmod, but it would try to anyway. <span><p> would result in too many paragraph closing elements, leading to an exception in ve.dm.Document complaining about unbalanced input. <span>\n<p> would result in an exception in the converter itself while trying to perform whitespace preservation on the newline. This change makes the converter detect these scenarios and alienate the offending node. So <span><p>Foo</p></span> converts to a wrapper paragraph containing an alienInline whose HTML is "<p>Foo</p>" and which is annotated with a TextStyleSpanAnnotation. ve.dm.Converter.getDomFromData(): * Change the criteria for alienBlock vs alienInline ** Only infer from the node type if we're in wrapping mode AND we're at the same level where the wrapping started (wrappingIsOurs). If the latter isn't the case, we can't split the wrapper in the block case because we're at the wrong level. ** Use alienInline not only if the branch is a content branch, but also if there are active annotations. This catches e.g. <li><b><p> (and generally <span><p> on the top level). * Before converting a child element, check that the child isn't "bad". Bad children are non-content children in content branches, and non-content children encountered within a wrapper that we can't split. Only good children are converted, and bad children are alienated (cue Santa/Sinterklaas jokes). * Add childIsContent and rename branchIsContent to branchHasContent Change-Id: If420ae80ab0777424a9a5517335ef9d0170e87ae
2012-12-05 22:35:10 +00:00
]
},
'block node inside annotation node surrounded by tables': {
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'html': '<body><table></table><span>\n<p>Bar</p></span><table></table></body>',
(bug 42487) Don't crash the converter for "<span>\n<p>Foo</p></span>" The converter was misbehaving when handling <p>s inside <span>s. This can't be expressed in the linmod, but it would try to anyway. <span><p> would result in too many paragraph closing elements, leading to an exception in ve.dm.Document complaining about unbalanced input. <span>\n<p> would result in an exception in the converter itself while trying to perform whitespace preservation on the newline. This change makes the converter detect these scenarios and alienate the offending node. So <span><p>Foo</p></span> converts to a wrapper paragraph containing an alienInline whose HTML is "<p>Foo</p>" and which is annotated with a TextStyleSpanAnnotation. ve.dm.Converter.getDomFromData(): * Change the criteria for alienBlock vs alienInline ** Only infer from the node type if we're in wrapping mode AND we're at the same level where the wrapping started (wrappingIsOurs). If the latter isn't the case, we can't split the wrapper in the block case because we're at the wrong level. ** Use alienInline not only if the branch is a content branch, but also if there are active annotations. This catches e.g. <li><b><p> (and generally <span><p> on the top level). * Before converting a child element, check that the child isn't "bad". Bad children are non-content children in content branches, and non-content children encountered within a wrapper that we can't split. Only good children are converted, and bad children are alienated (cue Santa/Sinterklaas jokes). * Add childIsContent and rename branchIsContent to branchHasContent Change-Id: If420ae80ab0777424a9a5517335ef9d0170e87ae
2012-12-05 22:35:10 +00:00
'data': [
{ 'type': 'table' },
{ 'type': '/table' },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
[ '\n', [ ve.dm.example.span ] ],
{
'type': 'alienInline',
'attributes': {
'domElements': $( '<p>Bar</p>' ).toArray()
(bug 42487) Don't crash the converter for "<span>\n<p>Foo</p></span>" The converter was misbehaving when handling <p>s inside <span>s. This can't be expressed in the linmod, but it would try to anyway. <span><p> would result in too many paragraph closing elements, leading to an exception in ve.dm.Document complaining about unbalanced input. <span>\n<p> would result in an exception in the converter itself while trying to perform whitespace preservation on the newline. This change makes the converter detect these scenarios and alienate the offending node. So <span><p>Foo</p></span> converts to a wrapper paragraph containing an alienInline whose HTML is "<p>Foo</p>" and which is annotated with a TextStyleSpanAnnotation. ve.dm.Converter.getDomFromData(): * Change the criteria for alienBlock vs alienInline ** Only infer from the node type if we're in wrapping mode AND we're at the same level where the wrapping started (wrappingIsOurs). If the latter isn't the case, we can't split the wrapper in the block case because we're at the wrong level. ** Use alienInline not only if the branch is a content branch, but also if there are active annotations. This catches e.g. <li><b><p> (and generally <span><p> on the top level). * Before converting a child element, check that the child isn't "bad". Bad children are non-content children in content branches, and non-content children encountered within a wrapper that we can't split. Only good children are converted, and bad children are alienated (cue Santa/Sinterklaas jokes). * Add childIsContent and rename branchIsContent to branchHasContent Change-Id: If420ae80ab0777424a9a5517335ef9d0170e87ae
2012-12-05 22:35:10 +00:00
},
'annotations': [ ve.dm.example.span ]
},
{ 'type': '/alienInline' },
{ 'type': '/paragraph' },
{ 'type': 'table' },
{ 'type': '/table' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
(bug 42487) Don't crash the converter for "<span>\n<p>Foo</p></span>" The converter was misbehaving when handling <p>s inside <span>s. This can't be expressed in the linmod, but it would try to anyway. <span><p> would result in too many paragraph closing elements, leading to an exception in ve.dm.Document complaining about unbalanced input. <span>\n<p> would result in an exception in the converter itself while trying to perform whitespace preservation on the newline. This change makes the converter detect these scenarios and alienate the offending node. So <span><p>Foo</p></span> converts to a wrapper paragraph containing an alienInline whose HTML is "<p>Foo</p>" and which is annotated with a TextStyleSpanAnnotation. ve.dm.Converter.getDomFromData(): * Change the criteria for alienBlock vs alienInline ** Only infer from the node type if we're in wrapping mode AND we're at the same level where the wrapping started (wrappingIsOurs). If the latter isn't the case, we can't split the wrapper in the block case because we're at the wrong level. ** Use alienInline not only if the branch is a content branch, but also if there are active annotations. This catches e.g. <li><b><p> (and generally <span><p> on the top level). * Before converting a child element, check that the child isn't "bad". Bad children are non-content children in content branches, and non-content children encountered within a wrapper that we can't split. Only good children are converted, and bad children are alienated (cue Santa/Sinterklaas jokes). * Add childIsContent and rename branchIsContent to branchHasContent Change-Id: If420ae80ab0777424a9a5517335ef9d0170e87ae
2012-12-05 22:35:10 +00:00
]
},
'block node inside annotation node is alienated and continues wrapping': {
Make the converter work with full HTML documents rather than fragments The Parsoid output will also be expected to be a full HTML document. For backwards compatibility, we allow for the Parsoid output to be a document fragment as well. We don't send a full document back yet, also for b/c -- we'll change this later once Parsoid has been updated in production. ve.dm.Converter.js: * Make getDataFromDom() accept a document rather than a node ** Split off the recursion (which does use nodes) into its own function ** For now we just convert the <body>. In the future, we'll want to do things with the <head> as well * Pass the document around so we can use it when creating elements * Make getDomFromData() return a document rather than a <div> ve.init.mw.Target.js: * Store a document (this.doc) rather than a DOM node (this.dom) * Pass around documents rather than DOM nodes * Detect whether the Parsoid output is an HTML document or a fragment using a hacky regex * When submitting to Parsoid, submit the innerHTML of the <body> ve.init.mw.ViewPageTarget.js: * s/dom/doc/ * Store body.innerHTML in this.originalHtml ve.Surface.js: * s/dom/doc/ demos/ve/index.php: * Don't wrap HTML in <div> * Pass HTML document rather than DOM node to ve.Surface ve.dm.Converter.test.js: * Construct a document from the test HTML, rather than a <div> ve.dm.example.js: * Wrap the HTML in the converter test cases in <body> tags to prevent misinterpretation (HTML fragments starting with comments, <meta>, <link> and whitespace are problematic) Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
2013-02-11 19:46:58 +00:00
'html': '<body>Foo<span>\n<p>Bar</p></span>Baz</body>',
(bug 42487) Don't crash the converter for "<span>\n<p>Foo</p></span>" The converter was misbehaving when handling <p>s inside <span>s. This can't be expressed in the linmod, but it would try to anyway. <span><p> would result in too many paragraph closing elements, leading to an exception in ve.dm.Document complaining about unbalanced input. <span>\n<p> would result in an exception in the converter itself while trying to perform whitespace preservation on the newline. This change makes the converter detect these scenarios and alienate the offending node. So <span><p>Foo</p></span> converts to a wrapper paragraph containing an alienInline whose HTML is "<p>Foo</p>" and which is annotated with a TextStyleSpanAnnotation. ve.dm.Converter.getDomFromData(): * Change the criteria for alienBlock vs alienInline ** Only infer from the node type if we're in wrapping mode AND we're at the same level where the wrapping started (wrappingIsOurs). If the latter isn't the case, we can't split the wrapper in the block case because we're at the wrong level. ** Use alienInline not only if the branch is a content branch, but also if there are active annotations. This catches e.g. <li><b><p> (and generally <span><p> on the top level). * Before converting a child element, check that the child isn't "bad". Bad children are non-content children in content branches, and non-content children encountered within a wrapper that we can't split. Only good children are converted, and bad children are alienated (cue Santa/Sinterklaas jokes). * Add childIsContent and rename branchIsContent to branchHasContent Change-Id: If420ae80ab0777424a9a5517335ef9d0170e87ae
2012-12-05 22:35:10 +00:00
'data': [
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'F',
'o',
'o',
[ '\n', [ ve.dm.example.span ] ],
{
'type': 'alienInline',
'attributes': {
'domElements': $( '<p>Bar</p>' ).toArray()
(bug 42487) Don't crash the converter for "<span>\n<p>Foo</p></span>" The converter was misbehaving when handling <p>s inside <span>s. This can't be expressed in the linmod, but it would try to anyway. <span><p> would result in too many paragraph closing elements, leading to an exception in ve.dm.Document complaining about unbalanced input. <span>\n<p> would result in an exception in the converter itself while trying to perform whitespace preservation on the newline. This change makes the converter detect these scenarios and alienate the offending node. So <span><p>Foo</p></span> converts to a wrapper paragraph containing an alienInline whose HTML is "<p>Foo</p>" and which is annotated with a TextStyleSpanAnnotation. ve.dm.Converter.getDomFromData(): * Change the criteria for alienBlock vs alienInline ** Only infer from the node type if we're in wrapping mode AND we're at the same level where the wrapping started (wrappingIsOurs). If the latter isn't the case, we can't split the wrapper in the block case because we're at the wrong level. ** Use alienInline not only if the branch is a content branch, but also if there are active annotations. This catches e.g. <li><b><p> (and generally <span><p> on the top level). * Before converting a child element, check that the child isn't "bad". Bad children are non-content children in content branches, and non-content children encountered within a wrapper that we can't split. Only good children are converted, and bad children are alienated (cue Santa/Sinterklaas jokes). * Add childIsContent and rename branchIsContent to branchHasContent Change-Id: If420ae80ab0777424a9a5517335ef9d0170e87ae
2012-12-05 22:35:10 +00:00
},
'annotations': [ ve.dm.example.span ]
},
{ 'type': '/alienInline' },
'B',
'a',
'z',
{ 'type': '/paragraph' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
(bug 42487) Don't crash the converter for "<span>\n<p>Foo</p></span>" The converter was misbehaving when handling <p>s inside <span>s. This can't be expressed in the linmod, but it would try to anyway. <span><p> would result in too many paragraph closing elements, leading to an exception in ve.dm.Document complaining about unbalanced input. <span>\n<p> would result in an exception in the converter itself while trying to perform whitespace preservation on the newline. This change makes the converter detect these scenarios and alienate the offending node. So <span><p>Foo</p></span> converts to a wrapper paragraph containing an alienInline whose HTML is "<p>Foo</p>" and which is annotated with a TextStyleSpanAnnotation. ve.dm.Converter.getDomFromData(): * Change the criteria for alienBlock vs alienInline ** Only infer from the node type if we're in wrapping mode AND we're at the same level where the wrapping started (wrappingIsOurs). If the latter isn't the case, we can't split the wrapper in the block case because we're at the wrong level. ** Use alienInline not only if the branch is a content branch, but also if there are active annotations. This catches e.g. <li><b><p> (and generally <span><p> on the top level). * Before converting a child element, check that the child isn't "bad". Bad children are non-content children in content branches, and non-content children encountered within a wrapper that we can't split. Only good children are converted, and bad children are alienated (cue Santa/Sinterklaas jokes). * Add childIsContent and rename branchIsContent to branchHasContent Change-Id: If420ae80ab0777424a9a5517335ef9d0170e87ae
2012-12-05 22:35:10 +00:00
]
},
'whitespace before meta node in wrapping mode': {
'html': '<body><table><tbody><tr><td>Foo\n<meta content="bar" /></td></tr></tbody></table></body>',
'data': [
{ 'type': 'table' },
{ 'type': 'tableSection', 'attributes': { 'style': 'body' } },
{ 'type': 'tableRow' },
{
'type': 'tableCell',
'attributes': { 'style': 'data' },
'internal': { 'whitespace': [ undefined, undefined, '\n' ] }
},
{
'type': 'paragraph',
'internal': {
'generated': 'wrapper',
'whitespace': [ undefined, undefined, undefined, '\n' ]
}
},
'F',
'o',
'o',
{ 'type': '/paragraph' },
{
'type': 'alienMeta',
'internal': { 'whitespace': [ '\n' ] },
'attributes': {
'domElements': $( '<meta content="bar" />' ).toArray()
}
},
{ 'type': '/alienMeta' },
{ 'type': '/tableCell' },
{ 'type': '/tableRow' },
{ 'type': '/tableSection' },
{ 'type': '/table' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
},
'table with caption, head, foot and body': {
'html': ve.dm.example.complexTableHtml,
'data': ve.dm.example.complexTable
},
'div set to RTL with paragraph inside': {
'html': '<body><div style="direction: rtl;"><p>a<b>b</b>c<i>d</i>e</p></body>',
'data': [
{
'type': 'div',
'htmlAttributes': [ { 'values': { 'style': 'direction: rtl;' } } ]
},
{ 'type': 'paragraph' },
'a',
['b', [ ve.dm.example.bold ]],
'c',
['d', [ ve.dm.example.italic ]],
'e',
{ 'type': '/paragraph' },
{ 'type': '/div' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
]
}
};
ve.dm.example.isolationHtml =
'<ul><li>Item 1</li><li>Item 2</li><li>Item 3</li></ul>' +
'Paragraph' +
'<ul><li>Item 4</li><li>Item 5</li><li>Item 6</li></ul>' +
'<table><tbody><tr><td>Cell 1</td><td>Cell 2</td><td>Cell 3</td></tr><tr><td>Cell 4</td></tr></tbody></table>' +
'Not allowed by dm:' +
'<ul><li><h1>Title in list</h1></li><li><pre>Preformatted in list</pre></li></ul>' +
'<ul><li><ol><li>Nested 1</li><li>Nested 2</li><li>Nested 3</li></ol></li></ul>' +
'<ul><li><p>P1</p><p>P2</p><p>P3</p></li></ul>';
ve.dm.example.isolationData = [
// 0
{ 'type': 'list', 'attributes': { 'style': 'bullet' } },
{ 'type': 'listItem' },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'I', 't', 'e', 'm', ' ', '1',
{ 'type': '/paragraph' },
// 10
{ 'type': '/listItem' },
{ 'type': 'listItem' },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'I', 't', 'e', 'm', ' ', '2',
{ 'type': '/paragraph' },
// 20
{ 'type': '/listItem' },
{ 'type': 'listItem' },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'I', 't', 'e', 'm', ' ', '3',
{ 'type': '/paragraph' },
// 30
{ 'type': '/listItem' },
{ 'type': '/list' },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'P', 'a', 'r', 'a', 'g', 'r', 'a',
// 40
'p', 'h',
{ 'type': '/paragraph' },
{ 'type': 'list', 'attributes': { 'style': 'bullet' } },
{ 'type': 'listItem' },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'I', 't', 'e', 'm',
// 50
' ', '4',
{ 'type': '/paragraph' },
{ 'type': '/listItem' },
{ 'type': 'listItem' },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'I', 't', 'e', 'm',
// 60
' ', '5',
{ 'type': '/paragraph' },
{ 'type': '/listItem' },
{ 'type': 'listItem' },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'I', 't', 'e', 'm',
// 70
' ', '6',
{ 'type': '/paragraph' },
{ 'type': '/listItem' },
{ 'type': '/list' },
{ 'type': 'table' },
{ 'type': 'tableSection', 'attributes': { 'style': 'body' } },
{ 'type': 'tableRow' },
{ 'type': 'tableCell', 'attributes': { 'style': 'data' } },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
// 80
'C', 'e', 'l', 'l', ' ', '1',
{ 'type': '/paragraph' },
{ 'type': '/tableCell' },
{ 'type': 'tableCell', 'attributes': { 'style': 'data' } },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
// 90
'C', 'e', 'l', 'l', ' ', '2',
{ 'type': '/paragraph' },
{ 'type': '/tableCell' },
{ 'type': 'tableCell', 'attributes': { 'style': 'data' } },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
// 100
'C', 'e', 'l', 'l', ' ', '3',
{ 'type': '/paragraph' },
{ 'type': '/tableCell' },
{ 'type': '/tableRow' },
{ 'type': 'tableRow' },
// 110
{ 'type': 'tableCell', 'attributes': { 'style': 'data' } },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'C', 'e', 'l', 'l', ' ', '4',
{ 'type': '/paragraph' },
{ 'type': '/tableCell' },
// 120
{ 'type': '/tableRow' },
{ 'type': '/tableSection' },
{ 'type': '/table' },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'N', 'o', 't', ' ', 'a', 'l',
// 130
'l', 'o', 'w', 'e', 'd', ' ', 'b', 'y', ' ', 'd',
// 140
'm', ':',
{ 'type': '/paragraph' },
{ 'type': 'list', 'attributes': { 'style': 'bullet' } },
{ 'type': 'listItem' },
{ 'type': 'heading', 'attributes': { 'level': 1 } },
'T', 'i', 't', 'l',
// 150
'e', ' ', 'i', 'n', ' ', 'l', 'i', 's', 't',
{ 'type': '/heading' },
// 160
{ 'type': '/listItem' },
{ 'type': 'listItem' },
{ 'type': 'preformatted' },
'P', 'r', 'e', 'f', 'o', 'r', 'm',
// 170
'a', 't', 't', 'e', 'd', ' ', 'i', 'n', ' ', 'l',
// 180
'i', 's', 't',
{ 'type': '/preformatted' },
{ 'type': '/listItem' },
{ 'type': '/list' },
{ 'type': 'list', 'attributes': { 'style': 'bullet' } },
{ 'type': 'listItem' },
{ 'type': 'list', 'attributes': { 'style': 'number' } },
{ 'type': 'listItem' },
// 190
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'N', 'e', 's', 't', 'e', 'd', ' ', '1',
{ 'type': '/paragraph' },
// 200
{ 'type': '/listItem' },
{ 'type': 'listItem' },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'N', 'e', 's', 't', 'e', 'd', ' ',
// 210
'2',
{ 'type': '/paragraph' },
{ 'type': '/listItem' },
{ 'type': 'listItem' },
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
'N', 'e', 's', 't', 'e',
// 220
'd', ' ', '3',
{ 'type': '/paragraph' },
{ 'type': '/listItem' },
{ 'type': '/list' },
{ 'type': '/listItem' },
{ 'type': '/list' },
{ 'type': 'list', 'attributes': { 'style': 'bullet' } },
{ 'type': 'listItem' },
// 230
{ 'type': 'paragraph' },
'P', '1',
{ 'type': '/paragraph' },
{ 'type': 'paragraph' },
'P', '2',
{ 'type': '/paragraph' },
{ 'type': 'paragraph' },
'P',
// 240
'3',
{ 'type': '/paragraph' },
{ 'type': '/listItem' },
{ 'type': '/list' },
{ 'type': 'internalList' },
{ 'type': '/internalList' }
// 246
];
ve.dm.example.references = [
{ 'type': 'paragraph' },
{
'type': 'mwReference',
'attributes': {
'about': '#mwt2',
'contentsUsed': true,
'listGroup': 'mwReference/',
'listIndex': 0,
'listKey': '"No name 1"',
'mw': {
'attrs': {},
'body': { 'html': 'No name 1' },
'name': 'ref'
},
'originalMw': '{"name":"ref","body":{"html":"No name 1"},"attrs":{}}',
'refGroup': ''
},
'htmlAttributes': [ { 'values': {
'about': '#mwt2',
'class': 'reference',
'data-mw': '{"name":"ref","body":{"html":"No name 1"},"attrs":{}}',
'data-parsoid': '{"src":"<ref>No name 1</ref>","dsr":[0,20,5,6]}',
'id': 'cite_ref-1-0',
'rel': 'dc:references',
'typeof': 'mw:Extension/ref'
} } ]
},
{ 'type': '/mwReference' },
{ 'type': '/paragraph' },
{ 'type': 'paragraph', 'htmlAttributes': [ { 'values': { 'data-parsoid': '{"dsr":[22,108,0,0]}' } } ] },
'F', 'o', 'o',
{
'type': 'mwReference',
'attributes': {
'about': '#mwt6',
'contentsUsed': true,
'listGroup': 'mwReference/',
'listIndex': 1,
'listKey': 'bar',
'mw': {
'attrs': { 'name': 'bar' },
'body': { 'html': 'Bar' },
'name': 'ref'
},
'originalMw': '{"body":{"html":""},"attrs":{"name":"bar"}}',
'refGroup': ''
},
'htmlAttributes': [ { 'values': {
'about': '#mwt6',
'class': 'reference',
'data-mw': '{"name":"ref","body":{"html":"Bar"},"attrs":{"name":"bar"}}',
'data-parsoid': '{"src":"<ref name=\\"bar\\">Bar</ref>","dsr":[25,50,16,6]}',
'id': 'cite_ref-bar-2-0',
'rel': 'dc:references',
'typeof': 'mw:Extension/ref'
} } ]
},
{ 'type': '/mwReference' },
' ', 'B', 'a', 'z',
{
'type': 'mwReference',
'attributes': {
'about': '#mwt7',
'contentsUsed': true,
'listGroup': 'mwReference/',
'listIndex': 2,
'listKey': 'quux',
'mw': {
'attrs': { 'name': 'quux' },
'body': { 'html': 'Quux' },
'name': 'ref'
},
'originalMw': '{"name":"ref","body":{"html":"Quux"},"attrs":{"name":"quux"}}',
'refGroup': ''
},
'htmlAttributes': [ { 'values': {
'about': '#mwt7',
'class': 'reference',
'data-mw': '{"name":"ref","body":{"html":"Quux"},"attrs":{"name":"quux"}}',
'data-parsoid': '{"src":"<ref name=\\"quux\\">Quux</ref>","dsr":[54,81,17,6]}',
'id': 'cite_ref-quux-3-0',
'rel': 'dc:references',
'typeof': 'mw:Extension/ref'
} } ]
},
{ 'type': '/mwReference' },
' ', 'W', 'h', 'e', 'e',
{
'type': 'mwReference',
'attributes': {
'about': '#mwt8',
'contentsUsed': false,
'listGroup': 'mwReference/',
'listIndex': 1,
'listKey': 'bar',
'mw': {
'attrs': { 'name': 'bar' },
'name': 'ref'
},
'originalMw': '{"body":{"html":""},"attrs":{"name":"bar"}}',
'refGroup': ''
},
'htmlAttributes': [ { 'values': {
'about': '#mwt8',
'class': 'reference',
'data-mw': '{"name":"ref","attrs":{"name":"bar"}}',
'data-parsoid': '{"src":"<ref name=\\"bar\\" />","dsr":[86,104,18,0]}',
'id': 'cite_ref-bar-2-1',
'rel': 'dc:references',
'typeof': 'mw:Extension/ref'
} } ]
},
{ 'type': '/mwReference' },
' ', 'Y', 'a', 'y',
{ 'type': '/paragraph' },
{ 'type': 'paragraph' },
{
'type': 'mwReference',
'attributes': {
'about': '#mwt11',
'contentsUsed': true,
'listGroup': 'mwReference/',
'listIndex': 3,
'listKey': '"No name 2"',
'mw': {
'attrs': {},
'body': { 'html': 'No name 2' },
'name': 'ref'
},
'originalMw': '{"name":"ref","body":{"html":"No name 2"},"attrs":{}}',
'refGroup': ''
},
'htmlAttributes': [ { 'values': {
'about': '#mwt11',
'class': 'reference',
'data-mw': '{"name":"ref","body":{"html":"No name 2"},"attrs":{}}',
'data-parsoid': '{"src":"<ref>No name 2</ref>","dsr":[110,130,5,6]}',
'id': 'cite_ref-4-0',
'rel': 'dc:references',
'typeof': 'mw:Extension/ref'
} } ]
},
{ 'type': '/mwReference' },
{
'type': 'mwReference',
'attributes': {
'about': '#mwt12',
'contentsUsed': true,
'listGroup': 'mwReference/',
'listIndex': 4,
'listKey': '"No name 3"',
'mw': {
'attrs': {},
'body': { 'html': 'No name 3' },
'name': 'ref'
},
'originalMw': '{"name":"ref","body":{"html":"No name 3"},"attrs":{}}',
'refGroup': ''
},
'htmlAttributes': [ { 'values': {
'about': '#mwt12',
'class': 'reference',
'data-mw': '{"name":"ref","body":{"html":"No name 3"},"attrs":{}}',
'data-parsoid': '{"src":"<ref>No name 3</ref>"',
'id': 'cite_ref-5-0',
'rel': 'dc:references',
'typeof': 'mw:Extension/ref'
} } ]
},
{ 'type': '/mwReference' },
{ 'type': '/paragraph' },
{
'type': 'mwReferenceList',
'attributes': {
'about': '#mwt12',
'mw': {
'name': 'references',
'attrs': {}
},
'originalMw': '{"name":"references","attrs":{}"}',
//'domElements': HTML,
'listGroup': 'mwReference/',
'refGroup': ''
}
},
{ 'type': '/mwReferenceList' },
{ 'type': 'internalList' },
{ 'type': 'internalItem' },
{ 'type': 'paragraph' },
'N', 'o', ' ', 'n', 'a', 'm', 'e', ' ', '1',
{ 'type': '/paragraph' },
{ 'type': '/internalItem' },
{ 'type': 'internalItem' },
{ 'type': 'paragraph' },
'B', 'a', 'r',
{ 'type': '/paragraph' },
{ 'type': '/internalItem' },
{ 'type': 'internalItem' },
{ 'type' : 'paragraph' },
'Q', 'u', 'u', 'x',
{ 'type': '/paragraph' },
{ 'type': '/internalItem' },
{ 'type': 'internalItem' },
{ 'type' : 'paragraph' },
'N', 'o', ' ', 'n', 'a', 'm', 'e', ' ', '2',
{ 'type': '/paragraph' },
{ 'type': '/internalItem' },
{ 'type': 'internalItem' },
{ 'type' : 'paragraph' },
'N', 'o', ' ', 'n', 'a', 'm', 'e', ' ', '3',
{ 'type': '/paragraph' },
{ 'type': '/internalItem' },
{ 'type': '/internalList' }
];
ve.dm.example.selectNodesCases = [
{
'range': new ve.Range( 1 ),
'mode': 'branches',
'expected': [
// heading
{
'node': [ 0 ],
'range': new ve.Range( 1 ),
'index': 0,
'nodeRange': new ve.Range( 1, 4 ),
'nodeOuterRange': new ve.Range( 0, 5 ),
'parentOuterRange': new ve.Range( 0, 63 )
}
]
},
{
'range': new ve.Range( 10 ),
'mode': 'branches',
'expected': [
// table/tableSection/tableRow/tableCell/paragraph
{
'node': [ 1, 0, 0, 0, 0 ],
'range': new ve.Range( 10 ),
'index': 0,
'nodeRange': new ve.Range( 10, 11 ),
'nodeOuterRange': new ve.Range( 9, 12 ),
'parentOuterRange': new ve.Range( 8, 34 )
}
]
},
{
'range': new ve.Range( 20 ),
'mode': 'branches',
'expected': [
// table/tableSection/tableRow/tableCell/list/listItem/list/listItem/paragraph
{
'node': [ 1, 0, 0, 0, 1, 0, 1, 0, 0 ],
'range': new ve.Range( 20 ),
'index': 0,
'nodeRange': new ve.Range( 20, 21 ),
'nodeOuterRange': new ve.Range( 19, 22 ),
'parentOuterRange': new ve.Range( 18, 23 )
}
]
},
{
'range': new ve.Range( 1, 20 ),
'mode': 'branches',
'expected': [
// heading
{
'node': [ 0 ],
'range': new ve.Range( 1, 4 ),
'index': 0,
'nodeRange': new ve.Range( 1, 4 ),
'nodeOuterRange': new ve.Range( 0, 5 ),
'parentOuterRange': new ve.Range( 0, 63 )
},
// table/tableSection/tableRow/tableCell/paragraph
{
'node': [ 1, 0, 0, 0, 0 ],
'index': 0,
'nodeRange': new ve.Range( 10, 11 ),
'nodeOuterRange': new ve.Range( 9, 12 ),
'parentOuterRange': new ve.Range( 8, 34 )
},
// table/tableSection/tableRow/tableCell/list/listItem/paragraph
{
'node': [ 1, 0, 0, 0, 1, 0, 0 ],
'index': 0,
'nodeRange': new ve.Range( 15, 16 ),
'nodeOuterRange': new ve.Range( 14, 17 ),
'parentOuterRange': new ve.Range( 13, 25 )
},
// table/tableSection/tableRow/tableCell/list/listItem/list/listItem/paragraph
{
'node': [ 1, 0, 0, 0, 1, 0, 1, 0, 0 ],
'range': new ve.Range( 20 ),
'index': 0,
'nodeRange': new ve.Range( 20, 21 ),
'nodeOuterRange': new ve.Range( 19, 22 ),
'parentOuterRange': new ve.Range( 18, 23 )
}
]
},
{
'range': new ve.Range( 1 ),
'mode': 'branches',
'expected': [
// heading
{
'node': [ 0 ],
'range': new ve.Range( 1 ),
'index': 0,
'nodeRange': new ve.Range( 1, 4 ),
'nodeOuterRange': new ve.Range( 0, 5 ),
'parentOuterRange': new ve.Range( 0, 63 )
}
]
},
{
'range': new ve.Range( 0, 3 ),
'mode': 'leaves',
'expected': [
// heading/text
{
'node': [ 0, 0 ],
'range': new ve.Range( 1, 3 ),
'index': 0,
'nodeRange': new ve.Range( 1, 4 ),
'nodeOuterRange': new ve.Range( 1, 4 ),
'parentOuterRange': new ve.Range( 0, 5 )
}
],
'msg': 'partial leaf results have ranges with global offsets'
},
{
'range': new ve.Range( 0, 11 ),
'mode': 'leaves',
'expected': [
// heading/text
{
'node': [ 0, 0 ],
'index': 0,
'nodeRange': new ve.Range( 1, 4 ),
'nodeOuterRange': new ve.Range( 1, 4 ),
'parentOuterRange': new ve.Range( 0, 5 )
},
// table/tableSection/tableRow/tableCell/paragraph/text
{
'node': [ 1, 0, 0, 0, 0, 0 ],
'index': 0,
'nodeRange': new ve.Range( 10, 11 ),
'nodeOuterRange': new ve.Range( 10, 11 ),
'parentOuterRange': new ve.Range( 9, 12 )
}
],
'msg': 'leaf nodes do not have ranges, leaf nodes from different levels'
},
{
'range': new ve.Range( 29, 43 ),
'mode': 'leaves',
'expected': [
// table/tableSection/tableRow/tableCell/list/listItem/paragraph/text
{
'node': [ 1, 0, 0, 0, 2, 0, 0, 0 ],
'index': 0,
'nodeRange': new ve.Range( 29, 30 ),
'nodeOuterRange': new ve.Range( 29, 30 ),
'parentOuterRange': new ve.Range( 28, 31 )
},
// preformatted/text
{
'node': [ 2, 0 ],
'index': 0,
'nodeRange': new ve.Range( 38, 39 ),
'nodeOuterRange': new ve.Range( 38, 39 ),
'parentOuterRange': new ve.Range( 37, 43 )
},
// preformatted/image
{
'node': [ 2, 1 ],
'index': 1,
'nodeRange': new ve.Range( 40, 40 ),
'nodeOuterRange': new ve.Range( 39, 41 ),
'parentOuterRange': new ve.Range( 37, 43 )
},
// preformatted/text
{
'node': [ 2, 2 ],
'index': 2,
'nodeRange': new ve.Range( 41, 42 ),
'nodeOuterRange': new ve.Range( 41, 42 ),
'parentOuterRange': new ve.Range( 37, 43 )
}
],
'msg': 'leaf nodes that are not text nodes'
},
{
'range': new ve.Range( 2, 16 ),
'mode': 'siblings',
'expected': [
// heading
{
'node': [ 0 ],
'range': new ve.Range( 2, 4 ),
'index': 0,
'nodeRange': new ve.Range( 1, 4 ),
'nodeOuterRange': new ve.Range( 0, 5 ),
'parentOuterRange': new ve.Range( 0, 63 )
},
// table
{
'node': [ 1 ],
'range': new ve.Range( 6, 16 ),
'index': 1,
'nodeRange': new ve.Range( 6, 36 ),
'nodeOuterRange': new ve.Range( 5, 37 ),
'parentOuterRange': new ve.Range( 0, 63 )
}
],
'msg': 'siblings at the document level'
},
{
'range': new ve.Range( 2, 51 ),
'mode': 'siblings',
'expected': [
// heading
{
'node': [ 0 ],
'range': new ve.Range( 2, 4 ),
'index': 0,
'nodeRange': new ve.Range( 1, 4 ),
'nodeOuterRange': new ve.Range( 0, 5 ),
'parentOuterRange': new ve.Range( 0, 63 )
},
// table
{
'node': [ 1 ],
'index': 1,
'nodeRange': new ve.Range( 6, 36 ),
'nodeOuterRange': new ve.Range( 5, 37 ),
'parentOuterRange': new ve.Range( 0, 63 )
},
// preformatted
{
'node': [ 2 ],
'index': 2,
'nodeRange': new ve.Range( 38, 42 ),
'nodeOuterRange': new ve.Range( 37, 43 ),
'parentOuterRange': new ve.Range( 0, 63 )
},
// definitionList
{
'node': [ 3 ],
'range': new ve.Range( 44, 51 ),
'index': 3,
'nodeRange': new ve.Range( 44, 54 ),
'nodeOuterRange': new ve.Range( 43, 55 ),
'parentOuterRange': new ve.Range( 0, 63 )
}
],
'msg': 'more than 2 siblings at the document level'
},
{
'range': new ve.Range( 1, 1 ),
'mode': 'leaves',
'expected': [
// heading/text
{
'node': [ 0, 0 ],
'range': new ve.Range( 1, 1 ),
'index': 0,
'nodeRange': new ve.Range( 1, 4 ),
'nodeOuterRange': new ve.Range( 1, 4 ),
'parentOuterRange': new ve.Range( 0, 5 )
}
],
'msg': 'zero-length range at the start of a text node returns text node rather than parent'
},
{
'range': new ve.Range( 4, 4 ),
'mode': 'leaves',
'expected': [
// heading/text
{
'node': [ 0, 0 ],
'range': new ve.Range( 4, 4 ),
'index': 0,
'nodeRange': new ve.Range( 1, 4 ),
'nodeOuterRange': new ve.Range( 1, 4 ),
'parentOuterRange': new ve.Range( 0, 5 )
}
],
'msg': 'zero-length range at the end of a text node returns text node rather than parent'
},
{
'range': new ve.Range( 2, 3 ),
'mode': 'leaves',
'expected': [
// heading/text
{
'node': [ 0, 0 ],
'range': new ve.Range( 2, 3 ),
'index': 0,
'nodeRange': new ve.Range( 1, 4 ),
'nodeOuterRange': new ve.Range( 1, 4 ),
'parentOuterRange': new ve.Range( 0, 5 )
}
],
'msg': 'range entirely within one leaf node'
},
{
'range': new ve.Range( 5, 5 ),
'mode': 'leaves',
'expected': [
// document
{
'node': [],
'range': new ve.Range( 5, 5 ),
// no 'index' because documentNode has no parent
'indexInNode': 1,
'nodeRange': new ve.Range( 0, 63 ),
'nodeOuterRange': new ve.Range( 0, 63 )
}
],
'msg': 'zero-length range between two children of the document'
},
{
'range': new ve.Range( 0, 0 ),
'mode': 'leaves',
'expected': [
// document
{
'node': [],
'range': new ve.Range( 0, 0 ),
// no 'index' because documentNode has no parent
'indexInNode': 0,
'nodeRange': new ve.Range( 0, 63 ),
'nodeOuterRange': new ve.Range( 0, 63 )
}
],
'msg': 'zero-length range at the start of the document'
},
{
'range': new ve.Range( 32, 39 ),
'mode': 'leaves',
'expected': [
// table/tableSection/tableRow/tableCell/list
{
'node': [ 1, 0, 0, 0, 2 ],
'range': new ve.Range( 32, 32 ),
'index': 2,
'indexInNode': 1,
'nodeRange': new ve.Range( 27, 32 ),
'nodeOuterRange': new ve.Range( 26, 33 )
},
// preformatted/text
{
'node': [ 2, 0 ],
// no 'range' because the text node is covered completely
'index': 0,
'nodeRange': new ve.Range( 38, 39 ),
'nodeOuterRange': new ve.Range( 38, 39 ),
'parentOuterRange': new ve.Range( 37, 43 )
}
],
'msg': 'range with 5 closings and a text node'
},
{
'range': new ve.Range( 2, 57 ),
'mode': 'covered',
'expected': [
// heading/text
{
'node': [ 0, 0 ],
'range': new ve.Range( 2, 4 ),
'index': 0,
'nodeRange': new ve.Range( 1, 4 ),
'nodeOuterRange': new ve.Range( 1, 4 ),
'parentOuterRange': new ve.Range( 0, 5 )
},
// table
{
'node': [ 1 ],
// no 'range' because the table is covered completely
'index': 1,
'nodeRange': new ve.Range( 6, 36 ),
'nodeOuterRange': new ve.Range( 5, 37 ),
'parentOuterRange': new ve.Range( 0, 63 )
},
// preformatted
{
'node': [ 2 ],
// no 'range' because the node is covered completely
'index': 2,
'nodeRange': new ve.Range( 38, 42 ),
'nodeOuterRange': new ve.Range( 37, 43 ),
'parentOuterRange': new ve.Range( 0, 63 )
},
// definitionList
{
'node': [ 3 ],
// no 'range' because the node is covered completely
'index': 3,
'nodeRange': new ve.Range( 44, 54 ),
'nodeOuterRange': new ve.Range( 43, 55 ),
'parentOuterRange': new ve.Range( 0, 63 )
},
// paragraph/text
{
'node': [ 4, 0 ],
// no 'range' because the text node is covered completely
'index': 0,
'nodeRange': new ve.Range( 56, 57 ),
'nodeOuterRange': new ve.Range( 56, 57 ),
'parentOuterRange': new ve.Range( 55, 58 )
}
],
'msg': 'range from the first heading into the second-to-last paragraph, in covered mode'
},
{
'range': new ve.Range( 14, 14 ),
'mode': 'siblings',
'expected': [
// table/tableSection/tableRow/tableCell/list/listItem
{
'node': [ 1, 0, 0, 0, 1, 0 ],
'range': new ve.Range( 14, 14 ),
'index': 0,
'indexInNode': 0,
'nodeRange': new ve.Range( 14, 24 ),
'nodeOuterRange': new ve.Range( 13, 25 )
}
],
'msg': 'zero-length range at the beginning of a listItem, in siblings mode'
},
{
'range': new ve.Range( 25, 27 ),
'mode': 'covered',
'expected': [
// table/tableSection/tableRow/tableCell/list
{
'node': [ 1, 0, 0, 0, 1 ],
'range': new ve.Range( 25, 25 ),
'index': 1,
'indexInNode': 1,
'nodeRange': new ve.Range( 13, 25 ),
'nodeOuterRange': new ve.Range( 12, 26 )
},
// table/tableSection/tableRow/tableCell/list
{
'node': [ 1, 0, 0, 0, 2 ],
'range': new ve.Range( 27, 27 ),
'index': 2,
'indexInNode': 0,
'nodeRange': new ve.Range( 27, 32 ),
'nodeOuterRange': new ve.Range( 26, 33 )
}
],
'msg': 'range covering a list closing and a list opening'
},
{
'range': new ve.Range( 39, 39 ),
'mode': 'leaves',
'expected': [
// preformatted/text
{
'node': [ 2, 0 ],
'range': new ve.Range( 39, 39 ),
'index': 0,
'nodeRange': new ve.Range( 38, 39 ),
'nodeOuterRange': new ve.Range( 38, 39 ),
'parentOuterRange': new ve.Range( 37, 43 )
}
],
'msg': 'zero-length range in text node before inline node'
},
{
'range': new ve.Range( 41, 41 ),
'mode': 'leaves',
'expected': [
// preformatted/text
{
'node': [ 2, 2 ],
'range': new ve.Range( 41, 41 ),
'index': 2,
'nodeRange': new ve.Range( 41, 42 ),
'nodeOuterRange': new ve.Range( 41, 42 ),
'parentOuterRange': new ve.Range( 37, 43 )
}
],
'msg': 'zero-length range in text node after inline node'
},
{
'doc': 'emptyBranch',
'range': new ve.Range( 1 ),
'mode': 'leaves',
'expected': [
// table
{
'node': [ 0 ],
'range': new ve.Range( 1, 1 ),
'index': 0,
'indexInNode': 0,
'nodeRange': new ve.Range( 1, 1 ),
'nodeOuterRange': new ve.Range( 0, 2 ),
'parentOuterRange': new ve.Range( 0, 2 )
}
],
'msg': 'Zero-length range in empty branch node'
}
];