Этот вопрос является продолжением: Какие существуют методы преобразования текстовых узлов RTF в XML с использованием XSLT 2 / Saxon HE 11.3?.
После реализации решения с ответом я запустил код для большого набора данных. Во время обработки всех этих данных элемент в исходном RTF вызвал ошибку приложения.
Ошибка:
Error on line 11 column 92 of urn:from-string: SXXP0003 Error reported by XML parser: The element type "a" must be terminated by the matching end-tag "</a>".: The element type "a" must be terminated by the matching end-tag "</a>".
Я взглянул на исходный xml, который содержал несколько кодов RTF HYPERLINK. Источник:
<SPECORMETHOD>{\rtf1\ansi\deff0\uc1\ansicpg1252\deftab720{\fonttbl{\f0\fnil\fcharset1 Arial;}{\f1\fnil\fcharset1 Times New Roman;}{\f2\fnil\fcharset1 WingDings;}}{\colortbl\red0\green0\blue0;\red255\green0\blue0;\red0\green128\blue0;\red0\green0\blue255;\red255\green255\blue0;\red255\green0\blue255;\red128\green0\blue128;\red128\green0\blue0;\red0\green255\blue0;\red0\green255\blue255;\red0\green128\blue128;\red0\green0\blue128;\red255\green255\blue255;\red192\green192\blue192;\red128\green128\blue128;\red0\green0\blue0;\red128\green128\blue0;}\wpprheadfoot1\paperw12240\paperh15840\margl720\margr720\margt720\margb720\headery720\footery720\endnhere\sectdefaultcl{\*\generator WPTools_5.17;}{\stylesheet{\s1\li0\fi0\ri0\sb0\sa0\ql\vertalt\fs20 Normal;}{\s2\li0\fi0\ri0\sb0\sa0\ql\vertalt\fs20 Default Paragraph Font;}{\s3\li0\fi0\ri0\sb0\sa0\ql\vertalt\fs20\cf3\ul\sbasedon2 Hyperlink;}}{\pard\plain\plain\f1\fs36\par\pard\plain\plain\f1\fs36\par\plain\f1\fs28\tab 10\'94Flour Tortilla\par\plain\f1\fs28\tab Caesar \f1\b\i DIP\f1\i0 : {\field{\*\fldinst{HYPERLINK "..\\\\..\\\\SAUCES\\\\Dips\\\\Dip, Caesar.doc"}}{\*\fldtitle{..\\\\..\\\\SAUCES\\\\Dips\\\\Dip, Caesar.doc}}{\fldrslt{\f1\cf3\cs103\ul\cs3 Dip, Caesar.doc\plain\f1\fs28\b}}}\par\plain\f1\fs28\tab Ripped Romaine\par\plain\f1\fs28\tab Blackened Salmon julienne\par\plain\f1\fs28\tab Shaved Red Onion\par\plain\f1\fs28\tab Julienne Tomato\par\plain\f1\fs28\tab Grated Parmesan\par\plain\f1\fs28\tab Blackening spice: {\field{\*\fldinst{HYPERLINK "..\\\\..\\\\SPICE\\\\Blackening Spice.doc"}}{\*\fldtitle{..\\\\..\\\\SPICE\\\\Blackening Spice.doc}}{\fldrslt{\f1\cf3\cs103\ul\cs3 Blackening Spice.doc\plain\f1\fs28}}}\par\pard\plain\plain\f1\fs28\par\plain\f1\fs28 Method\par\plain\f1\fs28 Procedure Text \par\pard\plain\plain\f1\fs36\par}}</SPECORMETHOD>
Для моих целей URL-адрес не будет функциональным компонентом, но ради полезности этого проекта преобразования RTF, что может потребоваться для правильной работы кодов гиперссылок или для вывода их в виде текста для справки? Один из способов справиться с этим — в XSLT перехватить элемент, найти код ГИПЕРССЫЛКИ и заменить его обычным текстом.
Желаемый вывод для гиперссылки из этого примера будет (только текст):
CAESAR DIP: ..\..\SAUCES\Dips\Dip, Caesar.doc
Единственная модификация исходного кода заключалась в том, что в XSLT выполнялась проверка на наличие пустого элемента при обработке <SPECORMETHOD>
.
<xsl:choose>
<xsl:when test = "string-length(SPECORMETHOD) > 0">
<rtf-as-xhtml>
<xsl:sequence select = "tika:parse-rtf(SPECORMETHOD[string-length(.) > 0])"/>
</rtf-as-xhtml>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select = "'[EMPTY]'"/>
</xsl:otherwise>
</xsl:choose>
Я построил этот проект в Eclipse 2022-12 (4.26.0). Это проект Maven с использованием Apache Tika 2.7.0 и Saxon HE 11.3 с использованием Java SE 1.8. Особая благодарность Мартину Х.
Я прогнал ваш образец rtf через Tika, и предполагаемый вывод XHTML, к сожалению, не имеет правильного формата:
<html xmlns = "http://www.w3.org/1999/xhtml">
<head>
<meta name = "X-TIKA:Parsed-By" content = "org.apache.tika.parser.DefaultParser" />
<meta name = "X-TIKA:Parsed-By" content = "org.apache.tika.parser.microsoft.rtf.RTFParser" />
<meta name = "Content-Type" content = "application/rtf" />
<title></title>
</head>
<body><p />
<p />
<p> 10”Flour Tortilla</p>
<p> Caesar <b><i>DIP</i>: <a href = "..\\..\\SAUCES\\Dips\\Dip, Caesar.doc">Dip, Caesar.doc</b><b /></b></p>
<p><b /> Ripped Romaine</p>
<p> Blackened Salmon julienne</p>
<p> Shaved Red Onion</p>
<p> Julienne Tomato</p>
<p> Grated Parmesan</p>
<p> Blackening spice: <a href = "..\\..\\SPICE\\Blackening Spice.doc">Blackening Spice.doc</a></p>
<p />
<p>Method</p>
<p>Procedure Text </p>
<p />
<p />
</body></html>
Значит ошибка во фрагменте <p> Caesar <b><i>DIP</i>: <a href = "..\\..\\SAUCES\\Dips\\Dip, Caesar.doc">Dip, Caesar.doc</b><b /></b></p>
.
Я не знаю точно, проблема ли это в том, что ввод каким-то образом не является правильным rtf, но это больше похоже на ошибку в анализаторе Tia и ToXmlContentHandler.
Я поднял потенциальную проблему https://issues.apache.org/jira/browse/TIKA-3972
В конце концов, с помощью ребят из Saxonica (спасибо Майклу Кею и Норму Уолшу) я нашел лучший (вероятно, во всяком случае) подход к использованию Saxon с парсером Tika; вместо использования ToXMLContentHandler() Tika и результата его метода toString()
, переданного в Saxon DocumentBuilder
, можно передать Saxon BuildingContentHandler
парсеру Tika напрямую, чтобы получить XdmNode
:
public static XdmNode parseRtfToHTML2(String rtf, Processor processor) throws IOException, SAXException, TikaException, URISyntaxException, SaxonApiException {
DocumentBuilder docBuilder = processor.newDocumentBuilder();
BuildingContentHandler handler = docBuilder.newBuildingContentHandler();
AutoDetectParser parser = new AutoDetectParser();
Metadata metadata = new Metadata();
try (InputStream stream = new ByteArrayInputStream(rtf.getBytes("utf8"))) {
parser.parse(stream, handler, metadata);
return handler.getDocumentNode();//docBuilder.build(new StreamSource(new StringReader(handler.toString())));
} catch (SaxonApiException e) {
throw new RuntimeException(e);
}
}
При таком подходе, по крайней мере, в коротком тесте, для примера RTF с гиперссылкой не возникает никаких ошибок, см. обновленный проект https://github.com/martin-honnen/SaxonTikaRtfTest1 для кода в более подробном контексте.
Спасибо, что посмотрели. В моем наборе данных не так много таких гиперссылок, поэтому для целесообразности я перехватил их в XSLT, когда и вернул необработанный RTF обратно в данные для ручной очистки. Среди десятков тысяч, некоторые здесь и там хороши в краткосрочной перспективе. Спасибо за ваше терпение, пока я работал над этой проблемой и учился в ваше время.
@MichaelFriedman, с помощью ребят из Saxonica мне удалось найти альтернативную и, возможно, более эффективную интеграцию Tika и Saxon в встроенную функцию расширения Saxon, которая не дает никаких ошибок в вашем образце гиперссылки RTF. Смотрите отредактированный ответ.
Я протестировал новый код на полном наборе данных и могу подтвердить, что это решает проблему. Результат xhtml для одной из этих гиперссылок: <p> Caesar Dressing: <a href = "..\\..\\..\\DRESSING\\Emulsified\\Caesar Dressing.doc">Caesar Dressing.doc</a> </p>
Я ценю вашу настойчивость и эксперименты, а также помощь от людей из Saxonica. Форматирование RTF представляет большую проблему для данных, с которыми я работаю, и я был удивлен, не обнаружив дополнительных возможностей XML/XSLT. Это имеет большое значение.
Звучит так, как будто
ContentHandler handler = new ToXMLContentHandler();
, как я думал, вернет правильно сформированный XHTML при использовании сhandler.toString()
вreturn docBuilder.build(new StreamSource(new StringReader(handler.toString())));
, не делает этого для этого ввода. Я попытаюсь изучить его, но это может занять день или два, я не знаком с деталями используемого инструмента Tika и его надежностью, или я просто не смог правильно использовать его API.