Acts as a cache of values we inject and gather from the document.
Beispiel #1
0
 /// <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);
 }
Beispiel #2
0
        /// <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;
        }
Beispiel #3
0
        /// <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);
            }
        }
Beispiel #4
0
 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
 }
Beispiel #5
0
        /// <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);
            }
        }
Beispiel #6
0
        // 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("&amp;"), "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);
            }
        }
Beispiel #7
0
        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;
        }
Beispiel #8
0
        /// <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;
        }
Beispiel #9
0
        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;
        }
Beispiel #10
0
 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);
 }
Beispiel #11
0
        /// <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("&amp;"),"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);
            }
        }
Beispiel #12
0
        /// <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;
        }
Beispiel #13
0
        /// <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;
        }
Beispiel #14
0
        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));
            }
        }
Beispiel #15
0
        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);
            }
        }
Beispiel #16
0
        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");
 }
Beispiel #18
0
 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);
 }