diff --git a/src/BloomExe/Book/BookStorage.cs b/src/BloomExe/Book/BookStorage.cs index a0a6c92c3d4a..003fe84e3474 100644 --- a/src/BloomExe/Book/BookStorage.cs +++ b/src/BloomExe/Book/BookStorage.cs @@ -331,6 +331,19 @@ public void Save() Dom.UpdateMetaElement("BloomFormatVersion", kBloomFormatVersion); } BookInfo.FormatVersion = kBloomFormatVersion; + + VersionRequirement[] requiredVersions = GetRequiredVersions(Dom).ToArray(); + if (requiredVersions != null && requiredVersions.Length >= 1) + { + string json = JsonConvert.SerializeObject(requiredVersions); + Dom.UpdateMetaElement("FeatureRequirement", json); + } + else + { + // Might be necessary if you duplicated a book, or modified a book such that it no longer needs this + Dom.RemoveMetaElement("FeatureRequirement"); + } + var watch = Stopwatch.StartNew(); string tempPath = SaveHtml(Dom); watch.Stop(); @@ -366,6 +379,26 @@ public void Save() BookInfo.Save(); } + // Determines which features will have serious breaking effects if not opened in the proper version of any relevant Bloom products + // Note: This should include not only BloomDesktop considerations, but needs to insert enough information for things like BloomReader to be able to figure it out too + public static IOrderedEnumerable GetRequiredVersions(HtmlDom dom) + { + var reqList = new List(); + + if (dom.DoesContainNarrationAudioRecordedUsingWholeTextBox()) + { + reqList.Add(new VersionRequirement() + { + FeatureId = "wholeTextBoxAudio", + FeaturePhrase = "Whole Text Box Audio", + BloomDesktopMinVersion = "4.4", + BloomReaderMinVersion = "1.0" + }); + } + + return reqList.OrderByDescending(x => x.BloomDesktopMinVersion); + } + public const string BackupFilename = "bookhtml.bak"; // need to know this in BookCollection too. private string GetBackupFilePath() diff --git a/src/BloomExe/Book/HtmlDom.cs b/src/BloomExe/Book/HtmlDom.cs index f55c51a51a43..07df6d61f534 100644 --- a/src/BloomExe/Book/HtmlDom.cs +++ b/src/BloomExe/Book/HtmlDom.cs @@ -1825,6 +1825,12 @@ public static XmlNodeList SelectAudioSentenceElementsWithRecordingMd5(XmlElement return element.SafeSelectNodes("descendant-or-self::node()[contains(@class,'audio-sentence') and @recordingmd5]"); } + public bool DoesContainNarrationAudioRecordedUsingWholeTextBox() + { + var nodes = _dom.SafeSelectNodes("//*[@data-audiorecordingmode='TextBox']"); + return nodes?.Count >= 1; + } + public static bool IsImgOrSomethingWithBackgroundImage(XmlElement element) { return element.SelectNodes("self::img | self::*[contains(@style,'background-image')]").Count == 1; diff --git a/src/BloomTests/Book/BookStorageTests.cs b/src/BloomTests/Book/BookStorageTests.cs index 552986b86879..b3ad0c8a790f 100644 --- a/src/BloomTests/Book/BookStorageTests.cs +++ b/src/BloomTests/Book/BookStorageTests.cs @@ -71,6 +71,25 @@ public void Save_BookHadEditStyleSheet_NowHasPreviewAndBase() AssertThatXmlIn.HtmlFile(_bookPath).HasSpecifiedNumberOfMatchesForXpath("//link[contains(@href, 'basePage')]", 1); AssertThatXmlIn.HtmlFile(_bookPath).HasSpecifiedNumberOfMatchesForXpath("//link[contains(@href, 'preview')]", 1); } + [Test] + public void Save_BookHadNarrationAudioRecordedByWholeTextBox_AddsFeatureRequirementMetadata() + { + // Enhance: need an example in the future to test the result if two are generated. But right now this is the only feature that generates it. + GetInitialStorageWithCustomHtml("
"); + AssertThatXmlIn.HtmlFile(_bookPath).HasSpecifiedNumberOfMatchesForXpath("//meta[@name='FeatureRequirement']", 1); + + // Note: No need to HTML-encode the XPath. The comparison will automatically figure that out (I guess by decoding the encoding version) + string expectedContent = "[{\"BloomDesktopMinVersion\":\"4.4\",\"BloomReaderMinVersion\":\"1.0\",\"FeatureId\":\"wholeTextBoxAudio\",\"FeaturePhrase\":\"Whole Text Box Audio\"}]"; + AssertThatXmlIn.HtmlFile(_bookPath).HasSpecifiedNumberOfMatchesForXpath($"//meta[@content='{expectedContent}']", 1); + } + + [Test] + public void Save_BookHadNoAudio_CleansUpFeatureRequirementMetadata() + { + // Enhance: need an example in the future to test the result if two are generated. But right now this is the only feature that generates it. + GetInitialStorageWithCustomHtml("
"); + AssertThatXmlIn.HtmlFile(_bookPath).HasSpecifiedNumberOfMatchesForXpath("//meta[@name='FeatureRequirement']", 0); + } [Test] public void CleanupUnusedVideoFiles_BookHadUnusedVideo_VideosRemoved()