/// <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); }
/// <summary> /// In some cases, we're better off copying from another national language than leaving the field empty. /// </summary> /// <remarks> /// This is a tough decision. Without this, if we have, say, an English Contributors list but English isn't the N1 (L2), then the /// book won't show it at all. An ideal solution would just order them and then "display the first non-empty one", but that would require some java script... not /// something could be readily done in CSS, far as I can think. /// For now, I *think* this won't do any harm, and if it does, it's adding data, not losing it. Users had complained about "losing" the contributor data before. ///</remarks> private string PossiblyCopyFromAnotherLanguage(XmlElement element, string languageCodeOfTargetField, DataSet data, string key) { string classes = element.GetAttribute("class"); if (!string.IsNullOrEmpty(classes)) { // if this field is normally read-only, make it readable so they can do any translation that might be needed element.SetAttribute("class", classes.Replace("bloom-readOnlyInTranslationMode", "")); } if (!classes.Contains("bloom-copyFromOtherLanguageIfNecessary")) { return ""; } LanguageForm formToCopyFromSinceOursIsMissing = null; string s = ""; if ((languageCodeOfTargetField == _collectionSettings.Language2Iso639Code || //is it a national language? languageCodeOfTargetField == _collectionSettings.Language3Iso639Code)) { formToCopyFromSinceOursIsMissing = data.TextVariables[key].TextAlternatives.GetBestAlternative(new[] {languageCodeOfTargetField, "*", "en", "fr", "es", "pt"}); if (formToCopyFromSinceOursIsMissing != null) s = formToCopyFromSinceOursIsMissing.Form; if (string.IsNullOrEmpty(s)) { //OK, well even on a non-global language is better than none //s = data.TextVariables[key].TextAlternatives.GetFirstAlternative(); formToCopyFromSinceOursIsMissing = GetFirstAlternativeForm(data.TextVariables[key].TextAlternatives); if (formToCopyFromSinceOursIsMissing != null) s = formToCopyFromSinceOursIsMissing.Form; } } /* this was a fine idea, execpt that if the user then edits it, well, it's not borrowed anymore but we'll still have this sitting there misleading us //record our dubious deed for posterity if (formToCopyFromSinceOursIsMissing != null) { node.SetAttribute("bloom-languageBloomHadToCopyFrom", formToCopyFromSinceOursIsMissing.WritingSystemId); } */ return s; }
/// <summary> /// Where, for example, somewhere on a page something has data-book='foo' lan='fr', /// we set the value of that element to French subvalue of the data item 'foo', if we have one. /// </summary> private void UpdateDomFromDataSet(DataSet data, string elementName,XmlDocument targetDom) { try { string query = String.Format("//{0}[(@data-book or @data-collection or @data-library)]", elementName); XmlNodeList nodesOfInterest = targetDom.SafeSelectNodes(query); foreach (XmlElement node in nodesOfInterest) { string key = node.GetAttribute("data-book").Trim(); if (key == String.Empty) { key = node.GetAttribute("data-collection").Trim(); if(key==string.Empty) { key = node.GetAttribute("data-library").Trim(); //"library" is the old name for what is now "collection" } } if (!String.IsNullOrEmpty(key) && data.TextVariables.ContainsKey(key)) { if (node.Name.ToLower() == "img") { string imageName = WebUtility.HtmlDecode(data.TextVariables[key].TextAlternatives.GetFirstAlternative()); string oldImageName = WebUtility.HtmlDecode(node.GetAttribute("src")); node.SetAttribute("src", imageName); if (oldImageName != imageName) { Guard.AgainstNull(_updateImgNode, "_updateImgNode"); _updateImgNode(node); } } else { string lang = node.GetOptionalStringAttribute("lang", "*"); if (lang == "N1" || lang == "N2" || lang == "V") lang = data.WritingSystemAliases[lang]; // //see comment later about the inability to clear a value. TODO: when we re-write Bloom, make sure this is possible // if(data.TextVariables[key].TextAlternatives.Forms.Length==0) // { // //no text forms == desire to remove it. THe multitextbase prohibits empty strings, so this is the best we can do: completly remove the item. // targetDom.RemoveChild(node); // } // else if (!String.IsNullOrEmpty(lang)) //if we don't even have this language specified (e.g. no national language), the give up { //Ideally, we have this string, in this desired language. string s = data.TextVariables[key].TextAlternatives.GetBestAlternativeString(new []{lang, "*"}); //But if not, maybe we should copy one in from another national language if(string.IsNullOrEmpty(s)) s = PossiblyCopyFromAnotherLanguage(node, lang, data, key); //NB: this was the focus of a multi-hour bug search, and it's not clear that I got it right. //The problem is that the title page has N1 and n2 alternatives for title, the cover may not. //the gather page was gathering no values for those alternatives (why not), and so GetBestAlternativeSTring //was giving "", which we then used to remove our nice values. //REVIEW: what affect will this have in other pages, other circumstances. Will it make it impossible to clear a value? //Hoping not, as we are differentiating between "" and just not being in the multitext at all. //don't overwrite a datadiv alternative with empty just becuase this page has no value for it. if (s == "" && !data.TextVariables[key].TextAlternatives.ContainsAlternative(lang)) continue; //hack: until I think of a more elegant way to avoid repeating the language name in N2 when it's the exact same as N1... if (data.WritingSystemAliases.Count != 0 && lang == data.WritingSystemAliases["N2"] && s == data.TextVariables[key].TextAlternatives.GetBestAlternativeString(new[] { data. WritingSystemAliases ["N1"] , "*" })) { s = ""; //don't show it in N2, since it's the same as N1 } node.InnerXml = s; //meaning, we'll take "*" if you have it but not the exact choice. * is used for languageName, at least in dec 2011 } } } } } catch (Exception error) { throw new ApplicationException( "Error in MakeAllFieldsOfElementTypeConsistent(," + elementName + "). RawDom was:\r\n" + targetDom.OuterXml, error); } }
private static void SendDataToDebugConsole(DataSet data) { #if DEBUG foreach (var item in data.TextVariables) { foreach (LanguageForm form in item.Value.TextAlternatives.Forms) { Debug.WriteLine("Gathered: {0}[{1}]={2}", item.Key, form.WritingSystemId, form.Form); } } #endif }
/// <summary> /// walk throught the sourceDom, collecting up values from elements that have data-book or data-collection attributes. /// </summary> private void GatherDataItemsFromXElement(DataSet data, XmlNode sourceElement /* can be the whole sourceDom or just a page */) { string elementName = "*"; try { string query = String.Format(".//{0}[(@data-book or @data-library or @data-collection)]", elementName); XmlNodeList nodesOfInterest = sourceElement.SafeSelectNodes(query); foreach (XmlElement node in nodesOfInterest) { bool isCollectionValue = false; string key = node.GetAttribute("data-book").Trim(); if (key == String.Empty) { key = node.GetAttribute("data-collection").Trim(); if (key == String.Empty) { key = node.GetAttribute("data-library").Trim(); //the old (pre-version 1) name of collections was 'library' } isCollectionValue = true; } string value = node.InnerXml.Trim(); //may contain formatting if (node.Name.ToLower() == "img") { value = node.GetAttribute("src"); //Make the name of the image safe for showing up in raw html (not just in the relatively safe confines of the src attribut), //becuase it's going to show up between <div> tags. E.g. "Land & Water.png" as the cover page used to kill us. value = WebUtility.HtmlEncode(WebUtility.HtmlDecode(value)); } if (!String.IsNullOrEmpty(value) && !value.StartsWith("{")) //ignore placeholder stuff like "{Book Title}"; that's not a value we want to collect { string lang = node.GetOptionalStringAttribute("lang", "*"); if (lang == "") //the above doesn't stop a "" from getting through lang = "*"; if ((elementName.ToLower() == "textarea" || elementName.ToLower() == "input" || node.GetOptionalStringAttribute("contenteditable", "false") == "true") && (lang == "V" || lang == "N1" || lang == "N2")) { throw new ApplicationException( "Editable element (e.g. TextArea) should not have placeholder @lang attributes (V,N1,N2)\r\n\r\n" + node.OuterXml); } //if we don't have a value for this variable and this language, add it if (!data.TextVariables.ContainsKey(key)) { var t = new MultiTextBase(); t.SetAlternative(lang, value); data.TextVariables.Add(key, new NamedMutliLingualValue(t, isCollectionValue)); } else if (!data.TextVariables[key].TextAlternatives.ContainsAlternative(lang)) { MultiTextBase t = data.TextVariables[key].TextAlternatives; t.SetAlternative(lang, value); } } } } catch (Exception error) { throw new ApplicationException( "Error in GatherDataItemsFromDom(," + elementName + "). RawDom was:\r\n" + sourceElement.OuterXml, error); } }
// records key, lang pairs for which we found an empty element in the source. /// <summary> /// walk through the sourceDom, collecting up values from elements that have data-book or data-collection or data-book-attributes attributes. /// </summary> private void GatherDataItemsFromXElement(DataSet data, XmlNode sourceElement, // can be the whole sourceDom or just a page HashSet<Tuple<string, string>> itemsToDelete = null) { string elementName = "*"; try { string query = String.Format(".//{0}[(@data-book or @data-library or @data-collection or @data-book-attributes) and not(contains(@class,'bloom-writeOnly'))]", elementName); XmlNodeList nodesOfInterest = sourceElement.SafeSelectNodes(query); foreach (XmlElement node in nodesOfInterest) { bool isCollectionValue = false; string key = node.GetAttribute("data-book").Trim(); if (key == String.Empty) { key = node.GetAttribute("data-book-attributes").Trim(); if (key != String.Empty) { GatherAttributes(data, node, key); continue; } key = node.GetAttribute("data-collection").Trim(); if (key == String.Empty) { key = node.GetAttribute("data-library").Trim(); //the old (pre-version 1) name of collections was 'library' } isCollectionValue = true; } string value; if (HtmlDom.IsImgOrSomethingWithBackgroundImage(node)) { value = HtmlDom.GetImageElementUrl(new ElementProxy(node)).UrlEncoded; KeysOfVariablesThatAreUrlEncoded.Add(key); } else { var node1 = node.CloneNode(true); // so we can remove labels without modifying node // Datadiv content should be node content without labels. The labels are not really part // of the content we want to replicate, they are just information for the user, and // specific to one context. Also, including them causes them to get repeated in each location; // SetInnerXmlPreservingLabel() assumes data set content does not include label elements. var labels = node1.SafeSelectNodes(".//label").Cast<XmlElement>().ToList(); foreach (var label in labels) label.ParentNode.RemoveChild(label); value = node1.InnerXml.Trim(); //may contain formatting if(KeysOfVariablesThatAreUrlEncoded.Contains(key)) { value = UrlPathString.CreateFromHtmlXmlEncodedString(value).UrlEncoded; } } string lang = node.GetOptionalStringAttribute("lang", "*"); if (lang == "") //the above doesn't stop a "" from getting through lang = "*"; if (lang == "{V}") lang = _collectionSettings.Language1Iso639Code; if(lang == "{N1}") lang = _collectionSettings.Language2Iso639Code; if(lang == "{N2}") lang = _collectionSettings.Language3Iso639Code; if (string.IsNullOrEmpty(value)) { // This is a value we may want to delete if (itemsToDelete != null) itemsToDelete.Add(Tuple.Create(key, lang)); } else if (!value.StartsWith("{")) //ignore placeholder stuff like "{Book Title}"; that's not a value we want to collect { if ((elementName.ToLowerInvariant() == "textarea" || elementName.ToLowerInvariant() == "input" || node.GetOptionalStringAttribute("contenteditable", "false") == "true") && (lang == "V" || lang == "N1" || lang == "N2")) { throw new ApplicationException( "Editable element (e.g. TextArea) should not have placeholder @lang attributes (V,N1,N2)\r\n\r\n" + node.OuterXml); } //if we don't have a value for this variable and this language, add it if (!data.TextVariables.ContainsKey(key)) { var t = new MultiTextBase(); t.SetAlternative(lang, value); data.TextVariables.Add(key, new NamedMutliLingualValue(t, isCollectionValue)); } else if (!data.TextVariables[key].TextAlternatives.ContainsAlternative(lang)) { MultiTextBase t = data.TextVariables[key].TextAlternatives; t.SetAlternative(lang, value); } } if (KeysOfVariablesThatAreUrlEncoded.Contains(key)) { Debug.Assert(!value.Contains("&"), "In memory, all image urls should be encoded such that & is just &."); } } } catch (Exception error) { throw new ApplicationException( "Error in GatherDataItemsFromDom(," + elementName + "). RawDom was:\r\n" + sourceElement.OuterXml, error); } }
private static DataSet GatherDataItemsFromCollectionSettings(CollectionSettings collectionSettings) { var data = new DataSet(); data.WritingSystemAliases.Add("N1", collectionSettings.Language2Iso639Code); data.WritingSystemAliases.Add("N2", collectionSettings.Language3Iso639Code); // if (makeGeneric) // { // data.WritingSystemCodes.Add("V", collectionSettings.Language2Iso639Code); // //This is not an error; we don't want to use the verncular when we're just previewing a book in a non-verncaulr collection // data.AddGenericLanguageString("iso639Code", collectionSettings.Language1Iso639Code, true); // //review: maybe this should be, like 'xyz" // data.AddGenericLanguageString("nameOfLanguage", "(Your Language Name)", true); // data.AddGenericLanguageString("nameOfNationalLanguage1", "(Region Lang)", true); // data.AddGenericLanguageString("nameOfNationalLanguage2", "(National Lang)", true); // data.AddGenericLanguageString("country", "Your Country", true); // data.AddGenericLanguageString("province", "Your Province", true); // data.AddGenericLanguageString("district", "Your District", true); // data.AddGenericLanguageString("languageLocation", "(Language Location)", true); // } // else { data.WritingSystemAliases.Add("V", collectionSettings.Language1Iso639Code); data.AddLanguageString("nameOfLanguage", collectionSettings.Language1Name, "*", true); data.AddLanguageString("nameOfNationalLanguage1", collectionSettings.GetLanguage2Name(collectionSettings.Language2Iso639Code), "*", true); data.AddLanguageString("nameOfNationalLanguage2", collectionSettings.GetLanguage3Name(collectionSettings.Language2Iso639Code), "*", true); data.UpdateGenericLanguageString("iso639Code", collectionSettings.Language1Iso639Code, true); data.UpdateGenericLanguageString("country", collectionSettings.Country, true); data.UpdateGenericLanguageString("province", collectionSettings.Province, true); data.UpdateGenericLanguageString("district", collectionSettings.District, true); string location = ""; if (!String.IsNullOrEmpty(collectionSettings.District)) location += collectionSettings.District + @", "; if (!String.IsNullOrEmpty(collectionSettings.Province)) location += collectionSettings.Province; location = location.TrimEnd(new[] {' '}).TrimEnd(new[] {','}); if (!String.IsNullOrEmpty(collectionSettings.Country)) { location += "<br/>" + collectionSettings.Country; } data.UpdateGenericLanguageString("languageLocation", location, true); } return data; }
/// <summary> /// Given a node in the content section of the book that has a data-book attribute, see /// if this node holds an image and if so, look up the url of the image from the supplied /// dataset and stick it in there. Handle both img elements and divs that have a /// background-image in an inline style attribute. /// /// At the time of this writing, the only image that is handled here is the cover page. /// The URLs of images in the content of the book are not known to the data-div. /// But each time the book is loaded up, we collect up data from the xmatter and stick /// it in the data-div(in the DOM) / dataSet (in an object here in code), stick in a /// blank xmatter, then push the values back into the xmatter. /// </summary> /// <returns>true if this node is an image holder of some sort.</returns> private bool UpdateImageFromDataSet(DataSet data, XmlElement node, string key) { if (!HtmlDom.IsImgOrSomethingWithBackgroundImage(node)) return false; var newImageUrl = UrlPathString.CreateFromUrlEncodedString(data.TextVariables[key].TextAlternatives.GetFirstAlternative()); var oldImageUrl = HtmlDom.GetImageElementUrl(node); var imgOrDivWithBackgroundImage = new ElementProxy(node); HtmlDom.SetImageElementUrl(imgOrDivWithBackgroundImage,newImageUrl); if (!newImageUrl.Equals(oldImageUrl)) { Guard.AgainstNull(_updateImgNode, "_updateImgNode"); try { _updateImgNode(node); } catch (TagLib.CorruptFileException e) { NonFatalProblem.Report(ModalIf.Beta, PassiveIf.All, "Problem reading image metadata", newImageUrl.NotEncoded, e); return false; } } return true; }
public Metadata GetLicenseMetadata() { var data = new DataSet(); GatherDataItemsFromXElement(data, _dom.RawDom); var metadata = new Metadata(); NamedMutliLingualValue d; if (data.TextVariables.TryGetValue("copyright", out d)) { metadata.CopyrightNotice = WebUtility.HtmlDecode(d.TextAlternatives.GetFirstAlternative()); } string licenseUrl = ""; if (data.TextVariables.TryGetValue("licenseUrl", out d)) { licenseUrl = d.TextAlternatives.GetFirstAlternative(); } //Enhance: have a place for notes (amendments to license). It's already in the frontmatter, under "licenseNotes" if (licenseUrl == null || licenseUrl.Trim() == "") { //NB: we are mapping "RightsStatement" (which comes from XMP-dc:Rights) to "LicenseNotes" in the html. //custom licenses live in this field if (data.TextVariables.TryGetValue("licenseNotes", out d)) { string licenseNotes = d.TextAlternatives.GetFirstAlternative(); metadata.License = new CustomLicense {RightsStatement = licenseNotes}; } else { //how to detect a null license was chosen? We're using the fact that it has a description, but nothing else. if (data.TextVariables.TryGetValue("licenseDescription", out d)) { metadata.License = new NullLicense(); //"contact the copyright owner } else { //looks like the first time. Nudge them with a nice default metadata.License = new CreativeCommonsLicense(true, true, CreativeCommonsLicense.DerivativeRules.Derivatives); } } } else { metadata.License = CreativeCommonsLicense.FromLicenseUrl(licenseUrl); } return metadata; }
private void UpdateAttributes(DataSet data, XmlElement node, string key) { List<KeyValuePair<string, string>> attributes; if (!data.Attributes.TryGetValue(key, out attributes)) return; foreach (var attribute in attributes) node.SetAttribute(attribute.Key, attribute.Value); }
/// <summary> /// Where, for example, somewhere on a page something has data-book='foo' lang='fr', /// we set the value of that element to French subvalue of the data item 'foo', if we have one. /// </summary> private void UpdateDomFromDataSet(DataSet data, string elementName,XmlDocument targetDom, HashSet<Tuple<string, string>> itemsToDelete) { try { var query = String.Format("//{0}[(@data-book or @data-collection or @data-library or @data-book-attributes)]", elementName); var nodesOfInterest = targetDom.SafeSelectNodes(query); foreach (XmlElement node in nodesOfInterest) { var key = node.GetAttribute("data-book").Trim(); if (key == string.Empty) { key = node.GetAttribute("data-book-attributes").Trim(); if (key != string.Empty) { UpdateAttributes(data, node, key); continue; } key = node.GetAttribute("data-collection").Trim(); if (key == string.Empty) { key = node.GetAttribute("data-library").Trim(); //"library" is the old name for what is now "collection" } } if (string.IsNullOrEmpty(key)) continue; if (data.TextVariables.ContainsKey(key)) { if (UpdateImageFromDataSet(data, node, key)) continue; var lang = node.GetOptionalStringAttribute("lang", "*"); if (lang == "N1" || lang == "N2" || lang == "V") lang = data.WritingSystemAliases[lang]; // //see comment later about the inability to clear a value. TODO: when we re-write Bloom, make sure this is possible // if(data.TextVariables[key].TextAlternatives.Forms.Length==0) // { // //no text forms == desire to remove it. THe multitextbase prohibits empty strings, so this is the best we can do: completly remove the item. // targetDom.RemoveChild(node); // } // else if (!string.IsNullOrEmpty(lang)) //if we don't even have this language specified (e.g. no national language), the give up { //Ideally, we have this string, in this desired language. var s = data.TextVariables[key].TextAlternatives.GetBestAlternativeString(new[] {lang, "*"}); if(KeysOfVariablesThatAreUrlEncoded.Contains(key)) { Debug.Assert(!s.Contains("&"),"In memory, all image urls should be encoded such that & is just &."); } //But if not, maybe we should copy one in from another national language if (string.IsNullOrEmpty(s)) s = PossiblyCopyFromAnotherLanguage(node, lang, data, key); //NB: this was the focus of a multi-hour bug search, and it's not clear that I got it right. //The problem is that the title page has N1 and n2 alternatives for title, the cover may not. //the gather page was gathering no values for those alternatives (why not), and so GetBestAlternativeSTring //was giving "", which we then used to remove our nice values. //REVIEW: what affect will this have in other pages, other circumstances. Will it make it impossible to clear a value? //Hoping not, as we are differentiating between "" and just not being in the multitext at all. //don't overwrite a datadiv alternative with empty just becuase this page has no value for it. if (s == "" && !data.TextVariables[key].TextAlternatives.ContainsAlternative(lang)) continue; //hack: until I think of a more elegant way to avoid repeating the language name in N2 when it's the exact same as N1... if (data.WritingSystemAliases.Count != 0 && lang == data.WritingSystemAliases["N2"] && s == data.TextVariables[key].TextAlternatives.GetBestAlternativeString(new[] { data. WritingSystemAliases ["N1"] , "*" })) { s = ""; //don't show it in N2, since it's the same as N1 } SetInnerXmlPreservingLabel(key, node, s); } } else if (!HtmlDom.IsImgOrSomethingWithBackgroundImage(node)) { // See whether we need to delete something var lang = node.GetOptionalStringAttribute("lang", "*"); if (lang == "N1" || lang == "N2" || lang == "V") lang = data.WritingSystemAliases[lang]; if (itemsToDelete.Contains(Tuple.Create(key, lang))) { SetInnerXmlPreservingLabel(key, node, "");// a later process may remove node altogether. } } } } catch (Exception error) { throw new ApplicationException( "Error in UpdateDomFromDataSet(," + elementName + "). RawDom was:\r\n" + targetDom.OuterXml, error); } }
/// <summary> /// Topics are uni-directional value, react™-style. The UI tells the book to change the topic /// key, and then eventually the page/book is re-evaluated and the appropriate topic is displayed /// on the page. /// To differentiate from fields with @data-book, which are two-way, the topic on the page instead /// has a @data-derived attribute (in the data-div, it is still a data-book... perhaps that too could /// change to something like data-book-source, but it's not clear to me yet, so.. not yet). /// When the topic is changed, the javascript sends c# a message with the new English Key for the topic is set in the data-div, /// and then the page is re-computed. That leads to this method, which grabs the /// english topic (which serves as the 'key') from the datadiv. It then finds the placeholder /// for the topic and fills it with the best translation it can find. /// </summary> private void SetUpDisplayOfTopicInBook(DataSet data) { var topicPageElement = this._dom.SelectSingleNode("//div[@data-derived='topic']"); if (topicPageElement == null) { //old-style. here we don't have the data-derived, so we need to avoid picking from the datadiv topicPageElement = this._dom.SelectSingleNode("//div[not(id='bloomDataDiv')]//div[@data-book='topic']"); if (topicPageElement == null) { //most unit tests do not have complete books, so this not surprising. It just means we don't have anything to do return; } } //clear it out what's there now topicPageElement.RemoveAttribute("lang"); topicPageElement.InnerText = ""; NamedMutliLingualValue topicData; var parentOfTopicDisplayElement = ((XmlElement)(topicPageElement.ParentNode)); //this just lets us have css rules that vary if there is a topic (allows other text to be centered instead left-aligned) //we'll change it later if we find there is a topic parentOfTopicDisplayElement.SetAttribute("data-have-topic", "false"); //if we have no topic element in the data-div //leave the field in the page with an empty text. if (!data.TextVariables.TryGetValue("topic", out topicData)) { return; } //we use English as the "key" for topics. var englishTopic = topicData.TextAlternatives.GetExactAlternative("en"); //if we have no topic, just clear it out from the page if (string.IsNullOrEmpty(englishTopic) || englishTopic == "NoTopic") return; parentOfTopicDisplayElement.SetAttribute("data-have-topic", "true"); var stringId = "Topics." + englishTopic; //get the topic in the most prominent language for which we have a translation var langOfTopicToShowOnCover = _collectionSettings.Language1Iso639Code; if (LocalizationManager.GetIsStringAvailableForLangId(stringId, _collectionSettings.Language1Iso639Code)) { langOfTopicToShowOnCover = _collectionSettings.Language1Iso639Code; } else if (LocalizationManager.GetIsStringAvailableForLangId(stringId, _collectionSettings.Language2Iso639Code)) { langOfTopicToShowOnCover = _collectionSettings.Language2Iso639Code; } else if (LocalizationManager.GetIsStringAvailableForLangId(stringId, _collectionSettings.Language3Iso639Code)) { langOfTopicToShowOnCover = _collectionSettings.Language3Iso639Code; } else { langOfTopicToShowOnCover = "en"; } var bestTranslation = LocalizationManager.GetDynamicStringOrEnglish("Bloom", stringId, englishTopic, "this is a book topic", langOfTopicToShowOnCover); //NB: in a unit test environment, GetDynamicStringOrEnglish is going to give us the id back, which is annoying. if (bestTranslation == stringId) bestTranslation = englishTopic; topicPageElement.SetAttribute("lang", langOfTopicToShowOnCover); topicPageElement.InnerText = bestTranslation; }
/// <summary> /// In some cases, we're better off copying from another national language than leaving the field empty. /// </summary> /// <remarks> /// This is a tough decision. Without this, if we have, say, an English Contributors list but English isn't the N1 (L2), then the /// book won't show it at all. An ideal solution would just order them and then "display the first non-empty one", but that would require some java script... not /// something could be readily done in CSS, far as I can think. /// For now, I *think* this won't do any harm, and if it does, it's adding data, not losing it. Users had complained about "losing" the contributor data before. ///</remarks> private string PossiblyCopyFromAnotherLanguage(XmlElement element, string languageCodeOfTargetField, DataSet data, string key) { string classes = element.GetAttribute("class"); if (!string.IsNullOrEmpty(classes)) { // if this field is normally read-only, make it readable so they can do any translation that might be needed element.SetAttribute("class", classes.Replace("bloom-readOnlyInTranslationMode", "")); } if (!classes.Contains("bloom-copyFromOtherLanguageIfNecessary")) { return ""; } LanguageForm formToCopyFromSinceOursIsMissing = null; string s = ""; if ((languageCodeOfTargetField == _collectionSettings.Language2Iso639Code || //is it a national language? languageCodeOfTargetField == _collectionSettings.Language3Iso639Code) || //this one is a kludge as we clearly have this case of a vernacular field that people have used //to hold stuff that should be copied to every shell. So we can either remove the restriction of the //first two clauses in this if statement, or add another bloom-___ class in order to make execptions. //Today, I'm not seing the issue clearly enough, so I'm just going to path this one exisint hole. classes.Contains("smallCoverCredits")) { formToCopyFromSinceOursIsMissing = data.TextVariables[key].TextAlternatives.GetBestAlternative(new[] {languageCodeOfTargetField, "*", "en", "fr", "es", "pt"}); if (formToCopyFromSinceOursIsMissing != null) s = formToCopyFromSinceOursIsMissing.Form; if (string.IsNullOrEmpty(s)) { //OK, well even on a non-global language is better than none //s = data.TextVariables[key].TextAlternatives.GetFirstAlternative(); formToCopyFromSinceOursIsMissing = GetFirstAlternativeForm(data.TextVariables[key].TextAlternatives); if (formToCopyFromSinceOursIsMissing != null) s = formToCopyFromSinceOursIsMissing.Form; } } /* this was a fine idea, execpt that if the user then edits it, well, it's not borrowed anymore but we'll still have this sitting there misleading us //record our dubious deed for posterity if (formToCopyFromSinceOursIsMissing != null) { node.SetAttribute("bloom-languageBloomHadToCopyFrom", formToCopyFromSinceOursIsMissing.WritingSystemId); } */ return s; }
private void MigrateData(DataSet data) { //Until late in Bloom 3, we collected the topic in the National language, which is messy because then we would have to know how to //translate from all those languages to all other languages. Now, we just save English, and translate from English to whatever. //By far the largest number of books posted to bloomlibrary with this problem were Tok Pisin books, which actually just had //an English word as their value for "topic", so there we just switch it over to English. NamedMutliLingualValue topic; if (!data.TextVariables.TryGetValue("topic", out topic)) return; var topicStrings = topic.TextAlternatives; if (string.IsNullOrEmpty(topicStrings["en"] ) && topicStrings["tpi"] != null) { topicStrings["en"] = topicStrings["tpi"]; topicStrings.RemoveLanguageForm(topicStrings.Find("tpi")); } // BL-2746 For awhile during the v3.3 beta period, after the addition of ckeditor // our topic string was getting wrapped in html paragraph markers. There were a good // number of beta testers, so we need to clean up that mess. topicStrings.Forms .ForEach( languageForm => topicStrings[languageForm.WritingSystemId] = languageForm.Form.Replace("<p>", "").Replace("</p>", "")); if (!string.IsNullOrEmpty(topicStrings["en"])) { //starting with 3.5, we only store the English key in the datadiv. topicStrings.Forms .Where(lf => lf.WritingSystemId != "en") .ForEach(lf => topicStrings.RemoveLanguageForm(lf)); _dom.SafeSelectNodes("//div[@id='bloomDataDiv']/div[@data-book='topic' and not(@lang='en')]") .Cast<XmlElement>() .ForEach(e => e.ParentNode.RemoveChild(e)); } }
private void InjectXMatter(string initialPath, BookStorage storage, Layout sizeAndOrientation) { //now add in the xmatter from the currently selected xmatter pack if (!TestingSoSkipAddingXMatter) { var data = new DataSet(); Debug.Assert(!string.IsNullOrEmpty(_collectionSettings.Language1Iso639Code)); Debug.Assert(!string.IsNullOrEmpty(_collectionSettings.Language2Iso639Code)); data.WritingSystemCodes.Add("V", _collectionSettings.Language1Iso639Code); data.WritingSystemCodes.Add("N1", _collectionSettings.Language2Iso639Code); data.WritingSystemCodes.Add("N2", _collectionSettings.Language3Iso639Code); //by default, this comes from the collection, but the book can select one, inlucing "null" to select the factory-supplied empty xmatter var xmatterName = storage.Dom.GetMetaValue("xmatter", _collectionSettings.XMatterPackName); var helper = new XMatterHelper(storage.Dom, xmatterName, _fileLocator); helper.FolderPathForCopyingXMatterFiles = storage.FolderPath; helper.InjectXMatter(data.WritingSystemCodes, sizeAndOrientation); } }
public void SetLicenseMetdata(Metadata metadata) { var data = new DataSet(); GatherDataItemsFromXElement(data, _dom.RawDom); string copyright = metadata.CopyrightNotice; data.UpdateLanguageString("copyright", copyright, "*", false); string description = metadata.License.GetDescription("en"); data.UpdateLanguageString("licenseDescription", description, "en", false); string licenseUrl = metadata.License.Url; data.UpdateLanguageString("licenseUrl", licenseUrl, "*", false); string licenseNotes = metadata.License.RightsStatement; data.UpdateLanguageString("licenseNotes", licenseNotes, "*", false); string licenseImageName = metadata.License.GetImage() == null ? "" : "license.png"; data.UpdateGenericLanguageString("licenseImage", licenseImageName, false); UpdateDomFromDataSet(data, "*", _dom.RawDom); //UpdateDomFromDataSet() is not able to remove items yet, so we do it explicity RemoveDataDivElementIfEmptyValue("licenseDescription", description); RemoveDataDivElementIfEmptyValue("licenseImage", licenseImageName); RemoveDataDivElementIfEmptyValue("licenseUrl", licenseUrl); RemoveDataDivElementIfEmptyValue("copyright", copyright); RemoveDataDivElementIfEmptyValue("licenseNotes", licenseNotes); }
public void Setup() { _dom = new HtmlDom(@"<html><head> <link href='file://blahblah\\a5portrait.css' type='text/css' /></head><body><div id='bloomDataDiv'></div><div id ='firstPage' class='bloom-page'>1st page</div></body></html>"); _dataSet = new DataSet(); _dataSet.WritingSystemCodes.Add("V","xyz"); _dataSet.WritingSystemCodes.Add("N1", "fr"); _dataSet.WritingSystemCodes.Add("N2", "en"); }
private void GatherAttributes(DataSet data, XmlElement node, string key) { if (data.Attributes.ContainsKey(key)) return; List<KeyValuePair<string, string>> attributes = new List<KeyValuePair<string, string>>(); foreach (XmlAttribute attribute in node.Attributes) { if (attribute.Name != "data-book-attributes") attributes.Add(new KeyValuePair<string, string>(attribute.Name, attribute.Value)); } data.Attributes.Add(key, attributes); }