public void BaseForRelativePaths_NoHead_NoLongerThrows() { var dom = new HtmlDom( @"<html></html>"); dom.BaseForRelativePaths = "theBase"; Assert.AreEqual("theBase", dom.BaseForRelativePaths); }
public static void AddUIDictionaryToDom(HtmlDom pageDom, CollectionSettings collectionSettings) { XmlElement dictionaryScriptElement = pageDom.RawDom.SelectSingleNode("//script[@id='ui-dictionary']") as XmlElement; if (dictionaryScriptElement != null) dictionaryScriptElement.ParentNode.RemoveChild(dictionaryScriptElement); dictionaryScriptElement = pageDom.RawDom.CreateElement("script"); dictionaryScriptElement.SetAttribute("type", "text/javascript"); dictionaryScriptElement.SetAttribute("id", "ui-dictionary"); var d = new Dictionary<string, string>(); d.Add(collectionSettings.Language1Iso639Code, collectionSettings.Language1Name); if (!String.IsNullOrEmpty(collectionSettings.Language2Iso639Code) && !d.ContainsKey(collectionSettings.Language2Iso639Code)) d.Add(collectionSettings.Language2Iso639Code, collectionSettings.GetLanguage2Name(collectionSettings.Language2Iso639Code)); if (!String.IsNullOrEmpty(collectionSettings.Language3Iso639Code) && !d.ContainsKey(collectionSettings.Language3Iso639Code)) d.Add(collectionSettings.Language3Iso639Code, collectionSettings.GetLanguage3Name(collectionSettings.Language3Iso639Code)); d.Add("vernacularLang", collectionSettings.Language1Iso639Code);//use for making the vernacular the first tab d.Add("{V}", collectionSettings.Language1Name); d.Add("{N1}", collectionSettings.GetLanguage2Name(collectionSettings.Language2Iso639Code)); d.Add("{N2}", collectionSettings.GetLanguage3Name(collectionSettings.Language3Iso639Code)); AddLocalizedHintContentsToDictionary(pageDom, d, collectionSettings); dictionaryScriptElement.InnerText = String.Format("function GetDictionary() {{ return {0};}}", JsonConvert.SerializeObject(d)); pageDom.Head.InsertAfter(dictionaryScriptElement, pageDom.Head.LastChild); }
public static void CopyImageMetadataToWholeBook(string folderPath, HtmlDom dom, Metadata metadata, IProgress progress) { progress.WriteStatus("Starting..."); //First update the images themselves int completed = 0; var imgElements = GetImagePaths(folderPath); foreach (string path in imgElements) { progress.ProgressIndicator.PercentCompleted = (int)(100.0 * (float)completed / imgElements.Count()); progress.WriteStatus("Copying to " + Path.GetFileName(path)); using (var image = PalasoImage.FromFile(path)) { image.Metadata = metadata; image.SaveUpdatedMetadataIfItMakesSense(); } ++completed; } //Now update the html attributes which echo some of it, and is used by javascript to overlay displays related to //whether the info is there or missing or whatever. foreach (XmlElement img in dom.SafeSelectNodes("//img")) { UpdateImgMetdataAttributesToMatchImage(folderPath, img, progress, metadata); } }
public static void CopyImageMetadataToWholeBook(string folderPath, HtmlDom dom, Metadata metadata, IProgress progress) { progress.WriteStatus("Starting..."); //First update the images themselves int completed = 0; var imgElements = GetImagePaths(folderPath); foreach (string path in imgElements) { progress.ProgressIndicator.PercentCompleted = (int)(100.0 * (float)completed / imgElements.Count()); progress.WriteStatus("Copying to " + Path.GetFileName(path)); try { metadata.WriteIntellectualPropertyOnly(path); } catch (TagLib.CorruptFileException e) { NonFatalProblem.Report(ModalIf.Beta, PassiveIf.All,"Image metadata problem", "Bloom had a problem accessing the metadata portion of this image " + path+ " ref(BL-3214)", e); } ++completed; } //Now update the html attributes which echo some of it, and is used by javascript to overlay displays related to //whether the info is there or missing or whatever. foreach (XmlElement img in dom.SafeSelectNodes("//img")) { UpdateImgMetdataAttributesToMatchImage(folderPath, img, progress, metadata); } }
public void SetBaseForRelativePaths_NoExistingBase_Adds() { var dom = new HtmlDom( @"<html><head/></html>"); dom.SetBaseForRelativePaths("theBase"); AssertThatXmlIn.Dom(dom.RawDom).HasSpecifiedNumberOfMatchesForXpath("html/head/base[@href='theBase']", 1); }
/// <summary>Loads the requested panel into the toolbox</summary> public static void AppendToolboxPanel(HtmlDom domForToolbox, string fileName) { var toolbox = domForToolbox.Body.SelectSingleNode("//div[@id='toolbox']"); var toolDom = new HtmlDom(XmlHtmlConverter.GetXmlDomFromHtmlFile(fileName)); AddToolDependencies(toolDom); AppendAllChildren(toolDom.Body, toolbox); }
public void BringBookUpToDate_DomHas2ContentLanguages_PulledIntoBookProperties() { _bookDom = new HtmlDom(@"<html><head><div id='bloomDataDiv'><div data-book='contentLanguage2'>okm</div><div data-book='contentLanguage3'>kbt</div></div></head><body></body></html>"); var book = CreateBook(); book.BringBookUpToDate(new NullProgress()); Assert.AreEqual("okm", book.MultilingualContentLanguage2); Assert.AreEqual("kbt", book.MultilingualContentLanguage3); }
public void BaseForRelativePaths_NullPath_SetsToEmpty() { var dom = new HtmlDom( @"<html><head><base href='original'/></head></html>"); dom.BaseForRelativePaths = null; AssertThatXmlIn.Dom(dom.RawDom).HasSpecifiedNumberOfMatchesForXpath("html/head/base", 0); Assert.AreEqual(string.Empty, dom.BaseForRelativePaths); }
/// <param name="dom">Set this parameter to, say, a page that the user just edited, to limit reading to it, so its values don't get overriden by previous pages. /// Supply the whole dom if nothing has priority (which will mean the data-div will win, because it is first)</param> /// <param name="collectionSettings"> </param> /// <param name="updateImgNodeCallback">This is a callback so as not to introduce dependencies on ImageUpdater & the current folder path</param> public BookData(HtmlDom dom, CollectionSettings collectionSettings, Action<XmlElement> updateImgNodeCallback) { _dom = dom; _updateImgNode = updateImgNodeCallback; _collectionSettings = collectionSettings; GetOrCreateDataDiv(); _dataset = GatherDataItemsFromCollectionSettings(_collectionSettings); GatherDataItemsFromXElement(_dataset,_dom.RawDom); }
public void BaseForRelativePaths_HasExistingBase_Removes() { var dom = new HtmlDom( @"<html><head><base href='original'/></head></html>"); AssertThatXmlIn.Dom(dom.RawDom).HasSpecifiedNumberOfMatchesForXpath("html/head/base[@href='original']", 1); dom.BaseForRelativePaths = "new"; AssertThatXmlIn.Dom(dom.RawDom).HasSpecifiedNumberOfMatchesForXpath("html/head/base", 0); Assert.AreEqual("new", dom.BaseForRelativePaths); }
public void RemoveMetaValue_IsThere_RemovesIt() { var dom = new HtmlDom( @"<html><head> <meta name='one' content='1'/> </head></html>"); dom.RemoveMetaElement("one"); AssertThatXmlIn.Dom(dom.RawDom).HasSpecifiedNumberOfMatchesForXpath("//meta[@name='one']", 0); }
public void SetBaseForRelativePaths_HasExistingBase_Replaces() { var dom = new HtmlDom( @"<html><head><base href='original'/></head></html>"); AssertThatXmlIn.Dom(dom.RawDom).HasSpecifiedNumberOfMatchesForXpath("html/head/base[@href='original']", 1); dom.SetBaseForRelativePaths("new"); AssertThatXmlIn.Dom(dom.RawDom).HasSpecifiedNumberOfMatchesForXpath("html/head/base[@href='original']", 0); AssertThatXmlIn.Dom(dom.RawDom).HasSpecifiedNumberOfMatchesForXpath("html/head/base[@href='new']", 1); }
public static void LoadPanelIntoToolbox(HtmlDom domForToolbox, ToolboxTool tool, List<string> checkedBoxes, string toolboxFolder) { // For all the toolbox tools, the tool name is used as the name of both the folder where the // assets for that tool are kept, and the name of the main htm file that represents the tool. var fileName = tool.ToolId + "ToolboxPanel.html"; var path = BloomFileLocator.sTheMostRecentBloomFileLocator.LocateFile(fileName); Debug.Assert(!string.IsNullOrEmpty(path)); AppendToolboxPanel(domForToolbox, path); checkedBoxes.Add(tool.ToolId + "Check"); }
public void Constructor_CollectionSettingsHasCountrProvinceDistrict_LanguageLocationFilledIn() { // var dom = new HtmlDom(@"<html><head><div id='bloomDataDiv'> // <div data-book='country'>the country</div> // <div data-book='province'>the province</div> // <div data-book='district'>the district</div> // </div></head><body></body></html>"); var dom = new HtmlDom(); var data = new BookData(dom, new CollectionSettings(){Country="the country", Province = "the province", District= "the district"}, null); Assert.AreEqual("the district, the province<br/>the country", data.GetVariableOrNull("languageLocation", "*")); }
public static IEnumerable <Layout> GetLayoutChoices(HtmlDom dom, IFileLocator fileLocator) { //here we walk through all the stylesheets, looking for one with the special style which tells us which page/orientations it supports foreach (XmlElement link in dom.SafeSelectNodes("//link[@rel='stylesheet']")) { var fileName = link.GetStringAttribute("href"); if (fileName.ToLower().Contains("mode") || fileName.ToLower().Contains("page") || fileName.ToLower().Contains("matter") || fileName.ToLower().Contains("languagedisplay")) { continue; } fileName = fileName.Replace("file://", "").Replace("%5C", "/"); var path = fileLocator.LocateFile(fileName); if (string.IsNullOrEmpty(path)) { throw new ApplicationException("Could not locate " + fileName); } var contents = File.ReadAllText(path); var start = contents.IndexOf("STARTLAYOUTS"); if (start < 0) { continue; //yield break; // continue;//move on to the next stylesheet } start += "STARTLAYOUTS".Length; var end = contents.IndexOf("ENDLAYOUTS", start); var s = contents.Substring(start, end - start); IEnumerable <Layout> layouts = null; try { layouts = Layout.GetConfigurationsFromConfigurationOptionsString(s); } catch (Exception e) { throw new ApplicationException("Problem parsing the 'layouts' comment of " + fileName + ". The contents were\r\n" + s, e); } foreach (var p in layouts) { yield return(p); } yield break; } //default to A5Portrait yield return(new Layout { SizeAndOrientation = FromString("A5Portrait") }); }
public static void AddUIDictionaryToDom(HtmlDom pageDom, CollectionSettings collectionSettings) { // add dictionary script to the page XmlElement dictionaryScriptElement = pageDom.RawDom.SelectSingleNode("//script[@id='ui-dictionary']") as XmlElement; if (dictionaryScriptElement != null) { dictionaryScriptElement.ParentNode.RemoveChild(dictionaryScriptElement); } dictionaryScriptElement = pageDom.RawDom.CreateElement("script"); dictionaryScriptElement.SetAttribute("type", "text/javascript"); dictionaryScriptElement.SetAttribute("id", "ui-dictionary"); var d = new Dictionary <string, string>(); d.Add(collectionSettings.Language1Iso639Code, collectionSettings.Language1Name); if (!String.IsNullOrEmpty(collectionSettings.Language2Iso639Code)) { SafelyAddLanguage(d, collectionSettings.Language2Iso639Code, collectionSettings.GetLanguage2Name(collectionSettings.Language2Iso639Code)); } if (!String.IsNullOrEmpty(collectionSettings.Language3Iso639Code)) { SafelyAddLanguage(d, collectionSettings.Language3Iso639Code, collectionSettings.GetLanguage3Name(collectionSettings.Language3Iso639Code)); } SafelyAddLanguage(d, "vernacularLang", collectionSettings.Language1Iso639Code); //use for making the vernacular the first tab SafelyAddLanguage(d, "{V}", collectionSettings.Language1Name); SafelyAddLanguage(d, "{N1}", collectionSettings.GetLanguage2Name(collectionSettings.Language2Iso639Code)); SafelyAddLanguage(d, "{N2}", collectionSettings.GetLanguage3Name(collectionSettings.Language3Iso639Code)); // TODO: Eventually we need to look through all .bloom-translationGroup elements on the current page to determine // whether there is text in a language not yet added to the dictionary. // For now, we just add a few we know we need AddSomeCommonNationalLanguages(d); MakePageLabelLocalizable(pageDom, d); AddLocalizedHintContentsToDictionary(pageDom, d, collectionSettings); // Hard-coded localizations for 2.0 AddHtmlUiStrings(d); dictionaryScriptElement.InnerText = String.Format("function GetInlineDictionary() {{ return {0};}}", JsonConvert.SerializeObject(d)); // add i18n initialization script to the page AddLocalizationTriggerToDom(pageDom); pageDom.Head.InsertAfter(dictionaryScriptElement, pageDom.Head.LastChild); }
public void UpdateAllHtmlDataAttributesForAllImgElements_HasBothImgAndBackgroundImageElements_UpdatesBoth() { var dom = new HtmlDom("<html><body><img src='test.png'/><div style='color:orange; background-image=url(\"test.png\")'/></body></html>"); using (var folder = new TemporaryFolder("bloom pictures test source")) { MakeSamplePngImageWithMetadata(folder.Combine("test.png")); ImageUpdater.UpdateAllHtmlDataAttributesForAllImgElements(folder.FolderPath, dom, new NullProgress()); } AssertThatXmlIn.Dom(dom.RawDom).HasSpecifiedNumberOfMatchesForXpath("//*[@data-copyright='Copyright 1999 by me']", 2); AssertThatXmlIn.Dom(dom.RawDom).HasSpecifiedNumberOfMatchesForXpath("//*[@data-creator='joe']", 2); AssertThatXmlIn.Dom(dom.RawDom).HasSpecifiedNumberOfMatchesForXpath("//*[@data-license='cc-by-nd']", 2); }
/// <summary> /// Create a Clearshare.Metadata object by reading values out of the dom's bloomDataDiv /// </summary> /// <param name="brandingNameOrFolderPath"> Normally, the branding is just a name, which we look up in the official branding folder //but unit tests can instead provide a path to the folder. /// </param> public static Metadata GetMetadata(HtmlDom dom, string brandingNameOrFolderPath = "") { if (ShouldSetToDefaultCopyrightAndLicense(dom)) { return GetMetadataWithDefaultCopyrightAndLicense(brandingNameOrFolderPath); } var metadata = new Metadata(); var copyright = dom.GetBookSetting("copyright"); if (!copyright.Empty) { metadata.CopyrightNotice = WebUtility.HtmlDecode(copyright.GetFirstAlternative()); } var licenseUrl = dom.GetBookSetting("licenseUrl").GetBestAlternativeString(new[] { "*", "en" }); if (string.IsNullOrWhiteSpace(licenseUrl)) { //NB: we are mapping "RightsStatement" (which comes from XMP-dc:Rights) to "LicenseNotes" in the html. //custom licenses live in this field, so if we have notes (and no URL) it is a custom one. var licenseNotes = dom.GetBookSetting("licenseNotes"); if (!licenseNotes.Empty) { metadata.License = new CustomLicense { RightsStatement = WebUtility.HtmlDecode(licenseNotes.GetFirstAlternative()) }; } else { // The only remaining current option is a NullLicense metadata.License = new NullLicense(); //"contact the copyright owner } } else // there is a licenseUrl, which means it is a CC license { try { metadata.License = CreativeCommonsLicense.FromLicenseUrl(licenseUrl); } catch (Exception e) { throw new ApplicationException("Bloom had trouble parsing this license url: '" + licenseUrl + "'. (ref BL-4108)", e); } //are there notes that go along with that? var licenseNotes = dom.GetBookSetting("licenseNotes"); if(!licenseNotes.Empty) { var s = WebUtility.HtmlDecode(licenseNotes.GetFirstAlternative()); metadata.License.RightsStatement = HtmlDom.ConvertHtmlBreaksToNewLines(s); } } return metadata; }
public static void AddUIDictionaryToDom(HtmlDom pageDom, CollectionSettings collectionSettings) { CheckDynamicStrings(); // add dictionary script to the page XmlElement dictionaryScriptElement = pageDom.RawDom.SelectSingleNode("//script[@id='ui-dictionary']") as XmlElement; if (dictionaryScriptElement != null) dictionaryScriptElement.ParentNode.RemoveChild(dictionaryScriptElement); dictionaryScriptElement = pageDom.RawDom.CreateElement("script"); dictionaryScriptElement.SetAttribute("type", "text/javascript"); dictionaryScriptElement.SetAttribute("id", "ui-dictionary"); var d = new Dictionary<string, string>(); d.Add(collectionSettings.Language1Iso639Code, collectionSettings.Language1Name); if (!String.IsNullOrEmpty(collectionSettings.Language2Iso639Code)) SafelyAddLanguage(d, collectionSettings.Language2Iso639Code, collectionSettings.GetLanguage2Name(collectionSettings.Language2Iso639Code)); if (!String.IsNullOrEmpty(collectionSettings.Language3Iso639Code)) SafelyAddLanguage(d, collectionSettings.Language3Iso639Code, collectionSettings.GetLanguage3Name(collectionSettings.Language3Iso639Code)); SafelyAddLanguage(d, "vernacularLang", collectionSettings.Language1Iso639Code);//use for making the vernacular the first tab SafelyAddLanguage(d, "{V}", collectionSettings.Language1Name); SafelyAddLanguage(d, "{N1}", collectionSettings.GetLanguage2Name(collectionSettings.Language2Iso639Code)); SafelyAddLanguage(d, "{N2}", collectionSettings.GetLanguage3Name(collectionSettings.Language3Iso639Code)); // TODO: Eventually we need to look through all .bloom-translationGroup elements on the current page to determine // whether there is text in a language not yet added to the dictionary. // For now, we just add a few we know we need AddSomeCommonNationalLanguages(d); MakePageLabelLocalizable(pageDom, d); // Hard-coded localizations for 2.0 AddHtmlUiStrings(d); // Do this last, on the off-chance that the page contains a localizable string that matches // a language code. AddLanguagesUsedInPage(pageDom.RawDom, d); dictionaryScriptElement.InnerText = String.Format("function GetInlineDictionary() {{ return {0};}}", JsonConvert.SerializeObject(d)); // add i18n initialization script to the page //AddLocalizationTriggerToDom(pageDom); pageDom.Head.InsertAfter(dictionaryScriptElement, pageDom.Head.LastChild); _collectDynamicStrings = false; }
private static void MakePageLabelLocalizable(HtmlDom singlePageHtmlDom, Dictionary <string, string> d) { foreach (XmlElement element in singlePageHtmlDom.RawDom.SelectNodes("//*[contains(@class, 'pageLabel')]")) { if (!element.HasAttribute("data-i18n")) { var englishLabel = element.InnerText; var key = "EditTab.ThumbnailCaptions." + englishLabel; AddTranslationToDictionaryUsingEnglishAsKey(d, key, englishLabel); element.SetAttribute("data-i18n", key); } } }
/// <summary> /// Propagating the copyright and license information in the bloomDataDiv to template fields /// found in the pages of the book (normally just the credits page). /// </summary> /// <remarks>This is "internal" just as a convention, that it is accessible for testing purposes only</remarks> internal static void UpdateDomFromDataDiv(HtmlDom dom, string bookFolderPath, CollectionSettings collectionSettings) { CopyItemToFieldsInPages(dom, "copyright"); CopyItemToFieldsInPages(dom, "licenseUrl"); CopyItemToFieldsInPages(dom, "licenseDescription", languagePreferences: collectionSettings.LicenseDescriptionLanguagePriorities.ToArray()); CopyItemToFieldsInPages(dom, "licenseNotes"); CopyItemToFieldsInPages(dom, "licenseImage", valueAttribute: "src"); CopyItemToFieldsInPages(dom, "originalCopyrightAndLicense"); if (!String.IsNullOrEmpty(bookFolderPath)) //unit tests may not be interested in checking this part { UpdateBookLicenseIcon(GetMetadata(dom), bookFolderPath); } }
public static void AddUIDictionaryToDom(HtmlDom pageDom, BookData bookData) { CheckDynamicStrings(); // add dictionary script to the page XmlElement dictionaryScriptElement = pageDom.RawDom.SelectSingleNode("//script[@id='ui-dictionary']") as XmlElement; if (dictionaryScriptElement != null) { dictionaryScriptElement.ParentNode.RemoveChild(dictionaryScriptElement); } dictionaryScriptElement = pageDom.RawDom.CreateElement("script"); dictionaryScriptElement.SetAttribute("type", "text/javascript"); dictionaryScriptElement.SetAttribute("id", "ui-dictionary"); var d = new Dictionary <string, string>(); foreach (var lang in bookData.GetAllBookLanguages()) { SafelyAddLanguage(d, lang.Iso639Code, lang.GetNameInLanguage(lang.Iso639Code)); } SafelyAddLanguage(d, "vernacularLang", bookData.Language1.Iso639Code); //use for making the vernacular the first tab SafelyAddLanguage(d, "{V}", bookData.Language1.Name); SafelyAddLanguage(d, "{N1}", bookData.MetadataLanguage1.GetNameInLanguage(bookData.MetadataLanguage1IsoCode)); if (!string.IsNullOrEmpty(bookData.Language3IsoCode)) { SafelyAddLanguage(d, "{N2}", bookData.MetadataLanguage2.GetNameInLanguage(bookData.MetadataLanguage2IsoCode)); } // TODO: Eventually we need to look through all .bloom-translationGroup elements on the current page to determine // whether there is text in a language not yet added to the dictionary. // For now, we just add a few we know we need AddSomeCommonNationalLanguages(d); // Hard-coded localizations for 2.0 AddHtmlUiStrings(d); // Do this last, on the off-chance that the page contains a localizable string that matches // a language code. AddLanguagesUsedInPage(pageDom.RawDom, d); dictionaryScriptElement.InnerText = String.Format("function GetInlineDictionary() {{ return {0};}}", JsonConvert.SerializeObject(d)); // add i18n initialization script to the page //AddLocalizationTriggerToDom(pageDom); pageDom.Head.InsertAfter(dictionaryScriptElement, pageDom.Head.LastChild); _collectDynamicStrings = false; }
/// <summary> /// stick in a json with various settings we want to make available to the javascript /// </summary> public static void AddUISettingsToDom(HtmlDom pageDom, BookData bookData, IFileLocator fileLocator) { CheckDynamicStrings(); XmlElement existingElement = pageDom.RawDom.SelectSingleNode("//script[@id='ui-settings']") as XmlElement; XmlElement element = pageDom.RawDom.CreateElement("script"); element.SetAttribute("type", "text/javascript"); element.SetAttribute("id", "ui-settings"); var d = new Dictionary <string, string>(); //d.Add("urlOfUIFiles", "file:///" + fileLocator.LocateDirectory("ui", "ui files directory")); if (!String.IsNullOrEmpty(Settings.Default.LastSourceLanguageViewed)) { d.Add("defaultSourceLanguage", Settings.Default.LastSourceLanguageViewed); } d.Add("languageForNewTextBoxes", bookData.Language1.Iso639Code); d.Add("isSourceCollection", bookData.CollectionSettings.IsSourceCollection.ToString()); // BL-2357 To aid in smart ordering of source languages in source bubble if (!String.IsNullOrEmpty(bookData.Language2IsoCode)) { d.Add("currentCollectionLanguage2", bookData.Language2IsoCode); } if (!String.IsNullOrEmpty(bookData.Language3IsoCode)) { d.Add("currentCollectionLanguage3", bookData.Language3IsoCode); } d.Add("browserRoot", FileLocationUtilities.GetDirectoryDistributedWithApplication(BloomFileLocator.BrowserRoot).ToLocalhost()); element.InnerText = String.Format("function GetSettings() {{ return {0};}}", JsonConvert.SerializeObject(d)); var head = pageDom.RawDom.SelectSingleNode("//head"); if (existingElement != null) { head.ReplaceChild(element, existingElement); } else { head.InsertAfter(element, head.LastChild); } _collectDynamicStrings = false; }
public static Layout FromDom(HtmlDom dom, Layout defaultIfMissing) { var firstPage = dom.SelectSingleNode("descendant-or-self::div[contains(@class,'bloom-page')]"); if (firstPage == null) { return(defaultIfMissing); } var layout = new Layout { SizeAndOrientation = defaultIfMissing.SizeAndOrientation, Style = defaultIfMissing.Style }; return(FromPage(firstPage, layout)); }
private static void EnsureIdsAreUnique(HtmlDom dom, string elementTag, List <string> ids, StringBuilder builder) { foreach (XmlElement element in dom.SafeSelectNodes("//" + elementTag + "[@id]")) { var id = element.GetAttribute("id"); if (ids.Contains(id)) { builder.AppendLine("The id of this " + elementTag + " must be unique, but is not: " + element.OuterXml); } else { ids.Add(id); } } }
/// <summary> /// We mirror several metadata tags in the html for quick access by the UI. /// This method makes sure they are all up to date. /// </summary> /// <param name="progress"> </param> public static void UpdateAllHtmlDataAttributesForAllImgElements(string folderPath, HtmlDom dom, IProgress progress) { //Update the html attributes which echo some of it, and is used by javascript to overlay displays related to //whether the info is there or missing or whatever. var imgElements = HtmlDom.SelectChildImgAndBackgroundImageElements(dom.RawDom.DocumentElement); int completed = 0; foreach (XmlElement img in imgElements) { progress.ProgressIndicator.PercentCompleted = (int)(100.0 * (float)completed / (float)imgElements.Count); UpdateImgMetdataAttributesToMatchImage(folderPath, img, progress); completed++; } }
/// <summary> /// stick in a json with various settings we want to make available to the javascript /// </summary> public static void AddUISettingsToDom(HtmlDom pageDom, CollectionSettings collectionSettings, IFileLocator fileLocator) { XmlElement element = pageDom.RawDom.SelectSingleNode("//script[@id='ui-settings']") as XmlElement; if (element != null) { element.ParentNode.RemoveChild(element); } element = pageDom.RawDom.CreateElement("script"); element.SetAttribute("type", "text/javascript"); element.SetAttribute("id", "ui-settings"); var d = new Dictionary <string, string>(); //d.Add("urlOfUIFiles", "file:///" + fileLocator.LocateDirectory("ui", "ui files directory")); if (!String.IsNullOrEmpty(Settings.Default.LastSourceLanguageViewed)) { d.Add("defaultSourceLanguage", Settings.Default.LastSourceLanguageViewed); } d.Add("languageForNewTextBoxes", collectionSettings.Language1Iso639Code); d.Add("isSourceCollection", collectionSettings.IsSourceCollection.ToString()); d.Add("bloomBrowserUIFolder", FileLocator.GetDirectoryDistributedWithApplication("BloomBrowserUI").ToLocalhost()); //If you modify any of these, consider modifying/updating the localization files; the localization ids for these are just the current English (which is fagile) //If you make changes/additions here, also synchronize with the bloomlibrary source in services.js var topics = new[] { "Agriculture", "Animal Stories", "Business", "Culture", "Community Living", "Dictionary", "Environment", "Fiction", "Health", "How To", "Math", "Non Fiction", "Spiritual", "Personal Development", "Primer", "Science", "Traditional Story" }; var builder = new StringBuilder(); builder.Append("["); TopicReversal = new Dictionary <string, string>(); foreach (var topic in topics) { var localized = LocalizationManager.GetDynamicString("Bloom", "Topics." + topic, topic, "shows in the topics chooser in the edit tab"); TopicReversal[localized] = topic; builder.Append("\"" + localized + "\", "); } builder.Append("]"); d.Add("topics", builder.ToString().Replace(", ]", "]")); // d.Add("topics", "['Agriculture', 'Animal Stories', 'Business', 'Culture', 'Community Living', 'Dictionary', 'Environment', 'Fiction', 'Health', 'How To', 'Math', 'Non Fiction', 'Spiritual', 'Personal Development', 'Primer', 'Science', 'Tradition']".Replace("'", "\\\"")); element.InnerText = String.Format("function GetSettings() {{ return {0};}}", JsonConvert.SerializeObject(d)); var head = pageDom.RawDom.SelectSingleNode("//head"); head.InsertAfter(element, head.LastChild); }
public static void UpdateImgMetadataAttributesToMatchImage(string folderPath, XmlElement imgElement, IProgress progress, Metadata metadata) { //see also PageEditingModel.UpdateMetadataAttributesOnImage(), which does the same thing but on the browser dom var url = HtmlDom.GetImageElementUrl(new ElementProxy(imgElement)); string fileName = url.PathOnly.NotEncoded; if (fileName.ToLowerInvariant() == "placeholder.png" || fileName.ToLowerInvariant() == "license.png") { return; } if (string.IsNullOrEmpty(fileName)) { Logger.WriteEvent("Book.UpdateImgMetdataAttributesToMatchImage() Warning: img has no or empty src attribute"); //Debug.Fail(" (Debug only) img has no or empty src attribute"); return; // they have bigger problems, which aren't appropriate to deal with here. } if (metadata == null) { // The fileName might be URL encoded. See https://silbloom.myjetbrains.com/youtrack/issue/BL-3901. var path = UrlPathString.GetFullyDecodedPath(folderPath, ref fileName); progress.WriteStatus("Reading metadata from " + fileName); if (!RobustFile.Exists(path)) // they have bigger problems, which aren't appropriate to deal with here. { imgElement.RemoveAttribute("data-copyright"); imgElement.RemoveAttribute("data-creator"); imgElement.RemoveAttribute("data-license"); Logger.WriteEvent("Book.UpdateImgMetdataAttributesToMatchImage() Image " + path + " is missing"); //Debug.Fail(" (Debug only) Image " + path + " is missing"); return; } try { metadata = RobustIO.MetadataFromFile(path); } catch (UnauthorizedAccessException e) { throw new BloomUnauthorizedAccessException(path, e); } } progress.WriteStatus("Writing metadata to HTML for " + fileName); imgElement.SetAttribute("data-copyright", String.IsNullOrEmpty(metadata.CopyrightNotice) ? "" : metadata.CopyrightNotice); imgElement.SetAttribute("data-creator", String.IsNullOrEmpty(metadata.Creator) ? "" : metadata.Creator); imgElement.SetAttribute("data-license", metadata.License == null ? "" : metadata.License.ToString()); }
/// <summary> /// Constructs by finding the file and folder of the xmatter pack, given the its key name e.g. "Factory", "SILIndonesia". /// The default key name is provided as a method parameter, but that can be overridden by a value from inside the book. /// The name of the file should be (key)-XMatter.htm. The name and the location of the folder is not our problem... /// we leave it to the supplied fileLocator to find it. /// </summary> /// <param name="xmatterNameFromCollectionSettings">e.g. "Factory", "SILIndonesia". This can be overridden inside the bookDom.</param> /// <param name="fileLocator">The locator needs to be able tell us the path to an xmatter html file, given its name</param> public XMatterHelper(HtmlDom bookDom, string xmatterNameFromCollectionSettings, IFileLocator fileLocator) { string directoryPath = null; _bookDom = bookDom; var bookSpecificXMatterPack = bookDom.GetMetaValue("xmatter", null); if (!String.IsNullOrWhiteSpace(bookSpecificXMatterPack)) { bookSpecificXMatterPack = MigrateXMatterName(bookSpecificXMatterPack); _nameOfXMatterPack = bookSpecificXMatterPack; var errorTemplate = LocalizationManager.GetString("Errors.XMatterSpecifiedByBookNotFound", "This book called for a Front/Back Matter pack named '{0}', but this version of Bloom does not have it, and Bloom could not find it on this computer. The book has been changed to use the Front/Back Matter pages from the Collection Settings."); directoryPath = GetXMatterDirectory(_nameOfXMatterPack, fileLocator, String.Format(errorTemplate, bookSpecificXMatterPack), false); if (directoryPath == null) { // Remove the xmatter specification from the DOM since it couldn't be found. _bookDom.RemoveMetaElement("xmatter"); } } if (directoryPath == null) { _nameOfXMatterPack = xmatterNameFromCollectionSettings; directoryPath = GetXMatterDirectory(_nameOfXMatterPack, fileLocator, "It should not be possible to get an error here, because the collection verifies its xmatter name in CheckAndFixDependencies()", true); } var htmName = _nameOfXMatterPack + "-XMatter.html"; PathToXMatterHtml = directoryPath.CombineForPath(htmName); if (!RobustFile.Exists(PathToXMatterHtml)) { htmName = _nameOfXMatterPack + "-XMatter.htm"; // pre- Bloom 3.7 PathToXMatterHtml = directoryPath.CombineForPath(htmName); } if (!RobustFile.Exists(PathToXMatterHtml)) { ErrorReport.NotifyUserOfProblem(new ShowOncePerSessionBasedOnExactMessagePolicy(), "Could not locate the file {0} in {1} (also checked .html)", htmName, directoryPath); throw new ApplicationException(); } PathToXMatterStylesheet = directoryPath.CombineForPath(GetStyleSheetFileName()); if (!RobustFile.Exists(PathToXMatterHtml)) { ErrorReport.NotifyUserOfProblem(new ShowOncePerSessionBasedOnExactMessagePolicy(), "Could not locate the file {0} in {1}", GetStyleSheetFileName(), directoryPath); throw new ApplicationException(); } XMatterDom = XmlHtmlConverter.GetXmlDomFromHtmlFile(PathToXMatterHtml, false); }
/// <summary> /// Propagating the copyright and license information in the bloomDataDiv to template fields /// found in the pages of the book (normally just the credits page). /// </summary> /// <remarks>This is "internal" just as a convention, that it is accessible for testing purposes only</remarks> internal static void UpdateDomFromDataDiv(HtmlDom dom, string bookFolderPath, BookData bookData, bool useOriginalCopyright) { CopyItemToFieldsInPages(dom, "copyright"); CopyItemToFieldsInPages(dom, "licenseUrl"); CopyItemToFieldsInPages(dom, "licenseDescription", languagePreferences: bookData.GetLanguagePrioritiesForLocalizedTextOnPage().ToArray()); CopyItemToFieldsInPages(dom, "licenseNotes"); CopyItemToFieldsInPages(dom, "licenseImage", valueAttribute: "src"); // If we're using the original copyright, we don't need to show it separately. // See https://issues.bloomlibrary.org/youtrack/issue/BL-7381. CopyStringToFieldsInPages(dom, "originalCopyrightAndLicense", useOriginalCopyright ? null : GetOriginalCopyrightAndLicenseNotice(bookData, dom), "*"); if (!String.IsNullOrEmpty(bookFolderPath)) //unit tests may not be interested in checking this part { UpdateBookLicenseIcon(GetMetadata(dom, bookData), bookFolderPath); } }
private static void CopyItemToFieldsInPages(HtmlDom dom, string key, string valueAttribute = null, string[] languagePreferences = null) { if (languagePreferences == null) { languagePreferences = new[] { "*", "en" } } ; MultiTextBase source = dom.GetBookSetting(key); foreach (XmlElement target in dom.SafeSelectNodes("//*[@data-derived='" + key + "']")) { //just put value into the text of the element if (string.IsNullOrEmpty(valueAttribute)) { //clear out what's there now target.RemoveAttribute("lang"); target.InnerText = ""; var form = source.GetBestAlternative(languagePreferences); if (form != null && !string.IsNullOrWhiteSpace(form.Form)) { // HtmlDom.GetBookSetting(key) returns the result of XmlNode.InnerXml which will be Html encoded (& < etc). // HtmlDom.SetElementFromUserStringPreservingLineBreaks() calls XmlNode.InnerText, which Html encodes if necessary. // So we need to decode here to prevent double encoding. See http://issues.bloomlibrary.org/youtrack/issue/BL-4585. // Note that HtmlDom.SetElementFromUserStringPreservingLineBreaks() handles embedded <br/> elements, but makes no // effort to handle p or div elements. var decoded = System.Web.HttpUtility.HtmlDecode(form.Form); HtmlDom.SetElementFromUserStringPreservingLineBreaks(target, decoded); target.SetAttribute("lang", form.WritingSystemId); //this allows us to set the font to suit the language } } else //Put the value into an attribute. The license image goes through this path. { target.SetAttribute(valueAttribute, source.GetBestAlternativeString(languagePreferences)); if (source.Empty) { //if the license image is empty, make sure we don't have some alternative text //about the image being missing or slow to load target.SetAttribute("alt", ""); //over in javascript land, @alt will get set appropriately when the image url is not empty. } } } }
public static void TransformCreditPageData(HtmlDom dom, BookData bookData, CollectionSettings collectionSettings, BookStorage storage, bool makingTranslation) { // If we're deriving a translation from an existing book, // we should save the original copyright and license of that book. if (makingTranslation) { SetOriginalCopyrightAndLicense(dom, bookData, collectionSettings); } // a new book should never have the copyright holder set, whether it's a template, shell, or translation bookData.RemoveAllForms("copyright"); // RemoveAllForms does modify the dom storage.BookInfo.Copyright = null; // this might be redundant but let's play safe // This is a place to put who it was translated by, usually in a national language. // Doesn't apply to templates or (usually) to shells; but a translation can serve again as a shell. // In that case, we expect it to be filled in with the new translator's information. // Keeping the previous translator's details there is confusing (BL-6271) bookData.RemoveAllForms("versionAcknowledgments"); }
public void BringBookUpToDate_CoverImageHasMetaData_HtmlForCoverPageHasMetaDataAttributes() { _bookDom = new HtmlDom(@" <html> <body> <div id='bloomDataDiv'> <div data-book='coverImage'>test.png</div> </div> </body> </html>"); var book = CreateBook(); var imagePath = book.FolderPath.CombineForPath("test.png"); MakeSamplePngImageWithMetadata(imagePath); book.BringBookUpToDate(new NullProgress()); AssertThatXmlIn.Dom(book.RawDom).HasSpecifiedNumberOfMatchesForXpath("//div/div/div/img[@data-creator='joe']",1); }
private static void CopyItemToFieldsInPages(HtmlDom dom, string key, string valueAttribute = null, string[] languagePreferences = null) { if (languagePreferences == null) { languagePreferences = new[] { "*", "en" } } ; MultiTextBase source = dom.GetBookSetting(key); var target = dom.SelectSingleNode("//*[@data-derived='" + key + "']"); if (target == null) { return; } //just put value into the text of the element if (string.IsNullOrEmpty(valueAttribute)) { //clear out what's there now target.RemoveAttribute("lang"); target.InnerText = ""; var form = source.GetBestAlternative(languagePreferences); if (form != null && !string.IsNullOrWhiteSpace(form.Form)) { HtmlDom.SetElementFromUserStringPreservingLineBreaks(target, form.Form); target.SetAttribute("lang", form.WritingSystemId); //this allows us to set the font to suit the language } } else //Put the value into an attribute. The license image goes through this path. { target.SetAttribute(valueAttribute, source.GetBestAlternativeString(languagePreferences)); if (source.Empty) { //if the license image is empty, make sure we don't have some alternative text //about the image being missing or slow to load target.SetAttribute("alt", ""); //over in javascript land, @alt will get set appropriately when the image url is not empty. } } }
public static void UpdateBook(HtmlDom dom, string language1Iso639Code) { int page = 0; foreach (XmlElement pageDiv in dom.SafeSelectNodes("/html/body//div[contains(@class,'bloom-page')]")) { var term = pageDiv.SelectSingleNode("//div[contains(@data-book,'term')]").InnerText.Trim(); XmlNode weekDataNode = pageDiv.SelectSingleNode("//div[contains(@data-book,'week')]"); if(weekDataNode==null) continue; // term intro books don't have weeks var week = weekDataNode.InnerText.Trim(); // TODO: need a better way to identify thumbnails, like a class that is always there, lest we replace some other img that we don't want to replace foreach (XmlElement thumbnailContainer in pageDiv.SafeSelectNodes(".//img")) { ++page; thumbnailContainer.SetAttribute("src", language1Iso639Code + "-t" + term + "-w" + week + "-p" + page + ".png"); } } }
private static void AddLocalizedHintContentsToDictionary(HtmlDom singlePageHtmlDom, Dictionary <string, string> dictionary, CollectionSettings collectionSettings) { /* Disabling this, generic data-hint localization at the moment, as it is interfering with the primary factory-supplied ones. * when we bring it back, lets think of ways to get nice ids in there that don't rely on the english. E.g., we could do * something like this: data-hint="[ColorBook.ColorPrompt]What color do you want?" and then we could take that id and prepend something * like "BookEdit.MiscBooks." so we end up with BookEdit.MiscBooks.ColorBook.ColorPrompt * * var nameOfXMatterPack = singlePageHtmlDom.GetMetaValue("xMatter", collectionSettings.XMatterPackName); * * * string idPrefix = ""; * var pageElement = singlePageHtmlDom.RawDom.SelectSingleNode("//div") as XmlElement; * if (XMatterHelper.IsFrontMatterPage(pageElement)) * { * idPrefix = "FrontMatter." + nameOfXMatterPack + "."; * } * else if (XMatterHelper.IsBackMatterPage(pageElement)) * { * idPrefix = "BackMatter." + nameOfXMatterPack + "."; * } * foreach (XmlElement element in singlePageHtmlDom.RawDom.SelectNodes("//*[@data-hint]")) * { * //why aren't we just doing: element.SetAttribute("data-hint", translation); instead of bothering to write out a dictionary? * //because (especially since we're currently just assuming it is in english), we would later save it with the translation, and then next time try to translate that, and poplute the * //list of strings that we tell people to translate * var key = element.GetAttribute("data-hint"); * if (!dictionary.ContainsKey(key)) * { * string translation; * var id = idPrefix + key; * if (key.Contains("{lang}")) * { * translation = LocalizationManager.GetDynamicString("Bloom", id, key, "Put {lang} in your translation, so it can be replaced by the language name."); * } * else * { * translation = LocalizationManager.GetDynamicString("Bloom", id, key); * } * dictionary.Add(key, translation); * } * } */ }
private static void CopyStringToFieldsInPages(HtmlDom dom, string key, string val, string lang) { foreach (XmlElement target in dom.SafeSelectNodes("//*[@data-derived='" + key + "']")) { if (target == null) // don't think this can happen, but something like it seemed to in one test... { continue; } if (string.IsNullOrEmpty(val)) { target.RemoveAttribute("lang"); target.InnerText = ""; } else { HtmlDom.SetElementFromUserStringSafely(target, val); target.SetAttribute("lang", lang); } } }
/// <summary> /// Constructs by finding the file and folder of the xmatter pack, given the its key name e.g. "Factory", "SILIndonesia". /// The name of the file should be (key)-XMatter.htm. The name and the location of the folder is not our problem... /// we leave it to the supplied fileLocator to find it. /// </summary> /// <param name="nameOfXMatterPack">e.g. "Factory", "SILIndonesia"</param> /// <param name="fileLocator">The locator needs to be able tell us the path to an xmater html file, given its name</param> public XMatterHelper(HtmlDom bookDom, string nameOfXMatterPack, IFileLocator fileLocator) { _bookDom = bookDom; _nameOfXMatterPack = nameOfXMatterPack; string directoryName = nameOfXMatterPack + "-XMatter"; string directoryPath; try { directoryPath = fileLocator.LocateDirectoryWithThrow(directoryName); } catch(ApplicationException error) { var errorTemplate = LocalizationManager.GetString("Errors.XMatterNotFound", "This Book called for Front/Back Matter pack named '{0}', but Bloom couldn't find that on this computer. You can either install a Bloom Pack that will give you '{0}', or go to Settings:Book Making and change to another Front/Back Matter Pack."); var msg = string.Format(errorTemplate, nameOfXMatterPack); ErrorReport.NotifyUserOfProblem(new ShowOncePerSessionBasedOnExactMessagePolicy(), msg); //NB: we don't want to put up a dialog for each one; one failure here often means 20 more are coming as the other books are loaded! throw new ApplicationException(msg); } var htmName = nameOfXMatterPack + "-XMatter.html"; PathToXMatterHtml = directoryPath.CombineForPath(htmName); if(!RobustFile.Exists(PathToXMatterHtml)) { htmName = nameOfXMatterPack + "-XMatter.htm"; // pre- Bloom 3.7 PathToXMatterHtml = directoryPath.CombineForPath(htmName); } if (!RobustFile.Exists(PathToXMatterHtml)) { ErrorReport.NotifyUserOfProblem(new ShowOncePerSessionBasedOnExactMessagePolicy(), "Could not locate the file {0} in {1} (also checked .html)", htmName, directoryPath); throw new ApplicationException(); } PathToStyleSheetForPaperAndOrientation = directoryPath.CombineForPath(GetStyleSheetFileName()); if (!RobustFile.Exists(PathToXMatterHtml)) { ErrorReport.NotifyUserOfProblem(new ShowOncePerSessionBasedOnExactMessagePolicy(), "Could not locate the file {0} in {1}", GetStyleSheetFileName(), directoryPath); throw new ApplicationException(); } XMatterDom = XmlHtmlConverter.GetXmlDomFromHtmlFile(PathToXMatterHtml, false); }
//TODO: make this be a real extension public static void UpdateBook(HtmlDom dom, string language1Iso639Code) { int day = 0; foreach (XmlElement pageDiv in dom.SafeSelectNodes("/html/body/div[contains(@class,'bloom-page')]")) { var term = pageDiv.SelectSingleNode("//div[contains(@data-book,'term')]").InnerText.Trim(); var week = pageDiv.SelectSingleNode("//div[contains(@data-book,'week')]").InnerText.Trim(); var thumbnailHolders = pageDiv.SafeSelectNodes(".//img"); if (thumbnailHolders.Count == 2) { ++day; ((XmlElement)thumbnailHolders[0]).SetAttribute("src", language1Iso639Code+"-t"+term + "-w" + week + "-d" + day + ".png"); ++day; ((XmlElement)thumbnailHolders[1]).SetAttribute("src", language1Iso639Code + "-t" + term + "-w" + week + "-d" + day + ".png"); } //day1Thumbnail day2Thumbnail day4Thumbnail //unfortunately Day3 went out with an img container just copied from day1, with erroneous "day1Thumbnail" class } }
private static void UpdateContentLanguageClassesOnElement(XmlElement e, Dictionary <string, string> contentLanguages, BookData bookData, string contentLanguageIso2, string contentLanguageIso3, string[] dataDefaultLanguages) { HtmlDom.RemoveClassesBeginingWith(e, "bloom-content"); var lang = e.GetAttribute("lang"); //These bloom-content* classes are used by some stylesheet rules, primarily to boost the font-size of some languages. //Enhance: this is too complex; the semantics of these overlap with each other and with bloom-visibility-code-on, and with data-language-order. //It would be better to have non-overlapping things; 1 for order, 1 for visibility, one for the lang's role in this collection. string orderClass; if (contentLanguages.TryGetValue(lang, out orderClass)) { HtmlDom.AddClass(e, orderClass); //bloom-content1, bloom-content2, bloom-content3 } //Enhance: it's even more likely that we can get rid of these by replacing them with bloom-content2, bloom-content3 if (lang == bookData.MetadataLanguage1IsoCode) { HtmlDom.AddClass(e, "bloom-contentNational1"); } // It's not clear that this class should be applied to blocks where lang == bookData.Language3IsoCode. // I (JohnT) added lang == bookData.MetadataLanguage2IsoCode while dealing with BL-10893 // but am reluctant to remove the old code as something might depend on it. I believe it is (nearly?) // always true that if we have Language3IsoCode at all, it will be equal to MetadataLanguage2IsoCode, // so at least for now it probably makes no difference. In our next major reworking of language codes, // hopefully we can make this distinction clearer and remove Language3IsoCode here. if (lang == bookData.Language3IsoCode || lang == bookData.MetadataLanguage2IsoCode) { HtmlDom.AddClass(e, "bloom-contentNational2"); } HtmlDom.RemoveClassesBeginingWith(e, "bloom-visibility-code"); if (ShouldNormallyShowEditable(lang, dataDefaultLanguages, contentLanguageIso2, contentLanguageIso3, bookData)) { HtmlDom.AddClass(e, "bloom-visibility-code-on"); } UpdateRightToLeftSetting(bookData, e, lang); }
/// <summary> /// Call this when we have a new set of metadata to use. It /// 1) sets the bloomDataDiv with the data, /// 2) causes any template fields in the book to get the new values /// 3) updates the license image on disk /// </summary> public static void SetMetadata(Metadata metadata, HtmlDom dom, string bookFolderPath, BookData bookData, bool useOriginalCopyright) { dom.SetBookSetting("copyright", "*", ConvertNewLinesToHtmlBreaks(metadata.CopyrightNotice)); dom.SetBookSetting("licenseUrl", "*", metadata.License.Url); // This is for backwards compatibility. The book may have licenseUrl in 'en' created by an earlier version of Bloom. // For backwards compatibility, GetMetaData will read that if it doesn't find a '*' license first. So now that we're // setting a licenseUrl for '*', we must make sure the 'en' one is gone, because if we're setting a non-CC license, // the new URL will be empty and the '*' one will go away, possibly exposing the 'en' one to be used by mistake. // See BL-3166. dom.SetBookSetting("licenseUrl", "en", null); string languageUsedForDescription; //This part is unfortunate... the license description, which is always localized, doesn't belong in the datadiv; it //could instead just be generated when we update the page. However, for backwards compatibility (prior to 3.6), //we localize it and place it in the datadiv. dom.RemoveBookSetting("licenseDescription"); var description = metadata.License.GetDescription(bookData.GetLanguagePrioritiesForLocalizedTextOnPage(), out languageUsedForDescription); dom.SetBookSetting("licenseDescription", languageUsedForDescription, ConvertNewLinesToHtmlBreaks(description)); // Book may have old licenseNotes, typically in 'en'. This can certainly show up again if licenseNotes in '*' is removed, // and maybe anyway. Safest to remove it altogether if we are setting it using the new scheme. dom.RemoveBookSetting("licenseNotes"); dom.SetBookSetting("licenseNotes", "*", ConvertNewLinesToHtmlBreaks(metadata.License.RightsStatement)); // we could do away with licenseImage in the bloomDataDiv, since the name is always the same, but we keep it for backward compatibility if (metadata.License is CreativeCommonsLicense) { dom.SetBookSetting("licenseImage", "*", "license.png"); } else { //CC licenses are the only ones we know how to show an image for dom.RemoveBookSetting("licenseImage"); } UpdateDomFromDataDiv(dom, bookFolderPath, bookData, useOriginalCopyright); }
/// <summary> /// Constructs by finding the file and folder of the xmatter pack, given the its key name e.g. "Factory", "SILIndonesia". /// The name of the file should be (key)-XMatter.htm. The name and the location of the folder is not our problem... /// we leave it to the supplied fileLocator to find it. /// </summary> /// <param name="nameOfXMatterPack">e.g. "Factory", "SILIndonesia"</param> /// <param name="fileLocator">The locator needs to be able tell use the path to an xmater html file, given its name</param> public XMatterHelper(HtmlDom bookDom, string nameOfXMatterPack, IFileLocator fileLocator) { _bookDom = bookDom; _nameOfXMatterPack = nameOfXMatterPack; string directoryName = nameOfXMatterPack + "-XMatter"; var directoryPath = fileLocator.LocateDirectory(directoryName, "xmatter pack directory named " + directoryName); string htmName = nameOfXMatterPack + "-XMatter.htm"; PathToXMatterHtml = directoryPath.CombineForPath(htmName); if(!File.Exists(PathToXMatterHtml)) { ErrorReport.NotifyUserOfProblem(new ShowOncePerSessionBasedOnExactMessagePolicy(), "Could not locate the file {0} in {1}", htmName, directoryPath); throw new ApplicationException(); } PathToStyleSheetForPaperAndOrientation = directoryPath.CombineForPath(GetStyleSheetFileName()); if (!File.Exists(PathToXMatterHtml)) { ErrorReport.NotifyUserOfProblem(new ShowOncePerSessionBasedOnExactMessagePolicy(), "Could not locate the file {0} in {1}", GetStyleSheetFileName(), directoryPath); throw new ApplicationException(); } XMatterDom = XmlHtmlConverter.GetXmlDomFromHtmlFile(PathToXMatterHtml, false); }
/// <summary> /// Adds a script to the page that triggers i18n after the page is fully loaded. /// </summary> /// <param name="pageDom"></param> private static void AddLocalizationTriggerToDom(HtmlDom pageDom) { XmlElement i18nScriptElement = pageDom.RawDom.SelectSingleNode("//script[@id='ui-i18n']") as XmlElement; if (i18nScriptElement != null) { i18nScriptElement.ParentNode.RemoveChild(i18nScriptElement); } i18nScriptElement = pageDom.RawDom.CreateElement("script"); i18nScriptElement.SetAttribute("type", "text/javascript"); i18nScriptElement.SetAttribute("id", "ui-i18n"); // Explanation of the JavaScript: // $(document).ready(function() {...}) tells the browser to run the code inside the braces after the document has completed loading. // $('body') is a jQuery function that selects the contents of the body tag. // .find('*[data-i18n]') instructs jQuery to return a collection of all elements inside the body tag that have a "data-i18n" attribute. // .localize() runs the jQuery.fn.localize() method, which loops through the above collection of elements and attempts to localize the text. i18nScriptElement.InnerText = "$(document).ready(function() { $('body').find('*[data-i18n]').localize(); });"; pageDom.Head.InsertAfter(i18nScriptElement, pageDom.Head.LastChild); }
private static void AddLocalizedHintContentsToDictionary(HtmlDom singlePageHtmlDom, Dictionary <string, string> dictionary, CollectionSettings collectionSettings) { var nameOfXMatterPack = singlePageHtmlDom.GetMetaValue("xMatter", collectionSettings.XMatterPackName); string idPrefix = ""; var pageElement = singlePageHtmlDom.RawDom.SelectSingleNode("//div") as XmlElement; if (XMatterHelper.IsFrontMatterPage(pageElement)) { idPrefix = "FrontMatter." + nameOfXMatterPack + "."; } else if (XMatterHelper.IsBackMatterPage(pageElement)) { idPrefix = "BackMatter." + nameOfXMatterPack + "."; } foreach (XmlElement element in singlePageHtmlDom.RawDom.SelectNodes("//*[@data-hint]")) { //why aren't we just doing: element.SetAttribute("data-hint", translation); instead of bothering to write out a dictionary? //because (especially since we're currently just assuming it is in english), we would later save it with the translation, and then next time try to translate that, and poplute the //list of strings that we tell people to translate var key = element.GetAttribute("data-hint"); if (!dictionary.ContainsKey(key)) { string translation; var id = idPrefix + key; if (key.Contains("{lang}")) { translation = LocalizationManager.GetDynamicString("Bloom", id, key, "Put {lang} in your translation, so it can be replaced by the language name."); } else { translation = LocalizationManager.GetDynamicString("Bloom", id, key); } dictionary.Add(key, translation); } } }
/// <summary> /// stick in a json with various settings we want to make available to the javascript /// </summary> public static void AddUISettingsToDom(HtmlDom pageDom, CollectionSettings collectionSettings, IFileLocator fileLocator) { XmlElement element = pageDom.RawDom.SelectSingleNode("//script[@id='ui-settings']") as XmlElement; if (element != null) element.ParentNode.RemoveChild(element); element = pageDom.RawDom.CreateElement("script"); element.SetAttribute("type", "text/javascript"); element.SetAttribute("id", "ui-settings"); var d = new Dictionary<string, string>(); //d.Add("urlOfUIFiles", "file:///" + fileLocator.LocateDirectory("ui", "ui files directory")); if (!String.IsNullOrEmpty(Settings.Default.LastSourceLanguageViewed)) { d.Add("defaultSourceLanguage", Settings.Default.LastSourceLanguageViewed); } d.Add("languageForNewTextBoxes", collectionSettings.Language1Iso639Code); d.Add("bloomBrowserUIFolder", FileLocator.GetDirectoryDistributedWithApplication("BloomBrowserUI")); var topics = new[] { "Agriculture", "Animal Stories", "Business", "Culture", "Community Living", "Dictionary", "Environment", "Fiction", "Health", "How To", "Math", "Non Fiction", "Spiritual", "Personal Development", "Primer", "Science", "Traditional Story" }; var builder = new StringBuilder(); builder.Append("["); foreach (var topic in topics) { var localized = LocalizationManager.GetDynamicString("Bloom", "Topics." + topic, topic, "shows in the topics chooser in the edit tab"); builder.Append("\""+localized+"\", "); } builder.Append("]"); d.Add("topics", builder.ToString().Replace(", ]","]")); // d.Add("topics", "['Agriculture', 'Animal Stories', 'Business', 'Culture', 'Community Living', 'Dictionary', 'Environment', 'Fiction', 'Health', 'How To', 'Math', 'Non Fiction', 'Spiritual', 'Personal Development', 'Primer', 'Science', 'Tradition']".Replace("'", "\\\"")); element.InnerText = String.Format("function GetSettings() {{ return {0};}}", JsonConvert.SerializeObject(d)); var head = pageDom.RawDom.SelectSingleNode("//head"); head.InsertAfter(element, head.LastChild); }
/// <summary> /// Find every place in the html file where an img element is nested inside a div with class bloom-imageContainer. /// Convert the img into a background image of the image container div. /// Specifically, make the following changes: /// - Copy any data-x attributes from the img element to the div /// - Convert the src attribute of the img to style="background-image:url('...')" (with the same source) on the div /// (any pre-existing style attribute on the div is lost) /// - Add the class bloom-backgroundImage to the div /// - delete the img element /// (See oldImg and newImg in unit test CompressBookForDevice_ImgInImgContainer_ConvertedToBackground for an example). /// </summary> /// <param name="wholeBookHtml"></param> /// <returns></returns> private static void ConvertImagesToBackground(XmlDocument dom) { foreach (var imgContainer in dom.SafeSelectNodes("//div[contains(@class, 'bloom-imageContainer')]").Cast <XmlElement>().ToArray()) { var img = imgContainer.ChildNodes.Cast <XmlNode>().FirstOrDefault(n => n is XmlElement && n.Name == "img"); if (img == null || img.Attributes["src"] == null) { continue; } // The filename should be already urlencoded since src is a url. var src = img.Attributes["src"].Value; HtmlDom.SetImageElementUrl(new ElementProxy(imgContainer), UrlPathString.CreateFromUrlEncodedString(src)); foreach (XmlAttribute attr in img.Attributes) { if (attr.Name.StartsWith("data-")) { imgContainer.SetAttribute(attr.Name, attr.Value); } } imgContainer.SetAttribute("class", imgContainer.Attributes["class"].Value + " bloom-backgroundImage"); imgContainer.RemoveChild(img); } }
/// <summary> /// Constructs by finding the file and folder of the xmatter pack, given the its key name e.g. "Factory", "SILIndonesia". /// The name of the file should be (key)-XMatter.htm. The name and the location of the folder is not our problem... /// we leave it to the supplied fileLocator to find it. /// </summary> /// <param name="nameOfXMatterPack">e.g. "Factory", "SILIndonesia"</param> /// <param name="fileLocator">The locator needs to be able tell use the path to an xmater html file, given its name</param> public XMatterHelper(HtmlDom bookDom, string nameOfXMatterPack, IFileLocator fileLocator) { _bookDom = bookDom; _nameOfXMatterPack = nameOfXMatterPack; string directoryName = nameOfXMatterPack + "-XMatter"; var directoryPath = fileLocator.LocateDirectory(directoryName, "xmatter pack directory named " + directoryName); string htmName = nameOfXMatterPack + "-XMatter.htm"; PathToXMatterHtml = directoryPath.CombineForPath(htmName); if (!File.Exists(PathToXMatterHtml)) { ErrorReport.NotifyUserOfProblem(new ShowOncePerSessionBasedOnExactMessagePolicy(), "Could not locate the file {0} in {1}", htmName, directoryPath); throw new ApplicationException(); } PathToStyleSheetForPaperAndOrientation = directoryPath.CombineForPath(GetStyleSheetFileName()); if (!File.Exists(PathToXMatterHtml)) { ErrorReport.NotifyUserOfProblem(new ShowOncePerSessionBasedOnExactMessagePolicy(), "Could not locate the file {0} in {1}", GetStyleSheetFileName(), directoryPath); throw new ApplicationException(); } XMatterDom = XmlHtmlConverter.GetXmlDomFromHtmlFile(PathToXMatterHtml, false); }
public static string GetMessageIfVersionIsIncompatibleWithThisBloom(HtmlDom dom) { //var dom = new HtmlDom(XmlHtmlConverter.GetXmlDomFromHtmlFile(path, false));//with throw if there are errors var versionString = dom.GetMetaValue("BloomFormatVersion", "").Trim(); if (string.IsNullOrEmpty(versionString)) { return(""); // "This file lacks the following required element: <meta name='BloomFormatVersion' content='x.y'>"; } float versionFloat = 0; if (!float.TryParse(versionString, out versionFloat)) { return("This file claims a version number that isn't really a number: " + versionString); } if (versionFloat > float.Parse(kBloomFormatVersion)) { return(string.Format("This book or template was made with a newer version of Bloom. Download the latest version of Bloom from bloomlibrary.org (format {0} vs. {1})", versionString, kBloomFormatVersion)); } return(null); }
public static void SetBaseForRelativePaths(HtmlDom dom, string folderPath, bool pointAtEmbeddedServer) { string path = ""; if (!string.IsNullOrEmpty(folderPath)) { if (pointAtEmbeddedServer && Settings.Default.ImageHandler == "http" && ImageServer.IsAbleToUsePort) { //this is only used by relative paths, and only img src's are left relative. //we are redirecting through our build-in httplistener in order to shrink //big images before giving them to gecko which has trouble with really hi-res ones var uri = folderPath + Path.DirectorySeparatorChar; uri = uri.Replace(":", "%3A"); uri = uri.Replace('\\', '/'); uri = ImageServer.PathEndingInSlash + uri; path = uri; } else { path = "file://" + folderPath + Path.DirectorySeparatorChar; } } dom.SetBaseForRelativePaths(path); }
/// <summary> /// Copy the copyright & license info to the originalCopyrightAndLicense, /// then remove the copyright so the translator can put in their own if they /// want. We retain the license, but the translator is allowed to change that. /// If the source is already a translation (already has original copyright or license) /// we keep them unchanged. /// </summary> public static void SetOriginalCopyrightAndLicense(HtmlDom dom, BookData bookData, CollectionSettings collectionSettings) { // If it already has some of this information, just keep it. if (bookData.BookIsDerivative()) { return; //leave the original there. } // If there's no copyright information in a source-collection book, we're presumably making // a new original book, and shouldn't try to record any original copyright and license information. // This is somewhat redundant with the check in BookStarter.SetupNewDocumentContents(), the one // non-unit-test current caller of this method, that doesn't call this at all if the source is // a template book. I was trying for a minimal reasonable change for BL-5131, and therefore // put in this extra check, since previously this method was simply NEVER called in a source // collection. var copyrightNotice = BookCopyrightAndLicense.GetMetadata(dom).CopyrightNotice; if (String.IsNullOrEmpty(copyrightNotice) && collectionSettings.IsSourceCollection) { return; } bookData.Set("originalLicenseUrl", BookCopyrightAndLicense.GetLicenseUrl(dom), "*"); bookData.Set("originalCopyright", System.Web.HttpUtility.HtmlEncode(copyrightNotice), "*"); bookData.Set("originalLicenseNotes", dom.GetBookSetting("licenseNotes").GetFirstAlternative(), "*"); }
// NB: make sure you assigned HtmlDom.BaseForRelativePaths if the temporary document might // contain references to files in the directory of the original HTML file it is derived from, // 'cause that provides the information needed // to fake out the browser about where the 'file' is so internal references work. public void Navigate(HtmlDom htmlDom, HtmlDom htmlEditDom = null, bool setAsCurrentPageForDebugging = false) { if (InvokeRequired) { Invoke(new Action<HtmlDom, HtmlDom, bool>(Navigate), htmlDom, htmlEditDom, setAsCurrentPageForDebugging); return; } XmlDocument dom = htmlDom.RawDom; XmlDocument editDom = htmlEditDom == null ? null : htmlEditDom.RawDom; _rootDom = dom;//.CloneNode(true); //clone because we want to modify it a bit _pageEditDom = editDom ?? dom; XmlHtmlConverter.MakeXmlishTagsSafeForInterpretationAsHtml(dom); var fakeTempFile = EnhancedImageServer.MakeSimulatedPageFileInBookFolder(htmlDom, setAsCurrentPageForDebugging: setAsCurrentPageForDebugging); SetNewDependent(fakeTempFile); _url = fakeTempFile.Key; UpdateDisplay(); }
/// <summary> /// Internal for testing because it's not yet clear this is the appropriate public routine. /// Probably some API gets a list of BloomInfo objects from the parse.com data, and we pass one of /// them as the argument for the public method. /// </summary> /// <param name="bucket"></param> /// <param name="s3BookId"></param> /// <param name="dest"></param> /// <returns></returns> internal string DownloadBook(string bucket, string s3BookId, string dest) { var destinationPath = _s3Client.DownloadBook(bucket, s3BookId, dest, _progressDialog); if (BookDownLoaded != null) { var bookInfo = new BookInfo(destinationPath, false); // A downloaded book is a template, so never editable. BookDownLoaded(this, new BookDownloadedEventArgs() {BookDetails = bookInfo}); } // Books in the library should generally show as locked-down, so new users are automatically in localization mode. // Occasionally we may want to upload a new authoring template, that is, a 'book' that is suitableForMakingShells. // Such books should not be locked down. // So, we try to lock it. What we want to do is Book.RecordedAsLockedDown = true; Book.Save(). // But all kinds of things have to be set up before we can create a Book. So we duplicate a few bits of code. var htmlFile = BookStorage.FindBookHtmlInFolder(destinationPath); if (htmlFile == "") return destinationPath; //argh! we can't lock it. var xmlDomFromHtmlFile = XmlHtmlConverter.GetXmlDomFromHtmlFile(htmlFile, false); var dom = new HtmlDom(xmlDomFromHtmlFile); if (!BookMetaData.FromString(MetaDataText(destinationPath)).IsSuitableForMakingShells) { Book.Book.RecordAsLockedDown(dom, true); XmlHtmlConverter.SaveDOMAsHtml5(dom.RawDom, htmlFile); } return destinationPath; }
public string UploadBook(string bookFolder, IProgress progress, out string parseId, string pdfToInclude = null) { // Books in the library should generally show as locked-down, so new users are automatically in localization mode. // Occasionally we may want to upload a new authoring template, that is, a 'book' that is suitableForMakingShells. // Such books must never be locked. // So, typically we will try to lock it. What we want to do is Book.RecordedAsLockedDown = true; Book.Save(). // But all kinds of things have to be set up before we can create a Book. So we duplicate a few bits of code. var htmlFile = BookStorage.FindBookHtmlInFolder(bookFolder); bool wasLocked = false; bool allowLocking = false; HtmlDom domForLocking = null; var metaDataText = MetaDataText(bookFolder); var metadata = BookMetaData.FromString(metaDataText); if (!string.IsNullOrEmpty(htmlFile)) { var xmlDomFromHtmlFile = XmlHtmlConverter.GetXmlDomFromHtmlFile(htmlFile, false); domForLocking = new HtmlDom(xmlDomFromHtmlFile); wasLocked = Book.Book.HtmlHasLockedDownFlag(domForLocking); allowLocking = !metadata.IsSuitableForMakingShells; if (allowLocking && !wasLocked) { Book.Book.RecordAsLockedDown(domForLocking, true); XmlHtmlConverter.SaveDOMAsHtml5(domForLocking.RawDom, htmlFile); } } string s3BookId; try { // In case we somehow have a book with no ID, we must have one to upload it. if (string.IsNullOrEmpty(metadata.Id)) { metadata.Id = Guid.NewGuid().ToString(); } // And similarly it should have SOME title. if (string.IsNullOrEmpty(metadata.Title)) { metadata.Title = Path.GetFileNameWithoutExtension(bookFolder); } metadata.SetUploader(UserId); s3BookId = S3BookId(metadata); metadata.DownloadSource = s3BookId; // Any updated ID at least needs to become a permanent part of the book. // The file uploaded must also contain the correct DownloadSource data, so that it can be used // as an 'order' to download the book. // It simplifies unit testing if the metadata file is also updated with the uploadedBy value. // Not sure if there is any other reason to do it (or not do it). // For example, do we want to send/receive who is the latest person to upload? metadata.WriteToFolder(bookFolder); // The metadata is also a book order...but we need it on the server with the desired file name, // because we can't rename on download. The extension must be the one Bloom knows about, // and we want the file name to indicate which book, so use the name of the book folder. var metadataPath = BookMetaData.MetaDataPath(bookFolder); var orderPath = Path.Combine(bookFolder, Path.GetFileName(bookFolder) + BookOrderExtension); RobustFile.Copy(metadataPath, orderPath, true); parseId = ""; try { _s3Client.UploadBook(s3BookId, bookFolder, progress, pdfToInclude: pdfToInclude); metadata.BaseUrl = _s3Client.BaseUrl; metadata.BookOrder = _s3Client.BookOrderUrlOfRecentUpload; progress.WriteStatus(LocalizationManager.GetString("PublishTab.Upload.UploadingBookMetadata", "Uploading book metadata", "In this step, Bloom is uploading things like title, languages, and topic tags to the BloomLibrary.org database.")); // Do this after uploading the books, since the ThumbnailUrl is generated in the course of the upload. var response = _parseClient.SetBookRecord(metadata.WebDataJson); parseId = response.ResponseUri.LocalPath; int index = parseId.LastIndexOf('/'); parseId = parseId.Substring(index + 1); if (parseId == "books") { // For NEW books the response URL is useless...need to do a new query to get the ID. var json = _parseClient.GetSingleBookRecord(metadata.Id); parseId = json.objectId.Value; } // if (!UseSandbox) // don't make it seem like there are more uploads than their really are if this a tester pushing to the sandbox { Analytics.Track("UploadBook-Success", new Dictionary<string, string>() { { "url", metadata.BookOrder }, { "title", metadata.Title } }); } } catch (WebException e) { DisplayNetworkUploadProblem(e, progress); if (!UseSandbox) // don't make it seem like there are more upload failures than their really are if this a tester pushing to the sandbox Analytics.Track("UploadBook-Failure", new Dictionary<string, string>() { { "url", metadata.BookOrder }, { "title", metadata.Title }, { "error", e.Message } }); return ""; } catch (AmazonS3Exception e) { if (e.Message.Contains("The difference between the request time and the current time is too large")) { progress.WriteError(LocalizationManager.GetString("PublishTab.Upload.TimeProblem", "There was a problem uploading your book. This is probably because your computer is set to use the wrong timezone or your system time is badly wrong. See http://www.di-mgt.com.au/wclock/help/wclo_setsysclock.html for how to fix this.")); if (!UseSandbox) Analytics.Track("UploadBook-Failure-SystemTime"); } else { DisplayNetworkUploadProblem(e, progress); if (!UseSandbox) // don't make it seem like there are more upload failures than their really are if this a tester pushing to the sandbox Analytics.Track("UploadBook-Failure", new Dictionary<string, string>() { { "url", metadata.BookOrder }, { "title", metadata.Title }, { "error", e.Message } }); } return ""; } catch (AmazonServiceException e) { DisplayNetworkUploadProblem(e, progress); if (!UseSandbox) // don't make it seem like there are more upload failures than their really are if this a tester pushing to the sandbox Analytics.Track("UploadBook-Failure", new Dictionary<string, string>() { { "url", metadata.BookOrder }, { "title", metadata.Title }, { "error", e.Message } }); return ""; } catch (Exception e) { progress.WriteError(LocalizationManager.GetString("PublishTab.Upload.UploadProblemNotice", "There was a problem uploading your book. You may need to restart Bloom or get technical help.")); progress.WriteError(e.Message.Replace("{", "{{").Replace("}", "}}")); progress.WriteVerbose(e.StackTrace); if (!UseSandbox) // don't make it seem like there are more upload failures than their really are if this a tester pushing to the sandbox Analytics.Track("UploadBook-Failure", new Dictionary<string, string>() {{"url", metadata.BookOrder}, {"title", metadata.Title}, {"error", e.Message}}); return ""; } } finally { if (domForLocking != null && allowLocking && !wasLocked) { Book.Book.RecordAsLockedDown(domForLocking, false); XmlHtmlConverter.SaveDOMAsHtml5(domForLocking.RawDom, htmlFile); } } return s3BookId; }
public static Metadata GetOriginalMetadata(HtmlDom dom, BookData bookData) { return(CreateMetadata(dom.GetBookSetting("originalCopyright"), dom.GetBookSetting("originalLicenseUrl").GetExactAlternative("*"), dom.GetBookSetting("originalLicenseNotes"), bookData)); }
internal static string GetOriginalCopyrightAndLicenseNotice(BookData bookData, HtmlDom dom) { var originalMetadata = GetOriginalMetadata(dom, bookData); // As of BL-7898, we are using the existence of an original copyright/license to determine if we are working with a derivative. if (!IsDerivative(originalMetadata)) { return(null); } // The originalTitle strategy used here is not ideal. We would prefer to have a placeholder specifically for it // in both EditTab.FrontMatter.OriginalCopyrightSentence and EditTab.FrontMatter.OriginalHadNoCopyrightSentence. // But we don't want to require a new set of translations if we can avoid it. var encodedTitle = dom.GetBookSetting("originalTitle")?.GetExactAlternative("*"); var originalTitle = HttpUtility.HtmlDecode(encodedTitle); var titleCitation = "<cite data-book=\"originalTitle\"" + (string.IsNullOrEmpty(originalTitle) ? " class=\"missingOriginalTitle\">" : ">") + originalTitle + "</cite>"; var languagePriorityIdsNotLang1 = bookData.GetLanguagePrioritiesForLocalizedTextOnPage(false); var originalLicenseSentence = GetOriginalLicenseSentence(languagePriorityIdsNotLang1, originalMetadata.License, out string licenseOnly); var rawCopyright = originalMetadata.CopyrightNotice; // If we have all the pieces available, we want to use this one. // At the very least it's easier to localize into the format the language wants to use. var fullFormatString = LocalizationManager.GetString( "EditTab.FrontMatter.FullOriginalCopyrightLicenseSentence", "Adapted from original, {0}, {1}. Licensed under {2}.", "On the Credits page of a book being translated, Bloom shows the original copyright. {0} is original title, {1} is original copyright, and {2} is license information.", languagePriorityIdsNotLang1, out string langUsed); // The last condition here (langUsed ==...) is meant to detect if the string has been translated // into the current language or not. if (!string.IsNullOrEmpty(originalTitle) && !string.IsNullOrEmpty(rawCopyright) && !string.IsNullOrEmpty(licenseOnly) && langUsed == languagePriorityIdsNotLang1.First()) { return(string.Format(fullFormatString, titleCitation, rawCopyright, licenseOnly)); } var copyrightNotice = GetOriginalCopyrightSentence( languagePriorityIdsNotLang1, rawCopyright, titleCitation).Trim(); return((copyrightNotice + " " + originalLicenseSentence).Trim()); }
public static void LogMetdata(HtmlDom dom) { Logger.WriteEvent("LicenseUrl: " + dom.GetBookSetting("licenseUrl")); Logger.WriteEvent("LicenseNotes: " + dom.GetBookSetting("licenseNotes")); Logger.WriteEvent(""); }
public void GetThumbnailAsync(int width, int height, HtmlDom dom,Action<Image> onReady ,Action<Exception> onError) { var thumbnailOptions = new HtmlThumbNailer.ThumbnailOptions() { BackgroundColor = Color.White, BorderStyle = HtmlThumbNailer.ThumbnailOptions.BorderStyles.None, CenterImageUsingTransparentPadding = false, Height = height, Width = width }; dom.UseOriginalImages = true; // apparently these thumbnails can be big...anyway we want printable images. _thumbNailer.HtmlThumbNailer.GetThumbnailAsync(String.Empty, string.Empty, dom, thumbnailOptions,onReady, onError); }
private static void EnsureIdsAreUnique(HtmlDom dom, string elementTag, List<string> ids, StringBuilder builder) { foreach(XmlElement element in dom.SafeSelectNodes("//" + elementTag + "[@id]")) { var id = element.GetAttribute("id"); if(ids.Contains(id)) builder.AppendLine("The id of this " + elementTag + " must be unique, but is not: " + element.OuterXml); else ids.Add(id); } }
public static void AddStylesheetFromAnotherBook(HtmlDom sourceBookDom, HtmlDom targetBookDom) { var addedModifiedStyleSheets = new List<string>(); //This was refactored from book, where there was these notes: // NB: at this point this code can't handle the "userModifiedStyles" from children, it'll ignore them (they would conflict with each other) // NB: at this point custom styles (e.g. larger/smaller font rules) from children will be lost. //At this point, this addedModifiedStyleSheets is just used as a place to track which stylesheets we already have foreach(string sheetName in sourceBookDom.GetTemplateStyleSheets()) { if(!addedModifiedStyleSheets.Contains(sheetName)) //nb: if two books have stylesheets with the same name, we'll only be grabbing the 1st one. { addedModifiedStyleSheets.Add(sheetName); targetBookDom.AddStyleSheetIfMissing(sheetName); } } }
public void UpdatePageToTemplate(HtmlDom pageDom, XmlElement templatePageDiv, string pageId) { var pageDiv = pageDom.SafeSelectNodes("//body/div[@id='" + pageId + "']").Cast<XmlElement>().FirstOrDefault(); if(pageDiv != null) { var idAttr = templatePageDiv.Attributes["id"]; var templateId = idAttr == null ? "" : idAttr.Value; var oldLineage = MigrateEditableData(pageDiv, templatePageDiv, templateId); var props = new Dictionary<string, string>(); props["newLayout"] = templateId; props["oldLineage"] = oldLineage; Analytics.Track("Change Page Layout", props); } }