Skip to content

Commit

Permalink
Merge pull request #563 from substance/jats-converter
Browse files Browse the repository at this point in the history
Adaptations evolved working on JATS converter
  • Loading branch information
Michael Aufreiter committed May 13, 2016
2 parents 1fcb3cc + 6c71f15 commit 5a612e0
Show file tree
Hide file tree
Showing 18 changed files with 231 additions and 52 deletions.
10 changes: 8 additions & 2 deletions model/DOMExporter.js
Expand Up @@ -2,6 +2,7 @@

var oo = require('../util/oo');
var extend = require('lodash/extend');
var isString = require('lodash/isString');
var Fragmenter = require('./Fragmenter');
var Registry = require('../util/Registry');
var encodeXMLEntities = require('../util/encodeXMLEntities');
Expand Down Expand Up @@ -78,8 +79,13 @@ DOMExporter.Prototype = function() {
};

this.convertNode = function(node) {
// always make sure that we have the doc in our state
this.state.doc = node.getDocument();
if (isString(node)) {
// Assuming this.state.doc has been set by convertDocument
node = this.state.doc.get(node);
} else {
this.state.doc = node.getDocument();
}

var converter = this.getNodeConverter(node);
if (!converter) {
converter = this.getDefaultBlockConverter();
Expand Down
131 changes: 99 additions & 32 deletions model/DOMImporter.js
Expand Up @@ -2,9 +2,12 @@

var oo = require('../util/oo');
var last = require('lodash/last');
var each = require('lodash/each');
var clone = require('lodash/clone');
var extend = require('lodash/extend');
var uuid = require('../util/uuid');
var ArrayIterator = require('../util/ArrayIterator');
var InlineWrapperConverter = require('./InlineWrapperConverter');

/**
A generic base implementation for XML/HTML importers.
Expand Down Expand Up @@ -50,6 +53,7 @@ function DOMImporter(config) {
}
var NodeClass = schema.getNodeClass(converter.type);
if (!NodeClass) {
console.error('No node type defined for converter', converter.type);
return;
}
if (defaultTextType === converter.type) {
Expand All @@ -66,33 +70,13 @@ function DOMImporter(config) {

}.bind(this));

this._initState();
this.state = new DOMImporter.State();
}

DOMImporter.Prototype = function DOMImporterPrototype() {

this._initState = function() {
var state = {
preserveWhitespace: false
};
this.state = state;
this.reset();
return state;
};

this.reset = function() {
this.state = extend(this.state, {
nodes: [],
inlineNodes: [],
containerId: null,
container: [],
ids: {},
// stack of contexts to handle reentrant calls
stack: [],
lastChar: "",
skipTypes: {},
ignoreAnnotations: false,
});
this.state.reset();
};

this.createDocument = function() {
Expand Down Expand Up @@ -144,7 +128,9 @@ DOMImporter.Prototype = function DOMImporterPrototype() {
var node;
if (blockTypeConverter) {
node = this._nodeData(el, blockTypeConverter.type);
state.pushElementContext(el.tagName);
node = blockTypeConverter.import(el, node, this) || node;
state.popElementContext();
this._createAndShow(node);
} else {
if (el.isCommentNode()) {
Expand Down Expand Up @@ -182,15 +168,18 @@ DOMImporter.Prototype = function DOMImporterPrototype() {
@returns {object} the created node as JSON
*/
this.convertElement = function(el) {
return this._convertElement(el);
var node = this._convertElement(el);
return node;
};

this._convertElement = function(el, mode) {
var node;
var converter = this._getConverterForElement(el, mode);
if (converter) {
node = this._nodeData(el, converter.type);
this.state.pushElementContext(el.tagName);
node = converter.import(el, node, this) || node;
this.state.popElementContext();
this.createNode(node);
} else {
throw new Error('No converter found for '+el.tagName);
Expand All @@ -204,6 +193,7 @@ DOMImporter.Prototype = function DOMImporterPrototype() {
}
this.state.ids[node.id] = true;
this.state.nodes.push(node);
return node;
};

this.show = function(node) {
Expand All @@ -216,10 +206,20 @@ DOMImporter.Prototype = function DOMImporterPrototype() {
};

this._nodeData = function(el, type) {
return {
var nodeData = {
type: type,
id: this.getIdForElement(el, type)
};
var NodeClass = this.schema.getNodeClass(type);
each(NodeClass.static.schema, function(prop, name) {
// check integrity of provided props, such as type correctness,
// and mandatory properties
var hasDefault = prop.hasOwnProperty('default');
if (hasDefault) {
nodeData[name] = clone(prop.default);
}
}.bind(this));
return nodeData;
};

// /**
Expand Down Expand Up @@ -253,13 +253,13 @@ DOMImporter.Prototype = function DOMImporterPrototype() {
this.annotatedText = function(el, path, options) {
var state = this.state;
if (path) {
if (state.stack.length>0) {
throw new Error('Contract: it is not allowed to bind a new call annotatedText to a path while the previous has not been completed.', el.outerHTML);
}
// if (state.stack.length>0) {
// throw new Error('Contract: it is not allowed to bind a new call annotatedText to a path while the previous has not been completed.', el.outerHTML);
// }
if (options && options.preserveWhitespace) {
state.preserveWhitespace = true;
}
state.stack = [{ path: path, offset: 0, text: ""}];
state.stack.push({ path: path, offset: 0, text: ""});
} else {
if (state.stack.length===0) {
throw new Error("Contract: DOMImporter.annotatedText() requires 'path' for non-reentrant call.", el.outerHTML);
Expand All @@ -269,9 +269,11 @@ DOMImporter.Prototype = function DOMImporterPrototype() {
// annotated text property. This feature is mainly used to eat up
// whitespace in XML/HTML at tag boundaries, produced by pretty-printed XML/HTML.
this.state.lastChar = '';

var text;
var iterator = el.getChildNodeIterator();
var text = this._annotatedText(iterator);
this.state.pushElementContext(el.tagName);
text = this._annotatedText(iterator);
this.state.popElementContext();
if (path) {
state.stack.pop();
state.preserveWhitespace = false;
Expand Down Expand Up @@ -347,7 +349,9 @@ DOMImporter.Prototype = function DOMImporterPrototype() {
throw new Error('Could not find converter for default type ', defaultTextType);
}
var node = this._nodeData(el, defaultTextType);
this.state.pushElementContext(el.tagName);
node = defaultConverter.import(el, node, converter) || node;
this.state.popElementContext();
return node;
};

Expand Down Expand Up @@ -398,7 +402,9 @@ DOMImporter.Prototype = function DOMImporterPrototype() {
if (inlineTypeConverter.import) {
// push a new context so we can deal with reentrant calls
state.stack.push({ path: context.path, offset: startOffset, text: ""});
state.pushElementContext(el.tagName);
inlineNode = inlineTypeConverter.import(el, inlineNode, this) || inlineNode;
state.popElementContext();

var NodeClass = this.schema.getNodeClass(inlineType);
// inline nodes are attached to an invisible character
Expand Down Expand Up @@ -444,11 +450,34 @@ DOMImporter.Prototype = function DOMImporterPrototype() {
} else {
converters = this._allConverters;
}
var converter = null;
for (var i = 0; i < converters.length; i++) {
if (converters[i].matchElement(el)) {
return converters[i];
if (this._converterCanBeApplied(converters[i], el)) {
converter = converters[i];
break;
}
}
// there are some block nodes which are used as inline nodes as well.
// In this case we wrap the block node into an InlineWrapper
if (!converter && mode === 'inline') {
var blockConverter = this._getConverterForElement(el, 'block');
if (blockConverter) {
converter = InlineWrapperConverter;
}
}
if (!converter) {
converter = this._getUnsupportedNodeConverter();
}
return converter;
};

this._getUnsupportedNodeConverter = function() {
console.warn('DOMImporter._getUnsupportedNodeConverter() is abstract.' +
'\nIf you want to add unsupported elements to your model you should override this method.');
};

this._converterCanBeApplied = function(converter, el) {
return converter.matchElement(el);
};

this._createElement = function(tagName) {
Expand Down Expand Up @@ -579,4 +608,42 @@ DOMImporter.Prototype = function DOMImporterPrototype() {
};
oo.initClass(DOMImporter);

DOMImporter.State = function() {
this.reset();
};

DOMImporter.State.Prototype = function() {

this.reset = function() {
this.preserveWhitespace = false;
this.nodes = [];
this.inlineNodes = [];
this.containerId = null;
this.container = [];
this.ids = {};
// stack for reentrant calls into _convertElement()
this.contexts = [];
// stack for reentrant calls into _annotatedText()
this.stack = [];
this.lastChar = "";
this.skipTypes = {};
this.ignoreAnnotations = false;
};

this.pushElementContext = function(tagName) {
this.contexts.push({ tagName: tagName });
};

this.popElementContext = function() {
return this.contexts.pop();
};

this.getCurrentElementContext = function() {
return last(this.contexts);
};

};

oo.initClass(DOMImporter.State);

module.exports = DOMImporter;
3 changes: 2 additions & 1 deletion model/DocumentSchema.js
Expand Up @@ -5,6 +5,7 @@ var DocumentNode = require('./DocumentNode');
var Container = require('./Container');
var PropertyAnnotation = require('./PropertyAnnotation');
var ContainerAnnotation = require('./ContainerAnnotation');
var InlineWrapper = require('./InlineWrapper');

/**
Used to define custom article formats. Predefined node types can be combined with custom ones.
Expand Down Expand Up @@ -60,7 +61,7 @@ DocumentSchema.Prototype = function() {
};

this.getBuiltIns = function() {
return [DocumentNode, PropertyAnnotation, Container, ContainerAnnotation];
return [DocumentNode, PropertyAnnotation, Container, ContainerAnnotation, InlineWrapper];
};

};
Expand Down
17 changes: 17 additions & 0 deletions model/InlineWrapper.js
@@ -0,0 +1,17 @@
'use strict';

var InlineNode = require('./InlineNode');

function InlineWrapper() {
InlineWrapper.super.apply(this, arguments);
}

InlineNode.extend(InlineWrapper);

InlineWrapper.static.name = 'inline-wrapper';

InlineWrapper.static.defineSchema({
wrappedNode: 'id'
});

module.exports = InlineWrapper;
16 changes: 16 additions & 0 deletions model/InlineWrapperConverter.js
@@ -0,0 +1,16 @@
'use strict';

module.exports = {
type: 'inline-wrapper',
import: function(el, node, converter) {
// HACK monkey patching the context
node.id = converter.nextId('inline-wrapper');
var state = converter.state;
state.popElementContext();
state.pushElementContext(state.getCurrentElementContext().tagName);
node.wrappedNode = converter.convertElement(el).id;
},
export: function(node, el, converter) {
return converter.convertNode(node.wrappedNode);
}
};
2 changes: 1 addition & 1 deletion model/XMLExporter.js
Expand Up @@ -96,7 +96,7 @@ XMLExporter.Prototype = function() {
if (name === 'id' || name === 'type') {
return;
}
var prop = this.$$(name);
var prop = converter.$$(name);
if (node.getPropertyType(name) === 'string') {
prop.append(converter.annotatedText([node.id, name]));
} else {
Expand Down
1 change: 0 additions & 1 deletion packages/_all.scss
Expand Up @@ -8,7 +8,6 @@
@import './link/_link';
@import './embed/_embed';
@import './image/_image';
@import './figure/_figure';
@import './table/_table';
@import './emphasis/_emphasis';
@import './strong/_strong';
Expand Down
3 changes: 3 additions & 0 deletions styles/components/_text-property.scss
@@ -1,4 +1,7 @@
.sc-text-property {

white-space: pre-wrap;

.se-cursor {
position: relative;

Expand Down
14 changes: 12 additions & 2 deletions ui/AnnotatedTextComponent.js
@@ -1,8 +1,9 @@
'use strict';

var Component = require('./Component');
var Fragmenter = require('../model/Fragmenter');
var Component = require('./Component');
var AnnotationComponent = require('./AnnotationComponent');
var InlineWrapperComponent = require('./InlineWrapperComponent');

/**
Renders an anotated text. Used internally by {@link ui/TextPropertyComponent}.
Expand Down Expand Up @@ -73,7 +74,16 @@ AnnotatedTextComponent.Prototype = function() {
.addClass(node.anno.getTypeNames().join(' ').replace(/_/g, "-"))
.addClass(node.isStart?"start-anchor":"end-anchor");
}
var ComponentClass = componentRegistry.get(node.type) || AnnotationComponent;
var ComponentClass;
if (node.type === "inline-wrapper") {
ComponentClass = InlineWrapperComponent;
} else {
ComponentClass = componentRegistry.get(node.type);
if (!ComponentClass) {
console.warn('No component registered for type %s. Using AnnotationComponent.', node.type);
ComponentClass = AnnotationComponent;
}
}
var el = $$(ComponentClass, { doc: doc, node: node });
return el;
};
Expand Down

0 comments on commit 5a612e0

Please sign in to comment.