[LyX/master] Fix bug #13069.

Richard Kimberly Heck rikiheck at lyx.org
Sun Jun 23 16:37:40 UTC 2024


commit fafe3ea5d7be1c06fb734e3bc621c551d617a6f7
Author: Richard Kimberly Heck <rikiheck at lyx.org>
Date:   Sun Jun 23 12:37:25 2024 -0400

    Fix bug #13069.
    
    Use XML parser to handle <mtext> issues.
    
    Some of the config/qt.m4 code by JMarc.
---
 config/qt.m4                | 14 ++++---
 src/mathed/InsetMathBox.cpp | 95 +++++++++++++++++++++++----------------------
 2 files changed, 56 insertions(+), 53 deletions(-)

diff --git a/config/qt.m4 b/config/qt.m4
index 6a455f9134..607fc6455e 100644
--- a/config/qt.m4
+++ b/config/qt.m4
@@ -28,10 +28,10 @@ AC_DEFUN([QT_CHECK_COMPILE],
 		qt_guilibs="'-lQtCore -lQtGui' '-lQtCore4 -lQtGui4'"
 		if test $USE_QT6 = "yes" ; then
 		    qt_corelibs="-lQt6Core"
-		    qt_guilibs="-lQt6Core -lQt6Concurrent -lQt6Gui -lQt6Svg -lQt6Widgets"
+		    qt_guilibs="-lQt6Core -lQt6Concurrent -lQt6Gui -lQt6Svg -lQt6Xml -lQt6Widgets"
 		else
 		    qt_corelibs="-lQt5Core"
-		    qt_guilibs="-lQt5Core -lQt5Concurrent -lQt5Gui -lQt5Svg -lQt5Widgets"
+		    qt_guilibs="-lQt5Core -lQt5Concurrent -lQt5Gui -lQt5Svg -lQt5Xml -lQt5Widgets"
 		fi
 		for libname in $qt_corelibs '-framework QtCore'
 		do
@@ -43,8 +43,8 @@ AC_DEFUN([QT_CHECK_COMPILE],
 		done
 		qt_cv_libname=
 		for libname in $qt_guilibs \
-		               '-framework QtCore -framework QtConcurrent -framework QtSvg -framework QtWidgets -framework QtMacExtras -framework QtGui'\
-		               '-framework QtCore -framework QtConcurrent -framework QtSvg -framework QtSvgWidgets -framework QtWidgets -framework QtGui'\
+		               '-framework QtCore -framework QtConcurrent -framework QtSvg -framework QtXml -framework QtWidgets -framework QtMacExtras -framework QtGui'\
+		               '-framework QtCore -framework QtConcurrent -framework QtSvg -framework QtSvgWidgets -framework QtXml -framework QtWidgets -framework QtGui'\
 		               '-framework QtCore -framework QtGui'
 		do
 			QT_TRY_LINK($libname)
@@ -264,7 +264,7 @@ AC_DEFUN([QT_DO_PKG_CONFIG],
 	  export PKG_CONFIG_PATH
 	fi
 	qt_corelibs="Qt5Core"
-	qt_guilibs="Qt5Core Qt5Concurrent Qt5Gui Qt5Svg Qt5Widgets"
+	qt_guilibs="Qt5Core Qt5Concurrent Qt5Gui Qt5Svg Qt5Widgets Qt5Xml"
 	lyx_use_x11extras=false
 	PKG_CHECK_EXISTS(Qt5X11Extras, [lyx_use_x11extras=true], [])
 	if $lyx_use_x11extras; then
@@ -339,7 +339,7 @@ AC_DEFUN([QT_DO_MANUAL_CONFIG],
 	QT_CORE_LDFLAGS=
 	if test -n "$qt_cv_includes"; then
 		QT_INCLUDES="-I$qt_cv_includes"
-		for i in Qt QtCore QtGui QtWidgets QtSvg QtConcurrent QtSvgWidgets QtMacExtras; do
+		for i in Qt QtCore QtGui QtWidgets QtSvg QtConcurrent QtSvgWidgets QtXml QtMacExtras; do
 			QT_INCLUDES="$QT_INCLUDES -I$qt_cv_includes/$i"
 			if test "$lyx_use_packaging" = "macosx" ; then
 				QT_INCLUDES="$QT_INCLUDES -I$qt_cv_libraries/${i}.framework/Headers"
@@ -435,6 +435,8 @@ qtHaveModule(concurrent)	{QT += concurrent} else {MISSING += concurrent}
 qtHaveModule(gui)		{QT += gui} else {MISSING += gui}
 qtHaveModule(gui-private)	{QT += gui-private} else {MISSING += gui-private}
 qtHaveModule(svg)		{QT += svg} else {MISSING += svg}
+qtHaveModule(svgwidgets)	{QT += svgwidgets} else {MISSING += svgwidgets}
+qtHaveModule(xml)       	{QT += xml} else {MISSING += xml}
 qtHaveModule(widgets)		{QT += widgets} else {MISSING += widgets}
 EOF2
 	        if test "$qt_major" = 6; then
diff --git a/src/mathed/InsetMathBox.cpp b/src/mathed/InsetMathBox.cpp
index 6feddf9cdd..09a0a0f351 100644
--- a/src/mathed/InsetMathBox.cpp
+++ b/src/mathed/InsetMathBox.cpp
@@ -28,6 +28,10 @@
 #include <iostream>
 #include <ostream>
 
+#include <QtXml/QDomDocument>
+#include <QtXml/QDomElement>
+#include "support/qstring_helpers.h"
+
 using namespace lyx::support;
 
 namespace lyx {
@@ -62,6 +66,10 @@ namespace {
 void splitAndWrapInMText(MathMLStream & ms, MathData const & cell,
 						 const std::string & attributes)
 {
+        // The goal of this function is to take an XML fragment and wrap
+        // anything that is outside of any tag in <mtext></mtext> tags,
+        // then wrap the whole thing in an <mrow></mrow> tag with attributes
+
 	// First, generate the inset into a string of its own.
 	docstring inset_contents;
 	{
@@ -74,53 +82,46 @@ void splitAndWrapInMText(MathMLStream & ms, MathData const & cell,
 		inset_contents = ostmp.str();
 	}
 
-	// No tags are allowed within <m:mtext>: split the string if there are tags.
-	std::vector<docstring> parts;
-	while (true) {
-		std::size_t angle_pos = inset_contents.find('<');
-		if (angle_pos == docstring::npos)
-			break;
-
-		// String structure:
-		// - prefix: pure text, no tag
-		// - tag to split: something like <m:mn>1</m:mn> or more complicated
-		//   (like nested tags), with or without name space
-		// - rest to be taken care of in the next iteration
-
-		// Push the part before the tag.
-		parts.emplace_back(inset_contents.substr(0, angle_pos));
-		inset_contents = inset_contents.substr(angle_pos);
-		// Now, inset_contents starts with the tag to isolate, so that
-		//     inset_contents[0] == '<'
-
-		// Push the tag, up to its end. Process: find the tag name (either
-		// before > or the first attribute of the tag), then the matching end
-		// tag, then proceed with pushing.
-		const std::size_t tag_name_end =
-				std::min(inset_contents.find(' ', 1), inset_contents.find('>', 1));
-		const std::size_t tag_name_length = tag_name_end - 1;
-		const docstring tag_name = inset_contents.substr(1, tag_name_length);
-
-		const std::size_t end_tag_start =
-				inset_contents.find(tag_name, tag_name_end + 1);
-		const std::size_t end_tag = inset_contents.find('>', end_tag_start);
-
-		parts.emplace_back(inset_contents.substr(0, end_tag + 1));
-		inset_contents = inset_contents.substr(end_tag + 1);
-	}
-	parts.emplace_back(inset_contents);
-
-	// Finally, output the complete inset: escape the test in <m:mtext>, leave
-	// the other tags untouched.
-	ms << MTag("mrow", attributes);
-	for (std::size_t i = 0; i < parts.size(); i += 2) {
-		ms << MTag("mtext")
-		   << parts[i]
-		   << ETag("mtext");
-		if (parts.size() > i + 1)
-			ms << parts[i + 1];
-	}
-	ms << ETag("mrow");
+        // We use the QT XML library. It's easier if we deal with an XML "document"
+        // rather than "fragment", so we wrap it in a fake root node (which we will
+        // remove at the end).
+        docstring inset_contents_xml = "<root>" + inset_contents + "</root>";
+
+        // Parse the XML into a DOM
+        QDomDocument xml;
+        xml.setContent(toqstr(inset_contents_xml));
+
+        QDomElement docElem = xml.documentElement();
+
+        // Iterate through the children of our fake root element.
+        QDomNode n = docElem.firstChild();
+        while (!n.isNull()) {
+			// try to convert the child into a text element
+			// (i.e. some text that is outside of an XML tag)
+            QDomText text = n.toText();
+            if (!text.isNull()) {
+				// if the result is not null, then the child was indeed
+				// bare text, so we need to wrap it in mtext tags
+				// make an mtext element
+                QDomElement wrapper = xml.createElement(toqstr(ms.namespacedTag("mtext")));
+				// put the mtext element in the document right before the text
+                docElem.insertBefore(wrapper,n);
+                // move the text node inside the mtext element (this has the side
+                // effect of removing it from where it was as a child of the root)
+				wrapper.appendChild(n);
+                n = wrapper.nextSibling();
+            } else {
+				// if the text is null, then we have something besides a text
+				// element (i.e. a tag), so we don't need to do anything and just
+				// move onto the next child
+                n = n.nextSibling();
+            }
+        }
+
+        ms << MTag("mrow", attributes);
+        docstring interior = qstring_to_ucs4(xml.toString(-1));
+        ms << interior.substr(6,interior.length()-13); // chop off the initial <root> and final </root> tags
+        ms << ETag("mrow");
 }
 }
 


More information about the lyx-cvs mailing list