Skip to content

Commit

Permalink
Merge pull request sandflow#47 from sandflow/issue-0008-metadata-call…
Browse files Browse the repository at this point in the history
…backs

Added support for metadata callbacks (issue sandflow#8)
  • Loading branch information
palemieux authored May 11, 2017
2 parents 43202d8 + 9a6b429 commit 142d2fc
Show file tree
Hide file tree
Showing 3 changed files with 303 additions and 112 deletions.
179 changes: 140 additions & 39 deletions src/main/js/doc.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,55 @@

;
(function (imscDoc, sax, imscNames, imscStyles, imscUtils) {



/**
* Allows a client to provide callbacks to handle children of the <metadata> element
* @typedef {Object} MetadataHandler
* @property {?OpenTagCallBack} onOpenTag
* @property {?CloseTagCallBack} onCloseTag
* @property {?TextCallBack} onText
*/

/**
* Called when the opening tag of an element node is encountered.
* @callback OpenTagCallBack
* @param {string} ns Namespace URI of the element
* @param {string} name Local name of the element
* @param {Object[]} attributes List of attributes, each consisting of a
* `uri`, `name` and `value`
*/

/**
* Called when the closing tag of an element node is encountered.
* @callback CloseTagCallBack
*/

/**
* Called when a text node is encountered.
* @callback TextCallBack
* @param {string} contents Contents of the text node
*/

/**
* Parses an IMSC1 document into an opaque in-memory representation that exposes
* a single method <pre>getMediaTimeEvents()</pre> that returns a list of time
* offsets (in seconds) of the ISD, i.e. the points in time where the visual
* representation of the document change.
* representation of the document change. `metadataHandler` allows the caller to
* be called back when nodes are present in <metadata> elements.
*
* @param {string} xmlstring XML document
* @param {?module:imscUtils.ErrorHandler} errorHandler Error callback
* @param {?MetadataHandler} metadataHandler Callback for <Metadata> elements
* @returns {Object} Opaque in-memory representation of an IMSC1 document
*/

imscDoc.fromXML = function (xmlstring, errorHandler) {
imscDoc.fromXML = function (xmlstring, errorHandler, metadataHandler) {
var p = sax.parser(true, {xmlns: true});
var estack = [];
var xmllangstack = [];
var xmlspacestack = [];
var metadata_depth = 0;
var doc = null;

p.onclosetag = function (node) {
Expand All @@ -58,7 +90,7 @@
for (var sid in estack[0].styles) {

mergeChainedStyles(estack[0], estack[0].styles[sid], errorHandler);

}

} else if (estack[0] instanceof P || estack[0] instanceof Span) {
Expand All @@ -74,7 +106,7 @@
for (c = 1; c < estack[0].contents.length; c++) {

if (estack[0].contents[c] instanceof Span && estack[0].contents[c].anon &&
cs[cs.length - 1] instanceof Span && cs[cs.length - 1].anon) {
cs[cs.length - 1] instanceof Span && cs[cs.length - 1].anon) {

cs[cs.length - 1].text += estack[0].contents[c].text;

Expand All @@ -93,16 +125,35 @@
// remove redundant nested anonymous spans (9.3.3(1)(c))

if (estack[0] instanceof Span &&
estack[0].contents.length === 1 &&
estack[0].contents[0] instanceof Span &&
estack[0].contents[0].anon &&
estack[0].text === null) {
estack[0].contents.length === 1 &&
estack[0].contents[0] instanceof Span &&
estack[0].contents[0].anon &&
estack[0].text === null) {

estack[0].text = estack[0].contents[0].text;
estack[0].contents = [];

}

} else if (estack[0] instanceof ForeignElement) {

if (estack[0].node.uri === imscNames.ns_tt &&
estack[0].node.local === 'metadata') {

/* leave the metadata element */

metadata_depth--;

} else if (metadata_depth > 0 &&
metadataHandler &&
'onCloseTag' in metadataHandler) {

/* end of child of metadata element */

metadataHandler.onCloseTag();

}

}

// TODO: delete stylerefs?
Expand All @@ -122,19 +173,29 @@

p.ontext = function (str) {

if (estack[0] === undefined ||
!(estack[0] instanceof Span || estack[0] instanceof P)) {
if (estack[0] === undefined) {

reportError("Ignoring text outside of <p> or <span> at (" + this.line + "," + this.column + ")");
/* ignoring text outside of elements */

return;
}
} else if (estack[0] instanceof Span || estack[0] instanceof P) {

/* create an anonymous span */

var s = Span.createAnonymousSpan(doc, estack[0], xmlspacestack[0], str, errorHandler);

// create an anonymous span
estack[0].contents.push(s);

var s = Span.createAnonymousSpan(doc, estack[0], xmlspacestack[0], str, errorHandler);
} else if (estack[0] instanceof ForeignElement &&
metadata_depth > 0 &&
metadataHandler &&
'onText' in metadataHandler) {

/* text node within a child of metadata element */

metadataHandler.onText(str);

}

estack[0].contents.push(s);
};


Expand Down Expand Up @@ -418,11 +479,11 @@
} else if (node.local === 'set') {

if (!(estack[0] instanceof Span ||
estack[0] instanceof P ||
estack[0] instanceof Div ||
estack[0] instanceof Body ||
estack[0] instanceof Region ||
estack[0] instanceof Br)) {
estack[0] instanceof P ||
estack[0] instanceof Div ||
estack[0] instanceof Body ||
estack[0] instanceof Region ||
estack[0] instanceof Br)) {

reportFatal(errorHandler, "Parent of <set> element is not a content element or a region at " + this.line + "," + this.column + ")");

Expand All @@ -440,16 +501,52 @@

} else {

// ignore other elements in the TTML namespace, e.g. metadata
/* element in the TT namespace, but not a content element */

estack.unshift(node);
estack.unshift(new ForeignElement(node));
}

} else {

// ignore elements not in the TTML namespace
/* ignore elements not in the TTML namespace unless in metadata element */

estack.unshift(new ForeignElement(node));

}

/* handle metadata callbacks */

if (estack[0] instanceof ForeignElement) {

if (node.uri === imscNames.ns_tt &&
node.local === 'metadata') {

/* enter the metadata element */

metadata_depth++;

} else if (
metadata_depth > 0 &&
metadataHandler &&
'onOpenTag' in metadataHandler
) {

estack.unshift(node);
/* start of child of metadata element */

var attrs = [];

for (var a in node.attributes) {
attrs[node.attributes[a].uri + " " + node.attributes[a].local] =
{
uri: node.attributes[a].uri,
local: node.attributes[a].local,
value: node.attributes[a].value
};
}

metadataHandler.onOpenTag(node.uri, node.local, attrs);

}

}

Expand Down Expand Up @@ -499,6 +596,10 @@
return doc;
};

function ForeignElement(node) {
this.node = node;
}

function TT() {
this.events = [];
this.head = null;
Expand Down Expand Up @@ -672,7 +773,7 @@
this.styleAttrs = elementGetStyles(node, errorHandler);

if (doc.head !== null && doc.head.styling !== null) {
mergeReferencedStyles(doc.head.styling, elementGetStyleRefs(node), this.styleAttrs, errorHandler);
mergeReferencedStyles(doc.head.styling, elementGetStyleRefs(node), this.styleAttrs, errorHandler);
}

this.contents = [];
Expand Down Expand Up @@ -798,7 +899,7 @@
this.end = t.end;

this.styleAttrs = elementGetStyles(node, errorHandler);

this.sets = [];

/* immediately merge referenced styles */
Expand Down Expand Up @@ -909,7 +1010,7 @@
s[qname] = val;

/* TODO: consider refactoring errorHandler into parse and compute routines */

if (sa === imscStyles.byName.zIndex) {
reportWarning(errorHandler, "zIndex attribute present but not used by IMSC1 since regions do not overlap");
}
Expand All @@ -933,7 +1034,7 @@
for (var i in node.attributes) {

if (node.attributes[i].uri === ns &&
node.attributes[i].local === name) {
node.attributes[i].local === name) {

return node.attributes[i].value;
}
Expand Down Expand Up @@ -1184,8 +1285,8 @@
} else if ((m = CLOCK_TIME_FRACTION_RE.exec(str)) !== null) {

r = parseInt(m[1]) * 3600 +
parseInt(m[2]) * 60 +
parseFloat(m[3]);
parseInt(m[2]) * 60 +
parseFloat(m[3]);

} else if ((m = CLOCK_TIME_FRAMES_RE.exec(str)) !== null) {

Expand All @@ -1194,9 +1295,9 @@
if (effectiveFrameRate !== null) {

r = parseInt(m[1]) * 3600 +
parseInt(m[2]) * 60 +
parseInt(m[3]) +
(m[4] === null ? 0 : parseInt(m[4]) / effectiveFrameRate);
parseInt(m[2]) * 60 +
parseInt(m[3]) +
(m[4] === null ? 0 : parseInt(m[4]) / effectiveFrameRate);
}

}
Expand Down Expand Up @@ -1449,7 +1550,7 @@


})(typeof exports === 'undefined' ? this.imscDoc = {} : exports,
typeof sax === 'undefined' ? require("sax") : sax,
typeof imscNames === 'undefined' ? require("./names") : imscNames,
typeof imscStyles === 'undefined' ? require("./styles") : imscStyles,
typeof imscUtils === 'undefined' ? require("./utils") : imscUtils);
typeof sax === 'undefined' ? require("sax") : sax,
typeof imscNames === 'undefined' ? require("./names") : imscNames,
typeof imscStyles === 'undefined' ? require("./styles") : imscStyles,
typeof imscUtils === 'undefined' ? require("./utils") : imscUtils);
24 changes: 24 additions & 0 deletions src/test/resources/unit-tests/metadataHandler.ttml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<tt:tt xmlns:tt="http://www.w3.org/ns/ttml" xmlns:ttp="http://www.w3.org/ns/ttml#parameter" xmlns="http://www.w3.org/ns/ttml" xmlns:tts="http://www.w3.org/ns/ttml#styling" xmlns:ttm="http://www.w3.org/ns/ttml#metadata" ttp:profile="http://www.w3.org/ns/ttml/profile/imsc1/text" xmlns:ebuttm="urn:ebu:metadata" xmlns:smpte="http://www.smpte-ra.org/schemas/2052-1/2010/smpte-tt">
<head>
<metadata>
<ttm:title>Metadata Handler Test</ttm:title>
<ebuttm:documentMetadata>
<ebuttm:conformsToStandard>urn:ebu:distribution:2014-01</ebuttm:conformsToStandard>
<ebuttm:conformsToStandard>http://www.w3.org/ns/ttml/profile/imsc1/text</ebuttm:conformsToStandard>
</ebuttm:documentMetadata>
<smpte:image imagetype="PNG" encoding="Base64" xml:id="img_1">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
</smpte:image>
</metadata>

<layout>
<tt:region tts:extent="100% 100%" xml:id="r1"/>
</layout>
</head>
<body>
<metadata>
<ttm:desc>Metadata at the body</ttm:desc>
</metadata>
<div begin="00:00:03.200" end="00:00:06.937" tts:extent="57% 6%" tts:origin="21% 75%" smpte:backgroundImage="#img_1" />
</body>
</tt:tt>
Loading

0 comments on commit 142d2fc

Please sign in to comment.