Skip to content

Commit

Permalink
Merge e07a770 into 9522e9f
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Mulich committed Aug 28, 2015
2 parents 9522e9f + e07a770 commit 7ffe68b
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 1 deletion.
8 changes: 7 additions & 1 deletion cnxpublishing/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,11 @@
ResourceFileExceededLimitError,
UserFetchError,
)
from .utils import parse_archive_uri, parse_user_uri
from .models import inject_mathml_svgs
from .publish import publish_model, republish_binders
from .utils import (
parse_archive_uri, parse_user_uri,
)


__all__ = (
Expand Down Expand Up @@ -582,6 +585,9 @@ def mark_invalid_reference(reference):
mark_invalid_reference(reference)
# else, it's a remote or cnx.org reference ...Do nothing.

# Generate SVGs for MathML
model.content = inject_mathml_svgs(model.content)

args = (psycopg2.Binary(model.content.encode('utf-8')),
publication_id, model.id,)
stmt = """\
Expand Down
69 changes: 69 additions & 0 deletions cnxpublishing/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-
# ###
# Copyright (c) 2015, Rice University
# This software is subject to the provisions of the GNU Affero General
# Public License version 3 (AGPLv3).
# See LICENCE.txt for details.
# ###
import requests
from lxml import etree
from pyramid.settings import asbool
from pyramid.threadlocal import get_current_registry


MATHML_NAMESPACE = "http://www.w3.org/1998/Math/MathML"
NSMAP = {'m': MATHML_NAMESPACE}


def _find_or_create_semantics_block(math_block):
"""Finds or creates the MathML semantics tag."""
try:
semantics_block = math_block.xpath(
'/m:samantics',
namespaces=NSMAP)[0]
except IndexError:
# Move all the contents of the math_block into a semantics wrapper.
children = []
for child in math_block.getchildren():
children.append(child)
math_block.remove(child) # why no pop?
semantics_block = etree.SubElement(
math_block,
'{{{}}}semantics'.format(MATHML_NAMESPACE))
for child in children:
semantics_block.append(child)
return semantics_block


def inject_mathml_svgs(content):
"""Inject MathML SVG annotations into HTML content."""
settings = get_current_registry().settings
is_enabled = asbool(settings.get('mathml2svg.enabled?', False))
url = settings.get('mathml2svg.url')

# Bailout when svg generation is disabled.
if not is_enabled:
return content

xml = etree.fromstring(content)
math_blocks = xml.xpath(
'//m:math[not(/m:annotation-xml[@encoding="image/svg+xml"])]',
namespaces=NSMAP)
for math_block in math_blocks:
# Submit the MathML block to the SVG generation service.
payload = {'MathML': etree.tostring(math_block)}
response = requests.post(url, data=payload)
# Inject the SVG into the MathML as an annotation
# only if the resposne was good, otherwise skip over it.
if response.status_code == 200:
semantics_wrapper = _find_or_create_semantics_block(math_block)
svg = response.text
content_type = response.headers['content-type']
# Insert the svg into the content
annotation = etree.SubElement(
semantics_wrapper,
'{{{}}}annotation-xml'.format(MATHML_NAMESPACE))
annotation.set('encoding', content_type)
annotation.append(etree.fromstring(svg))
modified_content = etree.tostring(xml)
return modified_content
54 changes: 54 additions & 0 deletions cnxpublishing/tests/test_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from cnxarchive import config as archive_config
from cnxarchive.database import initdb as archive_initdb
from cnxarchive.utils import join_ident_hash, split_ident_hash
from lxml import etree
from pyramid import testing

from . import use_cases
Expand Down Expand Up @@ -1137,6 +1138,59 @@ def test_add_pending_document_w_invalid_references(self, cursor):
self.assertEqual(len(state_messages), 2)
self.assertEqual(state_messages[-1], expected_state_message)

@db_connect
def test_add_pending_document_w_mathml(self, cursor):
"""Add a pending document with mathml that generates SVG."""
publication_id = self.make_publication()

# Insert a valid module for referencing...
cursor.execute("""\
INSERT INTO abstracts (abstract) VALUES ('abstract')
RETURNING abstractid""")
cursor.execute("""\
INSERT INTO modules
(module_ident, portal_type, name,
created, revised, abstractid, licenseid,
doctype, submitter, submitlog, stateid, parent, parentauthors,
language, authors, maintainers, licensors,
google_analytics, buylink)
VALUES
(1, 'Module', 'mathml module',
DEFAULT, DEFAULT, 1, 1,
0, 'admin', 'log', NULL, NULL, NULL,
'en', '{admin}', NULL, '{admin}',
DEFAULT, DEFAULT) RETURNING uuid || '@' || major_version""")
doc_ident_hash = cursor.fetchone()[0]

# Create and add a document for the publication.
metadata = {
'title': 'Document Title',
'summary': 'Document Summary',
'authors': [{u'id': u'able', u'type': u'cnx-id'}],
'publishers': [{'id': 'able', 'type': 'cnx-id'}],
'license_url': VALID_LICENSE_URL,
}
content = """\
<div class="equation">
<math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow> <mi>x</mi> <mo>=</mo> <mfrac> <mrow> <mo>&#8722;<!-- &#8722; --></mo> <mi>b</mi> <mo>&#177;<!-- &#177; --></mo> <msqrt> <msup> <mi>b</mi> <mn>2</mn> </msup> <mo>&#8722;<!-- &#8722; --></mo> <mn>4</mn> <mi>a</mi> <mi>c</mi> </msqrt> </mrow> <mrow> <mn>2</mn> <mi>a</mi> </mrow> </mfrac> </mrow></semantics></math>
</div>"""
document = self.make_document(content=content, metadata=metadata)

# Here we are testing the function of add_pending_document.
from ..db import add_pending_model, add_pending_model_content
document_ident_hash = add_pending_model(
cursor, publication_id, document)

# Enable the mathml2svg service for this test.
from pyramid.threadlocal import get_current_registry
get_current_registry().settings['mathml2svg.enabled?'] = 'on'

# Mock the inject_mathml_svgs function.
with mock.patch('cnxpublishing.db.inject_mathml_svgs') as func:
func.return_value = content
add_pending_model_content(cursor, publication_id, document)
self.assertEqual(func.call_count, 1)

@db_connect
def test_add_pending_binder_w_document_pointers(self, cursor):
"""Add a pending binder with document pointers."""
Expand Down
79 changes: 79 additions & 0 deletions cnxpublishing/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
# ###
# Copyright (c) 2015, Rice University
# This software is subject to the provisions of the GNU Affero General
# Public License version 3 (AGPLv3).
# See LICENCE.txt for details.
# ###
import unittest
try:
from unittest import mock
except ImportError:
import mock

from lxml import etree


class SVGInjectionUtilTestCase(unittest.TestCase):

def setUp(self):
# Mock the registry, since this is a utility test.
self.registry_patcher = mock.patch(
'cnxpublishing.models.get_current_registry')
self.mock_registry = self.registry_patcher.start()
self.mock_registry.return_value.settings = {
'mathml2svg.enabled?': 'on',
}

# Mock the external communication with the cnx-mathml2svg service.
self.requests_patcher = mock.patch(
'requests.post')
self.mocked_post = self.requests_patcher.start()
self.mocked_post.return_value.status_code = 200
self.mocked_post.return_value.headers = {
'content-type': 'image/svg+xml',
}
self.mocked_post.return_value.text = '<svg>mocked</svg>'

def tearDown(self):
self.registry_patcher.stop()
self.requests_patcher.stop()

@property
def target(self):
from ..models import inject_mathml_svgs
return inject_mathml_svgs

def test_mathml2svg(self):
content = """\
<div class="equation">
<math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow> <mi>x</mi> <mo>=</mo> <mfrac> <mrow> <mo>&#8722;<!-- &#8722; --></mo> <mi>b</mi> <mo>&#177;<!-- &#177; --></mo> <msqrt> <msup> <mi>b</mi> <mn>2</mn> </msup> <mo>&#8722;<!-- &#8722; --></mo> <mn>4</mn> <mi>a</mi> <mi>c</mi> </msqrt> </mrow> <mrow> <mn>2</mn> <mi>a</mi> </mrow> </mfrac> </mrow></semantics></math>
</div>"""

# Call the target function.
result = self.target(content)

elms = etree.fromstring(result)
annotation = elms.xpath(
'/div/m:math//m:annotation-xml[@encoding="image/svg+xml"]',
namespaces={'m': "http://www.w3.org/1998/Math/MathML"})[0]
expected = """<annotation-xml xmlns="http://www.w3.org/1998/Math/MathML" encoding="image/svg+xml"><svg>mocked</svg></annotation-xml>"""

self.assertEqual(etree.tostring(annotation), expected)

def test_missing_semantics_wrapper(self):
content = """\
<div class="equation">
<math xmlns="http://www.w3.org/1998/Math/MathML"><mrow> <mi>x</mi> <mo>=</mo> <mfrac> <mrow> <mo>&#8722;<!-- &#8722; --></mo> <mi>b</mi> <mo>&#177;<!-- &#177; --></mo> <msqrt> <msup> <mi>b</mi> <mn>2</mn> </msup> <mo>&#8722;<!-- &#8722; --></mo> <mn>4</mn> <mi>a</mi> <mi>c</mi> </msqrt> </mrow> <mrow> <mn>2</mn> <mi>a</mi> </mrow> </mfrac> </mrow></math>
</div>"""

# Call the target function.
result = self.target(content)

elms = etree.fromstring(result)
annotation = elms.xpath(
'/div/m:math/m:semantics/m:annotation-xml[@encoding="image/svg+xml"]',
namespaces={'m': "http://www.w3.org/1998/Math/MathML"})[0]
expected = """<annotation-xml xmlns="http://www.w3.org/1998/Math/MathML" encoding="image/svg+xml"><svg>mocked</svg></annotation-xml>"""

self.assertEqual(etree.tostring(annotation), expected)
5 changes: 5 additions & 0 deletions development.ini
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ db-connection-string = dbname=cnxarchive user=cnxarchive password=cnxarchive
# size limit of file uploads in MB
file-upload-limit = 50

# mathml2svg is disabled by default.
# To enable it use ``mathml2svg.enabled? = yes`` and set the url.
mathml2svg.url = http://localhost:5689
mathml2svg.enabled? = no

session_key = 'somkindaseekret'
# Application API keys with authentication information.
# This information is organized in the following form:
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
'pyramid>=1.5',
'pyramid_jinja2',
'pyramid_multiauth',
'requests',
)
tests_require = [
'webtest',
Expand Down
3 changes: 3 additions & 0 deletions testing.ini
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ openstax_accounts.login_path = /login
openstax_accounts.callback_path = /callback
openstax_accounts.logout_path = /logout

mathml2svg.url = http://localhost:5689
mathml2svg.enabled? = no

[server:main]
use = egg:waitress#main
host = 0.0.0.0
Expand Down

0 comments on commit 7ffe68b

Please sign in to comment.